#style Region
Purpose
The #style region sets component props by rule, so a module can say
once what its buttons, labels and panels look like instead of repeating it
at every instantiation. A rule names a component and gives values to props
the component declares; the compiler checks them like values written at
the instantiation and then compiles each styled prop as if it had been
written there. Nothing is left for run time: there is no style engine or
cascade in the program, and it behaves the same in the VM and natively.
#code
import ui from "ui";
import widgets from "widgets";
fn save() { }
fn quit() { }
#view
component Toolbar {
view {
ui.Row {
widgets.Button { label: "Save"; on_click: save; }
widgets.Button { label: "Quit"; bg: #CC3333; on_click: quit; }
}
}
}
#style
import theme from "theme#style";
widgets.Label { // every Label instantiated in this module
fg: #FFFFFF;
size: 16;
}
Toolbar {
widgets.Button { // Buttons inside a Toolbar
bg: theme.accent;
}
}“Save” gets the theme’s accent from the rule; “Quit” keeps #CC3333,
because a value written at the instantiation always wins.
Rules
A rule is a component name and a body of prop: value; lines:
widgets.Button {
bg: #2E7DD7;
fg: #FFFFFF;
}- The name is a component of this module (
Card) or of an imported one (widgets.Button), as you’d write it in a view tree. A component a generator emitted is named the same way:widgets.Rowsandwidgets.Colscome from one template, but a rule for one never reaches the other. - Each property must be a
propthe component declares, set at most once per rule. Events (on_click), slots,vars andstateflags are not props: bind or set those at the instantiation. - The value must fit the prop’s type: a misspelled prop, a string for a
coloror300for au8is a compile error at the rule.
Nested rules
A rule inside another one reaches only the instances inside that component:
Toolbar {
widgets.Button { bg: #2E7DD7; } // Buttons in a Toolbar's tree
ui.Row {
widgets.Button { bg: #1F5FA0; } // ... and in a Row inside it
}
}“Inside” is where the instance is written: in the component’s view tree,
as a child or a slot’s content, at any depth (other components may sit in
between). if and for in a tree don’t count; every instance a for
makes gets the same styled values.
Which rule wins
For each prop of an instance:
- a value written at the instantiation;
- else the rule with the most names in its selector
(
Toolbar { widgets.Button { } }beatswidgets.Button { }); - else, between equally specific rules, the later one (imported rules count as coming first);
- else the prop’s default.
Values
A value is a constant the compiler can work out:
- literals: numbers,
true/false, strings, colors (#RRGGBB,#RRGGBBAA),nullfor a pointer; - enum variants:
ui.ScrollDir.Both; - constants: the module’s
consts,pub consts of an imported module, and constants of an imported#styleregion (theme.accent); - arithmetic on them:
GAP * 2,-1,FLAGS | 4.
A function call, a variable or anything decided at run time is an error. Function-typed props can’t be styled: they are behaviour, not look.
Constants
#style
const GAP: i64 = 8;
pub const brand: color = #CC3366;Constants declared in #style belong to the module, so #code and
#view can use them too. A pub const is what other modules see when
they import this module’s styles.
Importing styles
#style
import theme from "theme#style";
import brand from "./brand.tape#style";A #style region imports other modules’ #style regions, with a path
ending in #style. That gives it
- their
pub consts, astheme.accent, in this region’s values; - their rules, which then style this module’s instances too, before this module’s own (so a local rule of the same specificity wins).
A rule never reaches instances written in another module: importing
widgets doesn’t restyle the buttons inside widgets’ own components,
and a module’s styles apply only where it, or a module importing its
#style, writes the instances. A plain import x from "x"; in #code
doesn’t bring x’s styles or style constants.
Spacing
Layout is styled the same way: ui.Column and ui.Row have gap,
padding and align, and every widget has width, height and
grow (Column and Row):
#style
ui.Row { gap: 8; padding: 4; } // every Row in this module
Toolbar {
widgets.Button { width: 96; } // Buttons in a Toolbar, a fixed width
}State selectors
Inside a rule, :state { ... } sets the look of a component while one
of its state flags is on:
#style
widgets.Button {
:hovered { bg: #2A6FB8; }
:active { bg: #1D5A99; border: #FFFFFF; }
}:hovered { bg: X; } is the prop hovered_bg set to X: a component
gives the look of each state as props named <state>_<prop>, and paints
with them while the state is on (widgets’ state
props). It is an error when
the component has no state of that name, or no prop of that name for
it. A state selector holds properties only. What a state means, when it
turns on and which wins when two are on, is the component’s own code:
the rule only sets values.
Your own component takes part the same way:
component Tab {
prop bg: color = #303030;
prop hovered_bg: color = #00000000;
state hovered: bool = false;
... // paints hovered_bg while hovered, when it is set
}Transitions
Animation is the widgets’ own: transition and transition_ease are
props, so a rule sets them like any other:
#style
widgets.Button { transition: 150; transition_ease: easing.Ease.CssOut; }(easing imported in #code.) A component of your own animates in its
frame method (tween and easing).
Styles and the theme
Styles are fixed when the program is compiled. To change the look while it
runs, light to dark for example, use the theme
module: widgets paint every color you leave unset with the active theme,
and theme.set(theme.dark()) switches it for the next frame. A prop a
rule sets keeps its value whatever the theme.
Not yet
- Struct-typed prop values.
Last modified: