docs
Loading...
Searching...
No Matches
LayoutNode.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
6#include <AppCore/Defines.h>
10#include <Ultralight/RefPtr.h>
11#include <Ultralight/String.h>
12
13#include <concepts>
14#include <utility>
15
16namespace ultralight {
17
18class Container;
19class Panel;
20
21///
22/// Base class for Panel%s and Container%s in a window layout tree.
23///
24/// An AppCore window arranges web views and native drawing regions into a tree of rows and columns,
25/// where each node shares space along its parent container's axis. LayoutNode represents an
26/// individual element in that hierarchy, whether it's a Container grouping child nodes or a Panel
27/// hosting a single View.
28///
29/// You use node handles to manage panes and containers dynamically at runtime, adapting the
30/// interface without rebuilding the layout tree.
31///
32/// This helper toggles a sidebar panel on and off:
33///
34/// ```
35/// void ToggleSidebar(RefPtr<Panel> sidebar) {
36/// if (sidebar->is_hidden())
37/// sidebar->Show(); // back at its previous size
38/// else
39/// sidebar->Hide(); // its siblings take the space
40/// }
41/// ```
42///
43/// ## Showing and Hiding
44///
45/// Calling Hide() takes a node off screen and redistributes its space to sibling nodes, and Show()
46/// restores the node at its remembered size.
47///
48/// Hiding affects different node types:
49///
50/// - **Hiding a Container hides its entire subtree.**
51/// - **Hiding a Panel suspends its hosted View.** Painting and animations pause, and the page
52/// receives a `visibilitychange` event.
53///
54/// ## Declared Sizes
55///
56/// Call SetSize(), SetMinSize(), or SetMaxSize() to update a node's declared size constraints at
57/// runtime. In a resizable container, double-clicking an adjacent divider restores both neighboring
58/// panes to the declared sizes set here (discarding any divider drags made since).
59///
60/// ## Layout Geometry
61///
62/// Accessors report layout rectangles across different coordinate spaces:
63///
64/// - **bounds() returns container-local logical pixels.**
65/// - **device_bounds() returns window back-buffer device pixels.** Native rendering passes use this
66/// coordinate space to draw beneath or above web content.
67///
68/// Both rectangles remain zero until the window completes its first layout pass. Calling SetSize()
69/// queues a layout pass for an upcoming frame.
70///
71/// This registration reads the updated dimensions once the layout pass completes:
72///
73/// ```
74/// sidebar->SetSize("320px");
75///
76/// // bounds() still holds the old rect here. Read it once the layout runs:
77/// on_resize_ = sidebar->OnLayoutChange([](LayoutNode& node) {
78/// Log("sidebar is now " + std::to_string(node.bounds().width()) + "px wide");
79/// });
80/// ```
81///
82/// Callback registrations report geometry changes:
83///
84/// - **OnLayoutChange() fires when layout changes the node's bounds.** It runs at most once per
85/// frame after the layout pass completes.
86/// - **OnUserResize() fires when a user resizes the node with a divider.** It fires on drag release
87/// and after a double-click restores declared sizes, only when the node's size changed.
88///
89/// Both methods return a LayoutCallback that manages the registration (destroying it unregisters
90/// the callback).
91///
92/// ## Node Lifetimes and Handles
93///
94/// A handle never keeps its node or its window alive (releasing one doesn't remove the node from
95/// the layout tree). To remove a node, call Container::Remove() or Foreground::Remove().
96///
97/// After a node is removed or its window closes, handles remain safe to call. Mutations do nothing,
98/// and accessors return safe fallback values.
99///
100/// Adding a child to a removed container or a closed window logs a warning and returns a handle
101/// that isn't attached to any window.
102///
103/// @note A View kept after its panel is removed loses its listeners-- register them again after
104/// adopting it into another panel.
105///
106/// @see Panel, Container, Container::Remove(), Foreground::Remove(), LayoutCallback
107///
109 public:
110 ///
111 /// Get the node's key (empty if unkeyed).
112 ///
113 virtual String key() const = 0;
114
115 ///
116 /// Get the node's parent container (null at the root or when detached).
117 ///
118 virtual RefPtr<Container> parent() const = 0;
119
120 ///
121 /// Get the node's index within its parent (-1 when detached).
122 ///
123 virtual int index() const = 0;
124
125 ///
126 /// Get this node as a Panel (null if it is not one).
127 ///
128 virtual RefPtr<Panel> AsPanel() = 0;
129
130 ///
131 /// Get this node as a Container (null if it is not one).
132 ///
134
135 ///
136 /// Hide the node and redistribute its space to its siblings.
137 ///
138 /// The node remembers its declared size, and Show() restores it.
139 ///
140 /// Hiding a Container hides its entire subtree.
141 ///
142 /// A hidden Panel's View stops rendering entirely-- painting and animations suspend. The page
143 /// receives a `visibilitychange` event.
144 ///
145 /// @note The `visibilitychange` event fires per panel rather than per window. Page code written
146 /// for tab-background semantics receives this event on every Hide() and Show().
147 ///
148 virtual void Hide() = 0;
149
150 ///
151 /// Show the node again, restoring its remembered size exactly.
152 ///
153 virtual void Show() = 0;
154
155 ///
156 /// Whether or not the node is hidden.
157 ///
158 /// This reports the node's own flag, so a node inside a hidden container still reports its
159 /// own state even though it is not on screen.
160 ///
161 virtual bool is_hidden() const = 0;
162
163 ///
164 /// Get the node's rect from the most recent layout in container-local logical pixels (the same
165 /// space SetBounds() takes).
166 ///
167 /// @note This value is zero until the node's first layout.
168 ///
169 virtual Rect bounds() const = 0;
170
171 ///
172 /// Get the node's rect from the most recent layout in window back-buffer device pixels (the
173 /// space your own drawing uses).
174 ///
175 /// @note This value is zero until the node's first layout.
176 ///
177 virtual IntRect device_bounds() const = 0;
178
179 ///
180 /// Place this node manually.
181 ///
182 /// @param bounds The node's new rect, in container-local logical pixels.
183 ///
184 /// @note This is only legal inside its container's layout delegate (see
185 /// Container::SetLayoutOverride()). Anywhere else it is a no-op with a warning.
186 ///
187 virtual void SetBounds(const Rect& bounds) = 0;
188
189 ///
190 /// Set the node's declared size along its container's axis.
191 ///
192 /// This acts like the size in the creation options-- double-clicking an adjacent divider
193 /// restores the node to the size declared here, discarding any user divider drags made since.
194 ///
195 /// @param size The new declared size. An unset Size means one flex share (`1fr`).
196 ///
197 /// @note A floating panel's size is fixed at creation. Calling SetSize(), SetMinSize(), or
198 /// SetMaxSize() on a floating panel does nothing and logs a warning.
199 ///
200 virtual void SetSize(Size size) = 0;
201
202 ///
203 /// Set the node's minimum size constraint.
204 ///
205 /// @param min_size The new minimum. An unset Length means no minimum.
206 ///
207 virtual void SetMinSize(Length min_size) = 0;
208
209 ///
210 /// Set the node's maximum size constraint.
211 ///
212 /// @param max_size The new maximum. An unset Length means no maximum.
213 ///
214 virtual void SetMaxSize(Length max_size) = 0;
215
216 ///
217 /// Register a callback fired when the user resizes this node with a divider.
218 ///
219 /// It fires on drag release, and again after a double-click resets the node to its declared
220 /// size. Either way it fires only when the node's size changed.
221 ///
222 /// @param callback The function to call. It receives `user_data` and this node.
223 ///
224 /// @param user_data Passed back to the callback on every fire (can be nullptr).
225 ///
226 /// @param destroy_user_data Called once when the registration drops `user_data` (can be
227 /// nullptr).
228 ///
229 /// @return Returns a LayoutCallback controlling how long the callback stays registered.
230 ///
231 /// @note Callbacks must not let exceptions propagate.
232 ///
233 [[nodiscard]] virtual LayoutCallback OnUserResize(
234 LayoutNodeCallback callback, void* user_data,
235 LayoutDestroyUserDataCallback destroy_user_data) = 0;
236
237 ///
238 /// Register a callback that fires after layout changes this node's bounds (at most once per
239 /// frame).
240 ///
241 /// Use this to follow a node's geometry (eg, a reserved region that your own drawing fills).
242 ///
243 /// @param callback The function to call. It receives `user_data` and this node.
244 ///
245 /// @param user_data Passed back to the callback on every fire (can be nullptr).
246 ///
247 /// @param destroy_user_data Called once when the registration drops `user_data` (can be
248 /// nullptr).
249 ///
250 /// @return Returns a LayoutCallback controlling how long the callback stays registered.
251 ///
252 /// @note Callbacks must not let exceptions propagate.
253 ///
254 [[nodiscard]] virtual LayoutCallback OnLayoutChange(
255 LayoutNodeCallback callback, void* user_data,
256 LayoutDestroyUserDataCallback destroy_user_data) = 0;
257
258 ///
259 /// Register a user-resize callback (invocable form).
260 ///
261 /// @param callback Any invocable. It may take the node as `LayoutNode&`, or nothing at
262 /// all.
263 ///
264 /// @return Returns a LayoutCallback controlling how long the callback stays registered.
265 ///
266 template <typename Callback>
267 requires std::invocable<Callback&, LayoutNode&> || std::invocable<Callback&>
268 [[nodiscard]] LayoutCallback OnUserResize(Callback callback) {
269 return RegisterInvocable<Callback>(std::move(callback), true);
270 }
271
272 ///
273 /// Register a layout-change callback (invocable form).
274 ///
275 /// @param callback Any invocable. It may take the node as `LayoutNode&`, or nothing at
276 /// all.
277 ///
278 /// @return Returns a LayoutCallback controlling how long the callback stays registered.
279 ///
280 template <typename Callback>
281 requires std::invocable<Callback&, LayoutNode&> || std::invocable<Callback&>
282 [[nodiscard]] LayoutCallback OnLayoutChange(Callback callback) {
283 return RegisterInvocable<Callback>(std::move(callback), false);
284 }
285
286 ///
287 /// Get the sibling after this node (null at the end, at the root, or when detached).
288 ///
289 RefPtr<LayoutNode> next_sibling() const;
290
291 ///
292 /// Get the sibling before this node (null at the start, at the root, or when detached).
293 ///
294 RefPtr<LayoutNode> previous_sibling() const;
295
296 protected:
297 virtual ~LayoutNode();
298
299 private:
300 template <typename Callback>
301 LayoutCallback RegisterInvocable(Callback callback, bool user_resize) {
302 auto* holder = new Callback(std::move(callback));
303 LayoutNodeCallback trampoline = [](void* user_data, LayoutNode* node) {
304 auto& cb = *static_cast<Callback*>(user_data);
305 if constexpr (std::invocable<Callback&, LayoutNode&>)
306 cb(*node);
307 else
308 cb();
309 };
311 = [](void* user_data) { delete static_cast<Callback*>(user_data); };
312 return user_resize ? OnUserResize(trampoline, holder, destroy)
313 : OnLayoutChange(trampoline, holder, destroy);
314 }
315};
316
317} // namespace ultralight
#define AExport
Definition Defines.h:41
A layout node that organizes child nodes into a row or column.
Definition Container.h:101
Scoped handle that controls how long a layout callback stays registered.
Definition Callback.h:144
Base class for Panels and Containers in a window layout tree.
Definition LayoutNode.h:108
virtual RefPtr< Panel > AsPanel()=0
Get this node as a Panel (null if it is not one).
virtual void SetSize(Size size)=0
Set the node's declared size along its container's axis.
virtual RefPtr< Container > parent() const =0
Get the node's parent container (null at the root or when detached).
virtual void SetMaxSize(Length max_size)=0
Set the node's maximum size constraint.
virtual int index() const =0
Get the node's index within its parent (-1 when detached).
virtual RefPtr< Container > AsContainer()=0
Get this node as a Container (null if it is not one).
LayoutCallback OnUserResize(Callback callback)
Register a user-resize callback (invocable form).
Definition LayoutNode.h:268
virtual LayoutCallback OnLayoutChange(LayoutNodeCallback callback, void *user_data, LayoutDestroyUserDataCallback destroy_user_data)=0
Register a callback that fires after layout changes this node's bounds (at most once per frame).
virtual void SetBounds(const Rect &bounds)=0
Place this node manually.
virtual void Show()=0
Show the node again, restoring its remembered size exactly.
virtual void Hide()=0
Hide the node and redistribute its space to its siblings.
LayoutCallback OnLayoutChange(Callback callback)
Register a layout-change callback (invocable form).
Definition LayoutNode.h:282
virtual Rect bounds() const =0
Get the node's rect from the most recent layout in container-local logical pixels (the same space Set...
virtual void SetMinSize(Length min_size)=0
Set the node's minimum size constraint.
virtual String key() const =0
Get the node's key (empty if unkeyed).
virtual LayoutCallback OnUserResize(LayoutNodeCallback callback, void *user_data, LayoutDestroyUserDataCallback destroy_user_data)=0
Register a callback fired when the user resizes this node with a divider.
virtual IntRect device_bounds() const =0
Get the node's rect from the most recent layout in window back-buffer device pixels (the space your o...
virtual bool is_hidden() const =0
Whether or not the node is hidden.
A distance or size constraint in an AppCore layout.
Definition Length.h:60
A layout node that hosts a single web view.
Definition Panel.h:59
Interface for all ref-counted objects that will be managed using the RefPtr<> smart pointer.
Definition RefPtr.h:49
A nullable smart pointer.
Definition RefPtr.h:126
Fixed, percentage, and flexible sizes for panels and containers.
Definition Length.h:371
Unicode string container with conversions for UTF-8, UTF-16, and UTF-32.
Definition String.h:31
Root namespace for every public Ultralight type, function, and enumeration.
void(*) LayoutNodeCallback(void *user_data, LayoutNode *node)
Callback fired for a layout node event.
Definition Callback.h:38
void(*) LayoutDestroyUserDataCallback(void *user_data)
Destroy hook for a callback registration's user data.
Definition Callback.h:27
Integer Rectangle Helper.
Definition Geometry.h:533
Float Rectangle Helper.
Definition Geometry.h:416