guido 0.12.0

A reactive Rust GUI library using wgpu for Wayland layer shell widgets
Documentation
# Custom Widgets and Layouts

Guido ships few widgets on purpose. `Container` is the box — padding, colours,
corners, borders, events — and `Text`, `Image` and `TextInput` are the things
that draw content. A checkbox, a toggle, a slider are compositions of those,
written as functions or as `#[component]`s in your own app.

Occasionally that is not enough: you need to draw something the primitives
cannot express, or arrange children in a way `Flex` and `ZStack` do not. Both
are extension points, and both are reachable from outside the crate.

## The two preludes

`guido::prelude` is the application's vocabulary. Writing a widget or a layout
needs a second one:

```rust
# extern crate guido;
# fn main() {
use guido::prelude::*;
use guido::widget_prelude::*;
# ;
# }
```

`widget_prelude` adds what the `Widget` and `Layout` signatures name — `Tree`,
`WidgetId`, `Constraints`, `PaintContext`, `RenderNode`, `LayoutHints`,
`ChildPaintOptions` — and `Transform`, which is what a widget positions what
it draws with. An application never names `Transform`: it declares `translate`,
`rotate` and `scale`, and those compose into one.

It also carries `with_signal_tracking` and `JobType`. Neither is needed to
write a widget any more — the framework scopes both passes — but they are what
a widget reaches for to claim a *narrower* attribution than its own pass: one
dynamic-children segment rather than the whole of it.

## A widget

`Widget` has two required methods. Everything else — `event`,
`advance_animations`, `reconcile_children`, `layout_hints`,
`register_children`, `refresh_paint_bounds`, and the three a removed widget is
asked about its exit, `begin_exit`, `is_exiting` and `cancel_exit` — has a
default.

A row that cannot fit its children shrinks the ones that can be narrower, and
it learns how narrow from what each recorded during its layout. A widget that
records nothing is never shrunk. One that can give room up — it ellipsizes,
or scales what it draws — says how far with `ctx.set_minimum(size)` in its
`layout`, on both axes.

`event` is only called with what was routed to your widget: a pointer event
when the widget can be under its point or the pointer still owes it one, a key
when it holds the focus or is on the way to what does. It is not offered every
event, so there is no need to check whether one was meant for it — and no way
to hear the keyboard from a widget nothing has focused, other than a
container's `on_key_down` and `on_key_up`. [Event Routing](../architecture/events.md#event-routing)
says where each kind goes.

One of those defaults is worth knowing about if your widget draws outside the
box it was given. A parent narrows its children to the visible region before
painting them, and it does that by their laid-out bounds — so a widget that
paints elsewhere, because it transforms itself or casts something past its
edges, has to say how far. That is `refresh_paint_bounds`, which reports it with
`Tree::set_own_paint_reach`. The framework calls it the same way it calls the
other two — inside your widget's own Paint scope — so read your signals
plainly here too, and a write to one repaints your widget without reflowing
anything. It runs after every layout *and* from the paint job, because either
can be the last pass to have run.

Read them untracked and you get the bug this closed: a widget culled before it
ever painted has read nothing, so nothing can tell it to come back, and a
transform that would carry it into view reaches no one.

Which pass you publish from follows what your reach depends on. If it moves with
something layout already tracks — a size, a font — publish it from `layout`,
which is what the built-in text widgets do for their stroke and shadow. If it
moves with a paint-only property such as a transform, publish it here.

```rust
# extern crate guido;
# use guido::prelude::*;
# use guido::widget_prelude::*;
struct Bar {
    extent: Signal<f32>,
    measured: f32,
}

impl Widget for Bar {
    fn layout(&mut self, _ctx: &mut LayoutCtx, _constraints: Constraints) -> Size {
        let extent = self.extent.get();
        self.measured = extent;
        Size::new(extent, extent)
    }

    fn paint(&self, ctx: &mut PaintContext) {
        // Local coordinates: the parent has already placed this node.
        ctx.draw_rounded_rect(
            Rect::new(0.0, 0.0, self.measured, self.measured),
            Color::RED,
            0.0,
        );
    }
}
# fn main() {}
```

Three things are worth spelling out.

**Position lives in the `Tree`, not on the widget.** `layout` measures and
reports; the parent decides where the result goes. Paint therefore happens in
local coordinates, with `(0, 0)` at the widget's own origin.

**A layout measures; it does not decide whether it runs.** The framework calls
it through `LayoutCtx::layout_child`, and that call holds the check that skips a
widget with the same constraints and nothing dirty, the scope that makes the
widget's reads belong to *it*, the pass it is in, and the cache the size goes
into. So there is nothing to remember: read the signals plainly and return a
size.

**A paint draws; it does not scope itself either.** The framework calls it
through `PaintContext::paint_child`, and that call holds the cull test that
skips a widget nobody can see, the cache a clean one is served from, and the
same scope layout gets — so a signal read while drawing belongs to this widget
and repaints this widget. `ctx.id()` is which widget it is and `ctx.tree()` is
the tree it is in.

**The scale is the frame's.** Everything a paint draws is in logical pixels,
and the renderer scales it to the surface. A widget that builds a raster of
its own — a chart drawn into a buffer, a generated texture — wants one source
pixel per device pixel, and `ctx.scale_factor()` says how many those are: the
scale of the frame being painted, fractional where the compositor says so, 1
until it has said anything. Read it while painting and nothing else is needed:
a scale change repaints the whole surface, and each surface answers with its
own. Round rather than truncate, as the surface's own buffer does, and keep
the raster between paints if building it is not cheap — the sample below
rebuilds it every time for brevity.

```rust
# extern crate guido;
# use guido::prelude::*;
# use guido::widget_prelude::*;
# use std::sync::Arc;
struct Noise;

impl Widget for Noise {
    fn layout(&mut self, _ctx: &mut LayoutCtx, constraints: Constraints) -> Size {
        constraints.constrain(Size::new(100.0, 20.0))
    }

    fn paint(&self, ctx: &mut PaintContext) {
        let scale = ctx.scale_factor();
        let (width, height) = ((100.0 * scale).round() as u32, (20.0 * scale).round() as u32);
        let pixels: Vec<u8> = (0..width * height * 4).map(|i| (i % 251) as u8).collect();
        ctx.draw_image(
            ImageSource::Rgba { width, height, pixels: Arc::from(pixels) },
            Rect::new(0.0, 0.0, 100.0, 20.0),
            ContentFit::Fill,
            None,
        );
    }
}
# fn main() {}
```

This is newer than it looks: until #390 a paint really did have to open its
own scope, and a widget that did not subscribed to *nothing* — it drew once and
then stayed as it was. The advice here used to say that the read fell through
to the parent container. It did not.

**Which pass is running is `ctx.measuring()`.** True while a natural size is
being measured, before a surface exists, which is when an animated value reads
as where it is going rather than where it is. A widget that animates nothing
never has to ask.

## A layout

`Layout` has one method: given the children and the constraints, place them and
report the size the parent should use.

```rust
# extern crate guido;
# use guido::prelude::*;
# use guido::widget_prelude::*;
struct Stagger {
    step: f32,
}

impl Layout for Stagger {
    fn layout(
        &mut self,
        ctx: &mut LayoutCtx,
        children: &[WidgetId],
        constraints: Constraints,
        origin: (f32, f32),
    ) -> Size {
        let mut size = Size::zero();
        for (i, &child_id) in children.iter().enumerate() {
            let child = ctx.layout_child(child_id, constraints).unwrap_or_default();

            let x = origin.0 + i as f32 * self.step;
            let y = origin.1 + i as f32 * self.step;
            ctx.tree().set_origin(child_id, x, y);

            size.width = size.width.max(x + child.width - origin.0);
            size.height = size.height.max(y + child.height - origin.1);
        }
        constraints.constrain(size)
    }
}
# fn main() {}
```

It plugs into any container:

```rust
# extern crate guido;
# use guido::prelude::*;
# use guido::widget_prelude::*;
# fn card(_label: &str) -> Container { container() }
# struct Stagger {
#     step: f32,
# }
# impl Layout for Stagger {
#     fn layout(
#         &mut self,
#         ctx: &mut LayoutCtx,
#         children: &[WidgetId],
#         constraints: Constraints,
#         origin: (f32, f32),
#     ) -> Size {
#         let mut size = Size::zero();
#         for (i, &child_id) in children.iter().enumerate() {
#             let child = ctx.layout_child(child_id, constraints).unwrap_or_default();
#             let x = origin.0 + i as f32 * self.step;
#             let y = origin.1 + i as f32 * self.step;
#             ctx.tree().set_origin(child_id, x, y);
#             size.width = size.width.max(x + child.width - origin.0);
#             size.height = size.height.max(y + child.height - origin.1);
#         }
#         constraints.constrain(size)
#     }
# }
# fn main() {
container()
    .layout(Stagger { step: 8.0 })
    .child(card("one"))
    .child(card("two"))
# ;
# }
```

The absence of a built-in grid is not a gap — this is where a grid goes.

If your layout reads signals of its own (a reactive spacing, an alignment that
follows a setting), read them straight from `layout`: the container runs it
inside its own tracking scope, so those reads are attributed correctly without
you opening one.

`ctx.tree_ref().with_widget(child_id, |w| w.layout_hints())` reports whether a child
wants to fill an axis, which is what lets a layout give the remaining space to
the children that asked for it, and whether it is hidden, which takes no space
at all — `ZStack` in the guido source is the short example to read.

A layout can record the least its children can be arranged in the same way,
with `ctx.set_minimum`, reading each child's with
`ctx.tree_ref().minimum(child_id)`. That is what lets the container holding it
give room up in a crowded row. One that records nothing keeps its container
at the size it was laid out — the stagger above never shrinks — and `ZStack`'s,
which records the largest of its children's, is the short one to copy.

## Checking your work

`tests/external_widget.rs` in the guido repository is a widget written against
the public API only, with a test that lays it out and paints it. It is the
shortest complete example of everything on this page.