You can arrange multiple web views across an AppCore window using a tree of panels and containers. Each panel hosts a single view, while containers organize them into rows and columns.

You declare sizes once— the window re-resolves the layout whenever it resizes or its display scale changes.

Panels tile the window surface. To float overlays like toasts or dialogs above the layout, see [Floating Panels](/docs/2.0/floating-panels).

## Building the Layout

You can build a window layout step by step using panels and containers, or declare the entire tree at once.

### Adding a Single Panel

Call `Window::AddPanel()` with no arguments to create a panel that fills the entire window:

```cpp
window->AddPanel()->view()->LoadURL("file:///app.html");
```

Call `Panel::view()` to access the hosted `View` and load web content.

For the equivalent C functions, see [AppCore in C](/docs/2.0/c-appcore).

### Arranging Rows and Columns

Call `Window::layout()` to access the root container, then add child rows or columns to split the space:

```cpp
///
/// Split the window into a fixed-width sidebar and a fluid content pane.
///
RefPtr<Container> body = window->layout()->AddRow({ .key = "body" });
RefPtr<Panel> sidebar =
    body->AddPanel({ .key = "sidebar", .size = "240px", .min_size = "160px" });
RefPtr<Panel> content = body->AddPanel({ .key = "content" });

sidebar->view()->LoadURL("file:///sidebar.html");
content->view()->LoadURL("file:///content.html");
```

The window's root container is a column that fills the window. Calling `Container::AddRow()` or `Container::AddColumn()` nests containers inside it, while `Container::AddPanel()` appends a panel to host web content.

Setting a `key` on any node lets you look it up later using `Window::FindPanel()` or `Container::Find()`.

### Single-Expression Layouts

Declare an entire layout tree in a single expression using `Window::BuildLayout()` alongside `panel()`, `row()`, and `column()`:

```cpp
RefPtr<Panel> toolbar, sidebar, content;

window->BuildLayout({},
  panel({ .key = "toolbar", .size = "44px", .fixed = true }, &toolbar),
  row({ .key = "body", .resizable = true },
    panel({ .key = "sidebar", .size = "240px" }, &sidebar),
    panel({ .key = "content" }, &content)));

toolbar->view()->LoadURL("file:///toolbar.html");
```

Passing pointers to node handles populates them directly during construction, so you don't need to look them up by key later.

> 👍 Appending Layout Elements
>
> `Window::BuildLayout()` and `Container::Build()` append children to the existing tree— calling either method a second time appends duplicate children. Call `layout()->RemoveAll()` first if you want to rebuild from a clean slate.

## Sizing Panels

You can control how nodes share space across a container and let users resize panes with draggable dividers.

### Sizing Panels and Containers

A child node accepts fixed, percentage, or flexible sizes along its parent container's axis:

| Value | Description |
|---|---|
| `"240px"` or `240` | Logical pixels. |
| `"30%"` | Percent of the container's free space. |
| `"2fr"` | Flex share of the free space. |

A child created without an explicit size receives a single flex share (`1fr`).

You can constrain child dimensions with `min_size` and `max_size`. Containers also accept `gap` and `padding`, which take values in logical pixels only.

Call `LayoutNode::SetSize()`, `LayoutNode::SetMinSize()`, or `LayoutNode::SetMaxSize()` to adjust sizes at runtime. The library batches layout updates into a single pass on an upcoming frame— `LayoutNode::bounds()` reflects the updated geometry once that pass completes.

### Creating Resizable Split Panes

Set `resizable = true` on a container to place draggable dividers between its direct children:

```cpp
RefPtr<Container> body =
    window->layout()->AddRow({ .key = "body", .resizable = true });
```

Dragging a divider resizes the panes on either side. Double-clicking a divider restores both neighboring panes to their declared sizes.

You can set `min_size` to prevent drags from collapsing a pane, or set `fixed = true` on a child so divider drags never resize it.

#### Styling Dividers

Customize divider appearance across the window with `Window::SetDividerStyle()`, or per container with `Container::SetDividerStyle()`:

```cpp
window->SetDividerStyle({ .visual_thickness = 1, .color = "#3c3c3c" });
```

You can configure the pointer hit width, visual line thickness, line color, and hover highlight color. Leaving the line color unset paints no line, allowing the window background to show through.

## Hiding and Removing Nodes

Call `LayoutNode::Hide()` to hide a node and redistribute its space to sibling nodes. Calling `LayoutNode::Show()` restores the node at its previous size.

Hiding a container hides its entire subtree. When a panel is hidden, its hosted `View` suspends painting and animations, and the page receives a `visibilitychange` event.

To detach a node, call `Container::Remove()` on its parent container. Removing a panel drops its reference to the hosted `View`, but any `View` you keep in a `RefPtr` survives and can be adopted into another panel with `Container::AdoptPanel()`.

> 📘 Node Handle Lifetimes
>
> Node handles reference nodes without keeping them or their window alive— releasing a handle does not remove the node. After a node is removed or its window closes, handles remain safe to use— mutations do nothing, and `Panel::view()` returns null.

## Embedding Custom Content

An empty container reserves layout space without painting anything, providing a dedicated region for native rendering (such as a 3D game viewport).

### Tracking Region Bounds

Register a layout listener with `LayoutNode::OnLayoutChange()` to receive updated geometry whenever the node moves or resizes:

```cpp
///
/// Reserve the right-hand pane for the engine's 3D viewport.
///
RefPtr<Container> viewport = body->AddColumn({ .key = "viewport" });

///
/// The callback fires for as long as viewport_tracker_ is kept.
///
viewport_tracker_ = viewport->OnLayoutChange([](LayoutNode& node) {
  ResizeViewport(node.device_bounds());
});
```

Call `LayoutNode::device_bounds()` to get the node's rectangle in window back-buffer device pixels (the coordinate space used for native drawing), or `LayoutNode::bounds()` for container-local logical pixels. Both values remain zero until the first layout resolve completes.

The `LayoutNode::OnLayoutChange()` callback fires at most once per frame when bounds change. Store the returned `LayoutCallback` instance— destroying it unregisters the listener.

### Rendering Beneath and Above

Implement `WindowListener::OnClear()` to render beneath web views, or `WindowListener::OnPaint()` to render above them.

When rendering from these callbacks, draw through the GPU driver into the window's render buffer (accessible via `Window::render_buffer_id()`), or onto the native window surface for CPU-rendered windows. Native drawing must never present the frame, and you cannot rely on GPU pipeline state persisting past the callback.

To keep animations updating when no web view has changed, call `Window::Invalidate()` to schedule the next frame.

### Overriding Container Layout

Call `Container::SetLayoutOverride()` to install a layout delegate that places direct children manually. This lets you implement custom positioning schemes that rows and columns cannot express.

## Managing Keyboard Focus

Clicking a panel grants it keyboard focus. To set focus programmatically, call `Panel::Focus()`. Attempting to focus a hidden panel is ignored with a warning.

To inspect which panel currently owns focus, call `Window::focused_panel()`.

> 🚧 Focusing Panels
>
> Never call `View::Focus()` directly on a panel's view. Focusing the view directly prevents the window from routing keyboard input to the active panel.

For handling text input methods and on-screen keyboards, see [Keyboard Focus and Editable State](/docs/2.0/keyboard-focus-and-editable-state).
