Styles and themes
A view's look comes from three things: colours, textures and styles. Colours and textures are named in a resource block, and a style gathers the look of each state of a control under one name. Resources can be written in the view file, or in a template file that several views share.
Colours
A colour is written as one of:
#RGB,#RGBA,#RRGGBBor#RRGGBBAA, such as#2f6fbf.rgb(...),rgba(...)orargb(...).- An HTML colour name, such as
CornflowerBlue.
Name a colour in a <Resources> block or a template, and use it anywhere a colour is accepted with
{Resource id}:
<Resources>
<Color Id="theme.panel" Value="#14141c" />
<Color Id="theme.text" Value="#e6e6ee" />
</Resources>
<Label Id="status" Size="{240,24}" Color="{Resource theme.text}" />
Gradients
A gradient is a named set of colour stops. Use it as a fill with FillGradient:
<Gradient Id="theme.header" Kind="Linear" Angle="90">
<Stop Offset="0" Color="#2a2a38" />
<Stop Offset="1" Color="#14141c" />
</Gradient>
| Attribute | Default | What it does |
|---|---|---|
Kind |
Linear |
Linear, or Radial from Center. |
Angle |
0 |
The direction of a linear gradient, in degrees, as in CSS. |
Center |
the centre | The centre of a radial gradient, as percentages of the control, such as {20,50}. |
Each Stop has an Offset from 0 to 1 and a Color. Refer to a gradient with
FillGradient="{Resource theme.header}". A FillGradient is drawn in place of FillColor when
both are set.
Styles
A Style is a named look for a control, with one block for each state:
<Style Id="theme.button" FontSize="18" LetterSpacing="1">
<NormalState FillColor="{Resource theme.panel}" FontColor="{Resource theme.text}"
BorderColor="#2a2a38" BorderThickness="2" />
<HoverState FillColor="#22222e" />
<ClickedState FillColor="#0c0c12" />
</Style>
A control uses a style with Style="theme.button". Anything the control sets for itself wins over
the style, so a style is a default that a control can override.
A style can build on another with BasedOn="theme.base". Anything the style does not set is taken
from the one it is based on.
| Attribute | Default | What it does |
|---|---|---|
Font |
A font path or a built-in font name, or {Resource id}. |
|
FontSize |
0 |
The point size of the text. 0 keeps the control's own size. |
LetterSpacing |
0 |
Extra space between glyphs, in pixels. |
The states a control reads:
| Control | States |
|---|---|
Button, TextBox, PasswordBox |
NormalState, HoverState, ClickedState, FocusedState |
CheckBox, RadioButton |
UncheckedState, CheckedState |
A state is one of the attributes below, plus at most one texture child.
| Attribute | What it does |
|---|---|
Color |
A tint for the texture. |
FontColor |
The colour of the text. Default #000000. |
FillColor |
A flat fill. Drawn with no texture, from the control's own bounds. |
FillGradient |
A gradient fill, drawn in place of FillColor. |
BorderColor, BorderThickness |
A border, drawn inside the control. The thickness defaults to 1. |
ShadowColor |
A soft shadow. Nothing is drawn without it. |
ShadowOffset, ShadowBlur, ShadowSpread |
The shadow's position as {x,y}, its blur, and how much larger than the control it is, in pixels. |
A button's or a text box's hover, pressed and focused states fall back to its normal state when they are not set, so a button styled only in its normal state keeps that look when it is hovered and pressed. Give it hover and pressed states to change its look then.
A flat look needs no textures:
<Button Id="ok" Size="{240,40}" Text="OK" Command="OkCommand">
<NormalState FillColor="#2f6fbf" FontColor="#ffffff" />
<HoverState FillColor="#4a8ad8" FontColor="#ffffff" />
</Button>
Textures
A texture is a picture from the content folder, set in a state, an Image or a template. Its path
is relative to the game's content root, as for other textures (see
Content).
A state takes a texture in one of these ways:
| Element | What it does |
|---|---|
<TextureFile Id="..." Src="..." /> |
A picture, given where it is used. |
<Reference Ref="..." /> |
A texture declared in a template, by its Id. |
<NinePatchReference Ref="..." /> |
A nine-patch texture declared in a template. |
<HorizontalThreePatchReference Ref="..." /> |
A three-patch texture declared in a template. |
A nine-patch texture stretches its middle to any size and keeps its corners. It is declared in a template, with the position and size of each of its nine parts:
<NinePatchTexture Id="theme.panelNine" Size="{8,8}">
<Texture Source="ui/panel.png" />
<TopLeft Position="{0,0}" Size="{8,8}" />
<TopMiddle Position="{8,0}" Size="{8,8}" />
<TopRight Position="{16,0}" Size="{8,8}" />
<MiddleLeft Position="{0,8}" Size="{8,8}" />
<MiddleMiddle Position="{8,8}" Size="{8,8}" />
<MiddleRight Position="{16,8}" Size="{8,8}" />
<BottomLeft Position="{0,16}" Size="{8,8}" />
<BottomMiddle Position="{8,16}" Size="{8,8}" />
<BottomRight Position="{16,16}" Size="{8,8}" />
</NinePatchTexture>
All nine parts are required, even though the schema allows any of them to be left out. A
Color on a part overrides the texture's own Color for that part.
A state refers to it:
<Button Id="ok" Size="{240,40}" Text="OK" Command="OkCommand">
<NormalState>
<NinePatchReference Ref="theme.panelNine" />
</NormalState>
</Button>
A three-patch texture is the same idea for a strip that stretches only along its width, with
Left, Middle and Right parts.
Decorations
A <Decorations> block at the end of a view draws marks pinned to a corner or edge of the view,
behind or over its controls. A decoration takes the same fills and borders as a state, and an
Anchor (default TopLeft), a Layer (Background or Foreground) and a Size:
<Decorations>
<Decoration Anchor="BottomRight" Size="{120,120}" BorderColor="#2a2a38" BorderThickness="2" />
</Decorations>
Set Stretch="true" for a decoration that covers the whole view.
Templates and themes
A template is a file of shared resources: colours, gradients, styles and textures. Its root element is
<Template>. A view uses it with a References element placed before its layout:
Theme.template.xml:
<Template>
<Color Id="theme.panel" Value="#14141c" />
<Color Id="theme.border" Value="#2a2a38" />
<Color Id="theme.text" Value="#e6e6ee" />
<Color Id="theme.accent" Value="#6aa0ff" />
<Style Id="theme.button" FontSize="18">
<NormalState FillColor="{Resource theme.panel}" FontColor="{Resource theme.text}"
BorderColor="{Resource theme.border}" BorderThickness="2" />
<HoverState FillColor="#22222e" BorderColor="{Resource theme.accent}" />
</Style>
</Template>
Views/Menu.view.xml:
<View>
<References Source="Theme.template.xml" />
<StackLayout Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"
VerticalSpacing="12">
<Label Id="title" Size="{240,32}" Text="Menu" Color="{Resource theme.accent}" />
<Button Id="ok" Style="theme.button" Size="{240,40}" Text="OK" Command="OkCommand" />
</StackLayout>
</View>
The path in Source is relative to the view's file. A value in a view's own <Resources> block
overrides the same id in a template the view references.
Resources are read when a view is built. A view is not restyled while it is open.
Variants
A theme can come in variants, such as light and dark. The view names one file, and the variant
decides which file that name means. With the variant Light active, Theme.template.xml is
replaced by Theme.Light.template.xml in the same folder, if that file exists. A variant needs
only the templates it changes. When the variant has no file of a name, the view uses the file it
names.
Switch the variant with IThemeManager, then rebuild what is on screen:
using BSGameLib.UI;
public class SettingsViewModel : ViewModel
{
private readonly IThemeManager _themes;
public SettingsViewModel(IViewManager viewManager, IThemeManager themes) : base(viewManager)
{
_themes = themes;
}
public void UseLightTheme()
{
_themes.SetVariant("Light");
ViewManager.ReloadActiveView();
}
public void UseDefaultTheme()
{
_themes.SetVariant(null);
ViewManager.ReloadActiveView();
}
}
ReloadActiveView keeps the view-models and their state. Focus and scroll positions are not kept. Passing null to SetVariant goes back to the file a view names directly.
Folders
By default a template is looked for in the folder of the view that names it. Folders listed in
TemplateSearchPaths are searched first, in order, so a player's copies of a view or template can
replace the installed ones:
using BSGameLib.UI;
public static class PlayerThemes
{
public static void Use(IThemeManager themes, string folder) =>
themes.TemplateSearchPaths.Insert(0, folder);
}
Built-in fonts
Two fonts are built in:
| Font | Name |
|---|---|
| Boogaloo | Boogaloo. Used when a control names no font. |
| Voltaire | Voltaire |
<Label Id="title" Size="{240,32}" Text="Menu" Font="Voltaire" FontSize="28" />
Any other font is a path under the content root: a BMFont file (.fnt) or a TrueType font (.ttf
or .otf). A TrueType font is drawn at FontSize, which defaults to 18, and a BMFont is scaled to
FontSize when one is given.