docs
Loading...
Searching...
No Matches
Container.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
8
9#include <concepts>
10#include <utility>
11
12namespace ultralight {
13
14struct DividerStyle;
15
16///
17/// A builder element (see `<AppCore/layout/Builder.h>`): anything that can append itself to
18/// a container.
19///
20template <typename T>
21concept LayoutBuildable = requires(const T& spec, Container& parent) { spec.ApplyTo(parent); };
22
23///
24/// A layout node that organizes child nodes into a row or column.
25///
26/// Containers form the rows and columns of an AppCore window's layout tree. The window's root
27/// layout is a container that fills the entire window, and adding child rows or columns divides
28/// that space into a hierarchy of panes.
29///
30/// Each child can be a Panel that hosts a View, or another Container that splits space further. The
31/// library recalculates child sizes automatically whenever the window resizes or moves between
32/// displays with different scale factors.
33///
34/// The following example creates a resizable row with a sidebar and a content pane:
35///
36/// ```
37/// RefPtr<Container> body =
38/// window->layout()->AddRow({ .key = "body", .resizable = true });
39/// RefPtr<Panel> sidebar =
40/// body->AddPanel({ .key = "sidebar", .size = "240px", .min_size = "160px" });
41/// RefPtr<Panel> content = body->AddPanel({ .key = "content" });
42///
43/// sidebar->view()->LoadURL("file:///sidebar.html");
44/// content->view()->LoadURL("file:///content.html");
45/// ```
46///
47/// ## Sizing Children
48///
49/// A container divides space along its main axis by first subtracting its padding and any gaps
50/// between children. It satisfies pixel sizes first, gives percentage sizes their shares of the
51/// free space left after pixel-sized children, and divides what remains among flexible children
52/// according to their flex factors. A child created without an explicit size defaults to one flex
53/// share (`1fr`). For what each size unit means, see Size and Length.
54///
55/// Containers also accept `gap` to separate adjacent children and `padding` to inset them from all
56/// four edges (both in logical pixels).
57///
58/// ## Resizable Containers
59///
60/// Setting `resizable = true` on a container places draggable dividers between its direct children.
61/// Dragging a divider resizes the panes on either side.
62///
63/// You can constrain how panes resize during a drag:
64///
65/// - **Set `min_size` to prevent a pane from collapsing.**
66/// - **Set `fixed = true` to keep a child's size constant.**
67///
68/// Double-clicking a divider resets both neighboring children to their declared sizes.
69///
70/// You can customize divider appearance by passing a DividerStyle to SetDividerStyle().
71///
72/// ## Finding Children
73///
74/// Assigning a `key` in a child's creation options lets you look up that node across the
75/// container's subtree with Find(), FindPanel(), or FindContainer(). If multiple descendants share
76/// the same key, the lookup returns the newest match.
77///
78/// ## Reserving Space for Drawing
79///
80/// An empty container reserves space in the layout without hosting a View or rendering web content.
81///
82/// To track the container's bounds as the window resizes, register a layout change listener:
83///
84/// ```
85/// RefPtr<Container> viewport = body->AddColumn({ .key = "viewport" });
86///
87/// // The callback runs while viewport_tracker_ is kept.
88/// viewport_tracker_ = viewport->OnLayoutChange([](LayoutNode& node) {
89/// ResizeViewport(node.device_bounds());
90/// });
91/// ```
92///
93/// ## Overriding Child Layout
94///
95/// Call SetLayoutOverride() to install a layout delegate that positions direct children manually by
96/// calling LayoutNode::SetBounds(). The delegate runs whenever the window updates layout.
97///
98/// @see LayoutNode, PanelOptions, ContainerOptions, Size, DividerStyle, Container::Build(),
99/// Window::BuildLayout()
100///
102 public:
103 ///
104 /// Create a panel with a new View (window-default view configuration).
105 ///
106 /// @param options The panel's declared options.
107 ///
108 /// @param position Where to insert the panel (default: append at the end).
109 ///
110 /// @return Returns the new panel (see LayoutNode if this container was removed).
111 ///
112 virtual RefPtr<Panel> AddPanel(const PanelOptions& options = {},
113 InsertPosition position = {}) = 0;
114
115 ///
116 /// Create a panel with a caller-supplied ViewConfig.
117 ///
118 /// The fields describing the hosting window are filled in from the window for you. Every
119 /// other field is yours:
120 ///
121 /// - `initial_device_scale` (the window's scale)
122 /// - `display_id` (the window's display)
123 /// - `is_accelerated` (whether the window composites on the GPU)
124 ///
125 /// To host a deliberately CPU-rendered View on a GPU window, create the View with
126 /// Renderer::CreateView() and use AdoptPanel().
127 ///
128 /// @param options The panel's declared options.
129 ///
130 /// @param view_config The configuration for the new View. The window-describing fields
131 /// listed above are overwritten.
132 ///
133 /// @param position Where to insert the panel (default: append at the end).
134 ///
135 /// @return Returns the new panel (see LayoutNode if this container was removed).
136 ///
137 virtual RefPtr<Panel> AddPanel(const PanelOptions& options, const ViewConfig& view_config,
138 InsertPosition position = {}) = 0;
139
140 ///
141 /// Create a panel that adopts an existing View (including a View created through a
142 /// Session).
143 ///
144 /// The panel takes a reference to the View and resizes it to the panel's bounds.
145 ///
146 /// @param view The View to host.
147 ///
148 /// @param options The panel's declared options.
149 ///
150 /// @param position Where to insert the panel (default: append at the end).
151 ///
152 /// @return Returns the new panel (see LayoutNode if this container was removed).
153 ///
154 /// @note The panel replaces the View's editor listener with its own (see Panel).
155 ///
156 virtual RefPtr<Panel> AdoptPanel(RefPtr<View> view, const PanelOptions& options = {},
157 InsertPosition position = {}) = 0;
158
159 ///
160 /// Adopt an existing View (shorthand for AdoptPanel()).
161 ///
162 /// @param view The View to host.
163 ///
164 /// @param options The panel's declared options.
165 ///
166 /// @param position Where to insert the panel (default: append at the end).
167 ///
168 /// @return Returns the new panel (see LayoutNode if this container was removed).
169 ///
170 template <typename ViewRef>
171 requires std::convertible_to<ViewRef&&, RefPtr<View>>
172 RefPtr<Panel> AddPanel(ViewRef&& view, const PanelOptions& options = {},
173 InsertPosition position = {}) {
174 return AdoptPanel(RefPtr<View>(std::forward<ViewRef>(view)), options, position);
175 }
176
177 ///
178 /// Create a child row container.
179 ///
180 /// @param options The container's declared options.
181 ///
182 /// @param position Where to insert the container (default: append at the end).
183 ///
184 /// @return Returns the new container (see LayoutNode if this container was removed).
185 ///
186 virtual RefPtr<Container> AddRow(const ContainerOptions& options = {},
187 InsertPosition position = {}) = 0;
188
189 ///
190 /// Create a child column container.
191 ///
192 /// @param options The container's declared options.
193 ///
194 /// @param position Where to insert the container (default: append at the end).
195 ///
196 /// @return Returns the new container (see LayoutNode if this container was removed).
197 ///
198 virtual RefPtr<Container> AddColumn(const ContainerOptions& options = {},
199 InsertPosition position = {}) = 0;
200
201 ///
202 /// Remove a direct child (and, for a container child, its whole subtree) from the tree.
203 ///
204 /// Removal detaches the child and drops each removed panel's reference to its View. A View
205 /// the application holds a RefPtr to survives removal and can be adopted into another
206 /// panel or window. For what removed handles do afterwards, see LayoutNode.
207 ///
208 /// @param node The direct child to remove.
209 ///
210 /// @return Returns whether or not the node was a direct child and was removed.
211 ///
212 virtual bool Remove(RefPtr<LayoutNode> node) = 0;
213
214 ///
215 /// Remove every child. Equivalent to Remove() on each child in turn.
216 ///
217 virtual void RemoveAll() = 0;
218
219 ///
220 /// Reorder a direct child within this container.
221 ///
222 /// A node from a different container (or window) is a no-op with a warning. A position
223 /// anchor that is not a direct child falls back to the end, also with a warning.
224 ///
225 /// @param node The direct child to move.
226 ///
227 /// @param position The new position (default: move to the end).
228 ///
229 /// @return Returns whether or not the node was reordered.
230 ///
231 virtual bool Move(RefPtr<LayoutNode> node, InsertPosition position = {}) = 0;
232
233 ///
234 /// Get the number of children.
235 ///
236 virtual int child_count() const = 0;
237
238 ///
239 /// Get the child at an index.
240 ///
241 /// @param index The zero-based index within this container's child list.
242 ///
243 /// @return Returns the child, or null when the index is out of range.
244 ///
245 virtual RefPtr<LayoutNode> child_at(int index) const = 0;
246
247 ///
248 /// Find a descendant by key.
249 ///
250 /// @param key The key to look for.
251 ///
252 /// @return Returns the matching node, or null when nothing matches. With duplicate keys
253 /// the most recently created match wins.
254 ///
255 virtual RefPtr<LayoutNode> Find(const String& key) = 0;
256
257 ///
258 /// Find a descendant panel by key.
259 ///
260 /// @param key The key to look for.
261 ///
262 /// @return Returns the matching panel, or null when nothing matches or the match is not a
263 /// panel.
264 ///
265 virtual RefPtr<Panel> FindPanel(const String& key) = 0;
266
267 ///
268 /// Find a descendant container by key.
269 ///
270 /// @param key The key to look for.
271 ///
272 /// @return Returns the matching container, or null when nothing matches or the match is
273 /// not a container.
274 ///
276
277 ///
278 /// Override this container's layout with a layout delegate.
279 ///
280 /// The delegate runs each time the window performs layout (after a layout change, a resize,
281 /// or a display scale change), but never while the container has no children.
282 ///
283 /// The delegate positions the container's direct children by calling LayoutNode::SetBounds():
284 ///
285 /// ```
286 /// grid->SetLayoutOverride([](Container& c, const Rect& content_box) {
287 /// float x = content_box.x(), y = content_box.y(), w = content_box.width();
288 /// c.child_at(0)->SetBounds(Rect::FromXYWH(x, y, w, 40));
289 /// c.child_at(1)->SetBounds(
290 /// Rect::FromXYWH(x, y + 40, w, content_box.height() - 40));
291 /// });
292 /// ```
293 ///
294 /// `content_box` is the container's content box (inside its padding) in container-local
295 /// logical pixels.
296 ///
297 /// Placed rectangles are snapped and clipped to this box.
298 ///
299 /// A placed rectangle persists until the delegate places that child again-- the delegate may
300 /// place only what changed.
301 ///
302 /// Children that the delegate has never placed occupy no region.
303 ///
304 /// Changing the tree from inside the delegate is allowed.
305 ///
306 /// Setting a new delegate replaces the existing one, and every child resets to never-placed.
307 ///
308 /// Replacing the delegate destroys the previous delegate's user data. If replaced from inside
309 /// the delegate, destruction is deferred until the delegate returns.
310 ///
311 /// @param callback The delegate to install. Pass nullptr to restore ordinary
312 /// layout.
313 ///
314 /// @param user_data Passed back to the delegate on every run (can be nullptr).
315 ///
316 /// @param destroy_user_data Called once when the delegate drops `user_data` (can be
317 /// nullptr).
318 ///
319 /// \parblock
320 /// @note `resizable` is ignored (with a warning) while a container has a layout delegate.
321 /// Dividers only exist between children that the container itself lays out.
322 /// \endparblock
323 ///
324 /// \parblock
325 /// @note Callbacks must not let exceptions propagate.
326 /// \endparblock
327 ///
328 virtual void SetLayoutOverride(LayoutOverrideCallback callback, void* user_data = nullptr,
329 LayoutDestroyUserDataCallback destroy_user_data = nullptr) = 0;
330
331 ///
332 /// Whether or not this container has a layout delegate installed.
333 ///
334 virtual bool HasLayoutOverride() const = 0;
335
336 ///
337 /// Set this container's divider style (see `<AppCore/layout/DividerStyle.h>`).
338 ///
339 /// Fields left unset fall back to the window's style (Window::SetDividerStyle()), then to
340 /// the built-in defaults.
341 ///
342 /// @param style The style to apply. A default-constructed style clears the override.
343 ///
344 virtual void SetDividerStyle(const DividerStyle& style) = 0;
345
346 ///
347 /// Install a layout delegate (invocable form).
348 ///
349 /// @param callback Any invocable. It may take `(Container&, const Rect&)` (the container
350 /// and its content box), or just `(const Rect&)`.
351 ///
352 template <typename Callback>
353 requires std::invocable<Callback&, Container&, const Rect&>
354 || std::invocable<Callback&, const Rect&>
355 void SetLayoutOverride(Callback callback) {
356 auto* holder = new Callback(std::move(callback));
357 LayoutOverrideCallback trampoline = [](void* user_data, Container* container,
358 Rect content_box) {
359 auto& cb = *static_cast<Callback*>(user_data);
360 if constexpr (std::invocable<Callback&, Container&, const Rect&>)
361 cb(*container, content_box);
362 else
363 cb(content_box);
364 };
365 SetLayoutOverride(trampoline, holder,
366 [](void* user_data) { delete static_cast<Callback*>(user_data); });
367 }
368
369 ///
370 /// Iterator over a container's children (a forward range of RefPtr<LayoutNode>).
371 ///
373 public:
375 using difference_type = int;
376
377 ChildIterator() = default;
378 ChildIterator(const Container* container, int index) : container_(container), index_(index) {}
379
380 RefPtr<LayoutNode> operator*() const { return container_->child_at(index_); }
382 ++index_;
383 return *this;
384 }
386 ChildIterator prev = *this;
387 ++index_;
388 return prev;
389 }
390 friend bool operator==(const ChildIterator&, const ChildIterator&) = default;
391
392 private:
393 const Container* container_ = nullptr;
394 int index_ = 0;
395 };
396
397 ///
398 /// A range over the container's children:
399 ///
400 /// ```
401 /// for (RefPtr<LayoutNode> child : container->children()) { ... }
402 /// ```
403 ///
404 /// The range reads through child_at(), so mutating the child list while iterating skews
405 /// the walk. Collect handles first when a loop mutates.
406 ///
408 public:
409 ChildRange(const Container* container, int count) : container_(container), count_(count) {}
410 ChildIterator begin() const { return ChildIterator(container_, 0); }
411 ChildIterator end() const { return ChildIterator(container_, count_); }
412
413 private:
414 const Container* container_;
415 int count_;
416 };
417
418 ///
419 /// Get a forward range over the children.
420 ///
421 ChildRange children() const { return ChildRange(this, child_count()); }
422
423 ///
424 /// Append builder elements (see `<AppCore/layout/Builder.h>`) to this container.
425 ///
426 /// Build **appends**, so calling twice appends twice. For a fresh start, call RemoveAll()
427 /// first.
428 ///
429 /// @param children The builder elements to append, in order.
430 ///
431 /// @return Returns this container, so the builder line composes with follow-up calls.
432 ///
433 template <typename... Children>
434 requires(LayoutBuildable<Children> && ...)
436 (children.ApplyTo(*this), ...);
437 return RefPtr<Container>(this);
438 }
439
440 protected:
441 virtual ~Container();
442};
443
446 if (!p)
447 return nullptr;
448 int i = index();
449 return i < 0 ? nullptr : p->child_at(i + 1);
450}
451
454 if (!p)
455 return nullptr;
456 int i = index();
457 return i <= 0 ? nullptr : p->child_at(i - 1);
458}
459
460} // namespace ultralight
#define AExport
Definition Defines.h:41
Iterator over a container's children (a forward range of RefPtr<LayoutNode>).
Definition Container.h:372
int difference_type
Definition Container.h:375
RefPtr< LayoutNode > value_type
Definition Container.h:374
friend bool operator==(const ChildIterator &, const ChildIterator &)=default
ChildIterator & operator++()
Definition Container.h:381
ChildIterator operator++(int)
Definition Container.h:385
RefPtr< LayoutNode > operator*() const
Definition Container.h:380
ChildIterator(const Container *container, int index)
Definition Container.h:378
A range over the container's children:
Definition Container.h:407
ChildIterator end() const
Definition Container.h:411
ChildRange(const Container *container, int count)
Definition Container.h:409
ChildIterator begin() const
Definition Container.h:410
A layout node that organizes child nodes into a row or column.
Definition Container.h:101
virtual bool Move(RefPtr< LayoutNode > node, InsertPosition position={})=0
Reorder a direct child within this container.
virtual RefPtr< Container > AddColumn(const ContainerOptions &options={}, InsertPosition position={})=0
Create a child column container.
virtual RefPtr< Panel > AdoptPanel(RefPtr< View > view, const PanelOptions &options={}, InsertPosition position={})=0
Create a panel that adopts an existing View (including a View created through a Session).
virtual RefPtr< Panel > FindPanel(const String &key)=0
Find a descendant panel by key.
virtual bool Remove(RefPtr< LayoutNode > node)=0
Remove a direct child (and, for a container child, its whole subtree) from the tree.
virtual void SetLayoutOverride(LayoutOverrideCallback callback, void *user_data=nullptr, LayoutDestroyUserDataCallback destroy_user_data=nullptr)=0
Override this container's layout with a layout delegate.
virtual RefPtr< LayoutNode > child_at(int index) const =0
Get the child at an index.
virtual RefPtr< Panel > AddPanel(const PanelOptions &options, const ViewConfig &view_config, InsertPosition position={})=0
Create a panel with a caller-supplied ViewConfig.
virtual RefPtr< LayoutNode > Find(const String &key)=0
Find a descendant by key.
void SetLayoutOverride(Callback callback)
Install a layout delegate (invocable form).
Definition Container.h:355
RefPtr< Panel > AddPanel(ViewRef &&view, const PanelOptions &options={}, InsertPosition position={})
Adopt an existing View (shorthand for AdoptPanel()).
Definition Container.h:172
ChildRange children() const
Get a forward range over the children.
Definition Container.h:421
virtual bool HasLayoutOverride() const =0
Whether or not this container has a layout delegate installed.
virtual RefPtr< Panel > AddPanel(const PanelOptions &options={}, InsertPosition position={})=0
Create a panel with a new View (window-default view configuration).
virtual void SetDividerStyle(const DividerStyle &style)=0
Set this container's divider style (see <AppCore/layout/DividerStyle.h>).
virtual void RemoveAll()=0
Remove every child.
virtual RefPtr< Container > FindContainer(const String &key)=0
Find a descendant container by key.
RefPtr< Container > Build(Children... children)
Append builder elements (see <AppCore/layout/Builder.h>) to this container.
Definition Container.h:435
virtual RefPtr< Container > AddRow(const ContainerOptions &options={}, InsertPosition position={})=0
Create a child row container.
virtual int child_count() const =0
Get the number of children.
Base class for Panels and Containers in a window layout tree.
Definition LayoutNode.h:108
virtual RefPtr< Container > parent() const =0
Get the node's parent container (null at the root or when detached).
RefPtr< LayoutNode > next_sibling() const
Get the sibling after this node (null at the end, at the root, or when detached).
Definition Container.h:444
virtual int index() const =0
Get the node's index within its parent (-1 when detached).
RefPtr< LayoutNode > previous_sibling() const
Get the sibling before this node (null at the start, at the root, or when detached).
Definition Container.h:452
virtual String key() const =0
Get the node's key (empty if unkeyed).
A nullable smart pointer.
Definition RefPtr.h:126
Unicode string container with conversions for UTF-8, UTF-16, and UTF-32.
Definition String.h:31
A builder element (see <AppCore/layout/Builder.h>): anything that can append itself to a container.
Definition Container.h:21
Root namespace for every public Ultralight type, function, and enumeration.
void(*) LayoutOverrideCallback(void *user_data, Container *container, Rect content_box)
Layout delegate, called to place a container's children yourself.
Definition Callback.h:92
void(*) LayoutDestroyUserDataCallback(void *user_data)
Destroy hook for a callback registration's user data.
Definition Callback.h:27
Configuration options for a row or column container.
Definition Options.h:265
Line thickness, colors, and grab width for draggable pane dividers.
Definition DividerStyle.h:47
Position relative to an existing sibling in a container.
Definition Options.h:348
Configuration options for a panel in a row or column.
Definition Options.h:182
Float Rectangle Helper.
Definition Geometry.h:416
View-specific configuration settings.
Definition View.h:100