Declare fonts, paint fills and borders, render text, and add hover and selected states. You will finish with a styled button that animates between its states.
Styles are written in .glx stylesheet files bundled with your app. Every Reflex app keeps its assets in a resources/ folder next to the code, and the view's stylesheet lives there:
MyApp/
├── code/
├── resources/
│ ├── styles.glx
│ ├── common.glx
│ └── fonts/
└── resources.xml
styles.glx is the view's own stylesheet. common.glx holds declarations shared between views (more on that under Splitting and importing stylesheets). Everything below is what goes inside these files.
The top level of the stylesheet styles the view itself, the window your app draws into. Root-level size and bg_colour set its dimensions and background, the starting canvas for everything else.
size: 512,384;
bg_colour: 245;
bg_colour is a single flat colour (bg_colour and bg_color both work), not the layered bg: slot you use to paint objects on the view.
Every style in a .glx stylesheet describes an object as a stack of layers. The two most common slots are bg (drawn behind content) and fg (drawn in front). Layers in a slot are comma separated and drawn in order, first at the bottom.
Card:
{
size: 320,180;
padding: 12;
bg:
Shadow(blur: 8; corner: 8; offset: 0,1; color: 0,64),
Fill(colour: 255; corner: 8),
Border(colour: 200; corner: 8);
};
Reading bottom to top: the drop shadow, then a solid fill over it, then a thin border on top. Fill draws a filled rectangle across the layer rect and becomes a rounded fill when corner is supplied. Border draws a rectangular outline, with width controlling stroke thickness.
Colour values accept a single greyscale value (232), greyscale with alpha (0,64 is black at low opacity, which is what makes the shadow soft), or full RGB (0,120,255). Both spellings, colour and color, are accepted everywhere.
Fonts live in named @Font blocks at the top of the stylesheet. The font file path and the rendered size are both fixed at the declaration.
@Font Heading:
{
path: "fonts/Roboto/Roboto-Bold.ttf";
size: 20;
};
@Font Body:
{
path: "fonts/Roboto/Roboto-Regular.ttf";
size: 13;
};
path can point at a font file relative to your resources, or at a resource key such as :res:MyApp/font. Optional parameters include render (smooth or lcd), antialias, and spacing.
The rule that trips people up: font size lives here and only here. If you need the same typeface at two sizes, declare two @Font blocks. There is no way to override size at the point of use.
Text renders a string using one of your declared fonts.
title:
{
size: 32;
bg: Text(font: Heading; colour: 32);
};
The full parameter set of Text(...): font, value, colour, justify, indent, offset, autofit, line_space, line_height, transform, and overflow.
One thing worth calling out:
size: parameter on Text. Size comes from the @Font. Writing Text(font: Body; size: 18; ...) is the single most common stylesheet mistake, and the parser will reject it.justify: center centres the text in the layer rect, and indent insets the text area, for example indent: 10,0 for a horizontal inset.
A style block can contain @State sub-blocks. Each one redeclares the layers to use while that state is active.
Button:
{
margin: 2;
size: 28;
transition: 0.25;
bg:
Fill(colour: 232; corner: 4),
Text(font: Body; justify: center; colour: 72);
@State hover:
{
bg:
Fill(colour: 255,214,10; corner: 4),
Text(font: Body; justify: center; colour: 32);
};
@State selected:
{
bg:
Fill(colour: 0,120,255; corner: 4),
Text(font: Body; justify: center; colour: 255);
};
};
Hover for the hover state, then click to toggle the selected state.
hover is driven by the engine, the moment the pointer enters the object its hover state layers take over, no code required.
Custom states like selected are yours to control from C++:
button->SetState("selected");
button->UnsetState("selected");
Any name works. The shipped Drag and Drop example uses a dragover state on its drop targets, set when a drag enters and unset when it leaves.
The transition: 0.25; line in the Button style is doing quiet work: instead of snapping between state layer sets, the object cross fades over 0.25 seconds. One property, and every state change on that object animates. Remove it and changes apply instantly.
Fills, borders, and text are not the only things a style can paint. Image resources are declared at the top of the stylesheet the same way fonts are, then used as layers in bg or fg.
@Bitmap app_icons:
{
path: ":res:MyApp/icons.png";
density: 2;
tile: 32;
};
@VectorSet app_glyphs:
{
path: ":res:MyApp/icons.txt";
};
save_button:
{
bg: Image(source: app_icons > save; anchor: center);
};
@Bitmap declares a bitmap image set (density marks it as a 2x asset, tile slices it into frames), and @VectorSet declares a vector icon set. In a layer, Image(source: app_icons > save) picks the named frame save from the set. An Image layer stacks with everything else in this tutorial, so an icon button is just Fill, Image, and a @State hover block together.
Fit modes (none, stretch, contain, cover), anchoring, and sprite-sheet details have their own tutorial, see Images.
Every layer below stacks inside a bg or fg slot exactly the same way, comma separated and drawn bottom to top. Layers marked experimental may change in a future release.
Align, Group (alias of Align), If, Tile, Split (experimental), Inline (experimental), ViewPortGrid (experimental).Fill, Border, Circle, CircleFill, CircleGradient, Triangle, TriangleFill, Polygon, Path, Line, Gradient, Bar, BarGraph, Canvas, ColourCanvas (ColorCanvas alias), Rotate, Scale, Translate.Blur, Shadow, InnerShadow, Clip, Mask, Render, Invert, Tint, Sepia, Saturate, Contrast, Brightness, Exposure, Grayscale (Greyscale alias), Pixelate (experimental).Text, TextEdit.@Bitmap or @VectorSet: Image, ImageFill, ImageSelector, ImageMask.One stylesheet per view gets crowded fast. A stylesheet can import another with include, placed at the top of the file:
include: "common.glx";
Everything declared in common.glx (fonts, image sets, shared style blocks) becomes available to the including file, so @Font Body can be declared once and referenced from every stylesheet in the project. The shipped examples use exactly this split: a common.glx for shared declarations, included by each view's stylesheet. Styles like the Button in this tutorial are good candidates to move there once a second view needs them.
include: "common.glx";
@Font Heading:
{
path: "fonts/Roboto/Roboto-Bold.ttf";
size: 20;
};
@Font Body:
{
path: "fonts/Roboto/Roboto-Regular.ttf";
size: 13;
};
size: 512,384;
bg_colour: 245;
Card:
{
size: 320,180;
padding: 12;
bg:
Shadow(blur: 8; corner: 8; offset: 0,1; color: 0,64),
Fill(colour: 255; corner: 8),
Border(colour: 200; corner: 8);
title:
{
size: 32;
bg: Text(font: Heading; colour: 32);
};
Button:
{
margin: 2;
size: 28;
transition: 0.25;
bg:
Fill(colour: 232; corner: 4),
Text(font: Body; justify: center; colour: 72);
@State hover:
{
bg:
Fill(colour: 240; corner: 4),
Text(font: Body; justify: center; colour: 32);
};
@State selected:
{
bg:
Fill(colour: 0,120,255; corner: 4),
Text(font: Body; justify: center; colour: 255);
};
};
};
On the C++ side, toggle the selected state from your click handler:
void ViewImpl::OnToggle(GLX::Object & button, bool on)
{
if (on)
button.SetState("selected");
else
button.UnsetState("selected");
}