add app layout shell to the layout crate

This commit is contained in:
2026-11-02 08:53:18 +07:00
parent 3e03137d1c
commit aa40003a37
15 changed files with 697 additions and 11 deletions
+2 -2
View File
@@ -285,7 +285,7 @@ Units, each through the review gate:
| Unit | Crate | Component | Notes |
| --- | --- | --- | --- |
| L1 | layout | ResizeHandle ✅ Landed (2026-11-01) | The drag divider primitive: a thin themed handle that resizes the adjacent region (sidebar edge, split seam). Pointer drag, double-click resets to the default fraction, splitter a11y role. Lives in `layout` — promotes to `core` if a second consumer appears (D16). Shipped as a delta reporter: `ResizeHandle::new(id).axis(Axis).on_resize(delta)` hands the owner the signed seam movement per event — the owner stores a pixel size and clamps, the `InputState::scroll_by` pattern — with `.on_reset()` for double-click; no geometry parameters, so it works at any edge or seam. The hairline is `border`, highlights to `ring` on hover/drag; hit slop = `dense_padding_x` (component-own decoration, D25), hairline 1 px like the input's paint hairlines; `Role::Splitter` + focus ring, arrow keys move the seam (`KEYBOARD_STEP` 32 px, input-style const). One gpui dispatch lesson locked in tests: bubble runs user mouse-down listeners before the div's own focus transfer, so a listener that stops propagation must focus explicitly first — the keyboard test caught it. Feature `resize_handle`; 6 headless tests in `crates/layout/tests/resize_handle.rs`; gallery section shows a draggable split with live width |
| L2 | layout | AppLayout | The bootstrap shell: left sidebar region (on by default) + center + right sidebar region (off by default). Sidebar toggle in each sidebar's header, drag-resize widths through `ResizeHandle`; visibility and widths live on an application-owned `LayoutState` (the `InputState` pattern: `set_left_sidebar_visible` / `set_right_sidebar_visible` / `is_*` readers). The center region hosts the `Dock`. |
| L2 | layout | AppLayout ✅ Landed (2026-11-01) | The bootstrap shell: left sidebar region (on by default) + center + right sidebar region (off by default). Sidebar toggle in each sidebar's header, drag-resize widths through `ResizeHandle`; visibility and widths live on an application-owned `LayoutState` (the `InputState` pattern: `set_left_sidebar_visible` / `set_right_sidebar_visible` / `is_*` readers). The center region hosts the `Dock`. Shipped so: the left sidebar is the data crate's `Sidebar` component (`.left_sidebar(sidebar)` — selection, keyboard nav, and a11y come for free; `app_layout` pulls `data/sidebar`, per the "may compose data" note), the right sidebar and center are app elements (the right side is a details pane, not a list). Each visible sidebar renders a `strip_height` header strip with an end-aligned ghost icon toggle (`IconRole::PanelLeft`/`PanelRight` — new roles this unit: Segoe `DockLeft`/`DockRight` E90C/E90D per the MDL2 table, Lucide `panel-left`/`panel-right` bundled, freedesktop `sidebar-expand-*` → `sidebar-show-symbolic` cascade); `LayoutState` holds visibility + pixel widths with 160/480 px drag clamps (menu-cap px precedent); the right handle inverts the delta so dragging left grows it. `app_layout` pulls only core's `button` + `data/sidebar`; the facade `layout` feature re-requests `full` (core/data workspace deps are now `default-features = false`). 5 headless tests in `crates/layout/tests/app_layout.rs`: defaults, toggling via the state API, the header toggle button (clicked at metric-derived coordinates — gpui dispatch lesson from L1 applies to `on_click` too), seam resize + both clamps with no drift, and the inverted right seam. **Follow-up (2026-11-01):** the left sidebar is now the data crate's `Sidebar` component itself (`.left_sidebar(sidebar)` — selection, keyboard nav, and a11y come free; `app_layout` pulls `data/sidebar`), the right sidebar stays a free element (a details pane is not a list), and `Sidebar` gained a `sidebar-{id}` debug selector for headless tests |
| L3 | layout | Dock | The center pane tree, controlled: `DockState` = `Pane { tabs, active }` or `Split { axis, ratio, a, b }`; panels are registered descriptors (`id` + view builder); add / close / activate are plain state ops; a pane emptied by closing its last tab collapses its split. Pane headers show titles, the active tab's close button, and a context menu (via core `ContextMenu`). |
| L4 | layout | Dock drag-to-split | Dragging a tab: drop on a pane's edge splits a new pane there; drop on a pane's center moves the tab into that pane; a pane emptied by the move collapses. Edge drop zones paint from theme tokens while a drag is live. Split/merge go through the same `DockState` ops L3 exposed — the drag is interaction, not a second API. |
| L5 | layout | Sidebar auto-open | Panels declare `needs_right_sidebar` on their descriptor; activating such a panel opens the right sidebar; closing it leaves visibility to the user. The gallery gains a workspace-style shell page — the bootstrap example apps copy. |
@@ -374,6 +374,6 @@ Dropped from rev 3: D2, D5, D9 (port-era decisions); the review gate returns in
6. ~~Unit 14 — motion, per component (§6 row 14, D21)~~ ✅ Landed in five passes (2026-10-30): (a) theme `reduce_motion` → `cx.set_reduce_motion` in `reapply`; (b) Switch thumb `with_spring`; (c) Toast 8 px slide-in; (d) Dialog scrim enters with the panel; (e) Progress 30 fps cap. Exits and hover washes stay deferred (§6 deferred list).
7. ~~Phase F, data first~~ ✅ data complete (2026-10-30) — `crates/data` opened with List, then Tree, Tabs, Sidebar (the dead `sheet` feature dropped from core along the way; Table dropped from the roadmap). Also extracted `crates/utils` (2026-10-30): `h_flex`/`v_flex` (core's `common` re-exports them), `focus_handle` (the namespaced keyed handle, 8 call sites), `focus_ring` (7 sites), `adjacent_selectable` + `nav_key` (4 sites), and the selectable-row paint helpers `selectable_label_color`/`paint_selectable_row` (3 sites, reading `&Theme` directly — no intermediate color struct), plus the `StyledExt` trait (`refine_style` — adapted from gpui-kit `base`, D15 — replacing the per-component `style().refine` pattern; 5 sites) — `core` and `data` depend on it; the facade doesn't re-export it. **Linux theme sources landed (2026-10-30)**: Omarchy (colors.toml parse + state-dir detection + inotify on `current`), KDE (kdeglobals `[Colors:*]` + AccentColor + `~/.config` inotify), GNOME/generic (portal `ReadAll` + `SettingChanged` via zbus 5 blocking on its own thread, gsettings fallback with the libadwaita accent table) — `SourceKind::Omarchy/Kde/Gnome`, detection cascade Omarchy → KDE → portal → fallback, all parsers fixture-tested on every OS and the Linux build compile-checked via `cargo check --target x86_64-unknown-linux-gnu`. **Windows source + icon resolvers landed (2026-10-30)**: `SourceKind::Windows` (WinRT `UISettings` + `Personalize` fallback + `ColorValuesChanged`/`HighContrastChanged`, §5.3), the Segoe Fluent codepoint renderer, and the freedesktop theme walker (§4.4); the WinRT surface was verified against the real `windows` 0.62 bindings via a standalone cross-check (gpui's own manifest build needs llvm-rc from macOS). Remaining Phase F: **side-by-side gallery screenshots per platform** — Omarchy/KDE/GNOME need a Linux run, Windows needs its first hardware build. NumberInput stays deferred until needed; multiline landed as §6.3 M7 (2026-11-01).
8. Then — Phase G is the migration additions (§6.3): Badge ✅, Select ✅, Segmented control ✅, Context menu ✅, Empty state ✅, password input ✅, multiline textarea ✅, then Avatar image (M8) — each unblocks a concrete signed screen. Pickers (Calendar/DatePicker, TimeField, ColorPicker) stay decided-at-unit-time. `crates/editor` starts when file viewing (M15) is reached.
9. Then — the layout crate (§6.4): ResizeHandle ✅, the AppLayout bootstrap, the center Dock with drag-to-split, and panel-declared sidebar auto-open — each through the review gate, with signed's `crates/dock` as the usage reference and the gallery workspace page as the example. Order against M9+ is decided when the crate starts.
9. Then — the layout crate (§6.4): ResizeHandle ✅, AppLayout ✅, the center Dock with drag-to-split, and panel-declared sidebar auto-open — each through the review gate, with signed's `crates/dock` as the usage reference and the gallery workspace page as the example. Order against M9+ is decided when the crate starts.
Batched polish candidates between units: scrollbar auto-hide + horizontal thumb, `Undo`/`Redo` menu events.