-
Notifications
You must be signed in to change notification settings - Fork 47
Ultraviolet Style Sheets
Ultraviolet Style Sheets (UVSS) is the language used by the Ultraviolet Presentation Foundation to apply visual styles to interface elements.
The syntax of the UVSS language is designed to be similar to that of Cascading Style Sheets (CSS). A UVSS file consists of a list of styles. Each style consists of a list of selectors which specify which elements the style targets, as well as a list of styling rules which modify the property values of targeted elements.
*
{
font: #Global:Fonts:DefaultUI;
foreground: #ffffffff;
}
The above is an example of a UVSS style. The selector here, *
, is the universal selector: this style will apply to all elements in the view. Within the curly braces there are two styling rules: one which will set the value of the font
property on elements which have it, and another which will set the value of the foreground
property. Note that only dependency properties can be styled, and that we refer to them here by their styling names, rather than their standard names.
The values specified for styling rules must be strings which can be resolved to an instance of the property type by the Nucleus object resolver. Therefore they are equivalent to the values used in the XML loaded by data object registries. Note in particular that resources, such as the font specified in the above example, are referenced by their name in the game's content manifest.
In addition to the universal selector, there are a number of other selectors and operators which can be used to target elements based on different attributes that they possess.
A selector beginning with no special symbol targets an element's type. The styling system understands inheritance relationships, so a value of FrameworkElement
will apply to elements of type Button
, and so on.
ToggleButton {
/* targets all ToggleButton elements...
* ...INCLUDING all RadioButton and CheckBox elements (which inherit from ToggleButton) */
}
A type selector which ends with the !
operator disregards inheritance relationships and will only match an element of the exact type specified.
ToggleButton! {
/* targets all ToggleButton elements...
* ...but NOT RadioButton or CheckBox elements (which inherit from ToggleButton) */
}
A selector beginning with the #
symbol targets an element's name (the Name
property of a FrameworkElement
):
#foo {
/* targets an element named "foo" */
}
A selector beginning with the .
symbol targets an element's classes (the Classes
property of a UIElement
):
.bar {
/* targets elements with the "bar" class */
}
You can combine these selectors together, just as you can in CSS:
button#foo.bar {
/* targets buttons named "foo" with class "bar" */
}
Putting a space between the selector parts indicates an ancestor/descendant relationship:
Button #foo .bar {
/* targets elements with class "bar" which are...
* ...descendants of elements named "foo" which are...
* ...descendants of Buttons */
}
You can also use the >
operator to require a direct parent/child relationship (following the visual tree):
Button > #foo > .bar {
/* targets elements with class "bar" which are...
* ...the immediate visual children of elements named "foo" which are...
* ...the immediate visual children of Buttons */
}
You can use the >?
operator to require a direct parent/child relationship (following the logical tree):
Button >? #foo >? .bar {
/* targets elements with class "bar" which are...
* ...the immediate logical children of elements named "foo" which are...
* ...the immediate logical children of Buttons */
}
You can use the >>
operator to require a templated parent/child relationship:
button >> #foo >> .bar {
/* targets elements with class "bar" which are...
* ...the templated children of elements named "foo" which are...
* ...the templated children of Buttons */
}
Finally, you can apply one set of styling rules to multiple targets by separating selectors with commas:
button, #foo, .bar {
/* targets all buttons...
* ...AND all elements named "foo"...
* ...AND and all elements with the "bar" class /*
}
Sometimes, you need to style objects which are not elements, but rather are objects exposed by properties on elements. Consider, for example, a Button
with an attached DropShadowEffect
. You can style the effect's properties using a navigation expression, indicated by the pipe (|
) operator combined with the type conversion operator (as
).
#mybtn | effect as DropShadowEffect
{
blur-radius: 2.0;
}
The type conversion operator is necessary because the Effect
property is defined with the Effect
type, which is the base class of DropShadowEffect
. Therefore, we must specify the exact type in order to access the properties of DropShadowEffect
.
The UVSS language also allows you to define storyboards which represent animations. A storyboard is declared by prepending the @
symbol to the storyboard's name, like so:
@storyboard name
{
/* etc */
}
A storyboard is played against a particular element. It can target various descendants of that root element in order to animate their properties; this list of targets is specified within the storyboard's curly braces.
@storyboard name
{
target optional_type (selector)
{
/* etc */
}
}
In the above code, the optional_type
argument can be excluded, or it can be the type name of any element class, such as Button
or CheckBox
or, more broadly, UIElement
. The animations specified within this target will only be played against elements of the specified type. If the type is excluded, it defaults to UIElement
.
The selector
argument is a normal UVSS selector like those described in the previous section; only elements matching this selector will be targeted.
Within each target is a list of animations; each animation specifies the name of the property which it animates and contains a list of keyframes which specify how the property's value evolves with time.
@storyboard name
{
target optional_type (selector)
{
animation propname
{
keyframe time_in_ms { value }
}
}
}
The value
specified in the keyframe can be left blank (i.e. keyframe 0 { }
), in which case the property will animate back to its underlying, non-animated value.
Here's an example of an actual storyboard defined in the default style sheet, which is responsible for transitioning a button into its pressed state:
@button-to-pressed
{
target control (*:storyboard-root)
{
animation foreground
{
keyframe 0 { #ffacb3bf }
}
}
target image (*:storyboard-root > grid > .button-background)
{
animation source-image
{
keyframe 0 { #Global:Textures:DefaultUI 256 69 71 33 4 4 4 4 }
}
}
}
Note the use of the :storyboard-root
pseudo-selector; this selector will only target the root element to which the storyboard as a whole was applied.
The primary use of storyboards is for transitions between visual states. Different elements can define different sets of these states, which are grouped together into visual state groups. The ButtonBase
element class, for example, defines normal
, hover
, and pressed
visual states, and moves between them in response to user input. You can specify that a storyboard should be played when an element transitions between states by using the transition
keyword in a list of styling rules:
Button!, RepeatButton!, ToggleButton!
{
transition (common, pressed): button-to-pressed;
}
The above specifies that the button-to-pressed
storyboard should be played against a button whenever it moves into its pressed
state - which is a part of the common
visual state group - from any other visual state within that group. You can further restrain this by specifying that it should only run when a transition occurs between two specific states:
Button!, RepeatButton!, ToggleButton!
{
transition (common, normal, pressed): button-normal-to-pressed;
}
In addition to styles, UVSS can be used to create triggers. A trigger is a collection of actions which are performed in response to some particular event taking place.
Currently, UPF supports two types of triggers: property triggers and event triggers. A property trigger fires whenever a specified list of dependency properties are set to a particular range of values. An event trigger fires whenever a particular routed event is raised against the target element.
You can create a property trigger like this:
ToggleButton!
{
trigger property checked = { false }, mouse-over = { true }
{
set source-image (*:trigger-root > grid > .button-background) { #Global:Textures:DefaultUI 256 35 71 33 4 4 4 4 }
}
}
This trigger will fire when every condition in its condition list is satisfied; that is, the checked
property of the trigger target must be false
, and the mouse-over
property of the trigger target must be true
.
You can create an event trigger like this:
Button!, RepeatButton!, ToggleButton!
{
trigger event mouse.mouse-enter
{
play-sfx { #Global:SoundEffects:Rollover3 }
}
}
This trigger will fire whenever the target element processes the mouse-enter
attached event.
Event triggers can also specify two optional arguments: handled
and set-handled
.
- If the
handled
argument is specified, then the trigger will fire even if the routed event has already been handled (that is, theHandled
property of its event metadata has been set totrue
). - If the
set-handled
argument is specified, then the trigger will set theHandled
property of the event metadata totrue
after it has fired.
These arguments should be placed inside of parentheses, after the name of the trigger's event name; if both are present, they should be separated by a comma.
trigger event event_name (handled, set-handled)
{
// etc
}
Each trigger can specify a collection of actions to perform when it is raised; in the above examples, these are set
and play-sfx
. Currently, Ultraviolet supports the following trigger actions:
-
set
set propname (optional_selector) { value }
Sets the value of the specified dependency property on any descendants of the triggered element which match the specified selector. The selector is optional; if none is specified, the triggered element itself is used. If this action is performed by a property trigger, the dependency property will return to its underlying value when the trigger's conditions are no longer satisfied. Note the use of the
:trigger-root
pseudo-selector in the above example, which is analogous to the:storyboard-root
pseudo-selector. -
play-sfx
play-sfx { value }
Plays the sound effect with the specified manifest resource name.
-
play-storyboard
play-storyboard (optional_selector) { value }
Plays the storyboard with the specified name on any elements in the view which match the specified selector. The selector is optional; if none is specified, the triggered element itself is used. As with
set
, the:trigger-root
pseudo-selector works similarly to the:storyboard-root
pseudo-selector, specifying the triggered element only.
- Contributing
- Dependencies
- Basic Concepts
- First Look- Platform
- First Look- Graphics
- First Look- Audio
- First Look- Input
- First Look- Content
- First Look- UI
- sRGB Color
- Customizing SpriteBatch
- Creating Fonts
- Creating Effects
- Creating Glyph Shaders
- FreeType2 Fonts
- Rendering 3D Models