This thread is inspired by the work being done in this pull request:
Styling is a complicated beast to tame. In my previous attempts in Kludgine, I liked parts of how I designed the styling and theming, but other parts I disliked. It’s kind of funny, I know for a fact, one of the concepts I reimplemented in
stylecs I had previously removed from Kludgine. I don’t remember why I removed it from Kludgine originally, so a part of my brain is still worried I’m going to rediscover some reason this was a bad idea.
To adequately describe how styling is working, I want first to take a walkthrough of the crate that I extracted and refined from Kludgine and then give a primer on how Gooey uses the crate.
stylecs is short for Style Component System. It is an attempt at providing a minimalistic approach to the data structures needed to implement extensible styling in any application. It doesn’t directly contain any knowledge of fonts, colors, measurements. If you adopt
stylecs directly into your application, you will be responsible for defining the style components your application will use.
At its core is the
Style type. If you’ve used an Entity Component System, the interface should look fairly similar to an “entity” in most APIs. You can add components and get components based on their static type.
Each component must implement the
StyleComponent trait. A style component is defined with two bits of behavior: should it be inherited in a parent/child context, and what happens when merging two Styles with the same component.
Style provides the
merge_with() function, which takes a parameter denoting whether the merge should be considered inheritance. This is useful, for example, in a fashion similar to CSS’s
border-* rules: When you set the border of a DOM element in CSS, the child DOM elements don’t inherit the border values. However, when you set the font family on a DOM element, the children inherit that value.
The decision with
stylecs was to use the Rust type system to our advantage and allow each
StyleComponent to dictate how it is meant to be handled.
For example, here’s how
StyleComponents are meant to be immutable. Still, there are some situations where you might want to merge two
Style instances and have the merge operation produce a merge of the contained component. By default, components will overwrite each other based on the order in which they are merged/inherited. However, a component can provide an implementation for
merge() to customize the behavior.
One of the challenges faced in Gooey is that I don’t want a one-to-one mapping with DOM elements. For example, I might have a
FormInput widget with a label and a text edit control. As the consumer of my widget, you have the ability to style
FormInput as a whole, but not the individual widgets
FormInput is constructing. This is different than in CSS – you can write a more specific selector to target nearly any DOM node.
The proposed solution to this is
FormInput widget could expose new style components:
FormEditorColor. They should have sensible defaults, however, so they implement
TextColor as its
At the point of applying styles, the widget can use
FormLabelColor, its value will be returned. If not, but it contains
TextColor, the text color will be returned. Finally, if it has
ForegroundColor, that value would be returned. If not,
None is returned.
You can see it in action in the Kludgine rasterizer looks up the TextColor component:
StyleSheet type is a work in progress – I only began using it in Gooey over the last couple of days.
At the core is the
Rule type which offers various filtering options for when to apply the
Style contained in the rule. The evaluation is very simple and straightforward – I fear that it will need to become more sophisticated to properly style full applications.
StyleSheets can also be merged, allowing for a user style sheet to be evaluated on top of a style sheet provided by a theme.
gooey::core::styles re-exports parts of stylecs but also provides some basic style components. Each of the built-in widgets will use these core style components and build atop them. Third-party widgets are expected to use these core style components directly or through the
The core colors for the built-in StyleSheet are hardcoded right now. I want to abstract this list of colors into some sort of color palette structure. The goal would be to allow you to re-colorize an application by providing a new palette.
Of course, if you want to provide your own StyleSheet and not use any built-in rules, Gooey will support that workflow too.
The last thing I want to show off is
Container::from_registration(builder.register_widget( CounterWidgets::Button, Button::new( "Click Me!", builder.map_event(|_| CounterEvent::ButtonClicked), ), ))
This chunk is how the example creates a button within a container with an event callback for when the button is clicked. What if you wanted to change the button color?
Container::from_registration(builder.register_widget( CounterWidgets::Button, Button::new( "Click Me!", builder.map_event(|_| CounterEvent::ButtonClicked), ) .with(ButtonColor(/* ... */)), ))
I’m very happy with how concise the widget setup and styling API is turning out.
The pull request will be updated with a ToDo list as I start reigning in my vision. I try to keep pull requests focused, and this one has an opportunity to grow unwieldy if I don’t set some goals soon.
The next missing piece is that all of this style information isn’t handled in the browser implementation. I have a few ideas on how I’m going to approach this. It’s not actually a tough problem per se. Rather, it would be easy to implement inefficiently.