-
Notifications
You must be signed in to change notification settings - Fork 33
Styling
Lemur uses an advanced styling system that is similar in concept to web page cascading style sheets. GUI element attributes can be inherited from more general styling information or element-specific styling can override the more general styling.
For a custom GUI element to support styling its simply a matter of annotating the appropriate set methods with StyleAttribute annotations and then making sure to call styles.applyStyles() during construction.
An element's style is defined by both its 'element ID' as well as its 'style name'. A default style name can be setup that all new GUI elements will use if a specific style has not been specified. The 'element ID' determines which set of style attributes apply in a 'cascading' or 'inherited' fashion.
CSS-like but different. Cascading style sheets have a similar concept of inheritance or containment but in the case of CSS this is based on page layout. A style can apply to all paragraphs or paragraphs only under a particular class of 'div' and so on. In Lemur, style is applied before the element has even been added to a GUI and so the cascading is based directly on the element ID and its dotted sub-parts.
An element ID defines a GUI elements place in the style hierarchy. Whether it's just a "button" or it's a "slider.thumb.button" or a "megaSlider.thumb.button" or just a "menu.button", each of these things might require different style attributes while inheriting some common attributes from the regular "button" style.
In general, it's best to define element ID's as container.contained.contained where generally the first part is defined by the thing creating the element. In fact, the ElementId class makes this easy by providing a convenient child(childId) method.
The actual attributes that apply for a given ElementID will be determined by the 'selectors' that have been configured.
A selector defines a pattern to which a set of style attributes will apply. The simple selectors will be based just on 'style name' or a simple ID like 'button'. More complicated containment-based styles are also supported.
For brevity, this document will use the style language way to specify selectors when presenting examples. The Java code version is not really different but is slightly more verbose.
selector(styleName) The simplest selector is the style name selector. This will apply a particular set of style attributes to any element of the specified style. A null style indicates that the attributes will apply as defaults to all styles.
selector(elementId, styleName) The next simplest selector applies to any element with the specified elementId and styleName. Again, a styleName of null sets up a default for all elements with the elementId, regardless of style. The elementId in this case, is the 'tail' of the match. In other words, selector("button", "glass") would match any of "button", "slider.thumb.button", "menu.button", and so on, if they have the 'glass' style.
selector(parentId, elementId, styleName) This is the most advanced and allows a style to only apply to a particular elementId and styleName if it is 'contained' in a particular parentId. 'Contained' in this case means that the full elementId has the parentId somewhere earlier in its value. In other words, you can think of it like parentId.*.elementId. For example, selector("slider", "button", "glass") would match all of a Slider element's buttons, including: "slider.thumb.button", "slider.up.button", "slider.down.button", "slider.left.button", and so on.
This is the tricky part to predict sometimes but generally the styling library tries to have the 'most specific' styling override the more general styling. Meaning that a selector for "button" is more general than a selector for "thumb.button" which is more general than a selector for "slider.thumb.button". It gets more complicated for containment selectors but the general idea holds true. Longer matching segments mean 'more specific', especially if they are in the tail. For really complicated styling needs, it might be necessary to turn on trace logging for the styler just to see how they are applied. Else, just go ahead and be more specific in the styling definition and rely on cascading less in some ambiguous looking case.
Internally, an attribute set for a specific full elementId is assembled by ordering the matching selectors from most specific to least specific and then building up a set of attribute values only if those values haven't already been set.
Example:
selector("glass") {
fontSize=20
}
selector("button", "glass") {
color = color(0.5, 0.75, 0.75, 0.85)
}
selector("slider", "button", "glass") {
fontSize=10
}
Should result in the following test-cases:
ElementId("label")
- fontSize=20
ElementId("button")
- fontSize=20
- color=color(0.5, 0.75, 0.75, 0.85)
ElementId("slider.thumb.button")
- fontSize=10
- color=color(0.5, 0.75, 0.75, 0.85)
..and hopefully you can start to see the flexibility.