NAME
Clay::UI - widget layer over Clay::XS: build a widget tree, lay it out, turn pointer input into events
SYNOPSIS
use v5.22;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';
use Object::Pad;
use Clay::XS qw(sizing_grow sizing_fixed);
use Clay::UI;
use Clay::UI::Box;
use Clay::UI::Text;
use Clay::UI::Role::Interaction::Pressable;
# Widget classes: Object::Pad classes composed from Clay::UI roles.
class My::Panel :strict(params) :does(Clay::UI::Box) {}
class My::Label :strict(params) :does(Clay::UI::Text) {}
class My::Button :strict(params)
:does(Clay::UI::Box)
:does(Clay::UI::Role::Interaction::Pressable)
{}
# The widget tree.
my $root = My::Panel->new(
id => 'root',
layout => {
sizing => { width => sizing_grow(), height => sizing_grow() },
padding => { left => 10, top => 10 },
},
background_color => [40, 50, 60, 255],
);
my $button = My::Button->new(
id => 'ok',
layout => { sizing => { width => sizing_fixed(80), height => sizing_fixed(30) } },
);
$button->add_child(My::Label->new(text => 'OK', font_size => 16));
$button->on('OnHoverStart', sub ($event) { say 'over the button'; return });
$button->on('OnRelease', sub ($event) { say 'clicked'; return });
$root->add_child($button);
# The UI that owns the tree and its Clay context.
my $ui = Clay::UI->new(width => 800, height => 600, root => $root);
# One call per frame: pointer input in, render commands out.
for my $down (0, 1, 0) {
my $commands = $ui->render(pointer_state => { x => 20, y => 20, down => $down });
for my $command (@$commands) {
my $widget = $ui->widget_for($command->{userData});
# draw $command; $widget is the widget that produced it
}
}
DESCRIPTION
Clay::UI is the widget layer of this distribution. You build a tree of widgets (Perl objects that describe boxes and text), hand its root widget to a Clay::UI object, and call "render" once per frame. render does three things:
it turns the frame's pointer and scroll input into widget events ("EVENTS") and calls your listeners,
it lays out the widget tree with Clay, and
it returns Clay's render commands: an arrayref of hashes, each one a rectangle, text, border or clipping instruction with a bounding box (see "RENDER COMMANDS" in Clay::XS).
Nothing is drawn. A renderer (your code) draws the render commands with whatever graphics library you use, and finds the widget behind each command with "widget_for".
A Clay::UI object owns one Clay context (a Clay::XS context: one independent Clay instance), the measure-text callback and the interaction tracker (Clay::UI::Interaction), which holds which widgets are hovered, pressed and focused. You never call Clay_* functions yourself, with one exception: to animate transitions, call Clay_SetTransitionHandlers right after Clay::UI->new, while the new UI's context is current (see "TRANSITIONS" in Clay::Manual). The low-level Clay::XS API stays usable on its own.
For a guided introduction read Clay::Manual; for ready-made solutions see Clay::Cookbook.
WIDGET CLASSES
A widget class is an Object::Pad class that composes roles from this distribution. The roles provide the attributes, the children and the behaviour; the class itself is often empty:
class My::Button :strict(params)
:does(Clay::UI::Box) # an element with layout and style
:does(Clay::UI::Role::Interaction::Pressable) # OnPress / OnRelease
{}
There are two kinds of widget:
- element widgets
-
Compose Clay::UI::Role::Core::Element (usually through Clay::UI::Box, which adds layout, background, border, corner radius, floating and the public child methods). An element widget becomes one Clay element and may have children.
- text widgets
-
Compose Clay::UI::Role::Core::TextNode (usually through Clay::UI::Text). A text widget becomes one Clay text element and has no children.
A grid is an element widget too: a class composing Clay::UI::Grid (a widget role like Clay::UI::Box and Clay::UI::Text) lays its children out in rows and columns.
Further roles add behaviour: Clay::UI::Role::Interaction::Hoverable, Clay::UI::Role::Interaction::Pressable, Clay::UI::Role::Interaction::Focusable, Clay::UI::Role::Interaction::Disableable, Clay::UI::Role::Interaction::HasFocusOrder, Clay::UI::Role::Layout::HasScroll (scroll containers) and Clay::UI::Role::Core::Preparable (widgets that rebuild their children right before the layout).
Every widget can listen to events with on (Clay::UI::Role::Events::Listener); widgets that compose Clay::UI::Role::Events::Emitter can also fire them. Clay::UI::Box, Clay::UI::Role::Interaction::Hoverable, Clay::UI::Role::Interaction::Pressable, Clay::UI::Role::Interaction::Focusable and Clay::UI::Role::Layout::HasScroll compose it; Clay::UI::Text, Disableable and HasFocusOrder do not.
The class keyword opens a new package, so use Clay::XS qw(...) imports made at file scope are not visible inside the class block.
KEYS AND VALIDATION
Clay::UI accepts the keys of Clay's structs in snake_case (child_gap, layout_direction, background_color) as well as in the camelCase spelling Clay::XS uses (childGap, layoutDirection). Widgets store every key in snake_case, so readers return snake_case whatever spelling was set:
my $box = My::Box->new(layout => { childGap => 4 });
$box->layout; # { child_gap => 4 }
Both spellings of the same key in one hash die where the value is set:
Clay::UI: key 'childGap' in 'layout' is 'child_gap' in snake_case, which is already present in the same hash
The layout pass converts every key to camelCase before it passes a widget's settings to Clay.
Widget attributes are validated where they are set: in the constructor and in the accessor that writes them, not later in render. The rules are exactly the ones Clay::XS applies when the value reaches Clay (see check_struct in "CHECKING STRUCTS" in Clay::XS). Errors name the attribute and the snake_case path inside it:
Clay::UI: 'layout.padding.left' expected an integer in 0..65535, got '-5'
Clay::UI: 'layout' has unknown key 'chld_gap' (known keys: sizing, padding, child_gap, ...)
Attributes are copied when they are set and when they are read, so a widget never shares a hash or array with your code: changing a hash after passing it, or changing what a reader returned, does not change the widget. Use the accessor to write a new value.
HOW A FRAME WORKS
One call to "render" is one frame. It runs these steps in order:
- 1. Pointer input
-
The pointer position and button state (the
pointer_stateargument, or the previous frame's when it is omitted) go to Clay. Clay tests the position against the layout of the previous frame and lists the elements under the pointer, topmost floating element first. Clay::UI maps those elements back to widgets: these are the widgets under the pointer. A widget added to the tree can be hovered from the frame after the one that first lays it out. - 2. Scrolling
-
The
scroll_delta,enable_drag_scrollinganddelta_timearguments go to Clay. Wheel input moves the scroll container under the pointer at once. Drag scrolling moves it while the button is held and, after the release, keeps it gliding (momentum) for some frames. Wheel input has no momentum. - 3. Events
-
The widgets under the pointer, the button state and the scroll containers that moved go to the interaction tracker ("update" in Clay::UI::Interaction). It updates the hovered, armed and pressed widgets first, then fires the events in this order: every
OnHoverStopped, everyOnHoverStart,OnPress,OnRelease, everyOnScroll. Events of one kind fire in tree order (depth-first pre-order of the last layout). See "EVENTS". - 4. Preparations
-
Every widget of this UI that called
request_preparesince its last preparation gets itsprepare_layoutmethod called (Clay::UI::Role::Core::Preparable), so it can rebuild its children from its own state: parents before their descendants, widgets at the same depth in the order they asked. A widget that an earlier preparation detached is not prepared and stays queued. Preparations may request more preparations; after 100 roundsrendergives up and dies. - 5. Layout pass
-
renderrecords the current revision as "laid_out_revision", then declares the widget tree to Clay: one Clay element per element widget, one text element per text widget, in tree order. Nothing may change the tree from here on. Clay computes the layout. - 6. Result
-
renderreturns the render commands. The frame is now the last completed frame: "widget_for", "bounding_box", "scroll_state", "scroll_to" and the next frame's pointer test refer to it.
Listeners and preparations run in steps 3 and 4, while no Clay element is open. They may change the tree, widget attributes and focus, and use other Clay::UI objects; the changes show in this frame's layout. They must not call render of the same Clay::UI.
If a listener dies, the remaining events still fire and the preparations still run. If a prepare_layout dies, the other widgets due in that round are still prepared; then preparation stops: later rounds do not run, and the requests made during that round stay queued for the next render (see "prepare_layout" in Clay::UI::Role::Core::Preparable). In both cases the layout pass still runs, so the frame shows the scroll input already applied and the changes made before the error. Then render dies with the first error and the frame's render commands are lost. The next render works normally.
CONSTRUCTOR
new
my $ui = Clay::UI->new(
root => $root_widget,
width => 800,
height => 600,
# optional: memory_size, max_element_count,
# max_measure_text_cache_word_count, error_handler, measure_text
);
Creates a Clay::UI, its Clay context and its interaction tracker, and makes root the root widget of this UI. Then every widget of the root's subtree gets its tree_changed call; new dies with the first error of those hooks once all ran. The new context is current when new returns. If new dies, it frees the new context and makes the context that was current before current again; the root is then free to become the root of another Clay::UI.
Unknown parameters die (Unrecognised parameters for Clay::UI constructor).
- root
-
Required. The root widget: an object composing Clay::UI::Role::Core::Element or Clay::UI::Role::Core::TextNode. It must have no parent and must not be the root of another Clay::UI; a widget that was removed from its parent may become a root. Read it back with "root"; it cannot be replaced, but the tree below it can change at any time. Dies with:
Clay::UI: 'root' must be a widget consuming Clay::UI::Role::Core::Element or TextNode Clay::UI: 'root' must not have a parent; the root is the top of its widget tree Clay::UI: 'root' is already the root of another Clay::UI - width
-
Required. The viewport width in layout units, a positive finite number. Change it later with "width".
- height
-
Required. The viewport height, like
width. Both die withClay::UI: 'width' and 'height' must be positive finite numbers. - memory_size
-
Optional. Bytes of memory for the Clay context: an integer of at least
Clay_MinMemorySize()for this UI'smax_element_countandmax_measure_text_cache_word_count, which is also the default. Dies withClay::UI: 'memory_size' must be an integer >= Clay_MinMemorySize() (...). - max_element_count
-
Optional. How many Clay elements this UI's context holds: a positive integer, default 8192 (Clay's default). Every element widget and every text widget is one element. Clay keeps two slots for itself, so one frame fits
max_element_count - 2widgets (none for a count below 3). Clay also wraps at most this many text lines per frame, over all the text widgets it lays out; past that it silently stops wrapping. Each Clay::UI has its own count, whatever context is current when it is constructed. A larger count needs more memory (seememory_size). Dies withClay::UI: 'max_element_count' must be a positive integer. Read it back with "max_element_count". - max_measure_text_cache_word_count
-
Optional. How many measured words Clay's text cache holds: an integer of at least 32, default twice
max_element_count(Clay's default). Clay measures every text it lays out word by word and keeps the words of the last few frames, visible or not, so a frame whose texts have more words than this makesrenderdie withClay::UI: the texts laid out in one frame have more words than max_measure_text_cache_word_count (16384) allows; ...(default error handler). A larger count needs more memory (seememory_size). Dies withClay::UI: 'max_measure_text_cache_word_count' must be an integer >= 32. Read it back with "max_measure_text_cache_word_count". - error_handler
-
Optional. A coderef Clay calls as
$handler->($error, $userdata)when it reports an error;$erroris a hashref witherrorType(aCLAY_ERROR_TYPE_*constant) anderrorText. The default handler dies withClay error: $error->{errorText}, which makesrenderdie (see "ERRORS FROM CALLBACKS" in Clay::XS); a handler that returns lets the frame continue. Dies withClay::UI: 'error_handler' must be a coderef.With the default handler, a tree with more widgets than
max_element_countallows makesrenderdie with a message that namesmax_element_count(see "render") instead of the error Clay reports for it (Clay drops the extra elements without a word and then reports elements it could not close). A handler of your own receives Clay's error unchanged.The handler runs inside Clay and must not use any Clay::UI object (see "What a callback may do" in Clay::XS).
- measure_text
-
Optional. A coderef Clay calls as
$measure->($text, $config, $userdata)to measure a piece of text; it returns{ width => $w, height => $h }.$configis the text element's configuration with camelCase keys (fontSize,fontId,letterSpacing, ...). The default is a monospace estimate:width = length($text) * fontSize,height = fontSize, with afontSizeof 16 when it is 0 or missing. Dies withClay::UI: 'measure_text' must be a coderef. Change it later with "measure_text".The callback runs inside Clay during the layout pass and must not use any Clay::UI object. Inside it:
renderdies (Clay::UI::render: called while this Clay::UI is already rendering).The writers
width($w),height($h)andmeasure_text($coderef)die (Clay_SetCurrentContext: cannot be called from inside a Clay callback); the readerswidth,heightandmeasure_textwork.bounding_box,scroll_stateandscroll_todie with the same message for a widget the last completed frame laid out, because they ask Clay; for a widget it did not lay out they return undef without asking Clay.
If the callback lets such an error escape, the outer
renderdies with it after the layout pass.
METHODS
root
my $root = $ui->root;
Returns the root widget passed to "new". Read-only.
max_element_count
my $count = $ui->max_element_count;
Returns the max_element_count passed to "new" (8192 by default). Read-only.
max_measure_text_cache_word_count
my $words = $ui->max_measure_text_cache_word_count;
Returns the max_measure_text_cache_word_count passed to "new" (twice max_element_count by default). Read-only.
width
my $width = $ui->width;
$ui->width(1024);
Reads or writes the viewport width. A write takes one positive finite number, passes it to Clay (Clay_SetLayoutDimensions), bumps the revision (Clay::UI::Revision) and returns the new width; the next "render" lays out at the new size. A refused value changes nothing. Dies with Clay::UI: width must be a positive finite number or Clay::UI: width takes one value.
height
my $height = $ui->height;
$ui->height(768);
Reads or writes the viewport height, exactly like "width".
measure_text
my $measure = $ui->measure_text;
$ui->measure_text(sub ($text, $config, $userdata) { ... });
Reads or replaces the measure-text callback (see measure_text under "new"). A write takes one coderef, passes it to Clay, clears Clay's cache of measurements made by the old callback (Clay_ResetMeasureTextCache), bumps the revision and returns the new callback. Dies with Clay::UI: measure_text must be a coderef or Clay::UI: measure_text takes one value.
render
my $commands = $ui->render(%args);
Runs one frame (see "HOW A FRAME WORKS"): processes the pointer and scroll input, fires the resulting events, prepares the widgets that asked for it, lays out the widget tree and returns the render commands as an arrayref of hashes (see "RENDER COMMANDS" in Clay::XS). Each command's userData identifies the widget that produced it (see "widget_for").
All arguments are named and optional:
- pointer_state
-
pointer_state => { x => 120, y => 80, down => 1 }The pointer for this frame: a hashref with finite numbers
xandy(layout units, origin at the top left of the viewport) anddown, a true value while the button is held (default false). No other keys. When omitted (or undef), the pointer state of the previous frame is used again. A change ofdownfrom false to true is a press, from true to false a release (see "EVENTS"). Until the firstpointer_state, the pointer is up and Clay tests no position, so nothing is under it and no press is reported. - delta_time
-
delta_time => 0.016Seconds since the previous frame, a finite number >= 0, default 0. Clay uses it for the momentum after drag scrolling and for transitions.
- scroll_delta
-
scroll_delta => { x => 0, y => -1 } # or [0, -1]Wheel input for this frame, as
{ x => ..., y => ... }(both keys required, no others) or[$x, $y], both finite numbers; default[0, 0]. Clay multiplies the delta by 10 and adds it to the scroll position of the scroll container under the pointer (the innermost one when containers are nested), on each axis that container can scroll (its content is larger than its viewport); the result is clamped to the content. A negativeyscrolls down, revealing what is below:scroll_delta => { x => 0, y => -4 }scrolls the container 40 layout units down: the content moves up, and itspositionin "scroll_state" goes from0to-40. A scroll container is a widget composing Clay::UI::Role::Layout::HasScroll. Wheel input has no momentum: the container stops where the delta puts it, and wheel input also stops a glide left over from drag scrolling. - enable_drag_scrolling
-
enable_drag_scrolling => 1A plain boolean, default false. When true, pressing and dragging inside a scroll container scrolls it, as on a touch screen. After the release the container keeps moving with momentum for some frames, slowing down each frame.
render dies, with the frame discarded, in these cases:
A bad argument, before anything happens:
Clay::UI::render: unknown argument(s),... 'pointer_state' must be a hashref,... unknown pointer_state key(s),... pointer_state 'x' must be a finite number,... pointer_state 'down' must be a plain boolean value,... 'delta_time' must be a finite number >= 0,... unknown scroll_delta key(s),... 'scroll_delta' must be ...,... 'enable_drag_scrolling' must be a plain boolean value.renderis called while the same Clay::UI is rendering, for example from an event listener, aprepare_layoutor the measure-text callback:Clay::UI::render: called while this Clay::UI is already rendering.An event listener died. All events of the frame fire, the preparations and the layout pass run, then
renderdies with the first listener error.A
prepare_layoutdied: the other preparations of that round still run, later rounds do not (see "HOW A FRAME WORKS"), the layout pass runs, thenrenderdies with the first error of the frame. Or preparations kept requesting new ones:Clay::UI: widgets kept requesting preparation; 100 rounds of prepare_layout did not settle.Clay reported an error and the error handler died. With the default handler this is
Clay error: ..., for example when two widgets have the sameid(Clay error: An element with this ID was already previously declared during this layout.).The tree has more widgets than
max_element_countallows (default error handler only):Clay::UI: the widget tree has more elements than max_element_count (8192) allows: ....The texts laid out in the frame have more words than
max_measure_text_cache_word_countallows (default error handler only):Clay::UI: the texts laid out in one frame have more words than max_measure_text_cache_word_count (16384) allows; ....The measure-text callback died:
renderdies with its error.A widget class produced settings Clay::XS rejects. Attributes are validated when they are set, so this happens only when a widget class builds settings of its own: a
Clay::XS::StructError(see "STRUCT ERRORS" in Clay::XS) such asClay_ElementDeclaration.layout.padding.left: expected an integer in 0..65535, got '-3', a key collision between a widget class's own camelCase key and a stored snake_case one (Clay::UI: key '...' camelizes to ...), orClay::UI: widget ... set user_data in its config(see "NOTES").
When a listener error and a layout error happen in the same frame, render dies with the listener error and appends (the layout pass also failed: ...) to it (when the error is a string).
After any of these, Clay stays usable and the next render works once the cause is fixed. Which frame counts as the last completed frame depends on the case:
After a bad argument, a nested
renderor a failed layout pass (a Clay error, too many widgets, a dying measure-text callback, rejected settings), the last completed frame is unchanged.After a listener error or a preparation error (a dying
prepare_layout, or the 100 rounds) without a layout error, the layout pass completed: that frame becomes the last completed frame for "widget_for", "bounding_box", "scroll_state" and the next frame's pointer test, even though its render commands were discarded.
widget_for
my $widget = $ui->widget_for($command->{userData});
Returns the widget that produced a render command, given the command's userData. Returns undef when $user_data is undef, 0 or unknown, or when the widget has been freed since. Lookups use the last completed frame. Not every command has a widget: Clay gives SCISSOR_END commands a userData of 0, so widget_for returns undef for them.
for my $command (@$commands) {
my $widget = $ui->widget_for($command->{userData}) or next;
# choose how to draw by ref($widget), read its attributes, ...
}
laid_out_revision
my $shown = $ui->laid_out_revision;
Returns the revision (Clay::UI::Revision) at which the last render started its layout pass, or undef before the first render. Every change up to that revision, including the changes the frame's listeners and preparations made, is part of that frame. A renderer that skips unchanged frames remembers this value when it draws a frame, and after each later render draws again only when laid_out_revision differs from the remembered value:
my $drawn; # the laid_out_revision of the last frame drawn
my $commands = $ui->render(%input);
if (!defined $drawn || $drawn != $ui->laid_out_revision) {
draw($commands);
$drawn = $ui->laid_out_revision;
}
A running transition ("TRANSITIONS" in Clay::Manual) counts as a change too: every frame that animates an element moves the revision, so the loop above redraws until the animation is over.
bounding_box
my $box = $ui->bounding_box($widget); # { x, y, width, height } or undef
Returns where the last completed frame placed an element widget, as a new hashref with x, y, width and height in layout units (the same numbers as the boundingBox of its render commands). Scrolling is included, so a widget scrolled out of view lies outside its scroll container. Returns undef for a widget that frame did not lay out (one added since, one removed, a text widget, or a widget of another UI).
Text widgets have no bounding box of their own: use the boundingBox of their TEXT render commands ("widget_for" maps each command back to its text widget).
The box is the size Clay gave the widget, which can be smaller than its sizing asks for: when the children of a parent do not fit into it, Clay shrinks them, down to their minimum size, and bounding_box reports the shrunk size. A parent that clips an axis (a scroll container scrolling that way) does not shrink its children along that axis.
Dies when $widget is not an object: Clay::UI: expected a widget, got ....
scroll_state
my $state = $ui->scroll_state($scroll_box);
# { position => { x, y }, viewport => { width, height }, content => { width, height } }
Returns the scroll data of a scroll container (a widget composing Clay::UI::Role::Layout::HasScroll) as a new hashref:
- position
-
Clay's scroll position:
0at the top and left, negative when the content is scrolled down or right. Reflects every scroll since the last frame, including "scroll_to". - viewport
-
The size of the visible area.
- content
-
The size of everything inside the container.
Returns undef when the last completed frame did not lay the container out. Dies for anything that is not a scroll container: Clay::UI: ... is not a scroll container (it does not compose Clay::UI::Role::Layout::HasScroll).
During the layout pass (from a contribute_ method, say) it returns the same: the position as this frame lays it out (the wheel, drag and momentum scrolling of the frame applied) and the viewport and content of the last completed frame.
scroll_to
$ui->scroll_to($scroll_box, { y => -12 }); # 12 units down
$ui->scroll_to($scroll_box, { x => 0, y => 0 }); # back to the top left
Moves a scroll container to a scroll position (in the sense of position under "scroll_state"). Each axis is clamped to the range from 0 down to viewport - content (or 0 when the content fits); an axis left out keeps its position. Returns the position as Clay now holds it, a new hashref { x => ..., y => ... } (Clay keeps single-precision floats, so a large position may come back rounded), or undef when the last completed frame did not lay the container out (then nothing moves). The next render shows the new position. The move counts as a change for Clay::UI::Revision. It fires no OnScroll (that event reports only the scrolling Clay does inside render), and it stops momentum: a glide started by drag scrolling ends at the new position. A position equal to the current one still ends a glide and counts as a change for the revision; it does not prepare the container (see below).
A container that composes Clay::UI::Role::Core::Preparable is prepared before the next layout pass when its position changed (its request_prepare is called), so a container that builds its children for the position it shows can follow a move made from code, or from a listener in the same frame.
Dies with Clay::UI: scroll_to needs a position { x => ..., y => ... }, Clay::UI: scroll_to got unknown position key(s), Clay::UI: scroll_to position 'y' must be a finite number, and for a widget that is not a scroll container (as "scroll_state").
interaction
my $interaction = $ui->interaction;
Returns this UI's interaction tracker, a Clay::UI::Interaction. It holds the hovered, armed, pressed and focused widgets and fires the pointer and focus events. Common uses:
# widgets under the pointer at the last frame
my $widgets = $ui->interaction->under_pointer;
$ui->interaction->set_focused_widget($input); # move the focus
$ui->interaction->focus_next; # Tab
$ui->interaction->update(over => [$button], down => 1); # synthetic input
EVENTS
render fires the pointer events below at widgets; focus events come from the focus methods of Clay::UI::Interaction. Listen with $widget->on($name, sub ($event) { ... }) (Clay::UI::Role::Events::Listener). An event that bubbles is then offered to the widget's parent, its parent's parent and so on, as its bubble mode allows (Clay::UI::Enum::Bubble). With IF_CONTINUE, an ancestor sees the event only when every listener of the widget below returned Clay::UI::Enum::Result->CONTINUE (or the widget has no listener for it).
A listener that returns nothing stops the event. An empty return, undef, or the value of the last statement (such as the return value of say) all count as Clay::UI::Enum::Result->HANDLED; only CONTINUE lets the event bubble on (Clay::UI::Enum::Result):
$button->on('OnPress', sub ($event) {
highlight($event->current_target);
return Clay::UI::Enum::Result->CONTINUE; # the card around the button sees it too
});
OnHoverStart
Fires at a Clay::UI::Role::Interaction::Hoverable widget in the frame it comes under the pointer. Does not bubble: every hovered widget, nested ones included, gets its own event. See Clay::UI::Events::OnHoverStart.
OnHoverStopped
Fires at a hovered widget in the frame it is no longer under the pointer, and at once, during the removal, when a hovered widget is removed from the tree. Does not bubble. See Clay::UI::Events::OnHoverStopped.
OnPress
Fires when the pointer goes down, at exactly one Clay::UI::Role::Interaction::Pressable: the enabled Pressable under the pointer that is drawn on top. A button inside a pressable card gets it, not the card; of two overlapping siblings (such as the children of a CLAY_BACK_TO_FRONT container) the later one gets it; a Pressable in a floating element beats every Pressable below that element. Every enabled Pressable under the pointer becomes armed. Bubbles with IF_CONTINUE. The precise rule is under "PRESS AND RELEASE" in Clay::UI::Interaction. See Clay::UI::Events::OnPress.
OnRelease
Fires when the pointer goes up, at the topmost armed Pressable still under the pointer, chosen like the target of OnPress: a completed click. Every release disarms all Pressables, so a press that started elsewhere, or one dragged off every armed widget, ends without OnRelease. Bubbles with IF_CONTINUE. See Clay::UI::Events::OnRelease.
A Pressable is pressed (is_pressed, the derived pressed state) while it is armed, enabled and under the pointer and the button is down.
OnScroll
Fires at a scroll container (Clay::UI::Role::Layout::HasScroll) whose scroll position changed in this frame through wheel input, drag scrolling or the momentum that follows drag scrolling; delta_x and delta_y say how far. Such a change also bumps the revision. Bubbles with IF_CONTINUE. See Clay::UI::Events::OnScroll.
OnFocus
Fires at a Clay::UI::Role::Interaction::Focusable that receives the focus through set_focused_widget, focus_next or focus_previous. render never moves the focus. Bubbles with IF_CONTINUE. See Clay::UI::Events::OnFocus.
OnBlur
Fires at the widget that loses the focus: when the focus moves elsewhere or is cleared, when the focused widget is removed from the tree, is disabled, or cannot take the focus any more. Bubbles with IF_CONTINUE. See Clay::UI::Events::OnBlur.
CLAY CONTEXTS
Clay has one current context per process. Every method of Clay::UI that talks to Clay (new, render, the width, height and measure_text writers, bounding_box, scroll_state, scroll_to) first makes this UI's context current and leaves it current. Several Clay::UI objects can coexist; code that mixes Clay::UI with direct Clay::XS calls must set the context it needs with Clay_SetCurrentContext before its own calls.
NOTES
- Back-references in render commands
-
The layout pass sets each element's
userDatato a number that identifies its widget, so "widget_for" can map a render command back to the widget. A widget class must therefore not setuser_data(oruserData) in its settings;renderdies withClay::UI: widget ... set user_data in its configif one does. - Scroll containers
-
For a scroll container without an explicit
child_offset, the layout pass sets Clay'schildOffsetto the container's current scroll offset, so its children follow the scroll position. Aclipsetting from any other widget clips, but does not scroll. - Widgets without an id
-
A widget without an
idgets one derived from its position below the nearest ancestor that has one; see "resolve_id" in Clay::UI::Role::Core::Element. User ids must not start withanon:. - Lifetime
-
The lookup tables behind "widget_for" and the interaction tracker hold widgets weakly. The Clay::UI object keeps its root widget, and so the whole tree, alive. The tracker and the widgets refer to the Clay::UI weakly; the methods of Clay::UI::Interaction that need it die once it is gone.
- Revision
-
Every setter that changes what a frame lays out or draws bumps the process-wide revision (Clay::UI::Revision): widget attributes, children, user states, this object's
width,heightandmeasure_text, and the hovered, armed, pressed and focused widgets. Reading never bumps it. Widget classes that keep state of their own callmark_changed("mark_changed" in Clay::UI::Role::Core::Element).
SEE ALSO
Clay::Manual (user guide), Clay::Cookbook (recipes), Clay::UI::Interaction, Clay::UI::Revision, Clay::UI::Box, Clay::UI::Text, Clay::UI::Grid, Clay::UI::Role::Core::Element, Clay::XS, Clay::XS::Structs.
LICENSE
Released under the same zlib/libpng license as Clay itself. See src/clay/LICENSE.md for the upstream notice.