Style Queries
Basic State Queries
Hover
Hover is active on elements that the mouse or other non-touch pointing device is currently over. It works by finding the deepest element in the list of all elements that intersect or contain the point and then all elements that are ancestors of that element are also marked as hovered.
style hover-me {
[hover] {
BackgroundColor = hotpink;
}
}
Focus
Focus is set explicitly by the runtime when an element becomes focused.
style focus-me {
[focus] {
TextFontSize = 100px;
}
}
Active
Active is triggered on the element that the mouse was down on (following the same hierarchy rules as [hover]
) and continues to apply until
the mouse is up.
style handle-active {
[active] {
CornerRadiusTopLeft = 50%;
}
}
Init
Init is set on elements for only the frame in which they were first created or became enabled. This is useful for tracking animations values or transitons.
On the first frame set opacity to 0 and then transition it the default value (100%)
style handle-init {
transition spring(default) = Opacity;
[init] {
Opacity = 0%;
}
}
Hierarchy State Queries
States within an element's hierarchy can be queried using a more advanced form of the state selectors.
The examples below will use the focus
state but the same behavior applies to hover
, active
and init
as well
style fade-when-ancestor-focused {
[focus ancestor] {
Opacity = 50%;
}
}
style turn-blue-when-child-focused {
[focus child] {
BackgroundColor = blue;
}
}
style round-corners-when-parent-focused {
[focus parent] {
CornerRadius = 25%;
}
}
style padd-left-when-descendant-focused {
[focus descendant] {
PaddingLeft = 25px;
}
}
Attribute Queries
Elements can also be styled based on the presence or lack of attributes that were set either in a ui
file or from C#.
Styling can be done by either checking if the attribute key exists and matching any value, or by specifically matching
only certain values.
style handle-selected {
// no value defined so as long as the element has a 'selected'
// attribute the text will be yellow
[attr:selected] {
TextColor = yellow;
}
// this will only match when the element has the attribute 'selected'
// and the value of that attribute is 'selection1'
[attr:selected="selection1"] {
BackgroundColor = blue;
}
}
We can also style based on the lack of an attribute match
style handle-selected {
// if the element doesn't have a 'selected' attribute, make the text red
[unless attr:selected] {
TextColor = red;
}
// this will match if the element either doesn't have the attribute 'selected',
// or it has the 'selected' attribute but the value is NOT equal to 'selection1'
[unless attr:selected="selection1"] {
BackgroundColor = blue;
}
}
Hierarchy Attribute Queries
Similar to how states can be queried across the hierarchy, attributes can also be queried.
style check-ancestor-attribute {
[when ancestor attr:key] {
}
[when ancestor attr:key = "SomeValue"] {
}
}
style check-children-attribute {
[when child attr:key] {}
[when child attr:key = "SomeValue"] {}
}
style check-descendant-attribute {
[when child attr:key] {}
[when child attr:key = "SomeValue"] {}
}
style check-parent-attribute {
[when parent attr:key] {}
[when parent attr:key = "SomeValue"] {}
}
Child Count Queries
You can also style an element based on how many children it has and even pass in a comparison expression. The numeric comparison value must be a positive integer constant.
style red-when-empty {
// no children
[when empty] {
BackgroundColor = red;
}
// same as [when empty] above
[when child-count(0)] {
BackgroundColor = red;
}
}
style child-count {
[when child-count(== 2)] {}
[when child-count(!= 2)] {}
[when child-count(<= 10)] {}
[when child-count(>= 10)] {}
[when child-count(> 5)] {}
[when child-count(< 5)] {}
}
Child Index Queries
It is often helpful to know if an element you are styling is the first child, or the last child or at some other index. Child Index Queries exist for exactly this purpose and are quite powerful!
Only Child
Matches when the element is the ony child within a parent
style blue-text-when-lonely {
[when only-child] {
TextColor = blue;
}
}
First and Last Child
Matches when the element is the first child of its parent or the last child respectively
style first-child-turns-orange-last-child-turns-green {
[when first-child] {
BackgroundColor = orange;
}
[when last-child] {
BackgroundColor = green;
}
}
Nth-Child
Takes a function expression that describes how children will be matched by this query in a 1-based indexing scheme. Works identically to n-th child expression in css
style fancy-matching {
// turn the element outline white if it is the second child
[when nth-child(2)] {
OutlineColor = white;
}
// turn every fourth element pink
[when nth-child(4n)] {
BackgroundColor = pink;
}
// turn the first three element blue
[when nth-child(-n + 3)] {
BackgroundColor = blue;
}
// turns all elements except the first three yellow
[when nth-child(n + 3)] {
BackgroundColor = yellow;
}
// Turns elements 4 [=(3×0)+4], 7 [=(3×1)+4], 10 [=(3×2)+4], 13 [=(3×3)+4], etc black
[when nth-child(3n + 4) {
BackgroundColor = black;
}
}
Even and Odd Children
Matches when the element is at an even or odd child index
style alternate-fonts {
[when nth-child(odd)] {
TextFontAsset = "Font1";
}
[when nth-child(even)] {
TextFontAsset = "Font2";
}
}
Tag Queries
Elements can also be styled based on their tag name with relational filtering.
style tag-based-styling {
[when tag(TagName)]
[when tag(ModuleName::TagName)]
[when root-tag(TagName)]
[when root-tag(ModuleName::TagName)]
[when parent-tag(TagName)]
[when parent-tag(ModuleName::TagName)]
[when ancestor-tag(TagName)]
[when ancestor-tag(ModuleName::TagName)]
[when child-with-tag(TagName)]
[when child-with-tag(ModuleName::TagName)]
[when descendant-with-tag(TagName)]
[when descendant-with-tag(ModuleName::TagName)]
[when first-with-tag(TagName)]
[when first-with-tag(ModuleName::TagName)]
[when last-with-tag(TagName)]
[when last-with-tag(ModuleName::TagName)]
[when only-with-tag(TagName)]
[when only-with-tag(ModuleName::TagName)]
}
Condition Queries
Sometimes you just need more power. Maybe you're implementing a resposive design or maybe you have a game that enters a certain state and you want to change the look of some elements based on game state. With condition queries you can make up your own query logic and have the style system respond to it. Unlike the other query types listed so far, condition queries require some C# code to work.
Here is how to define them. First we need to make callback that matches the delegate type.
delegate bool StyleConditionEvaluator(UIView view, DeviceInfo deviceInfo);
// DeviceInfo has the following interface:
struct DeviceInfo {
float dpiScale;
float GetScaledScreenWidth();
float GetScaledScreenHeight();
}
This example will style an element differently depending on the aspect ratio of our screen. So let's provide conditions callbacks to check if width is larger than height and vice versa; While this example is super simple, you can run any code you want in this function. Maybe you want to check if your player is injured, or possesses some item, or if the internet connection got interrupted in an MMO game... you can make this do whatever you like.
StyleConditionEvaluator widthLarger = (UIView view, DeviceInfo deviceInfo) => {
return Screen.width > Screen.height;
};
StyleConditionEvaluator heightLarger = (UIView view, DeviceInfo deviceInfo) => {
return Screen.height > Screen.width;
};
Now we need to register these functions with our UIApplication using the SetStyleCondition
api.
UIApplication application; // assume you set this somehow, probably from your UIRoot.
application.SetStyleCondition("WidthIsLarger", widthLarger);
application.SetStyleCondition("HeightIsLarger", heightLarger);
The evaluator functions are called every frame and are invoked once per active view in the application
(Hence the UIView
argument in the StyleConditionEvaluator
delegate). Now that our functions are
setup, let's write a style that can use them.
style size-based-on-screen-aspect {
// if the screen width is larger than height, make our width 500px
[when "WidthIsLarger"] {
PreferredWidth = 500px;
}
// if the screen width is smaller than height, make our width 250px
[when "HeightIsLarger"] {
PreferredWidth = 250px;
}
}