pub struct Element<W: Widget> { /* private fields */ }Expand description
A node under construction: its widget, its style, its pending bindings and its children.
Build one with a widget constructor such as panel,
chain the builder methods, and hand it to a parent with Element::child
or to show.
A builder takes either a plain value, which is written into the style immediately, or a closure, which becomes a binding that is read at mount and again whenever a signal it reads changes.
Implementations§
Source§impl<W: Widget> Element<W>
impl<W: Widget> Element<W>
Sourcepub fn new(widget: W) -> Element<W>
pub fn new(widget: W) -> Element<W>
Creates an element for widget with the default style.
use smudge_core::element::Element;
use smudge_core::widget::Panel;
let element = Element::new(Panel);Sourcepub fn child(self, child: impl IntoElement) -> Self
pub fn child(self, child: impl IntoElement) -> Self
Appends child after the children the element already has.
When Element::style_children set a style for the children, it is
applied to this one.
Sourcepub fn children(
self,
children: impl IntoIterator<Item = impl IntoElement>,
) -> Self
pub fn children( self, children: impl IntoIterator<Item = impl IntoElement>, ) -> Self
Appends every child of children, in order.
When Element::style_children set a style for the children, it is
applied to each one.
Sourcepub fn style_children(self, style: impl Fn(&mut Style) + 'static) -> Self
pub fn style_children(self, style: impl Fn(&mut Style) + 'static) -> Self
Applies style to every direct child of this element: to the children
added so far, at once, and to every child added afterwards as it is
added.
The children of a child keep their own style, and a child that mounts
later through show mounts under a slot, so it is styled only
through that slot. A binding on the same field of a child applies when
the child mounts, after this style, so the binding wins.
This is how a container decides how its children sit: a stack applies the absolute position that makes children overlap, for example.
use smudge_core::element::Element;
use smudge_core::widget::Panel;
let layers = Element::new(Panel).style_children(|style| {
style.paint.corner_radius = 4.0.into();
});Sourcepub fn width(self, width: impl Into<Value<Length>>) -> Self
pub fn width(self, width: impl Into<Value<Length>>) -> Self
Sets the width. This takes part in layout.
Sourcepub fn height(self, height: impl Into<Value<Length>>) -> Self
pub fn height(self, height: impl Into<Value<Length>>) -> Self
Sets the height. This takes part in layout.
Sourcepub fn size(self, size: impl Into<Value<Size>>) -> Self
pub fn size(self, size: impl Into<Value<Size>>) -> Self
Sets the width and the height, in logical pixels. This takes part in layout.
Sourcepub fn min_width(self, width: impl Into<Value<Length>>) -> Self
pub fn min_width(self, width: impl Into<Value<Length>>) -> Self
Sets the lower bound of the width. This takes part in layout.
Sourcepub fn min_height(self, height: impl Into<Value<Length>>) -> Self
pub fn min_height(self, height: impl Into<Value<Length>>) -> Self
Sets the lower bound of the height. This takes part in layout.
Sourcepub fn max_width(self, width: impl Into<Value<Length>>) -> Self
pub fn max_width(self, width: impl Into<Value<Length>>) -> Self
Sets the upper bound of the width. This takes part in layout.
Sourcepub fn max_height(self, height: impl Into<Value<Length>>) -> Self
pub fn max_height(self, height: impl Into<Value<Length>>) -> Self
Sets the upper bound of the height. This takes part in layout.
Sourcepub fn padding(self, padding: impl Into<Value<Length>>) -> Self
pub fn padding(self, padding: impl Into<Value<Length>>) -> Self
Sets the same padding on all four sides. This takes part in layout.
Sourcepub fn padding_xy(self, padding: impl Into<Value<Size>>) -> Self
pub fn padding_xy(self, padding: impl Into<Value<Size>>) -> Self
Sets the horizontal padding from width and the vertical padding from
height. This takes part in layout.
Sourcepub fn margin(self, margin: impl Into<Value<Length>>) -> Self
pub fn margin(self, margin: impl Into<Value<Length>>) -> Self
Sets the same margin on all four sides. This takes part in layout.
Sourcepub fn gap(self, gap: impl Into<Value<Size>>) -> Self
pub fn gap(self, gap: impl Into<Value<Size>>) -> Self
Sets the space between adjacent children. This takes part in layout.
Sourcepub fn direction(self, direction: impl Into<Value<Direction>>) -> Self
pub fn direction(self, direction: impl Into<Value<Direction>>) -> Self
Sets the main axis of the children’s flex layout. This takes part in layout.
Sourcepub fn wrap(self, wrap: impl Into<Value<bool>>) -> Self
pub fn wrap(self, wrap: impl Into<Value<bool>>) -> Self
Lets children that do not fit move to a new line instead of shrinking. This takes part in layout.
Sourcepub fn grow(self, grow: impl Into<Value<f32>>) -> Self
pub fn grow(self, grow: impl Into<Value<f32>>) -> Self
Sets the share of the leftover space on the main axis this node takes. This takes part in layout.
Sourcepub fn shrink(self, shrink: impl Into<Value<f32>>) -> Self
pub fn shrink(self, shrink: impl Into<Value<f32>>) -> Self
Sets how readily this node gives up space when its siblings overflow. This takes part in layout.
Sourcepub fn basis(self, basis: impl Into<Value<Length>>) -> Self
pub fn basis(self, basis: impl Into<Value<Length>>) -> Self
Sets the main axis size the node starts from before growing or shrinking. This takes part in layout.
Sourcepub fn align_items(self, align: impl Into<Value<Align>>) -> Self
pub fn align_items(self, align: impl Into<Value<Align>>) -> Self
Sets how the children line up across the main axis. This takes part in layout.
Sourcepub fn justify_content(self, justify: impl Into<Value<Justify>>) -> Self
pub fn justify_content(self, justify: impl Into<Value<Justify>>) -> Self
Sets how the children are spread along the main axis. This takes part in layout.
Sourcepub fn absolute(self) -> Self
pub fn absolute(self) -> Self
Takes the node out of normal flow, so its insets place it against its containing block and its siblings are laid out as if it were not there. This takes part in layout.
Sourcepub fn inset(self, inset: impl Into<Value<Edges<Length>>>) -> Self
pub fn inset(self, inset: impl Into<Value<Edges<Length>>>) -> Self
Sets the offsets from the containing block’s edges. This takes part in layout.
Sourcepub fn border(self, width: impl Into<Value<f32>>) -> Self
pub fn border(self, width: impl Into<Value<f32>>) -> Self
Sets the border width, the same on all four sides. This takes part in
layout, because the border takes space away from the content. The border
is painted in the color set by Element::border_color.
Sourcepub fn clip(self, clip: impl Into<Value<bool>>) -> Self
pub fn clip(self, clip: impl Into<Value<bool>>) -> Self
Clips the children to the node’s bounds. This takes part in layout, because clipping also changes the automatic minimum size of a flex item.
Sourcepub fn overflow(self, overflow: impl Into<Value<Overflow>>) -> Self
pub fn overflow(self, overflow: impl Into<Value<Overflow>>) -> Self
Sets what happens to children that do not fit inside the node. This
takes part in layout: Overflow::Scroll makes the node a scroll
container, whose automatic minimum size as a flex item is zero, and
Element::clip is the shorthand for Overflow::Clip.
Hides the node and its subtree, or shows them again. This takes part in layout, because a hidden node is not laid out and paints nothing.
Sourcepub fn scroll_offset(self, offset: impl Into<Value<Point>>) -> Self
pub fn scroll_offset(self, offset: impl Into<Value<Point>>) -> Self
Translates the node’s flowing children by this offset after layout, outside the layout engine.
This is what a scroll view changes when it scrolls: every child that stays in the flow moves by minus the offset in the window, without running the layout engine again. The frame damages the areas the children moved out of and into and wraps them in an offset display item, so a child that only moved does not paint again. An absolutely positioned child is left where its insets place it, so chrome drawn over the content does not scroll away with it.
The offset can be bound to a signal, like the style builders. It is a position, not a style: it takes part in no layout comparison, and an equal offset changes nothing.
Sourcepub fn pin_child(self, pin: impl Fn() -> Option<(usize, f32)> + 'static) -> Self
pub fn pin_child(self, pin: impl Fn() -> Option<(usize, f32)> + 'static) -> Self
Keeps one flowing child at a fixed place inside the node, whatever the heights of the children before it.
While pin returns Some((index, y)), every layout pass ends with
the top of the flowing child at index sitting y logical pixels
below the top of the node’s content box. The children keep their
order and the sizes the pass gave them, so the ones before the pinned
child stack upward from it and the ones after it stack downward,
edge to edge. A child that grows therefore pushes its neighbours away
from the pinned child in the frame it grows, and never overlaps them.
The pin moves the children the way Element::scroll_offset does,
outside the layout engine, so do not give the node a scroll offset
too. None, an index past the last child, or a pinned child that is
hidden or absolutely positioned leaves the children where they are. A change of the pin alone moves them without a
layout pass.
use smudge_core::element::Element;
use smudge_core::style::px;
use smudge_core::widget::{Panel, panel};
// The second row stays 100 pixels down, however tall the first is.
let rows = Element::new(Panel)
.column()
.pin_child(|| Some((1, 100.0)))
.child(panel().height(px(30.0)))
.child(panel().height(px(30.0)));Sourcepub fn background(self, color: impl Into<Value<ColorValue>>) -> Self
pub fn background(self, color: impl Into<Value<ColorValue>>) -> Self
Sets the fill painted behind the node. This repaints the node without laying it out again.
The color can be a Color or a
ColorToken. A token resolves against the
theme that is current when the node paints.
Sourcepub fn border_color(self, color: impl Into<Value<ColorValue>>) -> Self
pub fn border_color(self, color: impl Into<Value<ColorValue>>) -> Self
Sets the color of the border whose width Element::border sets. This
repaints the node without laying it out again.
The color can be a Color or a
ColorToken. A token resolves against the
theme that is current when the node paints.
Sourcepub fn text_color(self, color: impl Into<Value<ColorValue>>) -> Self
pub fn text_color(self, color: impl Into<Value<ColorValue>>) -> Self
Sets the color a widget’s text paints with. This repaints the node without laying it out again.
Widgets that show text read it; widgets without text ignore it. The
color can be a Color or a
ColorToken. A token resolves against the
theme that is current when the node paints.
Sourcepub fn corner_radius(self, radius: impl Into<Value<RadiusValue>>) -> Self
pub fn corner_radius(self, radius: impl Into<Value<RadiusValue>>) -> Self
Sets the corner radius of the background and the border, the same on all four corners. An image widget rounds the pixels it paints by the same radius. This repaints the node without laying it out again.
The radius can be an f32 or a
RadiusToken. A token resolves against
the theme that is current when the node paints.
Sourcepub fn hover(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self
pub fn hover(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self
Applies a patch while the node is in the hovered chain, which is the node under the pointer that can respond and its ancestors.
The closure records static values into a PatchBuilder; a reactive
value has no place in a patch, so the patch methods take plain values.
While the pointer is over the node the patch overrides the fields it
sets, and when the pointer leaves, the base style stands again. A node
with a variant is a target for hover and presses even without
handlers, so a styled card reacts to the pointer on its own.
A change of hover re-resolves the style: a patch that touches only paint properties repaints the node, one that touches layout properties lays it out again. Hover also follows a layout that moves nodes under a resting pointer, except that a layout never takes the hover away from a node whose hover patch changes its layout: such a patch can move the node off the pointer, and the node then stays hovered until the pointer moves, instead of flickering.
use smudge_core::geometry::Color;
use smudge_core::widget::panel;
let card = panel()
.background(Color::rgb(50, 50, 50))
.hover(|p| p.background(Color::rgb(70, 70, 70)));Sourcepub fn focused(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self
pub fn focused(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self
Applies a patch while the node holds the keyboard focus and the focus last moved by keyboard navigation.
The focus a press moved draws no focus style, so a ring follows Tab and
not a mouse click. Everything else works as
hover does.
use smudge_core::geometry::Color;
use smudge_core::style::px;
use smudge_core::widget::panel;
let field = panel()
.width(px(120.0))
.height(px(24.0))
.focusable()
.focused(|p| p.border(1.0).border_color(Color::rgb(0, 120, 215)));Sourcepub fn pressed(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self
pub fn pressed(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self
Applies a patch while a primary press is held on the node.
Everything works as hover does; the pressed state
starts when the button goes down on the node and ends when it is
released.
use smudge_core::geometry::Color;
use smudge_core::widget::panel;
let button = panel()
.background(Color::rgb(50, 50, 50))
.pressed(|p| p.background(Color::rgb(30, 30, 30)));Sourcepub fn disabled_style(
self,
patch: impl FnOnce(PatchBuilder) -> PatchBuilder,
) -> Self
pub fn disabled_style( self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder, ) -> Self
Applies a patch while the node is disabled.
The patch wins over every other state, so a disabled control keeps its
switched-off look while it is hovered or pressed. Everything else
works as hover does.
use smudge_core::geometry::Color;
use smudge_core::widget::panel;
let button = panel()
.background(Color::rgb(50, 50, 50))
.disabled_style(|p| p.background(Color::rgb(35, 35, 35)));Sourcepub fn on_press(self, handler: impl FnMut() + 'static) -> Self
pub fn on_press(self, handler: impl FnMut() + 'static) -> Self
Runs handler when the primary button is released over the node after
a press that started on it.
The press is captured: between the press and the release the node keeps the pointer even when it moves away, and the handler runs only when the release happens over the node again. A press released elsewhere runs nothing.
use std::cell::Cell;
use std::rc::Rc;
use smudge_core::app::{App, AppConfig, RepaintMode};
use smudge_core::geometry::{Color, Point, Size};
use smudge_core::input::{InputEvent, PointerButton};
use smudge_core::style::px;
use smudge_core::widget::panel;
let presses = Rc::new(Cell::new(0u32));
let counted = Rc::clone(&presses);
let mut app = App::new(
AppConfig {
viewport: Size {
width: 100.0,
height: 60.0,
},
scale: 1.0,
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
},
move || {
panel().width(px(40.0)).height(px(20.0)).on_press(move || {
counted.set(counted.get() + 1);
})
},
);
app.frame();
let corner = Point { x: 10.0, y: 5.0 };
app.handle_event(InputEvent::PointerMoved { position: corner });
app.handle_event(InputEvent::PointerDown {
position: corner,
button: PointerButton::Primary,
});
app.handle_event(InputEvent::PointerUp {
position: corner,
button: PointerButton::Primary,
});
assert_eq!(presses.get(), 1);Sourcepub fn on_hover(self, handler: impl FnMut(bool) + 'static) -> Self
pub fn on_hover(self, handler: impl FnMut(bool) + 'static) -> Self
Runs handler when the node enters (true) or leaves (false) the
hovered chain.
The hovered chain is the nearest enabled node under the pointer that can respond and its enabled ancestors, so a container stays hovered while the pointer is over one of its children, and a disabled node never hears it. While a press is held, hover does not change until the button is released. The handler runs inside the node’s owner, so it can write the signals the element’s bindings read.
Sourcepub fn on_pointer_down(self, handler: impl FnMut(Point) + 'static) -> Self
pub fn on_pointer_down(self, handler: impl FnMut(Point) + 'static) -> Self
Runs handler when the primary pointer button is pressed on the node.
The position is in logical pixels, relative to the top left of the node’s rectangle, so it does not depend on where layout or a scroll view put the node. Other buttons do not run it.
Sourcepub fn on_press_outside(self, handler: impl FnMut() + 'static) -> Self
pub fn on_press_outside(self, handler: impl FnMut() + 'static) -> Self
Runs handler when a primary press targets a node that is neither this
node nor one of its descendants.
A press on the node itself, on a child, or on a deeper descendant does not run it, so a popup asks for presses outside itself and closes on the ones it gets. A press that reaches no node at all is outside every node.
Sourcepub fn on_wheel(self, handler: impl FnMut(Point) -> Handled + 'static) -> Self
pub fn on_wheel(self, handler: impl FnMut(Point) -> Handled + 'static) -> Self
Runs handler when the wheel scrolls while the node is in the hovered
chain, which is the case when the pointer is over the node or over one
of its descendants.
The delta is in logical pixels, positive for scrolling right and down.
The handlers run from the deepest node in the hovered chain outward,
and the first that returns Handled::Yes takes the whole event, so
of two nested scroll views the one under the pointer scrolls and the
one around it does not. A handler that cannot use the event, such as
a view already scrolled to its end in the wheel’s direction, returns
Handled::No and the event bubbles to the next one. The handler
runs inside the node’s owner, so it can write the signals the
element’s bindings read.
Sourcepub fn on_pointer_move(self, handler: impl FnMut(Point) + 'static) -> Self
pub fn on_pointer_move(self, handler: impl FnMut(Point) + 'static) -> Self
Runs handler when the pointer moves while a press that started on
the node is held, which is how a drag reaches the node it started on.
With no press held, the handler runs when the node is the hover target and the pointer moves, so a node can follow the pointer without being pressed. While a press is held, hover does not change, and the moves go to the captured node alone. The position is in logical pixels, relative to the top left of the node’s rectangle. The handler runs inside the node’s owner, so it can write the signals the element’s bindings read.
Sourcepub fn on_pointer_up(self, handler: impl FnMut(Point) + 'static) -> Self
pub fn on_pointer_up(self, handler: impl FnMut(Point) + 'static) -> Self
Runs handler when the primary button is released.
After a press on the node, the handler runs when the button comes up wherever that happens, which is how a drag ends even when the pointer is no longer over the node. With no press held, the handler runs when the release happens over the node as the hover target. The position is in logical pixels, relative to the top left of the node’s rectangle. The handler runs inside the node’s owner, so it can write the signals the element’s bindings read.
Sourcepub fn on_ime_commit(self, handler: impl FnMut(&str) + 'static) -> Self
pub fn on_ime_commit(self, handler: impl FnMut(&str) + 'static) -> Self
Runs handler when the input method commits text while the node or
one of its descendants holds the keyboard focus.
A commit can arrive with no key event at all, because composing text with an input method ends in a commit rather than a key press, so an editor that takes text needs this handler. The commit goes to the deepest handler in the focused chain and does not bubble past it. While a focus trap is active and the focus is outside it, the commit starts at the trap instead, as a key does. The handler runs inside the node’s owner, so it can write the signals the element’s bindings read.
Sourcepub fn on_ime_preedit(
self,
handler: impl FnMut(&str, Option<(usize, usize)>) + 'static,
) -> Self
pub fn on_ime_preedit( self, handler: impl FnMut(&str, Option<(usize, usize)>) + 'static, ) -> Self
Runs handler when the input method changes its composing text while
the node or one of its descendants holds the keyboard focus.
The handler receives the composing text and the byte range of the
cursor inside it, or None when the input method shows no cursor. An
empty text means the composition was canceled or committed, and the
preedit must go. An editor draws the composing text itself, usually
underlined, and does not write it into its value until the commit
arrives. Like Element::on_ime_commit, the event goes to the
deepest handler in the focused chain and does not bubble past it, and
the handler runs inside the node’s owner.
Sourcepub fn disabled(self, disabled: impl Into<Value<bool>>) -> Self
pub fn disabled(self, disabled: impl Into<Value<bool>>) -> Self
Disables the node, which takes it out of hit testing, or enables it again.
A disabled node receives no pointer events of its own: its handlers never run, and a press over it passes to whatever enabled node is hit instead. Its children are still hit. This can be bound to a signal, like the style builders.
Sourcepub fn cursor(self, cursor: impl Into<Value<Option<Cursor>>>) -> Self
pub fn cursor(self, cursor: impl Into<Value<Option<Cursor>>>) -> Self
Sets the cursor the pointer shows while it is over the node, or over a descendant that sets none of its own.
The window shows the cursor of the deepest enabled node under the
pointer that sets one, so a nested setter wins over an ancestor’s.
Setting a cursor does not make the node a target: a press over a node
that sets only a cursor still lands on the nearest ancestor that
responds, and so does hover. This takes a Cursor, or an
Option<Cursor> where None leaves the choice to the ancestors, and
it can be bound to a signal, like the style builders: a binding that
returns None hands the choice back to the ancestors.
use smudge_core::geometry::Color;
use smudge_core::widget::panel;
use smudge_core::window::Cursor;
let link = panel()
.width(smudge_core::style::px(80.0))
.height(smudge_core::style::px(24.0))
.background(Color::rgb(40, 40, 48))
.cursor(Cursor::Pointer);Sourcepub fn role(self, role: Role) -> Self
pub fn role(self, role: Role) -> Self
Sets the role the accessibility projection reports for the node.
A widget that reports no role of its own stays out of the projection and its children attach to the nearest projected ancestor, so this builder places a plain container on the accessibility tree. The role is fixed for the node’s lifetime, and it overrides the role the widget itself reports.
Sourcepub fn accessible_label(self, label: impl Into<Value<String>>) -> Self
pub fn accessible_label(self, label: impl Into<Value<String>>) -> Self
Sets the accessible name a screen reader announces for the node.
This can be bound to a signal, like the style builders. It overrides the name the widget itself reports.
Sourcepub fn focusable(self) -> Self
pub fn focusable(self) -> Self
Makes the node able to take the keyboard focus, by a press on it or by Tab navigation.
While a node holds the focus, key events reach it before they bubble to
its ancestors, which is what Element::on_key hears.
Element::on_key implies this, so a key handler never needs a
separate focusable call.
Sourcepub fn autofocus(self) -> Self
pub fn autofocus(self) -> Self
Takes the keyboard focus on the first frame the node is laid out after it mounts.
The request resolves after the layout pass that lays the node out, so
the focus is set before the frame paints and a node a show mounts
takes the focus in the frame it appears in. The request runs once per
mount: when the focus moves away later, the node does not ask again,
and a node unmounted and mounted again by a fresh show run does.
A node that is hidden, or sits under a hidden ancestor, keeps its
request until it is first laid out visible, so a dialog that mounts
hidden and shows later takes the focus in the frame it shows in. A
request whose node is disabled when it resolves is dropped and never
asked again. When several nodes show in one frame, the node painted
last wins.
While a focus trap is mounted, only a node inside the trap can take the focus this way; a request for a node outside it is dropped, like any other request that cannot take the focus.
This makes the node focusable, like Element::on_key does.
use std::cell::Cell;
use std::rc::Rc;
use smudge_core::app::{App, AppConfig, RepaintMode};
use smudge_core::geometry::{Color, Size};
use smudge_core::widget::panel;
let focused = Rc::new(Cell::new(false));
let count = Rc::clone(&focused);
let mut app = App::new(
AppConfig {
viewport: Size {
width: 100.0,
height: 60.0,
},
scale: 1.0,
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
},
move || panel().autofocus().on_focus(move |on| count.set(on)),
);
app.frame();
assert!(focused.get(), "the node took the focus on its first frame");Sourcepub fn trap_focus(self) -> Self
pub fn trap_focus(self) -> Self
Keeps Tab and Shift+Tab cycling among the focusable nodes inside the node’s subtree, for as long as the node is mounted and not hidden.
A node the user Tabs to inside the trap never lands outside it: the cycle wraps within the subtree, and a Tab pressed with nothing focused enters the trap at its first focusable node. While the trap holds no focusable node, a Tab press leaves the focus where it is. A pointer press that lands on no node that can respond, or on a node outside the trap, leaves the focus where it is, and a press on a node inside the trap moves the focus as usual: to that node when it can take the focus, and away when it cannot. This is how a modal dialog keeps the keyboard inside itself instead of wandering into the view behind it.
While the trap is active, a key that would start outside it, because the focus is outside the trap or nothing holds the focus, starts at the trap node and bubbles from there to the root, so the trap’s own key handlers hear it and a control behind it does not.
When several traps are mounted, the last one wins, and a nested trap
therefore takes over from the one around it. Removing a trap, which
is what unmounting a show child does, restores the trap mounted
before it, and returns the focus to the node that held it when the
trap became active, when that node is still mounted and can take it.
A trap becomes active when it mounts visible or shows again. A trap
that cannot hold the focus, because it became disabled or hidden or
sits under a hidden ancestor, yields to an earlier one while it stays
mounted, and a trap that hides gives the focus back the way removing
it does, when the focus was inside it or nowhere. A hidden or
disabled state counts once a layout pass has run, which every frame
that changes the hidden state does.
Sourcepub fn follow(self, signal: RwSignal<bool>) -> Self
pub fn follow(self, signal: RwSignal<bool>) -> Self
Pins the node’s scroll offset to the end of its flowing content while
signal reads true.
Every layout pass that runs while the signal reads true ends with
the node’s offset at the furthest its flowing content reaches, minus
the height of its content box, floored at zero. The pin uses the
rectangles that pass just computed, so a frame that grows the content
shows the end in view in the same frame. A signal that reads false
leaves the offset where it is, and a write of true from anywhere
pins the node in the next frame.
What turns the signal is the caller’s decision, not the core’s: the scroll view widget pairs this builder with its wheel handling, so a step that leaves the end releases the pin and a step back to it engages it again.
use smudge_core::element::Element;
use smudge_core::reactive::RwSignal;
use smudge_core::widget::Panel;
let following = RwSignal::new(true);
let view = Element::new(Panel).follow(following);Sourcepub fn on_key(self, handler: impl FnMut(&KeyEvent) -> Handled + 'static) -> Self
pub fn on_key(self, handler: impl FnMut(&KeyEvent) -> Handled + 'static) -> Self
Runs handler when a key event reaches the node while the node or one
of its descendants holds the keyboard focus.
The event walks up from the focused node, and the first handler that
returns Handled::Yes stops it, so a handler that takes a key hides
it from its ancestors. Tab moves the focus away only when no handler
took the Tab key. This makes the node focusable, so a press on it or a
Tab puts the focus on it.
The handler runs inside the node’s owner, so it can write the signals the element’s bindings read.
use std::cell::Cell;
use std::rc::Rc;
use smudge_core::app::{App, AppConfig, RepaintMode};
use smudge_core::geometry::{Color, Size};
use smudge_core::input::{Handled, InputEvent, Key, KeyEvent};
use smudge_core::style::px;
use smudge_core::widget::panel;
let enters = Rc::new(Cell::new(0u32));
let counted = Rc::clone(&enters);
let mut app = App::new(
AppConfig {
viewport: Size {
width: 100.0,
height: 60.0,
},
scale: 1.0,
clear_color: Color::WHITE,
repaint: RepaintMode::Damage,
},
move || {
panel().child(
panel()
.width(px(40.0))
.height(px(20.0))
.on_key(move |event: &KeyEvent| {
if event.pressed && event.key == Key::Enter {
counted.set(counted.get() + 1);
Handled::Yes
} else {
Handled::No
}
}),
)
},
);
app.frame();
// Tab moves the focus to the panel, which then hears the Enter key.
app.handle_event(InputEvent::Key(KeyEvent {
key: Key::Tab,
pressed: true,
repeat: false,
modifiers: Default::default(),
text: None,
}));
app.handle_event(InputEvent::Key(KeyEvent {
key: Key::Enter,
pressed: true,
repeat: false,
modifiers: Default::default(),
text: None,
}));
assert_eq!(enters.get(), 1);Sourcepub fn activate_on_keys(self) -> Self
pub fn activate_on_keys(self) -> Self
Runs the handler set with Element::on_press when the node holds
the keyboard focus and a fresh press of Enter or Space arrives.
This is how a button fires from the keyboard with the same handler its
pointer press uses. The activation runs before any
Element::on_key handler on the same node, and a key repeat fires
nothing. A press handler that ran takes the key, so it does not reach
an ancestor; without a press handler the key bubbles as usual.
This makes the node focusable, like Element::on_key does.
Sourcepub fn on_focus(self, handler: impl FnMut(bool) + 'static) -> Self
pub fn on_focus(self, handler: impl FnMut(bool) + 'static) -> Self
Runs handler when the node gains (true) or loses (false) the
keyboard focus.
The handler does not make the node focusable; pair it with
Element::focusable for that. It runs inside the node’s owner, so it
can write the signals the element’s bindings read. A node that is
removed while focused clears the focus without hearing on_focus again,
because its handlers are being torn down with it.
Sourcepub fn bind<K: 'static, T: 'static>(
self,
_key: K,
value: impl Into<Value<T>>,
apply: fn(&mut W, T) -> Dirty,
) -> Self
pub fn bind<K: 'static, T: 'static>( self, _key: K, value: impl Into<Value<T>>, apply: fn(&mut W, T) -> Dirty, ) -> Self
Binds one property of the element’s own widget.
The first argument names the property: pass a value of a marker type, usually a unit struct, that stands for it and for nothing else. The last call for a key wins, fixed or bound, the way it does for the style builders: a later call drops the binding an earlier call with a key of the same type recorded. Bindings with different key types all stay, and they apply in the order they were made.
apply writes the value into the widget and returns how much of the
node the write invalidated. A write that changes what the widget
measures returns Dirty::Layout, and the node is laid out and painted
again. Any other write returns Dirty::Paint and only repaints the
node.
use smudge_core::element::Element;
use smudge_core::paint::PaintOp;
use smudge_core::tree::Dirty;
use smudge_core::widget::{PaintCx, Widget};
struct Dot(f32);
impl Widget for Dot {
fn paint(&self, _cx: &PaintCx<'_>, _out: &mut Vec<PaintOp>) {}
}
/// The key of the dot's radius.
struct Radius;
let element = Element::new(Dot(0.0)).bind(Radius, 3.0, |dot, value| {
dot.0 = value;
Dirty::Paint
});