1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//! Dockable layout: splits, tab groups, and tiles canvases that a host can
//! rearrange, persist, and restore — with no appearance of its own.
//!
//! # The tree is the source of truth
//!
//! Each region — the center, plus an optional left, right, and bottom dock —
//! is one [`PaneTree`]. A tree is pure data. Containers are addressed by
//! [`NodeId`], panels by [`PanelId`], and no GPUI entity handle is stored
//! anywhere in it, which is what lets the layout algebra be exercised without
//! an `App` and lets a whole layout be compared, normalized, and serialized as
//! an ordinary value. A container is a `Split`, a `Tabs`, or a `Tiles`; there
//! is no leaf variant, so a panel can only ever live inside a `Tabs` or a
//! `Tiles`.
//!
//! Both ids are *stable*, and the rest of the design leans on it:
//!
//! - A [`NodeId`] survives every edit and every normalization rule. A
//! container still present after a drag carries the same id it had before,
//! which is what keeps a reconcile from tearing down entities the drag never
//! touched.
//! - A [`PanelId`] is the panel entity's `EntityId`, so it identifies the
//! panel for as long as the entity lives, across any number of moves between
//! groups and regions.
//!
//! Every mutation goes through the tree — [`PaneTree::insert_panel`],
//! [`remove_panel`](PaneTree::remove_panel),
//! [`move_panel`](PaneTree::move_panel), [`split`](PaneTree::split),
//! [`set_active`](PaneTree::set_active), and the rest — and each normalizes
//! before returning, so the tree is self-consistent the instant an edit
//! returns. Every edit reports what it did as an [`EditResult`].
//!
//! # The area reconciles the tree into entities
//!
//! [`DockArea`] owns the trees and a cache of container entities keyed by
//! `NodeId` ([`TabGroup`] for a `Tabs` node, [`TilesState`] for a `Tiles`
//! one), plus the panel handles keyed by `PanelId`. After any edit that
//! reports a change it walks the tree, creates entities for ids the cache does
//! not have, drops entries for ids that are gone — telling those panels
//! [`Panel::on_removed`] — pushes sizes and active indices into the survivors,
//! and emits [`DockEvent::LayoutChanged`]. Nothing else turns a tree edit into
//! live entities.
//!
//! Because node ids are stable, a steady-state pass creates and drops nothing.
//!
//! A layout is described the same entity-free way: [`DockLayout`] builds a
//! tree, and [`DockArea::set_center`] / [`DockArea::set_dock`] install it.
//! [`DockArea::dump`] and [`DockArea::load`] round-trip a whole area through
//! [`DockAreaState`], rebuilding panels through the [`PanelRegistry`].
//!
//! # The renderer seam
//!
//! Base supplies behavior; the host supplies appearance. Nothing in this
//! module paints a color, a border, or a size. Three traits carry the
//! appearance in:
//!
//! - [`DockAreaRenderer`] — the area frame, each split's frame and the divider
//! between its slots, one dock's chrome, and the stand-in for a panel this
//! build cannot construct.
//! - [`TabGroupRenderer`] — the tab bar, how the displayed panel is placed,
//! and the drop indicator.
//! - [`TilesRenderer`] — a tiles canvas, its tile frames, and their drag bars.
//!
//! A renderer never sees a drag event or a mouse position. Base attaches the
//! drag sources, drop hit-testing, focus, and keyboard handling to the very
//! elements the renderer returns, and hands it resolved state through
//! [`DockContext`], [`TabGroupContext`], and [`TileContext`] — each of which
//! also carries the callbacks (`toggle`, `select_tab`, `close`, `resize_to`)
//! that the renderer invokes rather than reimplementing.
//!
//! An area built without a renderer still docks, drags, resizes and persists.
//! It simply draws nothing but the panels themselves.
//!
//! [`Panel`] splits at the same seam: this trait covers behavior, and a
//! presentation layer — `gpui_component::dock::Panel` — extends it with
//! titles, toolbars, and menus. A panel type implements both.
//!
//! Every hook is optional in the same way: a renderer that declines one gets
//! base's own minimum for it. [`DockAreaRenderer::render_split_handle`] is the
//! clearest case — return `None` and the divider falls back to a one-pixel
//! line colored from `Theme::resizable`, so a skin with no opinion about
//! dividers implements nothing, while one that has an opinion replaces the
//! paint without touching the hit area, the cursor, or the drag.
//!
//! # Why the layout is data
//!
//! The usual way to build a dock is to make each container a live view that
//! holds its children, so the widget tree *is* the layout. That is what this
//! module replaced, and the three costs it carries are the reason:
//!
//! - **Emptiness has to propagate.** When the last panel leaves a tab group,
//! the group must remove itself from its parent, which may empty the parent
//! in turn. With containers as views this is mutual recursion between two
//! types, reaching upward through parent handles — and those handles have to
//! be installed after construction, which in GPUI means a deferred pass.
//! There is a window in which the tree disagrees with itself.
//! - **Structure and identity are the same thing.** Rearranging the widget
//! tree means creating and dropping views, so a drag can reset the state of
//! containers it never touched.
//! - **Nothing is testable without a window.** Asserting that a split collapses
//! correctly requires an `App`, an entity, and a frame.
//!
//! Here the tree is a value and the entities are its projection. Collapse is
//! [`PaneTree::normalize`]: one post-order pass repeated to a fixpoint, no
//! parent pointers, no deferred work, and idempotent by construction. Identity
//! is a [`NodeId`] that survives every edit and every normalization rule, so
//! reconciliation is a diff rather than a rebuild. And the whole layout algebra
//! runs as plain `#[test]`.
//!
//! What this buys, stated as properties rather than adjectives: a layout can be
//! compared, cloned and serialized as an ordinary value; a steady-state
//! reconcile creates and drops nothing, so a drag leaves untouched panels
//! untouched; and `normalize(normalize(t)) == normalize(t)` holds for every
//! tree, which is what makes the persisted format canonical.
//!
//! # Where this sits among docking libraries
//!
//! Editors tend to build the layout engine into the application — Zed's
//! `PaneGroup` and VS Code's workbench are not reusable outside their hosts.
//! Standalone libraries split the engine from the view to varying degrees:
//! `golden-layout` owns its DOM outright, `FlexLayout` keeps a JSON model
//! beside a React renderer, and `dockview` goes furthest, running a
//! framework-agnostic engine behind thin adapters. All of them still paint
//! their own chrome and expose CSS as the way to change it.
//!
//! This module takes the same separation one step further: the engine paints
//! nothing at all. A renderer returns elements and base attaches the drag
//! sources, drop hit-testing, focus and keyboard handling to the elements it
//! got back, so appearance is not a set of overrides on top of a default look —
//! there is no default look. `crates/component/src/dock` and
//! `crates/base/examples/showcase/components/dock.rs` are two unrelated
//! appearances over one behavior.
//!
//! The naming follows the same neighborhood where it can. A tab group here is
//! what Zed calls a `Pane` and `dockview` calls a group; a [`Dock`] is Zed's
//! dock; a [`Panel`] is the dockable content, as in `dockview` — note that
//! VS Code uses "panel" for the bottom *region* instead, and `rc-dock` uses it
//! for the tab container.
//!
//! # A minimal area
//!
//! ```ignore
//! use std::rc::Rc;
//! use gpui::{px, Context, Window};
//! use gpui_base::dock::{DockArea, DockLayout, DockPlacement};
//!
//! let area = cx.new(|cx| {
//! DockArea::new("workspace", Some(1), window, cx).with_renderer(Rc::new(MySkin))
//! });
//!
//! area.update(cx, |area, cx| {
//! area.set_center(
//! DockLayout::h_split()
//! .child(DockLayout::tabs().panel(files.clone()), Some(px(240.)))
//! .child(DockLayout::tabs().panel(editor.clone()), None),
//! window,
//! cx,
//! );
//! area.set_dock(
//! DockPlacement::Bottom,
//! DockLayout::tabs().panel(terminal.clone()),
//! window,
//! cx,
//! );
//! });
//! ```
//!
//! `crates/base/examples/showcase/components/dock.rs` is that program in full,
//! renderers included — run it with `cargo run -p gpui-base dock`.
//! `crates/component/src/dock` is the production skin over the same seam.
pub
pub use ;
pub use ;
pub use ;
// `split_placement_at` stays internal for the same reason: where a drop lands
// is base's decision, and a renderer is told the result through
// `TabGroupContext::drop_indicator`.
pub use ;
pub use ;
pub use ;
pub use ;
/// Both halves of the persistence seam. `PaneTree::to_state` reads panel
/// properties through `PanelSource`; `PaneTree::from_state` turns persisted
/// leaves back into panels through `PanelBuilder`. Exporting only the first
/// left `from_state` public but uncallable, since no caller outside this crate
/// could name the trait its parameter requires.
pub use ;
pub use ;
/// What a skin actually needs off the tiles geometry: the two sizes it has to
/// draw to, and which edge a resize is pulling.
pub use ;
// The arithmetic itself is deliberately not re-exported. Base resolves every
// bound before a renderer sees it — a skin is handed finished `Bounds`, never
// asked to snap anything — so `magnetic_snap`, `snap_edge`, `round_to_grid`,
// `round_point_to_grid`, `compute_resized_bounds`, `apply_boundary_constraints`
// and `content_size` had no caller outside this crate and no purpose there.
// `ResizeDrag` and `TileChange` are `TilesState`'s own fields, and
// `MINIMUM_SIZE` is a constraint base applies on the skin's behalf.
pub use ;