Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ blit.style.pop(?ctx);
blit.style.pop(?ctx);
```

A paint's `fill_to` follows its `fill`: every write of a fill through a path
(a push, a class, `theme.set` and `set_path`, a TOML document) writes `fill_to`
too, so a fill alone gives a flat face. Writing `fill_to` as well, in the same
push, class or document in any order, or in a later push, gives the gradient,
and a pop restores both. A save writes `fill_to` after `fill` whenever the two
differ, so the gradient reads back.

`push_color`, `push_length`, `push_radii`, `push_edges` and the untyped `push`
refuse a path naming no field of their unit, and still open an empty push so
every push pairs with its pop. A frame left with pushes open is restored at
Expand Down Expand Up @@ -347,7 +354,7 @@ shows.
| `<kind>.shadow_y` | length | shadow offset down |
| `<kind>.shadow_blur` | length | how far the shadow fades out, 0 for a hard edge |
| `<kind>.<state>.fill` | color | the face, at its top when it is a gradient |
| `<kind>.<state>.fill_to` | color | the face at its bottom: equal to fill for a flat face, else a vertical gradient |
| `<kind>.<state>.fill_to` | color | the face at its bottom, a vertical gradient from fill: written with every write of fill, so a fill alone is flat |
| `<kind>.<state>.text` | color | text drawn on the face |
| `<kind>.<state>.mark` | color | secondary ink: a glyph, a caret, a reading or a hint |
| `<kind>.<state>.border` | color | the outline, transparent for none |
Expand Down
49 changes: 33 additions & 16 deletions src/sheet.mach
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
# popping it restores the theme exactly. one push writes every field its path
# names, so "*.hot.fill" is one push and one pop. a class is a named list of
# path and value settings, and pushing it is one push of all of them in
# order, so a later setting wins over an earlier one for the same field.
# order, so a later setting wins over an earlier one for the same field. a
# write of a paint's fill writes its fill_to too (see theme.follower), and the
# pop restores both.
# classes are allocator-backed and a sheet grows them; a push the allocator
# refuses writes nothing and reports it.

Expand Down Expand Up @@ -121,22 +123,30 @@ pub fun free(s: *Sheet) err[allo.Error] {
ret first;
}

# write every field a path names and save what it replaced, without a mark
# write every field a path names, each with the field that follows it (see
# theme.follower), and save what they replaced, without a mark
fun write(s: *Sheet, t: *th.Theme, p: th.Path, v: th.Value) bool {
val n: usize = th.span(t, p);
val room: res[usize, allo.Error] = vec.reserve[Saved](?s.saved, n);
val room: res[usize, allo.Error] = vec.reserve[Saved](?s.saved, n * 2);
if (sel room.err) { ret false; }
var i: usize = 0;
for (i < n) {
val r: th.Ref = th.ref_at(p, i);
s.saved.data[s.saved.len] = Saved{r: r, old: th.get(t, r)};
s.saved.len = s.saved.len + 1;
th.put(t, r, v);
keep(s, t, r, v);
val f: opt[th.Ref] = th.follower(r);
if (sel f.some) { keep(s, t, f.some, v); }
i = i + 1;
}
ret true;
}

# write one field and save what it replaced, its room reserved
fun keep(s: *Sheet, t: *th.Theme, r: th.Ref, v: th.Value) {
s.saved.data[s.saved.len] = Saved{r: r, old: th.get(t, r)};
s.saved.len = s.saved.len + 1;
th.put(t, r, v);
}

# open a push: one mark the next pop returns to
fun open(s: *Sheet) bool {
val pushed: res[usize, allo.Error] = vec.push[usize](?s.marks, s.saved.len);
Expand Down Expand Up @@ -332,7 +342,9 @@ pub fun class_clear(s: *Sheet, id: u32) {
#
# each setting's path is resolved against the theme now, and a setting
# naming no field of it, such as a kind the theme has not registered, is
# skipped.
# skipped. settings of fields that follow another (see theme.follower) are
# written after the rest, so a class's fill_to wins over its fills wherever
# it is listed.
# ---
# s: sheet
# t: the theme it overrides
Expand All @@ -341,17 +353,22 @@ pub fun class_clear(s: *Sheet, id: u32) {
pub fun push_class(s: *Sheet, t: *th.Theme, id: u32) bool {
if (id::usize >= s.classes.len) { ret false; }
if (!open(s)) { ret false; }
val c: *Class = ?s.classes.data[id];
var i: usize = 0;
for (i < c.sets.len) {
val p: opt[th.Path] = th.path(t, c.sets.data[i].text::str);
if (sel p.some) {
if (th.FIELDS[p.some.field].unit == c.sets.data[i].unit && !write(s, t, p.some, c.sets.data[i].v)) {
pop(s, t);
ret false;
val c: *Class = ?s.classes.data[id];
var late: u8 = 0;
for (late < 2) {
var i: usize = 0;
for (i < c.sets.len) {
val p: opt[th.Path] = th.path(t, c.sets.data[i].text::str);
if (sel p.some) {
val fits: bool = th.FIELDS[p.some.field].unit == c.sets.data[i].unit;
if (fits && th.follows(th.ref_at(p.some, 0)) == (late == 1) && !write(s, t, p.some, c.sets.data[i].v)) {
pop(s, t);
ret false;
}
}
i = i + 1;
}
i = i + 1;
late = late + 1;
}
ret true;
}
Expand Down
86 changes: 83 additions & 3 deletions src/style.mach
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
# blit.widget.button(?ctx, "delete");
# blit.style.pop(?ctx);
#
# draws one red button. a class is a named list of such settings, defined in
# code with add_class and set, or in a theme document's [class.<name>]
# tables, and push_class applies all of them to the widgets until its pop.
# draws one solid red button: a fill pushed alone writes fill_to too, and
# pushing fill_to as well, after it or in a class with it, makes the face a
# gradient (see blit.theme.follower). a class is a named list of such
# settings, defined in code with add_class and set, or in a theme document's
# [class.<name>] tables, and push_class applies all of them to the widgets
# until its pop.
# pushes nest, and begin undoes any a frame left open.
#
# load_theme replaces the context's theme with one read from TOML and reads
Expand Down Expand Up @@ -447,6 +450,83 @@ test registry__a_registered_kind_loads_from_toml_and_paints {
ret 0;
}

# whether a kind's paint in a state is a flat face of a color's red
#[testing]
fun flat(ctx: *context.Context, k: u32, state: u8, r: f32) bool {
val p: th.Paint = th.style(?ctx.theme, k).states[state];
ret p.fill.r == r && p.fill_to.r == r && p.fill_to.g == p.fill.g && p.fill_to.b == p.fill.b;
}

# whether a kind's paint in a state runs from one red at the top to another
#[testing]
fun graded(ctx: *context.Context, k: u32, state: u8, top: f32, bottom: f32) bool {
val p: th.Paint = th.style(?ctx.theme, k).states[state];
ret p.fill.r == top && p.fill_to.r == bottom;
}

test fill__alone_is_flat_and_fill_to_makes_the_gradient {
var a: allo.Allocator;
var ctx: context.Context;
if (!made(?ctx, ?a)) { ret 1; }
# the dark look shades its buttons, so every face starts as a gradient
val e: err[th.LoadError] = load_theme(?ctx, "base = \"dark\"\n");
if (sel e.err) { ret 2; }
var was: th.Theme = snapshot(?ctx, ?a);
val top: f32 = th.style(?was, th.BUTTON).states[th.HOT].fill.r;
if (th.style(?was, th.BUTTON).states[th.HOT].fill_to.r == top) { ret 3; }
val red: draw.Color = draw.rgba(1.0::f32, 0.0::f32, 0.0::f32, 1.0::f32);
val dark: draw.Color = draw.rgba(0.5::f32, 0.0::f32, 0.0::f32, 1.0::f32);

# a pushed fill is flat, a fill_to pushed after it grades it, and the
# pops restore the theme's gradient
push_color(?ctx, "button.*.fill", red);
if (!flat(?ctx, th.BUTTON, th.HOT, 1.0::f32)) { ret 4; }
push_color(?ctx, "button.hot.fill_to", dark);
if (!graded(?ctx, th.BUTTON, th.HOT, 1.0::f32, 0.5::f32)) { ret 5; }
if (!flat(?ctx, th.BUTTON, th.NORMAL, 1.0::f32)) { ret 6; }
pop(?ctx);
if (!flat(?ctx, th.BUTTON, th.HOT, 1.0::f32)) { ret 7; }
pop(?ctx);
if (!same_look(?ctx.theme, ?was)) { ret 8; }

# a class grades only where it sets fill_to, wherever it lists it
val id: opt[u32] = add_class(?ctx, "graded");
if (sel id.none) { ret 9; }
set(?ctx, id.some, "button.hot.fill_to", th.of_color(dark));
set(?ctx, id.some, "button.*.fill", th.of_color(red));
push_class(?ctx, id.some);
if (!graded(?ctx, th.BUTTON, th.HOT, 1.0::f32, 0.5::f32)) { ret 10; }
if (!flat(?ctx, th.BUTTON, th.ON, 1.0::f32)) { ret 11; }
pop(?ctx);
if (!same_look(?ctx.theme, ?was)) { ret 12; }

# so does a document, and setting a path
val doc: str = "base = \"dark\"\n[button.hot]\nfill_to = \"#800000\"\nfill = \"#ff0000\"\n[button.on]\nfill = \"#ff0000\"\n";
val d: err[th.LoadError] = load_theme(?ctx, doc);
if (sel d.err) { ret 13; }
if (!graded(?ctx, th.BUTTON, th.HOT, 1.0::f32, 128.0::f32 / 255.0::f32)) { ret 14; }
if (!flat(?ctx, th.BUTTON, th.ON, 1.0::f32)) { ret 15; }
val p: opt[th.Path] = th.path(?ctx.theme, "button.active.fill");
if (sel p.none) { ret 16; }
th.set_path(?ctx.theme, p.some, th.of_color(red));
if (!flat(?ctx, th.BUTTON, th.ACTIVE, 1.0::f32)) { ret 17; }

# a fill changed on a shaded face saves its fill_to and reads back the same
th.style(?ctx.theme, th.BUTTON).states[th.NORMAL].fill.r = 1.0::f32;
val saved: res[str, allo.Error] = save_theme(?ctx);
if (sel saved.err) { ret 18; }
var b: allo.Allocator;
var back: context.Context;
if (!made(?back, ?b)) { ret 19; }
val e2: err[th.LoadError] = load_theme(?back, saved.ok);
if (sel e2.err) { ret 20; }
if (!same_look(?ctx.theme, ?back.theme)) { ret 21; }
val f: err[allo.Error] = context.free(?ctx);
val f2: err[allo.Error] = context.free(?back);
val f3: err[allo.Error] = th.free(?was);
ret 0;
}

# whether a vertex of the frame's draw list has a premultiplied color's red
# and alpha, and how far down the lowest one lies
#[testing]
Expand Down
Loading
Loading