graphic design tool
git clone https://git.lucas.co/cce-designer.git
refactor(layout): remove the docks
With the network, the params HUD and the second network editor out of
them, the spreadsheet was the one pane the three docks could hold. The
dock model goes whole: Dock, dock_panes, NO_PANE, Move To, the left and
right dock widths and their edge drags, the spreadsheet's side tucks
(Full Width / Between Panes and its side-edge drags), dock_tabs in the
save, and the dock-width fields of PlateGeometry.
The spreadsheet is the strip along the bottom, its top edge resizing it;
the params HUD's left edge sets its width; the plate menus hold Collapse
and Detach. An older save's dock widths, tucks and dock lists are
ignored, its params_width read as the HUD's when it has no hud_width,
and a spreadsheet it had moved into a side dock opens along the bottom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 103 ++++++------
src/app.rs | 455 ++++++------------------------------------------------
src/main.rs | 246 +++++++++++------------------
src/plate_menu.rs | 95 ++----------
src/playbar.rs | 4 +-
src/project.rs | 69 +--------
6 files changed, 206 insertions(+), 766 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index c7af123..e17d545 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -247,20 +247,16 @@ gone from cce-ui with the wgpu path).
- `src/plate_menu.rs` — the plate menu: what can be done to a pane's PLATE
(`PLATE_SLOTS` — network, params, spreadsheet, playbar; NOT the viewport,
whose plate is the window-spanning lip) — Collapse/Expand, Detach/Reattach,
- Full Width (only with a plate in a side dock to tuck under) and **Move To
- Left/Right/Bottom**. (Docks held TABS until 2026-10-07 — several panes to a
- dock, with tab rows, Add Tab and Move To Own Plate here — retired once the
- spreadsheet was the one pane a dock could hold; a save keeps the
- `dock_tabs` field and its list shape, one name to a dock, and an older
- save's tab group loads as its first dockable name.) The network and
- the params HUD are in no dock, so theirs are Detach alone, and the
+ and nothing else (see "There are no docks": Full Width, the dock tabs and
+ Move To went on 2026-10-07). The network's and the params HUD's are
+ Detach alone, as neither collapses, and the
network's are in no menu: its Plate page went on 2026-10-07, Detach being the
`detach_circular_window` command in the palette. `plate_menu_rows(idx)` is the
one list. **There is no corner trigger** (since 2026-10-01; it was a small
circle on each plate's top-right, which opened these rows as a menu of their
own and, DRAGGED, moved the pane to another dock — the drag, its drop
- highlight and `AppDrag::DockDrag` went with it, and Move To is the rows'
- replacement). The rows are in each plate's RIGHT-CLICK menu: a **Plate**
+ highlight and `AppDrag::DockDrag` went with it, and Move To was the rows'
+ replacement until the docks went). The rows are in each plate's RIGHT-CLICK menu: a **Plate**
PAGE row (see "Page rows" below) at the foot of the playbar's menu
(`PlaybarMenuAction::PlatePage`), which turns the menu into them under a
band back to it (`open_plate_page`; until 2026-10-06 they were appended
@@ -2786,12 +2782,32 @@ the spreadsheet and the viewport followed) and the three PINS
`param_editor_selected`, `param_editor_dir` and `viewport_editor_dir` stay
as names for what they read — the one editor's selection and level.
-An older save's `"network2"` in its dock lists names no pane and is dropped
-wherever it stood, its `current_path2` and pins ignored
-(`an_older_saves_second_network_editor_is_dropped`). With the network and
-the params HUD out of the docks too, the spreadsheet is the one dockable
-pane: Move To still moves it between the three docks, and its Full Width row is shown only while a side dock
-holds a plate to tuck under — which, with nothing else dockable, is never.
+An older save's `"network2"` dock entry, its `current_path2` and pins are
+ignored (`an_older_saves_second_network_editor_is_dropped`).
+
+### There are no docks (since 2026-10-07)
+
+The floating layout had three DOCKS — a left column, a right column and a
+bottom strip — which owned the plate dimensions (`floating_network_layout`,
+`floating_param_width`, the spreadsheet's height and its side TUCKS under a
+side dock's plate), with panes assigned to them (`Dock`, `dock_panes`),
+held several to a dock as TABS (`dock_tabs`, Add Tab, Move To Own Plate),
+and swapped by the plate menus' **Move To** rows. The network, the params
+HUD and the second network editor left them one after another, which left
+the spreadsheet the one pane a dock could hold, and the model went whole:
+tabs first, then the docks.
+
+What is left is two plate edges. The **spreadsheet** is the strip along
+the bottom, a gap in from the window's sides and a gap above the playbar,
+as tall as `floating_spreadsheet_height` asks (`floating_spreadsheet_rect`,
+the one derivation); its TOP edge resizes it (`on_spreadsheet_resize_edge`
+— its sides tucked it under a side dock's plate). The **params HUD**'s left
+edge sets `params_hud_width`. The plate menus hold the window actions alone
+— Collapse / Expand, Detach / Reattach. `PlateGeometry` saves those two
+(`hud_width`, `spreadsheet_height`); an older save's dock widths, tucks and
+`dock_tabs` are ignored, its `params_width` read as the HUD's when it has
+no `hud_width`, and a spreadsheet it had moved into a side dock opens along
+the bottom. `the_plates_have_no_docks` is the test.
### The network has no plate (since 2026-10-06)
@@ -2815,25 +2831,17 @@ params HUD (`plates_over_params`) and over a point number (`under_a_plate`).
The pane keeps its focus domain, its menus, its clip and its keyboard
navigation.
-**The network is in no dock** (since 2026-10-07), as the params HUD is in
-none. The left dock starts EMPTY (`dock_panes` `[NO_PANE, NO_PANE,
-spreadsheet]`), so the spreadsheet runs flush to the window's left, where
-it stopped short of the network's invisible dock until then;
-the network's
-plate menu is Detach alone (no tab, Move To or Collapse rows) — and is in
-no menu: the network menu's Plate page went the next day, Detach being the
-palette's `detach_circular_window` — and
-`set_pane_collapsed` refuses it; the pane-state load takes it out of an
-older save's tab lists, the dock it fronted fronting its next tab or
-emptying (`the_params_hud_is_under_the_plates_and_stops_above_the_bottom_ones`
-loads one). `plate_at` never answers the network. The left dock's right edge
-resizes whatever plate is docked there (`on_left_dock_resize_edge`,
-`AppDrag::LeftDockResize`, writing `floating_network_layout.2`, which kept
-its name and its place in the save as the left dock's width) — it was the
-network's own edge (`network_resize_edge_at`, `NetworkResize`). And a press
-on the network's breadcrumb is asked where the breadcrumb is drawn: it was
-asked in the old dock's top strip, so a press in that band of the scene
-focused the network and went nowhere. `the_network_is_in_no_dock`.
+**The network is in no dock** (it left the left dock on 2026-10-07, and
+the docks went the same day — see "There are no docks"): the spreadsheet
+runs flush to the window's left, where it stopped short of the network's
+invisible dock until then. The network's plate menu is Detach alone, and
+in no menu — the network menu's Plate page went too, Detach being the
+palette's `detach_circular_window` — and `set_pane_collapsed` refuses it.
+`plate_at` never answers the network. It had a resize edge of its own
+(`network_resize_edge_at`, `NetworkResize`), gone with the dock. And a
+press on the network's breadcrumb is asked where the breadcrumb is drawn:
+it was asked in the old dock's top strip, so a press in that band of the
+scene focused the network and went nowhere. `the_network_is_in_no_dock`.
**The pane spans the whole window** (`network_overlay`, which is true while
the network is shown, not circular and not a detached window), laid out in
@@ -2931,33 +2939,24 @@ rows. `the_params_plate_fits_its_rows` and
### The params pane is a HUD on the scene (since 2026-10-06)
-The params pane is not a dock pane: it lives on the scene viewer, drawn
-right after it and under every plate, and its size has no relation to any
-plate. Until this it was the right dock's pane — as wide as that dock, its
+The params pane is a HUD on the scene viewer, drawn right after it and
+under every plate, and its size has no relation to any plate. Until this
+it was the right dock's pane (the docks went the next day) — as wide as that dock, its
bottom raised by a spreadsheet tucked under it, tabbable and movable.
- **Laid out from the viewport** (`State::params_hud_rect`): the
viewport's top-right corner a gap in, `params_hud_width` wide (its own
- field, apart from the right dock's `floating_param_width`; saved as
- `PlateGeometry::hud_width`, and an older save's `params_width` is read
- as it), as tall as the viewport — but **it stops a gap above the
+ field; saved as `PlateGeometry::hud_width`, and an older save's
+ `params_width` is read as it), as tall as the viewport — but **it stops a gap above the
spreadsheet or the playbar when one lies below it** (since later the
same day; for an afternoon they covered its bottom, and rows under them
could be neither seen nor reached). What does not fit then scrolls, the
pane's own scrolling, and the fitted plate fills the HUD. Laid out again
after the collapse and detach post-passes in `rebuild_positions`, so a
- stubbed spreadsheet is what it stops above. A plate in the right dock,
- over the HUD's top, sizes nothing and is drawn over it. Its left edge drags its width
+ stubbed spreadsheet is what it stops above. Its left edge drags its width
(`AppDrag::HudResize`, `on_param_resize_edge`), as far down as it claims.
- The right dock's own edge is `on_right_dock_resize_edge` /
- `AppDrag::RightDockResize`, asked first, its plate being on top.
-- **Out of the docks.** The right dock starts EMPTY (`dock_panes`
- `[NO_PANE, NO_PANE, spreadsheet]` — the network left the left dock the
- next day); the tab candidates (tabs went 2026-10-07) no longer listed params, its plate menu has no tab, Move To or Collapse rows (Detach
- stays), `set_pane_collapsed` refuses it, and the pane-state load
- takes it out of an older save's tab lists — the dock it fronted fronts
- its next tab or empties. A plate moved into the right dock is drawn over
- the HUD. The legacy column branches (circular network, detached circular
+- **Out of the docks.** Its plate menu has no Collapse row (Detach stays)
+ and `set_pane_collapsed` refuses it. The legacy column branches (circular network, detached circular
window) still place it in their right column.
- **Under every plate.** Draw order: viewport (-7), the HUD (-6), the
network panels (-5, drawn with no plate), then the rest.
@@ -3427,7 +3426,7 @@ down INTO the lip, and only its top edge is a plate's.
rect less that on the left, right and bottom (`Playbar::content`). Zero
in the legacy column layouts, which draw the old plate.
-What stands above it — the docked plates (`pb_off`, `playbar_shelf_h()`)
+What stands above it — the spreadsheet (`pb_off`, `playbar_shelf_h()`)
and the params HUD — stops a gap short of its top, and the viewport's
bottom-anchored text, a viewer state's line, stands
on it (`State::scene_text_floor`) rather than on its transport.
diff --git a/src/app.rs b/src/app.rs
index d1ccee1..8159147 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -284,16 +284,8 @@ pub struct ProjectViewState {
/// window size. None in older saves keeps the live positions.
#[serde(default)]
pub splitters: Option<(f32, f32)>,
- /// What each dock holds, Left/Right/Bottom order: a list of pane names,
- /// at most one since docks held no tabs (2026-10-07) — the name and the
- /// list shape kept, so an older save, whose lists were tab groups with
- /// the active tab first, still loads (its first dockable name is the
- /// dock's). Empty (older saves) keeps the default arrangement; a set
- /// that does not name each dockable pane exactly once is ignored the
- /// same way. ("network2", the second network editor, removed the same
- /// day, is dropped from an older save's.)
- #[serde(default)]
- pub dock_tabs: Vec<Vec<String>>,
+ // `dock_tabs`, what each dock held, rode here until 2026-10-07, when
+ // the docks went; an older save's is ignored.
// `current_path2` and the viewport / params / spreadsheet pins rode
// here until 2026-10-07, for the second network editor; an older save's
// are ignored.
@@ -352,26 +344,24 @@ pub struct DefaultCameraView {
pub pivot: [f32; 3],
}
-/// The user-dragged plate edges of the floating layout, each as a fraction
-/// of the window dimension it spans: widths and side insets of the width,
-/// the spreadsheet height of the height. The remaining plate coordinates
-/// (the network plate's top-left, the parameter plate's right anchor, the
-/// spreadsheet's bottom) are derived by `rebuild_positions`, so these five
-/// numbers fix every plate's size and position.
+/// The user-dragged plate edges, each as a fraction of the window
+/// dimension it spans: the params HUD's width and the spreadsheet's height.
+/// Everything else about where the plates stand is derived by
+/// `rebuild_positions`. (Until 2026-10-07 this also held the left and
+/// right docks' widths and the spreadsheet's tucks under them; an older
+/// save's are ignored.)
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct PlateGeometry {
- pub network_width: f32,
- pub params_width: f32,
pub spreadsheet_height: f32,
- /// How far the spreadsheet's left/right edge tucks under its neighbor
- /// (0 = flush beside it) — see `floating_spreadsheet_inset_left`.
- pub spreadsheet_inset_left: f32,
- pub spreadsheet_inset_right: f32,
/// The params HUD's width (since 2026-10-06, when it left the right
- /// dock). Absent in an older save, whose `params_width` was the params
- /// pane's and is read as the HUD's.
+ /// dock).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hud_width: Option<f32>,
+ /// The right dock's width in an older save: read as the HUD's when the
+ /// save has no `hud_width` (one from before the HUD left the dock, when
+ /// it was the params pane's). Never written.
+ #[serde(default, skip_serializing)]
+ pub params_width: Option<f32>,
}
fn default_camera() -> String {
@@ -2826,35 +2816,11 @@ pub struct ResizeDirection {
/// now ONLY ever names a widget drag (a slot whose `Input` drag hooks are driving:
/// panel move, graph node drag, param slider, spreadsheet scroll). Exactly one of
/// `app_drag`/`drag_widget` is armed per press.
-/// The floating layout's three dock slots. Dimensions belong to the DOCK
-/// (left/right column widths, bottom strip height and tucks), panes are
-/// assigned to docks — so swapping panes preserves the geometry.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Dock {
- Left,
- Right,
- Bottom,
-}
-
-/// `dock_panes` entry for a dock that holds nothing: no slot index, so `pane_shown` reads it as hidden and the dock lays out
-/// nothing. Never a valid `positions[..]` index.
-pub const NO_PANE: usize = usize::MAX;
-
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AppDrag {
- /// The left dock's right edge — the plate docked there, if any.
- LeftDockResize { start_w: f32, start_mouse_x: f32 },
- /// The right dock's left edge — the plate docked there, if any.
- RightDockResize { start_w: f32, start_mouse_x: f32 },
/// The params HUD's left edge: its own width, which no plate shares.
HudResize { start_w: f32, start_mouse_x: f32 },
SpreadsheetResize { start_h: f32, start_mouse_y: f32 },
- /// The spreadsheet's left edge drag, as an inset past the flush position
- /// beside the network pane: a positive inset tucks the spreadsheet UNDER
- /// the pane, whose bottom the layout raises to make room.
- SpreadsheetResizeLeft { start_inset: f32, start_mouse_x: f32 },
- /// The spreadsheet's right edge, symmetrically, tucking under the parameter pane.
- SpreadsheetResizeRight { start_inset: f32, start_mouse_x: f32 },
}
#[repr(C)]
@@ -3129,10 +3095,6 @@ pub struct State {
/// Panes shrunk to their title stub, indexed by slot. Only the
/// `plate_menu::PLATE_SLOTS` entries are ever set.
pub collapsed_panes: [bool; WIDGET_COUNT],
- /// Dock occupancy, indexed Left/Right/Bottom: the pane each dock holds,
- /// or [`NO_PANE`]. Swapped by the plate menu's Move To rows. One pane
- /// to a dock: the tabs that let several share one went on 2026-10-07.
- pub dock_panes: [usize; 3],
pub drag_widget: Option<usize>,
/// Where the pointer pressed when `drag_widget` armed — the drag
@@ -3286,21 +3248,13 @@ pub struct State {
pub node_tint: Option<[f32; 4]>,
pub last_design_mod_time: Option<std::time::SystemTime>,
pub last_config_mod_time: Option<std::time::SystemTime>,
- pub floating_network_layout: (f32, f32, f32, f32),
/// The active app-mode drag (pane edge resize), if any. See [`AppDrag`].
pub app_drag: Option<AppDrag>,
- pub floating_param_width: f32,
/// The params HUD's width as asked for (`params_hud_rect` fits it to
- /// the viewport). Its own, apart from the right dock's: the HUD lives
- /// on the scene, not in a dock.
+ /// the viewport). It lives on the scene, not in a dock — there are none
+ /// (since 2026-10-07).
pub params_hud_width: f32,
pub floating_spreadsheet_height: f32,
- /// How far the spreadsheet's left/right edge reaches INTO the neighboring
- /// pane's span past its flush position (0 = glued beside the neighbor).
- /// A positive inset tucks the spreadsheet UNDER that neighbor: the layout
- /// raises the neighbor's bottom edge to the spreadsheet's top.
- pub floating_spreadsheet_inset_left: f32,
- pub floating_spreadsheet_inset_right: f32,
/// Whether the compositor has told us the window's size yet. Until it
/// has, `width`/`height` are `State::new`'s 1280x800 placeholder.
pub window_configured: bool,
@@ -3325,7 +3279,7 @@ pub struct State {
pub gpu_at_launch: String,
pub last_saved_root_json: String,
/// The pane layout as of the last save — [`State::pane_layout_json`] —
- /// so a dragged plate edge, a collapse or a re-dock dirties the title
+ /// so a dragged plate edge or a collapse dirties the title
/// like an edit to the tree: the save file carries them, so unsaved
/// they are unsaved changes.
pub last_saved_layout_json: String,
@@ -3665,21 +3619,13 @@ impl State {
pub fn pane_layout_json(&self) -> String {
let vs = self.project_view_state();
let splitters = vs.splitters.map(|(a, b)| ((a * 1000.0).round(), (b * 1000.0).round()));
- let plates = (
- self.floating_network_layout.2.round(),
- self.floating_param_width.round(),
- self.params_hud_width.round(),
- self.floating_spreadsheet_height.round(),
- self.floating_spreadsheet_inset_left.round(),
- self.floating_spreadsheet_inset_right.round(),
- );
+ let plates = (self.params_hud_width.round(), self.floating_spreadsheet_height.round());
// The display settings ride the file now, so changing one is an
// edit the title's asterisk should show.
let display = serde_json::to_string(&vs.display).unwrap_or_default();
serde_json::to_string(&(
vs.collapsed_panes,
splitters,
- vs.dock_tabs,
vs.frame_range,
plates,
display,
@@ -4020,7 +3966,7 @@ impl State {
/// scrolled one.
/// Is the pointer CAPTURED — owned by a gesture or a modal rather than
/// free to hover whatever it is over? A widget drag, an app drag (pane
- /// edges, the dock), a camera orbit, a network pan, a grid expansion
+ /// edges), a camera orbit, a network pan, a grid expansion
/// drag, a viewer-tool handle grab, a held viewport-menu slider, or the
/// dialog. While it is, no pane hovers (`broadcast_pointer`).
pub(crate) fn pointer_captured(&self) -> bool {
@@ -4585,23 +4531,6 @@ impl State {
// the press handlers arm the matching `AppDrag` off it, and `pane_resize_cursor`
// shows the resize cursor over it, so the two can't drift apart.
- /// Whether (cx, cy) is on the left dock's right edge-resize hotspot —
- /// the edge of whatever plate is docked there. The network is not one
- /// (since 2026-10-06; it spans the window), so the dock is empty unless
- /// a plate was moved into it.
- pub fn on_left_dock_resize_edge(&self, cx: f32, cy: f32) -> bool {
- if self.circular_network_pane || !self.dock_shown(Dock::Left) {
- return false;
- }
- let pane = self.pane_in_dock(Dock::Left);
- if self.pane_is_stubbed(pane) {
- return false;
- }
- let (x, y, w, h) = self.positions[pane];
- let margin = 8.0_f32;
- w > 0.0 && cx >= x + w - margin && cx <= x + w + margin && cy >= y - margin && cy <= y + h + margin
- }
-
/// Whether (cx, cy) is on the params HUD's left edge-resize hotspot:
/// the edge as far down as the HUD claims (its rows, without its
/// plate), and nowhere a plate covers it.
@@ -4620,28 +4549,13 @@ impl State {
&& !self.plate_over_params_at(cx, cy)
}
- /// Whether (cx, cy) is on the right dock's left edge-resize hotspot —
- /// the edge of whatever plate is docked there.
- pub fn on_right_dock_resize_edge(&self, cx: f32, cy: f32) -> bool {
- if self.circular_network_pane || !self.dock_shown(Dock::Right) {
- return false;
- }
- let pane = self.pane_in_dock(Dock::Right);
- if self.pane_is_stubbed(pane) {
- return false;
- }
- let (x, y, w, h) = self.positions[pane];
- let margin = 8.0_f32;
- w > 0.0 && cx >= x - margin && cx <= x + margin && cy >= y - margin && cy <= y + h + margin
- }
-
/// The params HUD's rect: laid out from the VIEWPORT — its top right
/// corner, a gap in, as wide as `params_hud_width` asks — and as tall as
/// the viewport, except that it stops a gap above the spreadsheet or the
/// playbar when one lies below it (since 2026-10-06): rows under a plate
/// along the bottom could be neither seen nor reached, and with the HUD
- /// stopped short the pane scrolls them instead. A plate beside or over
- /// the HUD's top (the right dock's) sizes nothing; it is drawn over the
+ /// stopped short the pane scrolls them instead. A plate over the HUD
+ /// (the circular network pane's) sizes nothing; it is drawn over the
/// HUD (`plates_over_params`).
pub fn params_hud_rect(&self) -> (f32, f32, f32, f32) {
let gap = 18.0_f32;
@@ -4688,156 +4602,50 @@ impl State {
.any(|&(x, y, w, h)| px >= x && px < x + w && py >= y && py < y + h)
}
- /// The floating spreadsheet pane's rect (the single derivation the layout pass
- /// and the edge hotspots share). A positive side inset pulls that edge past its
- /// flush position into the neighbor's span — the pane tucks UNDER the neighbor,
- /// whose bottom the layout raises to the spreadsheet's top — so the height clamp
- /// keeps 100px of shortened neighbor above.
- pub fn pane_in_dock(&self, dock: Dock) -> usize {
- self.dock_panes[dock as usize]
- }
-
- pub fn dock_of_pane(&self, slot: usize) -> Option<Dock> {
- [Dock::Left, Dock::Right, Dock::Bottom]
- .into_iter()
- .find(|&d| self.dock_panes[d as usize] == slot)
- }
-
- /// Is the pane occupying a slot currently shown (its View toggle)?
- pub fn pane_shown(&self, slot: usize) -> bool {
- match slot {
- NETWORK_PANEL_IDX => self.show_network,
- PARAM_IDX => self.show_parameters,
- SPREADSHEET_IDX => self.show_spreadsheet,
- PLAYBAR_IDX => self.show_playbar,
- _ => false,
- }
- }
-
- pub(crate) fn dock_shown(&self, dock: Dock) -> bool {
- self.pane_shown(self.pane_in_dock(dock))
- }
-
- /// Move a plate to a dock, swapping with the pane that held it. The
- /// dock-owned dimensions stay put, so the geometry survives the swap.
- pub fn move_pane_to_dock(&mut self, slot: usize, dock: Dock) {
- let Some(from) = self.dock_of_pane(slot) else { return };
- if from == dock {
- return;
- }
- self.dock_panes.swap(from as usize, dock as usize);
- self.after_dock_change();
- }
-
- /// The full re-sync a dock change needs: layout, panel offsets, the
- /// graphs' grid origins (they follow their pane rects), and the node
- /// lists — a fronted pane must not wait for the next unrelated event to
- /// fill in.
- fn after_dock_change(&mut self) {
- self.rebuild_positions();
- self.apply_layout();
- self.read_panel_offsets();
- self.sync_grid_settings();
- self.sync_nodes();
- }
-
+ /// The spreadsheet's rect — the single derivation the layout pass and
+ /// the edge hotspot share: the strip along the bottom, a gap in from
+ /// the window's sides, standing a gap above the playbar, as tall as
+ /// `floating_spreadsheet_height` asks within the window. (Until
+ /// 2026-10-07 it was the bottom DOCK's rect: panes were assigned to a
+ /// left, right and bottom dock, swapped by Move To, and the
+ /// spreadsheet's sides could tuck under a side dock's plate.)
pub fn floating_spreadsheet_rect(&self) -> (f32, f32, f32, f32) {
let gap = 18.0_f32;
- let fx = gap;
- let fw = self.left_dock_width();
- let param_w = self.right_dock_width();
- let param_x = self.width - gap - param_w;
- let flush_left = if self.dock_shown(Dock::Left) { fx + fw + gap } else { gap };
- let flush_right = if self.dock_shown(Dock::Right) { param_x - gap } else { self.width - gap };
- let ss_x = (flush_left - self.floating_spreadsheet_inset_left.max(0.0)).max(gap);
- let ss_end = (flush_right + self.floating_spreadsheet_inset_right.max(0.0)).min(self.width - gap);
- let ss_w = (ss_end - ss_x).max(150.0);
// The playbar is attached to the bottom edge: what stands above it
// stops a gap short of its top, the gap the bottom edge gives it.
let pb_off = if self.show_playbar { playbar_shelf_h() } else { 0.0 };
let ss_y_end = self.height - STATUS_H - pb_off - gap;
- let max_h = if self.spreadsheet_tucks_left() || self.spreadsheet_tucks_right() {
- ss_y_end - (gap + 100.0 + gap)
- } else {
- ss_y_end - gap
- };
- let ss_h = self.floating_spreadsheet_height.clamp(100.0, max_h.max(100.0));
- let ss_y = ss_y_end - ss_h;
- (ss_x, ss_y, ss_w, ss_h)
+ let ss_h = self.floating_spreadsheet_height.clamp(100.0, (ss_y_end - gap).max(100.0));
+ (gap, ss_y_end - ss_h, (self.width - 2.0 * gap).max(150.0), ss_h)
}
- /// The left dock's width as drawn: `floating_network_layout.2` (the
- /// width asked for) fitted to this window. Read this, not the field,
- /// wherever the plate's ON-SCREEN width matters.
- pub fn left_dock_width(&self) -> f32 {
- let gap = 18.0_f32;
- self.floating_network_layout.2.clamp(150.0, (self.width - 2.0 * gap).max(150.0))
- }
-
- /// The right dock's width as drawn — `floating_param_width` fitted to
- /// this window, as [`Self::left_dock_width`] is for the left.
- pub fn right_dock_width(&self) -> f32 {
- let gap = 18.0_f32;
- self.floating_param_width.clamp(150.0, (self.width - 2.0 * gap).max(150.0))
- }
-
- /// Whether the spreadsheet is tucked under the network / parameter pane
- /// (side inset active while both panes are shown).
- pub fn spreadsheet_tucks_left(&self) -> bool {
- self.dock_shown(Dock::Bottom) && self.dock_shown(Dock::Left) && self.floating_spreadsheet_inset_left > 0.5
- }
-
- pub fn spreadsheet_tucks_right(&self) -> bool {
- self.dock_shown(Dock::Bottom) && self.dock_shown(Dock::Right) && self.floating_spreadsheet_inset_right > 0.5
- }
-
- /// The spreadsheet pane's edge-resize hotspot at (cx, cy): top resizes the
- /// pane's own height; the left/right edges drive the neighboring pane's width
- /// (network / parameters), so each exists only while that neighbor is shown
- /// to make room. `None` off every edge.
- pub fn spreadsheet_resize_edge_at(&self, cx: f32, cy: f32) -> Option<ResizeDirection> {
+ /// Whether (cx, cy) is on the spreadsheet's top edge, which resizes its
+ /// height. (Its left and right edges tucked it under a side dock's plate
+ /// until the docks went, 2026-10-07.)
+ pub fn on_spreadsheet_resize_edge(&self, cx: f32, cy: f32) -> bool {
if !self.show_spreadsheet {
- return None;
+ return false;
}
- let (ss_x, ss_y, ss_w, ss_h) = self.floating_spreadsheet_rect();
+ let (ss_x, ss_y, ss_w, _) = self.floating_spreadsheet_rect();
let margin = 8.0_f32;
- let in_v = cy >= ss_y - margin && cy <= ss_y + ss_h + margin;
- if self.show_network && !self.circular_network_pane && in_v && cx >= ss_x - margin && cx <= ss_x + margin {
- return Some(ResizeDirection { left: true, right: false, top: false, bottom: false });
- }
- if self.dock_shown(Dock::Right) && in_v && cx >= ss_x + ss_w - margin && cx <= ss_x + ss_w + margin {
- return Some(ResizeDirection { left: false, right: true, top: false, bottom: false });
- }
- if cx >= ss_x && cx <= ss_x + ss_w && cy >= ss_y - margin && cy <= ss_y + margin {
- return Some(ResizeDirection { left: false, right: false, top: true, bottom: false });
- }
- None
+ cx >= ss_x && cx <= ss_x + ss_w && cy >= ss_y - margin && cy <= ss_y + margin
}
/// The resize cursor for an active pane-edge drag, or for hovering one of the
/// hotspots above. `None` otherwise (the engine then falls back to its CSD cursors).
pub fn pane_resize_cursor(&self, cx: f32, cy: f32) -> Option<cce_ui::engine::CursorIcon> {
use cce_ui::engine::CursorIcon;
- let dir_cursor = |dir: ResizeDirection| {
- if dir.left || dir.right { CursorIcon::EwResize } else { CursorIcon::NsResize }
- };
if let Some(drag) = self.app_drag {
return Some(match drag {
- AppDrag::LeftDockResize { .. } | AppDrag::RightDockResize { .. } | AppDrag::HudResize { .. } => CursorIcon::EwResize,
+ AppDrag::HudResize { .. } => CursorIcon::EwResize,
AppDrag::SpreadsheetResize { .. } => CursorIcon::NsResize,
- AppDrag::SpreadsheetResizeLeft { .. } | AppDrag::SpreadsheetResizeRight { .. } => {
- CursorIcon::EwResize
- }
});
}
- if self.on_left_dock_resize_edge(cx, cy) {
- return Some(CursorIcon::EwResize);
- }
- if self.on_right_dock_resize_edge(cx, cy) || self.on_param_resize_edge(cx, cy) {
+ if self.on_param_resize_edge(cx, cy) {
return Some(CursorIcon::EwResize);
}
- if let Some(dir) = self.spreadsheet_resize_edge_at(cx, cy) {
- return Some(dir_cursor(dir));
+ if self.on_spreadsheet_resize_edge(cx, cy) {
+ return Some(CursorIcon::NsResize);
}
None
}
@@ -8510,10 +8318,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
plate_menu_slot: None,
plate_menu_actions: Vec::new(),
collapsed_panes: [false; WIDGET_COUNT],
- // The left and right docks start empty: neither the network nor
- // the params HUD is a dock pane (both live on the scene), and a
- // plate moved into either is drawn over them.
- dock_panes: [NO_PANE, NO_PANE, SPREADSHEET_IDX],
drag_widget: None,
drag_press_cursor: None,
focused_widget: None,
@@ -8614,13 +8418,9 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
mod_time
},
- floating_network_layout: (18.0, 44.0, 400.0, 710.0),
app_drag: None,
- floating_param_width: 300.0,
params_hud_width: 300.0,
floating_spreadsheet_height: 250.0,
- floating_spreadsheet_inset_left: 0.0,
- floating_spreadsheet_inset_right: 0.0,
window_configured: false,
pending_plates: None,
loaded_project_path: None,
@@ -9407,41 +9207,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.slots.spreadsheet_menubar.set_visible(false);
self.slots.playbar.set_visible(self.show_playbar);
} else {
- let gap = 18.0_f32;
- // The playbar is attached to the bottom edge (below), so the
- // plates above stop a gap short of its top.
- let pb_off = if self.show_playbar { playbar_shelf_h() } else { 0.0 };
- // The stored widths and height are what the user ASKED for;
- // the clamps below fit them to this window for drawing and are
- // never written back. Storing the clamp made every transient
- // shrink permanent — a window briefly narrower than a plate
- // left it that narrow when the window grew again.
- let fw = self.left_dock_width();
- let fx = paginator_w + gap;
- let fy = gap;
- let mut fh = (self.height - STATUS_H - pb_off - 2.0 * gap).max(100.0);
- self.floating_network_layout.0 = fx;
- self.floating_network_layout.1 = fy;
- self.floating_network_layout.3 = fh;
-
- let param_w = self.right_dock_width();
- let param_x = self.width - gap - param_w;
- let param_y = gap;
- let mut param_h = (self.height - STATUS_H - pb_off - 2.0 * gap).max(100.0);
-
- // The spreadsheet rect (shared derivation with the edge hotspots —
- // reads the clamped network width written back above). A side inset
- // tucks the spreadsheet UNDER that neighbor: the neighbor's bottom
- // rises to the spreadsheet's top edge to make room.
- let (ss_x, ss_y, ss_w, ss_h) = self.floating_spreadsheet_rect();
- if self.spreadsheet_tucks_left() {
- fh = (ss_y - gap - fy).max(100.0);
- self.floating_network_layout.3 = fh;
- }
- if self.spreadsheet_tucks_right() {
- param_h = (ss_y - gap - param_y).max(100.0);
- }
-
let viewport_visible = self.show_viewport;
let spreadsheet_visible = self.show_spreadsheet;
let right_visible = self.show_parameters;
@@ -9451,26 +9216,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let vp_y = 0.0;
let vp_h = if viewport_visible { body_h } else { 0.0 };
- // Dock model: the three rects computed above belong to the
- // DOCKS (left column, right column, bottom strip); which pane
- // wears which rect is the dock assignment, swapped by dragging
- // a plate's corner dot. A hidden pane zeroes its rect wherever
- // it is docked.
- let dock_rects = [
- (fx, fy, fw, fh),
- (param_x, param_y, param_w, param_h),
- (ss_x, ss_y, ss_w, ss_h),
- ];
- let rect_for = |slot: usize, state: &Self| -> (f32, f32, f32, f32) {
- if !state.pane_shown(slot) {
- return (0.0, 0.0, 0.0, 0.0);
- }
- match state.dock_of_pane(slot) {
- Some(d) => dock_rects[d as usize],
- None => (0.0, 0.0, 0.0, 0.0),
- }
- };
-
// The network is in no dock (since 2026-10-06): it has no
// plate, and is an overlay on the scene spanning the whole
// body — there is no surface to bound it, and a graph
@@ -9522,7 +9267,10 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
self.positions[SPREADSHEET_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
- self.positions[SPREADSHEET_IDX] = rect_for(SPREADSHEET_IDX, self);
+ // The strip along the bottom, its one place (there are no
+ // docks since 2026-10-07).
+ self.positions[SPREADSHEET_IDX] =
+ if spreadsheet_visible { self.floating_spreadsheet_rect() } else { (0.0, 0.0, 0.0, 0.0) };
self.slots.spreadsheet.set_rect(
self.positions[SPREADSHEET_IDX].0,
self.positions[SPREADSHEET_IDX].1,
@@ -11056,28 +10804,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
} else {
if let Some(drag) = self.app_drag {
match drag {
- AppDrag::LeftDockResize { start_w, start_mouse_x } => {
- let dx = self.cursor_x - start_mouse_x;
- self.floating_network_layout.2 = (start_w + dx).max(150.0);
- // A drag commits the width it SHOWS: storing an
- // overshoot past the window's limit would leave
- // the edge dead on the way back until the pointer
- // had unwound it.
- self.floating_network_layout.2 = self.left_dock_width();
- self.rebuild_positions();
- self.apply_layout();
- self.sync_grid_settings();
- changed = true;
- }
- AppDrag::RightDockResize { start_w, start_mouse_x } => {
- let dx = self.cursor_x - start_mouse_x;
- let new_w = (start_w - dx).max(150.0);
- self.floating_param_width = new_w;
- self.floating_param_width = self.right_dock_width();
- self.rebuild_positions();
- self.apply_layout();
- changed = true;
- }
AppDrag::HudResize { start_w, start_mouse_x } => {
let dx = self.cursor_x - start_mouse_x;
self.params_hud_width = (start_w - dx).max(PARAMS_HUD_MIN_W);
@@ -11095,35 +10821,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.apply_layout();
changed = true;
}
- AppDrag::SpreadsheetResizeLeft { start_inset, start_mouse_x } => {
- // Dragging the left edge past its flush position tucks the
- // spreadsheet under the network pane (the layout raises the
- // pane's bottom to make room); back to flush un-tucks it.
- let dx = self.cursor_x - start_mouse_x;
- let gap = 18.0_f32;
- let fx = self.floating_network_layout.0;
- let flush_left = fx + self.left_dock_width() + gap;
- let max_inset = (flush_left - gap).max(0.0);
- self.floating_spreadsheet_inset_left =
- (start_inset - dx).clamp(0.0, max_inset);
- self.rebuild_positions();
- self.apply_layout();
- self.sync_grid_settings();
- changed = true;
- }
- AppDrag::SpreadsheetResizeRight { start_inset, start_mouse_x } => {
- // The right edge tucks under the parameter pane, symmetrically.
- let dx = self.cursor_x - start_mouse_x;
- let gap = 18.0_f32;
- let param_x = self.width - gap - self.right_dock_width();
- let flush_right = param_x - gap;
- let max_inset = (self.width - gap - flush_right).max(0.0);
- self.floating_spreadsheet_inset_right =
- (start_inset + dx).clamp(0.0, max_inset);
- self.rebuild_positions();
- self.apply_layout();
- changed = true;
- }
}
} else if let Some(idx) = self.drag_widget {
// A widget drag: the slot's own Input drag hooks are driving.
@@ -11449,18 +11146,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
}
- // The left dock's edge: the plate docked there, if any.
- if *button == MouseButton::Left && self.on_left_dock_resize_edge(self.cursor_x, self.cursor_y) {
- self.app_drag = Some(AppDrag::LeftDockResize {
- start_w: self.left_dock_width(),
- start_mouse_x: self.cursor_x,
- });
- if let Some(old) = self.focused_widget {
- self.slots.get_dyn_mut(old).unfocus();
- self.focused_widget = None;
- }
- return true;
- }
// The network's breadcrumb hovers over the graph and
// claims its segments only. Asked where it is drawn:
// until 2026-10-07 this tested the old left dock's
@@ -11476,19 +11161,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
}
- // The right dock's plate is over the HUD, so its edge
- // is asked first.
- if *button == MouseButton::Left && self.on_right_dock_resize_edge(self.cursor_x, self.cursor_y) {
- self.app_drag = Some(AppDrag::RightDockResize {
- start_w: self.right_dock_width(),
- start_mouse_x: self.cursor_x,
- });
- if let Some(old) = self.focused_widget {
- self.slots.get_dyn_mut(old).unfocus();
- self.focused_widget = None;
- }
- return true;
- }
if *button == MouseButton::Left && self.show_parameters {
let cx = self.cursor_x;
let cy = self.cursor_y;
@@ -11511,23 +11183,11 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let cx = self.cursor_x;
let cy = self.cursor_y;
- if let Some(dir) = self.spreadsheet_resize_edge_at(cx, cy) {
- self.app_drag = Some(if dir.left {
- AppDrag::SpreadsheetResizeLeft {
- start_inset: self.floating_spreadsheet_inset_left,
- start_mouse_x: cx,
- }
- } else if dir.right {
- AppDrag::SpreadsheetResizeRight {
- start_inset: self.floating_spreadsheet_inset_right,
- start_mouse_x: cx,
- }
- } else {
- let (_, _, _, ss_h) = self.floating_spreadsheet_rect();
- AppDrag::SpreadsheetResize {
- start_h: ss_h,
- start_mouse_y: cy,
- }
+ if self.on_spreadsheet_resize_edge(cx, cy) {
+ let (_, _, _, ss_h) = self.floating_spreadsheet_rect();
+ self.app_drag = Some(AppDrag::SpreadsheetResize {
+ start_h: ss_h,
+ start_mouse_y: cy,
});
self.focused_pane = SPREADSHEET_MENUBAR_IDX;
if let Some(old) = self.focused_widget {
@@ -11895,27 +11555,14 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// shared teardown for faithfulness — the pane widget never began
// a drag in resize mode, so its commit/cancel hook is a no-op.
let idx = match drag {
- AppDrag::LeftDockResize { .. } => self.pane_in_dock(Dock::Left),
AppDrag::HudResize { .. } => PARAM_IDX,
- AppDrag::RightDockResize { .. } => self.pane_in_dock(Dock::Right),
- AppDrag::SpreadsheetResize { .. }
- | AppDrag::SpreadsheetResizeLeft { .. }
- | AppDrag::SpreadsheetResizeRight { .. } => SPREADSHEET_IDX,
+ AppDrag::SpreadsheetResize { .. } => SPREADSHEET_IDX,
};
{
let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&cce_ui::widget::Event::DragEnd, &mut self.ui_context); }
}
self.sync_layout();
- // Both of these resized the network pane, so the node
- // offsets need the same refresh.
- if matches!(
- drag,
- AppDrag::LeftDockResize { .. }
- | AppDrag::SpreadsheetResizeLeft { .. }
- ) {
- self.read_panel_offsets();
- }
changed = true;
}
if self.drag_widget.is_some() {
diff --git a/src/main.rs b/src/main.rs
index 7664c09..737e80c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -472,33 +472,61 @@ mod tests {
press_at(&mut state, 2.0, 2.0, MouseButton::Left);
}
- /// Move To swaps the pane, and the tabs riding it, with what holds the
- /// other dock — what dragging the corner used to do.
+ /// There are no docks (since 2026-10-07): the spreadsheet is the strip
+ /// along the bottom, a gap in from the sides, and nothing moves it there
+ /// or elsewhere; the plate menus hold the window actions alone; only the
+ /// spreadsheet's top edge resizes it; and an older save that had moved it
+ /// into a side dock opens with it along the bottom.
#[test]
- fn move_to_swaps_a_pane_into_another_dock() {
- use crate::app::{Dock, NO_PANE};
+ fn the_plates_have_no_docks() {
use crate::plate_menu::PlateMenuAction;
- use crate::slots::{NETWORK_PANEL_IDX, PARAM_IDX, SPREADSHEET_IDX};
+ use crate::slots::{NETWORK_PANEL_IDX, PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX};
let mut state = State::new(false);
state.resize(1600.0, 900.0, 1.0);
- state.show_spreadsheet = true;
- state.open_plate_menu(SPREADSHEET_IDX);
- assert!(state.plate_menu_actions.contains(&PlateMenuAction::MoveTo(Dock::Right)));
- assert!(!state.plate_menu_actions.contains(&PlateMenuAction::MoveTo(Dock::Bottom)), "not to its own dock");
- state.close_plate_menu();
- state.run_plate_menu_action(SPREADSHEET_IDX, PlateMenuAction::MoveTo(Dock::Left));
- assert_eq!(state.dock_of_pane(SPREADSHEET_IDX), Some(Dock::Left));
- assert_eq!(state.pane_in_dock(Dock::Bottom), NO_PANE, "it swapped with the empty dock");
- let (x, _, w, _) = state.positions[SPREADSHEET_IDX];
- assert_eq!((x, w), (18.0, state.left_dock_width()), "it wears the left dock's rect");
- // Neither the params HUD nor the network is in a dock, and neither
- // moves to one.
- for idx in [PARAM_IDX, NETWORK_PANEL_IDX] {
- state.open_plate_menu(idx);
- assert!(!state.plate_menu_actions.iter().any(|a| matches!(a, PlateMenuAction::MoveTo(_))));
- assert_eq!(state.dock_of_pane(idx), None);
- state.close_plate_menu();
+ state.execute_menu_action("Show Spreadsheet Pane");
+ if !state.show_playbar {
+ state.execute_menu_action("Show Playbar Pane");
+ }
+ let (x, y, w, h) = state.positions[SPREADSHEET_IDX];
+ assert_eq!((x, w), (18.0, 1600.0 - 36.0), "the window's width, a gap in");
+ assert_eq!(y + h, state.positions[PLAYBAR_IDX].1 - 18.0, "a gap above the playbar");
+
+ for idx in [SPREADSHEET_IDX, PARAM_IDX, NETWORK_PANEL_IDX, PLAYBAR_IDX] {
+ for (label, action) in state.plate_menu_rows(idx).0.iter().zip(state.plate_menu_rows(idx).1) {
+ assert!(
+ matches!(action, PlateMenuAction::Collapse | PlateMenuAction::Expand | PlateMenuAction::Detach | PlateMenuAction::Reattach),
+ "{label} on plate {idx}"
+ );
+ }
}
+
+ // Only the top edge resizes: the sides tucked under a dock's plate.
+ assert!(state.on_spreadsheet_resize_edge(x + w * 0.5, y));
+ assert!(!state.on_spreadsheet_resize_edge(x, y + h * 0.5));
+ assert!(!state.on_spreadsheet_resize_edge(x + w, y + h * 0.5));
+
+ // An older save with the spreadsheet moved to the left dock, its
+ // width and tucks recorded: it loads along the bottom, and the HUD
+ // width it recorded still loads.
+ let dir = std::env::temp_dir().join(format!("cce-designer-no-docks-test-{}", std::process::id()));
+ let _ = fs::remove_dir_all(&dir);
+ state.save_to_file(&dir).expect("save");
+ let file = dir.join("state.json");
+ let mut json: serde_json::Value = serde_json::from_str(&fs::read_to_string(&file).unwrap()).unwrap();
+ json["view_state"]["dock_tabs"] = serde_json::json!([["spreadsheet"], [], []]);
+ json["view_state"]["plates"] = serde_json::json!({
+ "network_width": 0.4, "params_width": 0.25, "spreadsheet_height": 0.3,
+ "spreadsheet_inset_left": 0.1, "spreadsheet_inset_right": 0.0,
+ });
+ fs::write(&file, serde_json::to_string(&json).unwrap()).unwrap();
+ let mut b = State::new(false);
+ b.resize(1600.0, 900.0, 1.0);
+ b.load_from_file(&dir).expect("load");
+ let (bx, _, bw, _) = b.positions[SPREADSHEET_IDX];
+ assert_eq!((bx, bw), (18.0, 1600.0 - 36.0), "along the bottom");
+ assert!((b.params_hud_width - 400.0).abs() < 0.5, "an older save's params width is the HUD's: {}", b.params_hud_width);
+ assert!((b.floating_spreadsheet_height - 270.0).abs() < 0.5);
+ let _ = fs::remove_dir_all(&dir);
}
/// Collapse must actually reclaim the plate AND take its body with it, and
@@ -598,9 +626,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
assert!(state.network_overlay(), "the network overlays the scene");
- // Spanning the window, it has no edge to drag where its dock's was.
- let (fx, fy, _, fh) = state.floating_network_layout;
- assert!(!state.on_left_dock_resize_edge(fx + state.left_dock_width(), fy + fh * 0.5));
let press = |state: &mut State, x: f32, y: f32, b: MouseButton| {
state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } });
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: b });
@@ -1061,31 +1086,6 @@ mod tests {
assert!(s.points().has("uv") && !s.points().has("UV"));
}
- /// Full width tucks the spreadsheet under BOTH neighbors (their bottoms
- /// rise via the tuck interlock); between-panes clears both tucks.
- #[test]
- fn test_spreadsheet_full_width_round_trip() {
- use crate::plate_menu::PlateMenuAction;
- use crate::slots::SPREADSHEET_IDX;
- let mut state = State::new(false);
- state.resize(1600.0, 900.0, 1.0);
- state.show_spreadsheet = true;
- state.rebuild_positions();
- // No plate beside it to tuck under (the spreadsheet is the one
- // dockable pane): it spans the window already, and Full Width is not
- // offered.
- let (ss_x, _, ss_w, _) = state.floating_spreadsheet_rect();
- assert!(ss_x <= 18.5 && ss_x + ss_w >= 1600.0 - 18.5, "not full width: x={ss_x} w={ss_w}");
- state.open_plate_menu(SPREADSHEET_IDX);
- assert!(!state.plate_menu_actions.contains(&PlateMenuAction::FullWidth));
- assert!(!state.plate_menu_actions.contains(&PlateMenuAction::BetweenPanes));
- state.close_plate_menu();
- state.set_spreadsheet_full_width(true);
- assert!(!state.spreadsheet_tucks_left() && !state.spreadsheet_tucks_right());
- let (ss_x, _, ss_w, _) = state.floating_spreadsheet_rect();
- assert!(ss_x <= 18.5 && ss_x + ss_w >= 1600.0 - 18.5);
- }
-
/// The way back. A detached pane's menu offers Reattach and nothing else,
/// and reattaching restores the pane in full.
#[test]
@@ -1498,15 +1498,14 @@ mod tests {
assert_eq!(m.match_command(&ctrl_shift, &lower), Some("save_document_as"));
}
- /// The network is in no dock (since 2026-10-07): it spans the body
- /// whatever the docks hold, the left dock starts empty — so the
- /// spreadsheet runs flush to the left — and the network neither
- /// collapses nor moves. The left dock's edge resizes the plate docked
- /// there, and a press in the band the network's dock had along the
- /// top, off the breadcrumb's segments, is the scene's.
+ /// The network spans the body, shown though it fronts no dock (it was
+ /// in the left dock until 2026-10-07, when it left, and the docks went
+ /// the same day), and it does not collapse: its plate menu is Detach.
+ /// A press in the band its dock had along the top, off the
+ /// breadcrumb's segments, is the scene's.
#[test]
fn the_network_is_in_no_dock() {
- use crate::app::{Dock, NO_PANE, HEADER_H};
+ use crate::app::HEADER_H;
use crate::plate_menu::PlateMenuAction;
use crate::slots::{BREADCRUMB_IDX, LEFT_MENUBAR_IDX, NETWORK_PANEL_IDX, SPREADSHEET_IDX};
use crate::window::{LocalPosition, WindowEvent};
@@ -1514,16 +1513,12 @@ mod tests {
let mut state = State::new(false);
state.resize(1600.0, 900.0, 1.0);
state.execute_menu_action("Show Spreadsheet Pane");
- assert_eq!(state.dock_of_pane(NETWORK_PANEL_IDX), None);
- assert_eq!(state.pane_in_dock(Dock::Left), NO_PANE);
assert_eq!(state.positions[NETWORK_PANEL_IDX], (0.0, HEADER_H, 1600.0, state.body_h()));
- assert!(state.slots.get_dyn(crate::slots::CONTENT_IDX).visible(), "shown though it fronts no dock");
- assert_eq!(state.positions[SPREADSHEET_IDX].0, 18.0, "flush left, a gap in");
+ assert!(state.slots.get_dyn(crate::slots::CONTENT_IDX).visible(), "shown");
+ assert_eq!(state.positions[SPREADSHEET_IDX].0, 18.0, "the spreadsheet flush left, a gap in");
state.set_pane_collapsed(NETWORK_PANEL_IDX, true);
assert!(!state.pane_is_collapsed(NETWORK_PANEL_IDX));
- state.move_pane_to_dock(NETWORK_PANEL_IDX, Dock::Bottom);
- assert_eq!(state.dock_of_pane(NETWORK_PANEL_IDX), None);
state.open_plate_menu(NETWORK_PANEL_IDX);
assert_eq!(state.plate_menu_actions, vec![PlateMenuAction::Detach]);
state.close_plate_menu();
@@ -1554,29 +1549,14 @@ mod tests {
assert!(state.orbit_drag.is_some(), "the press orbits");
press(&mut state, ElementState::Released);
assert_ne!(state.focused_pane, LEFT_MENUBAR_IDX);
-
- // A plate in the left dock: its right edge resizes the dock.
- state.move_pane_to_dock(SPREADSHEET_IDX, Dock::Left);
- let (qx, qy, qw, qh) = state.positions[SPREADSHEET_IDX];
- assert!(qw > 0.0);
- let w0 = state.left_dock_width();
- let edge = (qx + qw, qy + qh * 0.5);
- assert!(state.on_left_dock_resize_edge(edge.0, edge.1));
- at(&mut state, edge.0, edge.1);
- press(&mut state, ElementState::Pressed);
- at(&mut state, edge.0 + 40.0, edge.1);
- press(&mut state, ElementState::Released);
- assert!((state.left_dock_width() - (w0 + 40.0)).abs() < 0.5, "{} against {}", state.left_dock_width(), w0 + 40.0);
- assert_eq!(state.positions[SPREADSHEET_IDX].2, state.left_dock_width());
}
/// An older save docked the network and sometimes the second network
/// editor (removed 2026-10-07), in front of it or waiting behind it as a
- /// tab. Neither is a dock pane now: both are dropped wherever they stood,
- /// and the rest of the arrangement loads.
+ /// tab, and pinned panes to it. All of it is ignored — the docks went the
+ /// same day — and the save loads, the spreadsheet along the bottom.
#[test]
fn an_older_saves_second_network_editor_is_dropped() {
- use crate::app::{Dock, NO_PANE};
use crate::slots::SPREADSHEET_IDX;
let dir = std::env::temp_dir().join(format!("cce-designer-hidden-editor-test-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
@@ -1599,9 +1579,8 @@ mod tests {
serde_json::json!([[], ["network2"], ["spreadsheet"]]),
] {
let b = load_with(tabs.clone());
- assert_eq!(b.pane_in_dock(Dock::Left), NO_PANE, "{tabs}");
- assert_eq!(b.pane_in_dock(Dock::Right), NO_PANE, "{tabs}");
- assert_eq!(b.pane_in_dock(Dock::Bottom), SPREADSHEET_IDX, "{tabs}");
+ assert_eq!(b.floating_spreadsheet_rect().0, 18.0, "{tabs}");
+ assert_eq!(b.positions[SPREADSHEET_IDX].0, if b.show_spreadsheet { 18.0 } else { 0.0 }, "{tabs}");
}
let _ = fs::remove_dir_all(&dir);
}
@@ -1627,8 +1606,6 @@ mod tests {
assert!(!a.collapsed_panes[PARAM_IDX], "the params HUD does not collapse");
a.splitter_layout.splitter1_x = 400.0;
a.splitter_layout.splitter2_x = 1200.0;
- // Dock state: the spreadsheet moved to the left dock.
- a.move_pane_to_dock(crate::slots::SPREADSHEET_IDX, crate::app::Dock::Left);
a.save_to_file(&dir).expect("save");
let mut b = State::new(false);
@@ -1641,9 +1618,6 @@ mod tests {
assert!((b.splitter_layout.splitter1_x - 200.0).abs() < 1.0,
"splitters restore as fractions: 400/1600 of an 800-wide window = 200, got {}",
b.splitter_layout.splitter1_x);
- // The dock arrangement rides the file; the network is in none.
- assert_eq!(b.pane_in_dock(crate::app::Dock::Left), crate::slots::SPREADSHEET_IDX, "the spreadsheet loads where it was moved");
- assert_eq!(b.dock_of_pane(crate::slots::NETWORK_PANEL_IDX), None, "the network loads in no dock");
// A detached pane window must ignore the same file's pane state.
let mut d = State::new(true);
@@ -1667,43 +1641,33 @@ mod tests {
a.resize(1600.0, 900.0, 1.0);
a.execute_menu_action("Show Spreadsheet Pane");
assert!(a.show_spreadsheet);
- a.floating_network_layout.2 = 520.0;
- a.floating_param_width = 360.0;
a.params_hud_width = 420.0;
a.floating_spreadsheet_height = 300.0;
- a.set_spreadsheet_full_width(true);
a.rebuild_positions();
- let insets = (a.floating_spreadsheet_inset_left, a.floating_spreadsheet_inset_right);
- assert!(insets.0 > 0.0 && insets.1 > 0.0, "full width must set both tucks");
- assert!((a.floating_network_layout.2 - 520.0).abs() < 0.5 && (a.floating_param_width - 360.0).abs() < 0.5);
a.save_to_file(&dir).expect("save");
let plates = a.project_view_state().plates.expect("a sized window records its plates");
- assert!((plates.network_width - 520.0 / 1600.0).abs() < 1e-4, "widths save as window fractions");
+ assert!((plates.hud_width.unwrap() - 420.0 / 1600.0).abs() < 1e-4, "widths save as window fractions");
+ assert!(plates.params_width.is_none(), "the retired right dock width is never written");
let mut b = State::new(false);
b.resize(1600.0, 900.0, 1.0);
b.load_from_file(&dir).expect("load");
- assert!((b.floating_network_layout.2 - 520.0).abs() < 0.5, "network width: {}", b.floating_network_layout.2);
- assert!((b.floating_param_width - 360.0).abs() < 0.5, "param width: {}", b.floating_param_width);
assert!((b.params_hud_width - 420.0).abs() < 0.5, "HUD width: {}", b.params_hud_width);
assert!((b.floating_spreadsheet_height - 300.0).abs() < 0.5, "spreadsheet height: {}", b.floating_spreadsheet_height);
- assert!((b.floating_spreadsheet_inset_left - insets.0).abs() < 0.5 && (b.floating_spreadsheet_inset_right - insets.1).abs() < 0.5,
- "tucks: {:?} vs {:?}", (b.floating_spreadsheet_inset_left, b.floating_spreadsheet_inset_right), insets);
// Half the window: the same fractions land at half the pixels.
let mut c = State::new(false);
c.resize(800.0, 450.0, 1.0);
c.load_from_file(&dir).expect("load half-size");
- assert!((c.floating_network_layout.2 - 260.0).abs() < 0.5, "scaled network width: {}", c.floating_network_layout.2);
- assert!((c.floating_param_width - 180.0).abs() < 0.5, "scaled param width: {}", c.floating_param_width);
assert!((c.params_hud_width - 210.0).abs() < 0.5, "scaled HUD width: {}", c.params_hud_width);
+ assert!((c.floating_spreadsheet_height - 150.0).abs() < 0.5, "scaled spreadsheet height: {}", c.floating_spreadsheet_height);
// A detached pane window keeps its own plates.
let mut d = State::new(true);
- let before = d.floating_param_width;
+ let before = d.params_hud_width;
d.load_from_file(&dir).expect("load detached");
- assert_eq!(d.floating_param_width, before);
+ assert_eq!(d.params_hud_width, before);
let _ = fs::remove_dir_all(&dir);
}
@@ -1721,8 +1685,7 @@ mod tests {
let mut a = State::new(false);
a.resize(1400.0, 1080.0, 1.0);
a.execute_menu_action("Show Spreadsheet Pane");
- a.floating_network_layout.2 = 350.0;
- a.floating_param_width = 310.0;
+ a.params_hud_width = 310.0;
a.floating_spreadsheet_height = 100.0;
a.rebuild_positions();
a.save_to_file(&dir).expect("save");
@@ -1732,16 +1695,15 @@ mod tests {
assert!(!b.window_configured);
b.load_from_file(&dir).expect("load");
b.resize(1400.0, 1080.0, 1.0);
- assert!((b.left_dock_width() - 350.0).abs() < 0.5, "network width: {}", b.left_dock_width());
- assert!((b.right_dock_width() - 310.0).abs() < 0.5, "param width: {}", b.right_dock_width());
+ assert!((b.params_hud_width - 310.0).abs() < 0.5, "HUD width: {}", b.params_hud_width);
assert!((b.floating_spreadsheet_height - 100.0).abs() < 0.5, "spreadsheet height: {}", b.floating_spreadsheet_height);
let pg = b.project_view_state().plates.expect("plates");
- assert!((pg.network_width - 350.0 / 1400.0).abs() < 1e-4, "a save writes back what was loaded: {:?}", pg);
+ assert!((pg.hud_width.unwrap() - 310.0 / 1400.0).abs() < 1e-4, "a save writes back what was loaded: {:?}", pg);
// Only the FIRST configure: a later window resize keeps the plates'
// pixels, as it always has.
b.resize(1000.0, 1080.0, 1.0);
- assert!((b.left_dock_width() - 350.0).abs() < 0.5, "a later resize rescaled the plate: {}", b.left_dock_width());
+ assert!((b.params_hud_width - 310.0).abs() < 0.5, "a later resize rescaled the plate: {}", b.params_hud_width);
let _ = fs::remove_dir_all(&dir);
}
@@ -1757,8 +1719,8 @@ mod tests {
fs::create_dir_all(&dir).unwrap();
let mut a = State::new(false);
a.resize(1400.0, 1080.0, 1.0);
- a.floating_network_layout.2 = 350.0;
- a.floating_param_width = 310.0;
+ a.params_hud_width = 310.0;
+ a.floating_spreadsheet_height = 180.0;
a.rebuild_positions();
a.save_to_file(&dir).expect("save");
@@ -1787,8 +1749,6 @@ mod tests {
let mut s = State::new(false);
s.resize(1600.0, 900.0, 1.0);
s.execute_menu_action("Show Spreadsheet Pane");
- s.floating_network_layout.2 = 700.0;
- s.floating_param_width = 650.0;
s.params_hud_width = 650.0;
s.floating_spreadsheet_height = 600.0;
s.rebuild_positions();
@@ -1802,8 +1762,7 @@ mod tests {
s.resize(1600.0, 900.0, 1.0);
s.rebuild_positions();
- assert!((s.left_dock_width() - 700.0).abs() < 0.5, "network width: {}", s.left_dock_width());
- assert!((s.right_dock_width() - 650.0).abs() < 0.5, "param width: {}", s.right_dock_width());
+ assert!((s.params_hud_width - 650.0).abs() < 0.5, "HUD width: {}", s.params_hud_width);
assert!((s.floating_spreadsheet_rect().3 - 600.0).abs() < 0.5, "spreadsheet height: {:?}", s.floating_spreadsheet_rect());
assert!((s.positions[crate::slots::PARAM_IDX].2 - 650.0).abs() < 0.5, "drawn HUD width: {:?}", s.positions[crate::slots::PARAM_IDX]);
}
@@ -1821,8 +1780,7 @@ mod tests {
let mut main = State::new(false);
main.resize(1600.0, 900.0, 1.0);
- main.floating_network_layout.2 = 700.0;
- main.floating_param_width = 650.0;
+ main.params_hud_width = 650.0;
main.floating_spreadsheet_height = 420.0;
main.viewport_mut().rotation_x = 0.7;
main.rebuild_positions();
@@ -1838,8 +1796,7 @@ mod tests {
main.load_sync_channel(&channel, true).expect("main reloads");
assert!(main.fs_root.children.iter().any(|c| c.name == "synced_edit"), "the tree edit must sync");
- assert!((main.floating_network_layout.2 - 700.0).abs() < 0.5, "network width: {}", main.floating_network_layout.2);
- assert!((main.floating_param_width - 650.0).abs() < 0.5, "param width: {}", main.floating_param_width);
+ assert!((main.params_hud_width - 650.0).abs() < 0.5, "HUD width: {}", main.params_hud_width);
assert!((main.floating_spreadsheet_height - 420.0).abs() < 0.5, "spreadsheet height: {}", main.floating_spreadsheet_height);
assert!((main.viewport().rotation_x - 0.7).abs() < 1e-6, "camera: {}", main.viewport().rotation_x);
assert!(main.app_drag.is_some(), "a plate drag in progress survives the reload");
@@ -1860,7 +1817,7 @@ mod tests {
let mut main = State::new(false);
main.resize(1600.0, 900.0, 1.0);
- main.floating_param_width = 650.0;
+ main.params_hud_width = 650.0;
main.viewport_mut().rotation_x = 0.7;
main.rebuild_positions();
main.save_to_file(&channel).expect("main writes the channel");
@@ -1877,7 +1834,8 @@ mod tests {
assert!(proj.root.children.iter().any(|c| c.name == "synced_edit"), "the tree is the child's");
assert_eq!(proj.view_state.selected_node, Some(0), "so is the navigation");
let plates = proj.view_state.plates.expect("the main window's plates stay in the file");
- assert!((plates.params_width - 650.0 / 1600.0).abs() < 1e-4, "params width: {}", plates.params_width);
+ let hud = plates.hud_width.expect("the HUD's width");
+ assert!((hud - 650.0 / 1600.0).abs() < 1e-4, "HUD width: {hud}");
let view = proj.view_state.default_view.expect("the main window's camera stays in the file");
assert!((view.rotation.0 - 0.7).abs() < 1e-6, "camera: {:?}", view.rotation);
@@ -1908,7 +1866,7 @@ mod tests {
state.resize(1600.0, 900.0, 1.0);
assert!(!state.has_unsaved_changes(), "a window resize is not an edit");
- state.floating_param_width += 60.0;
+ state.floating_spreadsheet_height += 60.0;
state.rebuild_positions();
state.update_window_title();
assert!(state.has_unsaved_changes(), "a plate resize must dirty the title");
@@ -2138,7 +2096,6 @@ mod tests {
/// in the right dock, over its top, sizes nothing.
#[test]
fn the_params_hud_is_under_the_plates_and_stops_above_the_bottom_ones() {
- use crate::app::{Dock, NO_PANE};
use crate::slots::{PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX};
let mut state = State::new(false);
state.resize(1600.0, 900.0, 1.0);
@@ -2147,16 +2104,12 @@ mod tests {
let hud = state.positions[PARAM_IDX];
assert_eq!(hud, state.params_hud_rect());
assert_eq!(hud.1 + hud.3, 900.0 - crate::app::STATUS_H - 18.0, "the viewport's height, a gap in");
- assert_eq!(state.dock_of_pane(PARAM_IDX), None, "the HUD is in no dock");
- assert_eq!(state.pane_in_dock(Dock::Right), NO_PANE);
// The spreadsheet and the playbar below it: it stops a gap above
- // the higher of them. The right dock's width sizes nothing.
+ // the higher of them.
state.execute_menu_action("Show Spreadsheet Pane");
state.execute_menu_action("Show Playbar Pane");
state.floating_spreadsheet_height = 500.0;
- state.set_spreadsheet_full_width(true);
- state.floating_param_width = 700.0;
state.rebuild_positions();
state.apply_layout();
let (hx, hy, hw, hh) = state.positions[PARAM_IDX];
@@ -2196,37 +2149,12 @@ mod tests {
assert!(pb.scrollbar_visible(), "the sphere's rows overflow a {hh} px HUD and scroll");
assert_eq!(state.params_claim(), (hx, hy, hw, hh), "the plate fills the HUD");
- // Under a plate in the right dock: it takes the pointer there.
- state.set_spreadsheet_full_width(false);
- state.move_pane_to_dock(SPREADSHEET_IDX, Dock::Right);
- state.rebuild_positions();
- state.apply_layout();
- let (hx, hy, hw, _) = state.positions[PARAM_IDX];
- assert!(state.plates_over_params().contains(&state.positions[SPREADSHEET_IDX]));
- let (nx, ny, nw, _) = state.positions[SPREADSHEET_IDX];
- let over = (nx.max(hx) + 5.0, ny.max(hy) + 30.0);
- assert!(over.0 < nx + nw && over.0 < hx + hw);
- assert!(!state.params_claims(over.0, over.1), "a plate over the HUD takes the pointer");
- assert_eq!(state.plate_at(over.0, over.1), Some(SPREADSHEET_IDX));
-
- // An older save docked the params pane and the network; both load
- // out of the docks.
- let dir = std::env::temp_dir().join(format!("cce-designer-hud-dock-test-{}", std::process::id()));
- let _ = fs::remove_dir_all(&dir);
- let mut a = State::new(false);
- a.save_to_file(&dir).expect("save");
- let file = dir.join("state.json");
- let mut json: serde_json::Value = serde_json::from_str(&fs::read_to_string(&file).unwrap()).unwrap();
- json["view_state"]["dock_tabs"] = serde_json::json!([["parameters"], ["network"], ["spreadsheet"]]);
- fs::write(&file, serde_json::to_string(&json).unwrap()).unwrap();
- let mut b = State::new(false);
- b.load_from_file(&dir).expect("load");
- assert_eq!(b.pane_in_dock(Dock::Left), NO_PANE, "the dock the params fronted is empty");
- assert_eq!(b.pane_in_dock(Dock::Right), NO_PANE, "the dock the network fronted is empty");
- assert_eq!(b.pane_in_dock(Dock::Bottom), SPREADSHEET_IDX, "the rest of the arrangement loads");
- assert_eq!(b.dock_of_pane(PARAM_IDX), None);
- assert_eq!(b.dock_of_pane(crate::slots::NETWORK_PANEL_IDX), None);
- let _ = fs::remove_dir_all(&dir);
+ // (A plate in the right dock stood over the HUD's top and took the
+ // pointer there, until the docks went, 2026-10-07; the spreadsheet
+ // and the playbar it stops above are the plates left.)
+ assert!(!state.plates_over_params().iter().any(|&(x, y, w, h)| {
+ x < hx + hw && x + w > hx && y < hy + hh && y + h > hy
+ }), "no plate stands over the HUD");
}
/// What the plates leave of the HUD, as rects that do not overlap.
diff --git a/src/plate_menu.rs b/src/plate_menu.rs
index 2beb45c..1487262 100644
--- a/src/plate_menu.rs
+++ b/src/plate_menu.rs
@@ -1,5 +1,5 @@
-//! The plate menu: what can be done to a pane's PLATE — collapse, detach,
-//! where it is docked — as rows of that pane's right-click menu.
+//! The plate menu: what can be done to a pane's PLATE — collapse and
+//! detach — as rows of that pane's right-click menu.
//!
//! Until 2026-10-01 these rows were a menu of their own, opened by a small
//! circular trigger on the top-right of every plate (and that trigger, dragged,
@@ -8,14 +8,15 @@
//! playbar's) the rows are a PAGE of it, its **Plate** row turning the menu
//! into them (since 2026-10-06; they were appended inline until then), and
//! they make up the whole menu where it has none (the params pane off a
-//! row, the spreadsheet, and a collapsed or detached plate's stub). Moving a pane to another dock is a row too,
-//! `Move To …`, which swaps it with what is there as the drag did.
+//! row, the spreadsheet, and a collapsed or detached plate's stub). Moving
+//! a pane to another dock was a row too (`Move To …`) until the docks went,
+//! 2026-10-07.
//!
//! [`State::plate_menu_rows`] is the one list; [`State::open_plate_menu_at`]
//! shows it alone and [`State::open_plate_page`] as another menu's page,
//! dispatching a pick through [`State::run_plate_menu_action`].
-use crate::app::{Dock, State};
+use crate::app::State;
use crate::slots::{
NETWORK_PANEL_IDX, PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX, WIDGET_COUNT,
};
@@ -35,14 +36,6 @@ pub enum PlateMenuAction {
Detach,
/// Take a detached pane back, closing the window that held it.
Reattach,
- /// Spreadsheet: span the full window width, tucking under both neighbors
- /// (whose bottoms the layout raises to make room — the playbar treatment).
- FullWidth,
- /// Spreadsheet: back to the strip between the network and params panes.
- BetweenPanes,
- /// Move this pane to another dock, swapping with what is there — what
- /// dragging the plate's corner used to do.
- MoveTo(Dock),
/// A "-" row: engraved, inert — keeps the action list aligned with the
/// option rows so a click on the line dispatches nothing.
Separator,
@@ -59,14 +52,6 @@ pub fn plate_title(idx: usize) -> &'static str {
}
}
-/// A dock's name in a `Move To` row.
-fn dock_title(d: Dock) -> &'static str {
- match d {
- Dock::Left => "Left",
- Dock::Right => "Right",
- Dock::Bottom => "Bottom",
- }
-}
impl State {
/// The plate whose rect holds (px, py), topmost first — the plate a
@@ -122,56 +107,9 @@ impl State {
PlateDockAction::Reattach => PlateMenuAction::Reattach,
});
}
- if state.detached {
- return (options, actions);
- }
-
- // Group boundaries are engraved separators ("-" rows — the toolkit
- // convention): window actions | layout spans | Move To. Pushed lazily so a group that contributes nothing
- // leaves no orphaned line.
- let separate = |options: &mut Vec<String>, actions: &mut Vec<PlateMenuAction>| {
- if !options.is_empty() && options.last().map(String::as_str) != Some("-") {
- options.push("-".to_string());
- actions.push(PlateMenuAction::Separator);
- }
- };
-
- // Only with a plate beside it to tuck under: with neither side dock
- // holding one (the spreadsheet being the one dockable pane, the
- // usual case) it spans the window already, and the rows would do
- // nothing.
- if idx == SPREADSHEET_IDX
- && !self.collapsed_panes[idx]
- && (self.dock_shown(Dock::Left) || self.dock_shown(Dock::Right))
- {
- separate(&mut options, &mut actions);
- // Layout spans: full-width is the playbar treatment; the neighbors'
- // bottoms rise to make room via the tuck interlock.
- if !(self.spreadsheet_tucks_left() && self.spreadsheet_tucks_right()) {
- options.push("Full Width".to_string());
- actions.push(PlateMenuAction::FullWidth);
- }
- if self.spreadsheet_tucks_left() || self.spreadsheet_tucks_right() {
- options.push("Between Panes".to_string());
- actions.push(PlateMenuAction::BetweenPanes);
- }
- }
-
- // Move To — only on docked plates (the playbar's strip is not a
- // dock): every other dock, swapping with what is there, the drag the
- // corner trigger used to start, as rows. (Docks held TABS until
- // 2026-10-07 — several panes to a dock, switched, added and split
- // from here — retired when the spreadsheet became the one pane a
- // dock could hold.)
- if let Some(d) = self.dock_of_pane(idx) {
- separate(&mut options, &mut actions);
- for other in [Dock::Left, Dock::Right, Dock::Bottom] {
- if other != d {
- options.push(format!("Move To {}", dock_title(other)));
- actions.push(PlateMenuAction::MoveTo(other));
- }
- }
- }
+ // The window actions are the whole menu: the spreadsheet's Full
+ // Width / Between Panes rows and every docked plate's Move To rows
+ // went with the docks, 2026-10-07.
(options, actions)
}
@@ -246,25 +184,10 @@ impl State {
PlateMenuAction::Expand => self.set_pane_collapsed(idx, false),
PlateMenuAction::Detach => self.detach_plate(idx),
PlateMenuAction::Reattach => self.reattach_plate(idx),
- PlateMenuAction::FullWidth => self.set_spreadsheet_full_width(true),
- PlateMenuAction::BetweenPanes => self.set_spreadsheet_full_width(false),
- PlateMenuAction::MoveTo(d) => self.move_pane_to_dock(idx, d),
PlateMenuAction::Separator => {}
}
}
- /// Spreadsheet span: full width sets both tuck insets to their maxima
- /// (the rect derivation clamps to the usable span), between-panes clears
- /// them. The neighbors' heights follow through the existing tuck interlock.
- pub fn set_spreadsheet_full_width(&mut self, full: bool) {
- let v = if full { self.width.max(1.0) } else { 0.0 };
- self.floating_spreadsheet_inset_left = v;
- self.floating_spreadsheet_inset_right = v;
- self.rebuild_positions();
- self.apply_layout();
- self.read_panel_offsets();
- }
-
pub fn set_pane_collapsed(&mut self, idx: usize, collapsed: bool) {
// The params HUD and the network never collapse (see
// `plate_menu_rows`).
diff --git a/src/playbar.rs b/src/playbar.rs
index e50accf..798773a 100644
--- a/src/playbar.rs
+++ b/src/playbar.rs
@@ -1,5 +1,5 @@
-//! The playbar pane: a full-width animation-transport strip docked below the
-//! other panes (`PLAYBAR_IDX`). App-owned on the narrow traits wrapped in
+//! The playbar pane: a full-width animation-transport strip along the
+//! window's bottom edge, below the other panes (`PLAYBAR_IDX`). App-owned on the narrow traits wrapped in
//! `Adapted<Playbar>`, like `Viewport3D`. Unlike the other panes it paints
//! through the modern `paint()` path — the designer's render walk special-cases
//! `PLAYBAR_IDX` to `paint_self` (geometry AND text) instead of the legacy
diff --git a/src/project.rs b/src/project.rs
index f929438..2938b7f 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -96,7 +96,7 @@ impl State {
/// The view-state block every save and snapshot shares — the pane state
- /// (visibility, collapse, splitter proportions, docks, pins) beside the
+ /// (visibility, collapse, splitter proportions, plate sizes) beside the
/// camera/pan fields. Visibility rode the root meta node's View subnet
/// params into the file until that node was retired.
pub(crate) fn project_view_state(&self) -> ProjectViewState {
@@ -114,26 +114,14 @@ impl State {
} else {
None
};
- // What each dock holds, by name: a list of at most one, the shape
- // the save has kept since docks held tabs.
- let dock_tabs = (0..3)
- .map(|d| {
- crate::plate_menu::pane_name_from_slot(self.dock_panes[d])
- .map(|n| vec![n.to_string()])
- .unwrap_or_default()
- })
- .collect();
// A plate's stored size is what was asked for and may exceed a window
// that has since shrunk; the loader refuses a fraction past 1 (and
// with it the whole block), so a save caps each at the full window.
let plates = if self.width > 1.0 && self.height > 1.0 {
Some(PlateGeometry {
- network_width: (self.floating_network_layout.2 / self.width).min(1.0),
- params_width: (self.floating_param_width / self.width).min(1.0),
spreadsheet_height: (self.floating_spreadsheet_height / self.height).min(1.0),
- spreadsheet_inset_left: self.floating_spreadsheet_inset_left / self.width,
- spreadsheet_inset_right: self.floating_spreadsheet_inset_right / self.width,
hud_width: Some((self.params_hud_width / self.width).min(1.0)),
+ params_width: None,
})
} else {
None
@@ -152,7 +140,6 @@ impl State {
),
collapsed_panes,
splitters,
- dock_tabs,
plates,
frame_range: {
let pb = self.slots.playbar.inner();
@@ -318,45 +305,6 @@ impl State {
.map_or(false, |n| vs.collapsed_panes.iter().any(|c| c == n));
self.set_pane_collapsed(idx, desired);
}
- // What the docks hold: accepted only whole — three lists whose names
- // resolve and cover each dockable pane exactly once. Anything else
- // (older saves' empty list included) keeps the current layout rather
- // than loading half of one. A list of an older save is a tab group,
- // active first; its first dockable name is what the dock holds.
- if vs.dock_tabs.len() == 3 {
- let resolved: Vec<Vec<usize>> = vs
- .dock_tabs
- .iter()
- .map(|names| {
- names
- .iter()
- .filter_map(|n| crate::plate_menu::pane_slot_from_name(n))
- .collect()
- })
- .collect();
- // "network2", the second network editor (removed 2026-10-07),
- // names no pane and is dropped wherever an older save had it.
- let all: Vec<usize> = resolved.iter().flatten().copied().collect();
- // Neither the params pane nor the network is docked since
- // 2026-10-06 (the one a HUD on the scene, the other an overlay
- // spanning it): an older save lists them in docks, and they are
- // taken out — a dock one fronted holds its next tab, or is
- // empty.
- let undocked = |s: usize| s == crate::slots::PARAM_IDX || s == crate::slots::NETWORK_PANEL_IDX;
- let resolved: Vec<Vec<usize>> = resolved
- .into_iter()
- .map(|tabs| tabs.into_iter().filter(|&s| !undocked(s)).collect())
- .collect();
- let all: Vec<usize> = all.into_iter().filter(|&s| !undocked(s)).collect();
- let expected = vec![crate::slots::SPREADSHEET_IDX];
- if all == expected {
- for d in 0..3 {
- self.dock_panes[d] = resolved[d].first().copied().unwrap_or(crate::app::NO_PANE);
- }
- self.rebuild_positions();
- self.apply_layout();
- }
- }
if let Some((f1, f2)) = vs.splitters {
if self.width > 1.0 && f1 > 0.02 && f2 < 0.98 && f1 < f2 {
self.splitter_layout.splitter1_x = f1 * self.width;
@@ -375,20 +323,15 @@ impl State {
}
/// Scale saved plate fractions onto this window. The layout pass clamps
- /// them exactly as a drag would (minimum widths, the spreadsheet's tuck
- /// limits). A save with a nonsense value keeps the live geometry rather
+ /// them exactly as a drag would (minimum widths and heights). A save with a nonsense value keeps the live geometry rather
/// than loading half of one.
pub(crate) fn apply_plate_geometry(&mut self, pg: crate::app::PlateGeometry) {
let sane = |f: f32| f.is_finite() && (0.0..=1.0).contains(&f);
- let hud = pg.hud_width.unwrap_or(pg.params_width);
- let all = [pg.network_width, pg.params_width, pg.spreadsheet_height, pg.spreadsheet_inset_left, pg.spreadsheet_inset_right, hud];
+ let Some(hud) = pg.hud_width.or(pg.params_width) else { return };
+ let all = [pg.spreadsheet_height, hud];
if self.width > 1.0 && self.height > 1.0 && all.iter().all(|&f| sane(f)) {
- self.floating_network_layout.2 = pg.network_width * self.width;
- self.floating_param_width = pg.params_width * self.width;
self.params_hud_width = hud * self.width;
self.floating_spreadsheet_height = pg.spreadsheet_height * self.height;
- self.floating_spreadsheet_inset_left = pg.spreadsheet_inset_left * self.width;
- self.floating_spreadsheet_inset_right = pg.spreadsheet_inset_right * self.width;
self.rebuild_positions();
self.apply_layout();
}
@@ -449,7 +392,7 @@ impl State {
}
self.drag_widget = None;
if !keep_own_view {
- // A pane-edge or dock drag is layout, which a sync reload no
+ // A pane-edge drag is layout, which a sync reload no
// longer touches — the drag in hand stays in hand.
self.app_drag = None;
}