Implementing a stylesheet system is hard
Posted on 2017-08-02
It has been exactly 1 month since my last blogpost on Nimble Application Framework. In the past month I've been working on and off, particularly on stylesheet experimentation; which I have decided to cut for the time being.
There's also a new localization system for translating strings into different languages, and a new "page" system.
While implementing stylesheets, I asked myself a few questions:
- What syntax would stylesheet files use?
- How would selectors work?
- How would I efficiently set all values without duplicating code?
The first 2 points were simple. I adapted an ini-like format where sections are selector strings, keys are property names and values are property values. Pretty straight forward. (This ini parsing ended up coming in useful for localization as well.)
Next, we have the selectors. I decided I wanted something similar to CSS, where you can select by ID, classname, tag name, or both; so I ended up writing a parser for these selectors. The selector is "compiled", and then matched with all widgets. It also allows to chain children of widgets together, so that selectors can be more narrow in their search.
[#test button] margin = 5
Now it looks like there's a very clear path on how to continueu; let's actually apply the styles on the widgets now. This ended up being more difficult than I was expecting, because I didn't want to clog the code with a bunch of string comparisons for property names, essentially duplicating what I already have for main attribute loading.
This led me into researching the giant rabbithole that is autogenerated code. In the end I decided against doing this, although I did make some progress on a Go-based reflection-style code generator for C++ classes. So instead of doing that, the next obvious step to me would be to implement widgets in some kind of scripting language instead of purely C++. That way, you can dynamically set widget properties by name. The language needs to be very fast, as it would be rendering/updating a lot of different kinds of (big) widgets. The best solution I saw was to try implementing an embedded Mono .Net runtime.
The dotnet branch on Github shows the progress made there. Unfortunately, it ended up being a huge amount of work (much more than I was hoping), so I gave up on that as well for the time being. I don't want to rule out using C# just yet (it's a good idea for embedded scripting), but for now I've cut all of the stylesheet stuff as it became too much of a burden, and I wasn't getting anywhere in terms of progress.
So, despite having to cut the stylesheets for now, let's move on!
Localization was added, which is (as previously mentioned) aided by the ini file format. Whenever strings are used such as
##Section.Key, it will be replaced with the loaded localized string, if it exists.
[Main] Title = Nimble App Example CreateBlock = Create block InvalidateLayout = Invalidate layout ResizeWindow = Resize window
The latest (and important) addition is the introduction of "pages". Typically, big applications use multiple interfaces in which they operate. A page in Nimble App is a full-window container that draws and handles input individually. This lets you make interface stacks like open file dialogs, messageboxes, or any other full-sized interface.
As always, you can check the latest code on Github.