#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.

tape
#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:

tape
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.Rows and widgets.Cols come from one template, but a rule for one never reaches the other.
  • Each property must be a prop the component declares, set at most once per rule. Events (on_click), slots, vars and state flags 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 color or 300 for a u8 is a compile error at the rule.

Nested rules

A rule inside another one reaches only the instances inside that component:

tape
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:

  1. a value written at the instantiation;
  2. else the rule with the most names in its selector (Toolbar { widgets.Button { } } beats widgets.Button { });
  3. else, between equally specific rules, the later one (imported rules count as coming first);
  4. else the prop’s default.

Values

A value is a constant the compiler can work out:

  • literals: numbers, true/false, strings, colors (#RRGGBB, #RRGGBBAA), null for a pointer;
  • enum variants: ui.ScrollDir.Both;
  • constants: the module’s consts, pub consts of an imported module, and constants of an imported #style region (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

tape
#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

tape
#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, as theme.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):

tape
#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:

tape
#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:

tape
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:

tape
#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: