The base node type. Every widget derives from it, directly or through
TLayout<Derived, Base>.
namespace ZenitUI;
class Layout : public std::enable_shared_from_this<Layout> { /* ... */ };- Header:
Layout.hpp - Inherits:
std::enable_shared_from_this<Layout> - Friends:
StyleResolver,AnimationPlayer,InputController,ScrollController
For the full lifecycle and internals, see Lifecycle.
Creates a node of the given type.
auto node = std::make_shared<Layout>(LayoutType::Vertical);Note: a bare Layout has no onBuild — it's fully initialized
after construction. For subclasses with children, use the ::create()
factory instead (see §2).
Destroys the node. If the renderer is set, it destroys any cached
render targets (layerTarget_, layerScratch_).
Note: children are destroyed with the parent, in reverse order (unordered across siblings).
The recommended way to define a widget. TLayout adds a create()
factory that calls onBuild() after construction (when
shared_from_this() is valid).
template <typename Derived, typename Base = Layout>
class TLayout : public Base {
public:
template <typename... Args>
TLayout(Args&&... args);
template <typename... Args>
static std::shared_ptr<Derived> create(Args&&... args);
// Fluent helpers (return shared_ptr<Derived>)
std::shared_ptr<Derived> cls(const std::string& name);
std::shared_ptr<Derived> id(const std::string& node_id);
std::shared_ptr<Derived> size(Value w, Value h);
std::shared_ptr<Derived> passThrough(bool p = true);
std::shared_ptr<Derived> with(std::shared_ptr<Layout> c);
std::shared_ptr<Derived> self();
protected:
virtual void onBuild() {}
};Example:
class MyWidget : public TLayout<MyWidget> {
public:
MyWidget(int arg) : TLayout<MyWidget>(LayoutType::Vertical), arg_(arg) {}
protected:
void onBuild() override {
addChild(Label("hello"));
}
int arg_;
};
auto w = MyWidget::create(42); // calls onBuildNote: Base allows building on top of an existing widget:
class Popup : public TLayout<Popup, Panel> { /* ... */ };Adds a CSS class. Sets internal_.pendingTransition = 1. No-op if the class
is already present, so calling it twice does not create duplicates.
node->addClass("btn-primary");
node->addClass("btn-primary"); // ignoredRemoves every occurrence of the class. Sets internal_.pendingTransition = 1
if something was actually removed.
node->removeClass("btn-primary");
node->removeClass("missing"); // no-opRemoving a class is what makes CSS animations declared under it stop without having to rebuild the node:
// .pulse { animation: pulse 1s infinite; }
btn->addClass("pulse"); // starts the animation
// ...
btn->removeClass("pulse"); // stops it, immediatelyTrue if the class is currently set on this node.
if (node->hasClass("highlight")) { /* ... */ }Removes every class in one shot. No-op if there were none.
node->clearStyleClasses();Fluent wrapper for addClass. Returns self().
node->cls("card")->cls("elevated");There is no fluent counterpart for removeClass — removal is a runtime
operation, not part of the construction chain.
Sets the node id, used by #id selectors in .zstyle and by ZMarkup's
UINode::find().
Fluent wrapper for setId.
True if the node's id matches the given string. Returns false if the
node has no id, or if the input is empty. Useful for conditional
logic that doesn't want to read the public nodeId field directly.
if (node->hasId("save-button")) {
// ...
}Sets the node's ref — a logic handle that is independent of
nodeId. Refs do not participate in CSS matching (.zstyle has
no @ or [ref="..."] selectors), so setting a ref has zero impact
on the cascade. Used by UINode::ref/refAll in ZMarkup.
node->setRef("delete");
node->setRef(""); // clearMultiple nodes can share the same ref — this is the whole point. A
list of 100 delete buttons all set setRef("delete") and are
retrieved with ui.refAll<Button>("delete").
Fluent wrapper for setRef, matching the with(child) helper style.
Returns self() typed as shared_ptr<Derived>.
auto btn = Button::create(cb)->withRef("delete")->cls("btn-danger");Sets the style tag. Used by TagName { ... } selectors. Widgets set
this in onBuild (e.g. "Button", "Toggle").
node->setStyleTag("MyWidget");Returns the current style tag.
Returns the list of classes.
Sets width and height on the node's inline base style.
node->setSize(Px(100), Px(50));
node->setSize(Percent(100), Auto());Fluent wrapper for setSize.
Returns the node's final rect (set by arrange).
Rect r = node->getRect();
// r.x, r.y, r.width, r.heightReturns the intrinsic size computed by measure.
Note: measuredSize and rect.size() differ when the parent
stretches or constrains the node.
Adds a child. Sets the child's parent pointer and internal_.pendingTransition.
Warning: if weak_from_this() is expired (i.e. the node isn't
managed by shared_ptr), the call logs a warning and returns without
adding. Use X::create() instead of std::make_shared<X>().
Returns the node to which ZMarkup attaches children. Default: this.
Composite widgets that build an internal content wrapper override this
to return the wrapper instead. Currently only Modal does — its
onBuild creates a content VStack with the "Intro" animation, and
contentTarget() returns that VStack. Children written in markup
under a Modal element end up inside the wrapper, and therefore get
animated by it automatically.
// Default behavior (plain Layout, Panel, VStack, HStack, ...):
// contentTarget() returns the node itself.
auto stack = std::make_shared<Layout>(LayoutType::Vertical);
stack->contentTarget() == stack.get(); // true
// Modal: contentTarget() returns the internal content wrapper.
auto modal = Modal::create();
modal->contentTarget() != modal.get(); // true
modal->contentTarget() == modal->children[0].get(); // trueWhen to override: if you write a custom widget that builds an
internal wrapper and wants ZMarkup children to participate in the
wrapper's layout/animation, override contentTarget(). Don't override
it just to "protect" the widget from children — that's not what it's
for.
When NOT to use it directly: regular C++ code rarely needs to
call contentTarget(). Use addChild() on the widget and the
framework routes to the right place, the same way ZMarkup does.
contentTarget() exists so that ZMarkup::create (which doesn't know
the widget type) can attach children correctly.
Marks the node for removal. The actual removal happens in the parent's
next cullRemovedChildren pass (end of update).
Clears every entry in the children vector in one shot, and resets
each removed child's parent pointer to null.
parent->removeAllChildren();
// parent->children is now empty
// each removed child: hasParent() == falseUnlike removeFromParent() on each child, this is immediate: the
children vector is cleared during the call, not deferred to the next
cullRemovedChildren pass.
The grandchildren are not touched. Removing a child only breaks the
link between that child and its parent; the child keeps its own
children intact. If you still hold a shared_ptr to a removed child,
it stays alive — only the tree relationship is broken.
Use it to rebuild the content of a container (a list, a settings panel) without allocating a new node:
list->removeAllChildren();
for (const auto& item : newItems)
list->addChild(Label(item));Returns the parent, or null if this is the root.
True if the node has a parent.
True if this is an ancestor of other (walks up other's parent
chain).
Public vector of children. Direct manipulation is possible but
discouraged — prefer addChild / removeFromParent.
Public field. The node id. Set by setId or directly.
Whether the node participates in hit-testing and receives events.
Default: true for most nodes, false for Panel and Text.
Whether the node stops hit-testing at its rect (blocking nodes behind
it) without firing its own callbacks. Default: false.
Enables or disables the node. Disabling clears focus and pointer
capture if this node holds them, and calls onEnabledChanged(false).
True if enabled.
Sets the checked state. Triggers a style re-resolve, so :checked
rules apply.
True if checked.
bool isHoveredState() const; // pointer over the node's rect
bool isPressedState() const; // pointer down, node is press target
bool isFocusedState() const; // UIContext::focusedNode == this
bool isDisabledState() const; // !config.enabled
bool isCheckedState() const; // same as getChecked()If true, onUpdate(dt) runs even when the node is disabled.
Default: false.
Whether the node can be focused. isFocusable() returns
config.focusable != 0 && config.enabled != 0.
Whether the node is a focus scope (a boundary for Tab navigation).
Whether Enter / Space trigger onClick when the node is focused.
If true, click / press / right-click events bubble up to ancestors
after firing on this node.
Returns a mutable reference to the node's inline base style. Any
modification triggers internal_.pendingTransition = 1.
Note: the call itself sets the flag, even if you don't modify the style.
node->getInlineBase().background = Colors::Blue;
node->getInlineBase().radius = Px(8);Returns a mutable reference to the node's inline default style. Lower priority than CSS rules — used for widget defaults the user can override.
Merges s into inlineBase via overlay.
Returns the node's current resolved style (style_.currentStyle).
Does not include CSS animation values (which are overlaid only at
draw time).
Sets internal_.pendingTransition = 1, resets the transition timer, and
snapshots the current style as the transition start.
Registers an imperative animation under name.
Plays the imperative animation with the given name. If the animation is
finished forward and playReverse == false, restarts from 0. If
finished reverse and playReverse == true, restarts from the end.
True if the node or any descendant has active animations (recursive).
Adds a CSS animation by keyframe name to the node's active list.
Marks a CSS animation as finished.
Public callback. Fires once when the node's local animations (imperative + CSS node + CSS parts) all stop.
True if either overflow-x or overflow-y is not visible.
void scrollTo(float x, float y);
void scrollToX(float x);
void scrollToY(float y);
void scrollToTop();
void scrollToBottom();
void scrollToLeft();
void scrollToRight();float getScrollX() const;
float getScrollY() const;
float getMaxScrollX() const;
float getMaxScrollY() const;All values in logical pixels.
Ottimo, 843 check. Chiudiamo la 2.4 con la documentazione.
Cerca la sezione ## 9. Scroll. Attualmente contiene:
### Scroll position
```cpp
void scrollTo(float x, float y);
void scrollToX(float x);
void scrollToY(float y);
void scrollToTop();
void scrollToBottom();
void scrollToLeft();
void scrollToRight();float getScrollX() const;
float getScrollY() const;
float getMaxScrollX() const;
float getMaxScrollY() const;All values in logical pixels.
Scrolls the nearest scrollable ancestor so this node becomes visible.
Returns true if the scroll offset was changed, false if the node
has no scrollable ancestor or was already fully visible.
void onFocus() {
// Make sure the focused node is visible after Tab navigation.
scrollIntoView(/*margin=*/8.0f);
}margin reserves that many pixels of breathing room between the node
and the container's edge. Without it, the node ends up flush against
the border.
Same as above, but scrolls to make subRect (in the node's
coordinates) visible instead of the node's full rect. Useful when
the "interesting" part of a node is smaller than its bounding box —
a specific line of a Text, a specific cell of a grid.
// Scroll so that only the first line of a Text is visible.
Rect firstLine = text->getRect();
firstLine.height = lineHeight;
text->scrollIntoView(firstLine);The two overloads return the same thing. The no-argument version is
equivalent to scrollIntoView(getRect(), 0.0f).
If true, the node is drawn at the root's z-top and hit-tested first.
See Portals.
Pointer capture. While captured, all pointer events are routed to the node regardless of position. Capture is released automatically when the button is released.
Sets a fallback background texture for renderChrome.
Sets a hardcoded shader applied to the node's draw calls. Deprecated in
favor of the effect style property.
Resolves the font property via IAssetProvider::getFont, with a
fallback.
ResolvedBgTexture resolveBgTexture(const ComputedStyle& style, TextureHandle fallbackTex = {}, NineSlice fallbackSlice = {0,0,0,0})
Resolves the background-texture property via
IAssetProvider::getTexture.
Public std::function<void()> fields:
| Field | Fires on |
|---|---|
onClick |
Press + release on the same node. |
onPress |
Pointer down on the node. |
onRelease |
Pointer up while the press target was inside the node. |
onHoverEnter |
Pointer enters the node. |
onHoverExit |
Pointer leaves the node. |
onRightClick |
Right-click on the node. |
onAnimationsFinished |
All local animations stop. |
onButtonPress |
void(MouseButton). Fires on press for Left and Middle. Same click model as onPress but parameterized by button. |
onButtonRelease |
void(MouseButton). Fires on release for Left and Middle, when the click is valid. |
onButtonClick |
void(MouseButton). Fires on a complete click (press + release on the same target) for Left and Middle. |
All are void(). See Events for the exact
firing conditions.
onClick / onPress / onRelease are the historical Left-only API.
onRightClick is the historical Right-only API, with a different
semantic: it fires on press if the node is hovered, without
validating a full click pair. Right-click is a one-shot event, so the
press-on-hover model is intentional.
Callback fields (onClick, onPress, onButtonPress, …) are set
directly on a node, not by ref:
auto all = ui.refAll<Button>("delete");
for (auto& btn : all) {
btn->onClick = []{ /* ... */ };
}The UINode::onClick("id", cb) helper (different from the field
Layout::onClick) only takes an id, not a ref. A future extension may
accept a leading @ in the helper's string argument to signal "look
up by ref". Deferred for now.
onButtonPress / onButtonRelease / onButtonClick cover the same
click model as onClick but take a MouseButton parameter. They
currently fire for:
- Left — in addition to
onPress/onRelease/onClick. Both the legacy and the unified callback can be set at once; if they are, both fire. - Middle — the only way to react to a middle click on a node.
Right does not fire onButton*. Its semantic is different, and
unifying it would lose the "one-shot on press" behavior that
onRightClick provides. If a future design wants to unify right-click
under the same model, that's a separate decision.
// Left click (both fire):
node->onClick = []{ /* legacy */ };
node->onButtonClick = [](MouseButton b){ /* b == Left */ };
// Middle click (only the unified fires):
node->onButtonClick = [](MouseButton b){
if (b == MouseButton::Middle) { /* ... */ }
};
// Right click (only the legacy fires):
node->onRightClick = []{ /* ... */ };These are called by the runtime, not by user code. They're virtual so they can be overridden by the engine, not by widgets.
Bottom-up intrinsic size. Cached; short-circuits if the subtree is clean and the parent size is unchanged.
Top-down position. Sets rect and arranges children. If the node is a
scroll container, uses the scroll path.
Per-node update. Called recursively from updateTree. Handles state
flags, style resolution, animation ticks, children update, callbacks,
and onUpdate.
Recursive draw. Chooses inline or layer path, draws chrome + content +
children, then the scrollbar. If the node is the root, drains
framePortals.
Point query with z-ordered traversal. Returns the topmost node under
p, or null.
Entry point for the logic phase. Hit-tests the pointer, updates focus,
runs update on the root.
Convenience: beginFrame + updateTree + measure + arrange. Called
once per frame by the user, before BeginDrawing.
Convenience: renderer->beginFrame + draw + renderer->endFrame.
Called between BeginDrawing and EndDrawing.
Virtual methods for subclasses. Override only what you need.
Called once after construction, when the node is managed by a
shared_ptr. Set up the widget's style tag, flags, children.
Return the widget's natural size. Default: {0, 0}.
Called after arrange sets the final rect. Do not modify
measuredSize.
Per-frame logic. Skipped if config.enabled == 0 && config.updateWhenDisabled == 0.
Called when setEnabled changes the state.
Called when a descendant gains focus (via Tab navigation).
Draw the widget's own content. Called after renderChrome, before
children.
Draw the background, texture, and border. Default implementation handles all standard cases. Override only for custom backgrounds.
Resolves a ::part style with its own transitions and animations.
Called from renderContent.
Protected helper that resolves the effect shader to apply to this
node, considering both sources. Returns an EffectHandle — possibly
invalid if neither source is set.
Priority:
- The
effect:property fromcurrentStyle, resolved viaIAssetProvider::getEffect(name). - The
setShader()handle set from C++. - Invalid handle (no effect).
EffectHandle fx = resolveActiveEffect();
if (fx.valid() && renderer->supports(Feature::Effects))
renderer->pushEffect(fx);When to call it: any widget that overrides draw() and needs to
apply the node's effect. Currently only CanvasLayout does. Regular
widgets that go through Layout::draw() don't need to call this — the
framework resolves and pushes the effect for them in drawInline /
drawLayer.
Why it exists: the "priority" between effect: and setShader()
needs to be identical everywhere. Putting it in one place means a
subclass can't accidentally implement a different rule.
Free function. Tests a single compound selector against a node.
Free function. Tests a full rule (chain + media query) against a node.
Returns all rules that match this node (excluding ::part rules).
| Widget | Header | Base | Notes |
|---|---|---|---|
Panel |
Layouts.hpp |
TLayout<Panel> |
Non-interactive box, blocks raycast. |
Text |
Text.hpp |
TLayout<Text> |
Non-interactive, wraps optionally. |
Button |
Button.hpp |
TLayout<Button, Panel> |
Interactive, focusable, keyboard. |
ImageContainer |
ImageContainer.hpp |
TLayout<ImageContainer> |
Non-interactive, draws a texture. |
Toggle |
Toggle.hpp |
TLayout<Toggle> |
Checked state, knob child. |
Checkbox |
Checkbox.hpp |
TLayout<Checkbox> |
Checked state, custom render. |
Slider |
Slider.hpp |
TLayout<Slider> |
Value [0,1], drag + keyboard. |
ProgressBar |
ProgressBar.hpp |
TLayout<ProgressBar> |
Value [0,1], non-interactive. |
TextInput |
TextInput.hpp |
TLayout<TextInput> |
Editable text, focus, cursor. |
ScrollView |
ScrollView.hpp |
TLayout<ScrollView> |
Overflow set to scroll. |
Dropdown |
Dropdown.hpp |
TLayout<Dropdown> |
Select from options, portal list. |
CanvasLayout |
Canvas.hpp |
TLayout<CanvasLayout> |
Renders to a render target. |
Modal |
Modal.hpp |
TLayout<Modal> |
Full-screen overlay with intro animation. |
Tooltip |
Tooltip.hpp |
TLayout<Tooltip, Panel> |
Shows on hover after delay. |
Popup |
Popup.hpp |
TLayout<Popup, Panel> |
Floating panel, portal, context menus. |
See Widgets for usage.
- Lifecycle — the exact order of operations per frame.
- Style System — cascade, specificity, transitions.
- Layout Engine — measure, arrange, hit-test.
- Events — click semantics, focus, capture.
- Custom Widgets — writing a widget.