graphic design tool
git clone https://git.lucas.co/cce-designer.git
refactor(network): remove the second network editor
The second network editor (its plate, graph and breadcrumb slots, its own
path, Close Tab) is gone, with what existed only to choose between two
editors: param_editor, which the params pane, the spreadsheet and the
viewport followed, and the viewport / params / spreadsheet pins with their
Follow Active Editor and Pin rows.
An older save's "network2" dock entry, current_path2 and pins are dropped
on load. With the spreadsheet now the one dockable pane, its Full Width
row is offered only while a side dock holds a plate to tuck under.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 63 +++++---
src/app.rs | 422 +++++-----------------------------------------------
src/context.rs | 1 -
src/dialog.rs | 10 +-
src/edit_history.rs | 8 +-
src/main.rs | 382 ++++++++---------------------------------------
src/plate_menu.rs | 92 +++---------
src/project.rs | 62 +-------
src/render.rs | 56 ++-----
src/slots.rs | 15 +-
src/window.rs | 16 --
11 files changed, 183 insertions(+), 944 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 549c7c3..f9de9eb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -175,10 +175,9 @@ The rules:
one step. The graph is not looked at while a drag is held
(`gesture_held`), so a dragged node is one step from where it was picked
up.
-- **Slots are held by id across a structure step**: the two editors'
- paths and the selection, which a node coming or going would move. An
- editor inside a node that an undo takes out comes up to where the node
- was.
+- **Slots are held by id across a structure step**: the editor's path
+ and the selection, which a node coming or going would move. An editor
+ inside a node that an undo takes out comes up to where the node was.
- **Undo and Redo consult it LAST** — a code row, then a viewer state,
then this (`Application::undo` for the chord, `Action::Undo` for the
palette) — so the order ACROSS the three is by owner and not by time. A
@@ -246,11 +245,11 @@ gone from cce-ui with the wgpu path).
hard parts are about paint order and occlusion, none of which is guessable
from the widget.
- `src/plate_menu.rs` — the plate menu: what can be done to a pane's PLATE
- (`PLATE_SLOTS` — network, params, spreadsheet, playbar, network 2; NOT the
- viewport, whose plate is the window-spanning lip) — Collapse/Expand, Detach/
- Reattach, Full Width, the selection pins, the dock's tabs, Add Tab, Move To Own
- Plate, **Move To Left/Right/Bottom** and Close Tab. The network and the params
- HUD are in no dock, so theirs are Detach (and the HUD's pins) alone, and the
+ (`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), the dock's tabs,
+ Add Tab, Move To Own Plate and **Move To Left/Right/Bottom**. The network and
+ the params HUD are in no dock, so theirs are Detach alone, 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
@@ -262,8 +261,8 @@ gone from cce-ui with the wgpu path).
(`PlaybarMenuAction::PlatePage`), which turns the menu into them under a
band back to it (`open_plate_page`; until 2026-10-06 they were appended
inline there), and the whole menu where a pane has none of its own — the
- params pane off a row, the spreadsheet, the second network editor
- (`open_plate_menu`, at the pointer). A row from any of them runs through
+ params pane off a row, the spreadsheet (`open_plate_menu`, at the
+ pointer). A row from any of them runs through
`run_plate_menu_action`. Add Tab is a page row too: the menu turns into
the list of panes where it stood, under a back band to whichever menu it
turned from (`State::plate_page_from`) — from a Plate page, back to that
@@ -1402,9 +1401,8 @@ A bypassed node wears amber (`render::BYPASS_TINT`): its roll tinted
through the bevel's own tint channel, and a flat bar down its left side,
drawn after the bodies with the geometry toggles. The bar is what still
says so while the node is selected and its roll is the selection's colour.
-The node is found by `GraphController::node_at` at the body's centre, so
-the second network editor marks its own level's. Nothing in cce-ui
-changed.
+The node is found by `GraphController::node_at` at the body's centre.
+Nothing in cce-ui changed.
`a_bypassed_node_passes_its_input_through` covers the geometry resolver
and the scene walk, `a_bypassed_page_node_passes_its_sheet_through` the
@@ -1437,7 +1435,7 @@ node dragged onto a wire uses, asked about the body the new node will have
— so adding and dropping agree about what is on a wire, in every wire
style. It is asked BEFORE the add, since the new node's own wires would
touch the cell after. The rewiring is `app::splice_into_wire`, the one the
-drag drop runs (both editors): both Inputs or neither, so a node with no
+drag drop runs: both Inputs or neither, so a node with no
Input — a generator — is added beside the wire and cuts nothing. MCP's
`add_node` places at the coordinates it is given and does not splice.
`a_node_added_on_a_wire_is_wired_into_its_chain` is the test.
@@ -1457,7 +1455,7 @@ not copied still names that node.
A node dragged onto another node trades places with it, connections and
all — the chain's ORDER changes, not just the picture: in I → A → B → C,
-B dropped on A gives I → B → A → C. Both network editors turn cce-ui's
+B dropped on A gives I → B → A → C. The network editor turns cce-ui's
`Graph::set_swap_on_drop` on (see its CLAUDE.md, "A node dropped on a node
can swap with it"); the widget trades the two cells, which the drag's
position write-back carries into the tree, and the release drains
@@ -2764,6 +2762,27 @@ relief is two shapes", "Frost is one block"); this is the designer's view of
them, written because the question "what are the style parameters of the
plates" took a session to answer from the code.
+### There is one network editor (since 2026-10-07)
+
+A second network editor — its own plate, graph and breadcrumb
+(`NETWORK_PANEL2_IDX` / `CONTENT2_IDX` / `BREADCRUMB2_IDX`), its own path
+(`current_path2`), placed through the plate menus' tab rows and closed by
+Close Tab — was removed, with what existed only to choose between two:
+`param_editor` (the editor that took the last click, which the params pane,
+the spreadsheet and the viewport followed) and the three PINS
+(`viewport_pin`, `params_pin`, `spreadsheet_pin`, the "Follow Active Editor"
+/ "Pin: Network" radio rows of the viewport menu and the plate menus).
+`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, the tab rows have
+nothing to offer, 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.
+
### The network has no plate (since 2026-10-06)
The network's nodes and wires stand directly on the 3D scene. The viewport is
@@ -2782,7 +2801,6 @@ settings structs ignore keys they do not know), and the next save drops it.
What only the plate reached went with it: the network's flat focus ring
(the grid cursor carries its focus), the network panels as plates over the
params HUD (`plates_over_params`) and over a point number (`under_a_plate`).
-The SECOND network editor has no plate either; it keeps its dock rect.
The pane keeps its focus domain, its menus, its clip and its keyboard
navigation.
@@ -2791,16 +2809,13 @@ navigation.
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;
-`TAB_CANDIDATES` is the spreadsheet and the second editor; the network's
+`TAB_CANDIDATES` is the spreadsheet alone; 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 — except a second editor that waited behind it, which loads
-CLOSED: it was hidden, and fronting it put a second graph where the
-network's plate had been (`a_second_editor_hidden_behind_the_network_loads_closed`;
-the user's own project did exactly that the day the network left the docks) (`the_params_hud_is_under_the_plates_and_stops_above_the_bottom_ones`
+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
@@ -2928,8 +2943,8 @@ bottom raised by a spreadsheet tucked under it, tabbable and movable.
`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); `TAB_CANDIDATES` no longer lists params, its plate menu has no tab, Move To or Collapse rows (Detach and
- the pins stay), `set_pane_collapsed` refuses it, and the pane-state load
+ next day); `TAB_CANDIDATES` no longer lists 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
diff --git a/src/app.rs b/src/app.rs
index 9487cb3..0a9a7ec 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -237,14 +237,12 @@ pub(crate) fn regenerate_node_ids(n: &mut FsNode) {
}
}
-/// Which node the params pane is showing: the editor feeding it, that
-/// editor's level, the slot there, and the node's id. All four, because no
-/// one of them is an identity alone — an id is empty on nodes a bundled file
-/// was saved without, a slot is only meaningful at a level, and a level is
-/// only meaningful per editor.
+/// Which node the params pane is showing: the editor's level, the slot
+/// there, and the node's id. All three, because no one of them is an
+/// identity alone — an id is empty on nodes a bundled file was saved
+/// without, and a slot is only meaningful at a level.
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct ParamPaneTarget {
- editor: usize,
path: Vec<usize>,
slot: usize,
id: String,
@@ -289,24 +287,13 @@ pub struct ProjectViewState {
/// The docks' tab groups, Left/Right/Bottom order, pane names with the
/// ACTIVE tab first. Empty (older saves) keeps the default one-pane-per-
/// dock arrangement; a list that does not name each core docked pane
- /// exactly once (plus "network2" at most once — its presence recreates
- /// the second editor) is ignored the same way.
+ /// exactly once is ignored the same way. ("network2", the second network
+ /// editor, removed on 2026-10-07, is dropped from an older save's.)
#[serde(default)]
pub dock_tabs: Vec<Vec<String>>,
- /// The second network editor's own path. Clamped on load, so a save
- /// whose tree changed shape degrades to the deepest valid ancestor.
- #[serde(default)]
- pub current_path2: Vec<usize>,
- /// The viewport pin as a pane name ("network"/"network2"); absent or
- /// unresolvable follows the active editor.
- #[serde(default)]
- pub viewport_pin: Option<String>,
- /// The parameters pane's pin, same encoding.
- #[serde(default)]
- pub params_pin: Option<String>,
- /// The spreadsheet's pin, same encoding.
- #[serde(default)]
- pub spreadsheet_pin: Option<String>,
+ // `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.
/// The floating layout's plate geometry — every edge the user can drag
/// — as window fractions, so a project restores its plate sizes and
/// positions at any window size (the splitter convention). Absent in
@@ -499,10 +486,6 @@ pub enum ViewportMenuAction {
/// Put the pivot plane at true size: one world unit (the Guides "World
/// Unit") spans its real length on this display.
OneToOne,
- /// Follow whichever editor took the last node click (the default).
- PinFollow,
- /// Lock the viewport to one editor's level (CONTENT_IDX / CONTENT2_IDX).
- PinTo(usize),
/// Run a registry command — the display toggles, so the menu's rows are
/// the palette's and a row is exactly as scriptable as its command.
Command(&'static str),
@@ -2986,29 +2969,11 @@ pub struct State {
pub fs_root: FsNode,
pub node_templates: Vec<NodeTemplate>,
pub current_path: Vec<usize>,
- /// The SECOND network editor's own path — the point of having one: the
- /// two graph views dive independently. Always valid against `fs_root`
- /// (every structural edit re-clamps it); starts at root.
- pub current_path2: Vec<usize>,
- /// The editor whose SELECTION feeds the parameters pane (and the param
- /// writeback): CONTENT_IDX or CONTENT2_IDX — whichever took the last
- /// node click. Selection itself stays per-editor.
- pub param_editor: usize,
/// The node the params pane's rows were loaded FROM — set by
/// `sync_parameters_pane`, checked by `sync_parameters_to_project`, which
/// writes nothing when the selection has moved on since. See
/// [`State::param_pane_target`].
pub(crate) param_pane_source: Option<ParamPaneTarget>,
- /// The viewport's pin: None follows `param_editor`; Some(CONTENT_IDX /
- /// CONTENT2_IDX) locks the scene to that editor's level regardless of
- /// where clicks land. Set from the viewport's right-click menu.
- pub viewport_pin: Option<usize>,
- /// The parameters pane's pin — same shape, set from its plate's corner
- /// menu. Pinned, the pane shows and edits the pinned editor's selection
- /// no matter where clicks land.
- pub params_pin: Option<usize>,
- /// The spreadsheet's pin — same shape, set from its plate's corner menu.
- pub spreadsheet_pin: Option<usize>,
/// The copied nodes, with the positions they were copied FROM — a paste
/// lays them back out in the same shape, offset to the cursor. A Vec
/// rather than one node because an expanded cursor selects many, and a
@@ -3724,9 +3689,6 @@ impl State {
splitters,
vs.dock_tabs,
vs.frame_range,
- vs.viewport_pin,
- vs.params_pin,
- vs.spreadsheet_pin,
plates,
display,
))
@@ -4597,15 +4559,13 @@ impl State {
let inside = |(x, y, w, h): (f32, f32, f32, f32)| w > 0.0 && h > 0.0 && px >= x && px < x + w && py >= y && py < y + h;
if self.network_overlay() {
// The complement of the overlay: everything in the viewport the
- // network is not holding and no floating pane covers — the
- // second editor, docked, holds its rect. Bounded by the
+ // network is not holding and no floating pane covers. Bounded by the
// viewport's rect; until 2026-10-06 it stopped at the old
// column split (`splitter2_x`), so the scene under the params
// HUD's rows, right of it, was nobody's.
return inside(self.positions[VIEWPORT_IDX])
&& !self.in_network_pane()
- && !self.over_floating_pane()
- && !inside(self.positions[crate::slots::NETWORK_PANEL2_IDX]);
+ && !self.over_floating_pane();
}
// Minus the floating panes here too. The spreadsheet and the playbar
// sit INSIDE the centre column, over the full-bleed scene, and this
@@ -4625,7 +4585,7 @@ impl State {
// excluded by its callers (`in_circle_network_pane`): its rect is
// the circle's bounding box, whose corners are scene.
let over_network = !self.circular_network_pane
- && [NETWORK_PANEL_IDX, crate::slots::NETWORK_PANEL2_IDX].iter().any(|&idx| inside(self.positions[idx]));
+ && inside(self.positions[NETWORK_PANEL_IDX]);
inside(self.positions[VIEWPORT_IDX]) && !over_network && !self.over_floating_pane()
}
@@ -4708,9 +4668,8 @@ impl State {
(x, top, w, (bottom - top).max(PARAMS_DOT_D))
}
- /// The plates drawn over the params HUD, as rects: the network's
- /// (while it has one), the second editor's, the spreadsheet's, the
- /// playbar's — collapsed and detached stubs included, since those are
+ /// The plates drawn over the params HUD, as rects: the spreadsheet's,
+ /// the playbar's — collapsed and detached stubs included, since those are
/// the slots' rects too. The HUD is UNDER all of them: what it shows,
/// and what it takes of the pointer, is what they leave.
pub fn plates_over_params(&self) -> Vec<(f32, f32, f32, f32)> {
@@ -4759,14 +4718,11 @@ impl State {
PARAM_IDX => self.show_parameters,
SPREADSHEET_IDX => self.show_spreadsheet,
PLAYBAR_IDX => self.show_playbar,
- // The second network editor has no View-menu flag: being placed
- // in a dock's tab list IS its existence.
- crate::slots::NETWORK_PANEL2_IDX => true,
_ => false,
}
}
- fn dock_shown(&self, dock: Dock) -> bool {
+ pub(crate) fn dock_shown(&self, dock: Dock) -> bool {
self.pane_shown(self.pane_in_dock(dock))
}
@@ -4814,7 +4770,7 @@ impl State {
}
/// Pull `slot` out of its current dock (if it has one — an unplaced pane
- /// like a fresh second network editor simply joins) and tab it into
+ /// simply joins) and tab it into
/// `dock`, active. The dock it leaves fronts its next remaining tab, or
/// empties ([`NO_PANE`]) — its rect stays reserved by the dock-owned
/// dimensions either way, ready for a tab to move back.
@@ -4835,32 +4791,6 @@ impl State {
self.after_dock_change();
}
- /// Remove a CLOSABLE pane (the second network editor) from the docks
- /// entirely — it stops existing until a tab row re-adds it. Its dock
- /// fronts the next tab or empties.
- pub fn close_dock_tab(&mut self, slot: usize) {
- let Some(from) = self.tab_dock_of_pane(slot) else { return };
- // A closed editor cannot hold the viewport or the params pane.
- if slot == crate::slots::NETWORK_PANEL2_IDX {
- for pin in [&mut self.viewport_pin, &mut self.params_pin, &mut self.spreadsheet_pin] {
- if *pin == Some(crate::slots::CONTENT2_IDX) {
- *pin = None;
- }
- }
- if self.param_editor == crate::slots::CONTENT2_IDX {
- self.param_editor = CONTENT_IDX;
- }
- self.rebuild_scene_geometry();
- self.sync_parameters_pane();
- }
- let f = from as usize;
- self.dock_tabs[f].retain(|&s| s != slot);
- if self.dock_panes[f] == slot {
- self.dock_panes[f] = self.dock_tabs[f].first().copied().unwrap_or(NO_PANE);
- }
- self.after_dock_change();
- }
-
/// The dock a split would move a tab to: the first EMPTY one, Left,
/// Right, Bottom. There are four tab candidates for three docks, so two
/// docks can each hold two with none left free — the corner menu reads
@@ -5033,91 +4963,28 @@ impl State {
node
}
- /// One editor's selected slot (CONTENT_IDX / CONTENT2_IDX).
- pub fn editor_selected_of(&self, editor: usize) -> Option<usize> {
- if editor == crate::slots::CONTENT2_IDX {
- use cce_ui::widget::GraphController as _;
- self.slots.content2.selected_node()
- } else {
- self.graph().selected_node()
- }
- }
-
- /// One editor's displayed level.
- pub fn editor_dir_of(&self, editor: usize) -> &FsNode {
- if editor == crate::slots::CONTENT2_IDX {
- self.dir_at(&self.current_path2)
- } else {
- self.current_dir()
- }
- }
-
- /// [`Self::editor_dir_of`], mutable.
- pub fn editor_dir_of_mut(&mut self, editor: usize) -> &mut FsNode {
- if editor == crate::slots::CONTENT2_IDX {
- let p2 = self.current_path2.clone();
- self.dir_at_mut(&p2)
- } else {
- self.current_dir_mut()
- }
- }
-
- /// The editor the parameters pane is bound to: its pin, else the active
- /// (last-clicked) editor.
- pub fn params_editor(&self) -> usize {
- self.params_pin.unwrap_or(self.param_editor)
- }
-
- /// The editor the spreadsheet is bound to — same resolution.
- pub fn spreadsheet_editor(&self) -> usize {
- self.spreadsheet_pin.unwrap_or(self.param_editor)
- }
-
- /// The selected slot in the editor the parameters pane follows.
+ /// The network editor's selected slot: what the parameters pane, the
+ /// spreadsheet and the param writeback follow. (There were two editors,
+ /// each with a selection and a pin, until 2026-10-07.)
pub fn param_editor_selected(&self) -> Option<usize> {
- self.editor_selected_of(self.params_editor())
+ self.graph().selected_node()
}
- /// The level that editor is showing — where its selection resolves.
+ /// The level the network editor is showing — where its selection
+ /// resolves. [`Self::current_dir`] by another name, kept because it
+ /// says what a reader of the params pane wants.
pub fn param_editor_dir(&self) -> &FsNode {
- self.editor_dir_of(self.params_editor())
+ self.current_dir()
}
/// [`Self::param_editor_dir`], mutable — the param writeback target.
pub fn param_editor_dir_mut(&mut self) -> &mut FsNode {
- self.editor_dir_of_mut(self.params_editor())
- }
-
- /// The editor whose level the VIEWPORT renders: the pin when set, else
- /// the active (last-clicked) editor.
- pub fn viewport_editor(&self) -> usize {
- self.viewport_pin.unwrap_or(self.param_editor)
+ self.current_dir_mut()
}
- /// The level the viewport renders — [`Self::viewport_editor`]'s dir.
+ /// The level the viewport renders: the network editor's.
pub fn viewport_editor_dir(&self) -> &FsNode {
- if self.viewport_editor() == crate::slots::CONTENT2_IDX {
- self.dir_at(&self.current_path2)
- } else {
- self.current_dir()
- }
- }
-
- /// Truncate the second editor's path to its valid prefix — run after any
- /// structural edit, so `dir_at` clamping and the drawn breadcrumb agree.
- pub fn clamp_path2(&mut self) {
- let mut node = &self.fs_root;
- let mut valid = 0;
- for &i in &self.current_path2 {
- match node.children.get(i) {
- Some(child) => {
- node = child;
- valid += 1;
- }
- None => break,
- }
- }
- self.current_path2.truncate(valid);
+ self.current_dir()
}
/// The name a new node gets: the template's name and the lowest free
@@ -5140,11 +5007,9 @@ impl State {
/// The node a params-pane load would show right now — see
/// [`ParamPaneTarget`]. None when nothing is selected.
pub(crate) fn param_pane_target(&self) -> Option<ParamPaneTarget> {
- let editor = self.params_editor();
let slot = self.param_editor_selected()?;
let node = self.param_editor_dir().children.get(slot)?;
- let path = if editor == crate::slots::CONTENT2_IDX { &self.current_path2 } else { &self.current_path };
- Some(ParamPaneTarget { editor, path: path.clone(), slot, id: node.id.clone() })
+ Some(ParamPaneTarget { path: self.current_path.clone(), slot, id: node.id.clone() })
}
/// Write the params pane's rows back into the node they were loaded from.
@@ -7387,26 +7252,6 @@ impl State {
ViewportMenuAction::Command("attribute_visualizers"),
);
- // The viewport's editor binding, as a radio group: follow the active
- // editor, or pin to one. Pin rows appear only while a second editor
- // exists — with one editor, following IS pinned.
- if self.tab_dock_of_pane(crate::slots::NETWORK_PANEL2_IDX).is_some() {
- options.push("-".to_string());
- actions.push(ViewportMenuAction::Separator);
- let mark = |on: bool| if on { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF };
- options.push(format!("{}Follow Active Editor", mark(self.viewport_pin.is_none())));
- actions.push(ViewportMenuAction::PinFollow);
- options.push(format!(
- "{}Pin: Network",
- mark(self.viewport_pin == Some(CONTENT_IDX))
- ));
- actions.push(ViewportMenuAction::PinTo(CONTENT_IDX));
- options.push(format!(
- "{}Pin: Network 2",
- mark(self.viewport_pin == Some(crate::slots::CONTENT2_IDX))
- ));
- actions.push(ViewportMenuAction::PinTo(crate::slots::CONTENT2_IDX));
- }
(options, actions)
}
@@ -7429,14 +7274,6 @@ impl State {
self.close_viewport_menu();
self.open_network_menu_from_viewport(at);
}
- ViewportMenuAction::PinFollow => {
- self.viewport_pin = None;
- self.rebuild_scene_geometry();
- }
- ViewportMenuAction::PinTo(e) => {
- self.viewport_pin = Some(e);
- self.rebuild_scene_geometry();
- }
ViewportMenuAction::Command(id) => {
self.run_command(id);
}
@@ -8089,15 +7926,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let graph_nodes = Self::graph_nodes_of(&self.fs_root, self.current_dir(), frame);
self.graph_mut().set_nodes(&graph_nodes);
- // The second network editor views ITS OWN level.
- self.clamp_path2();
- let nodes2 = Self::graph_nodes_of(&self.fs_root, self.dir_at(&self.current_path2.clone()), frame);
- use cce_ui::widget::GraphController as _;
- self.slots.content2.set_nodes(&nodes2);
- let names2 = self.path_names_at(&self.current_path2.clone());
- use cce_ui::widget::PathController as _;
- self.slots.breadcrumb2.set_path(&names2);
-
let camera_nodes: Vec<String> = self.camera_level().children.iter()
.filter(|c| c.node_type == "camera")
.map(|c| c.name.clone())
@@ -8143,14 +7971,12 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// itself cost.
let mut sim_cache = std::mem::take(&mut self.sim_cache);
- // The spreadsheet (and the group markers with it) read the
- // SPREADSHEET's binding: its pin when set, else the active editor —
- // exactly the parameters pane's rule with its own pin.
+ // The spreadsheet (and the group markers with it) read the network
+ // editor's selection, as the parameters pane does.
let mut selected_node = None;
if !self.is_detached_network {
- let se = self.spreadsheet_editor();
- if let Some(slot_idx) = self.editor_selected_of(se) {
- let dir = self.editor_dir_of(se);
+ if let Some(slot_idx) = self.param_editor_selected() {
+ let dir = self.param_editor_dir();
if slot_idx < dir.children.len() {
selected_node = Some(&dir.children[slot_idx]);
}
@@ -8620,13 +8446,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
pb.set_visible(false);
pb
},
- network_panel2: PassivePlate::new(),
- content2: Graph::new(),
- breadcrumb2: {
- let mut bc = Breadcrumb::new();
- bc.set_raised(true);
- bc
- },
dialog: crate::dialog::Dialog::new(),
});
@@ -8634,11 +8453,9 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
slots.playbar.inner_mut().step_buttons = settings.playbar_step_buttons;
let wire_style = cce_ui::widget::display::WireStyle::parse(&settings.viewport.node_wire_style);
slots.content.inner_mut().set_wire_style(wire_style);
- slots.content2.inner_mut().set_wire_style(wire_style);
// A node dropped on a node swaps places with it, connections and all
// (`swap_places`).
slots.content.inner_mut().set_swap_on_drop(true);
- slots.content2.inner_mut().set_swap_on_drop(true);
slots.playbar.inner_mut().fps = settings.playbar_fps.clamp(1.0, 120.0);
if let Some(viewport) = slots.viewport.as_any_mut().downcast_mut::<Viewport3D>() {
viewport.show_grid = settings.viewport.show_grid_enabled;
@@ -8714,12 +8531,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
fs_root: fs_root.clone(),
node_templates,
current_path,
- current_path2: Vec::new(),
- param_editor: CONTENT_IDX,
param_pane_source: None,
- viewport_pin: None,
- params_pin: None,
- spreadsheet_pin: None,
node_clipboard: Vec::new(),
last_click: None,
shortcut_manager,
@@ -9224,26 +9036,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
breadcrumb.set_network_opacity(self.network_opacity);
}
- // The second editor's graph reads the SAME display settings but its
- // OWN rect as origin (no pan of its own yet — the rect is the view).
- let (qx, qy, _, _) = self.positions[crate::slots::CONTENT2_IDX];
- {
- use cce_ui::widget::GraphController as _;
- let g2 = &mut *self.slots.content2;
- g2.set_show_network_grid(network_grid_visible);
- g2.set_grid_pitch(grid_pitch_x, grid_pitch_y);
- g2.set_node_size(node_w, node_h);
- g2.set_grid_origin(qx, qy);
- g2.set_grid_snap_enabled(grid_snap_enabled);
- }
- if let Some(g2) = self.slots.content2.as_any_mut().downcast_mut::<cce_ui::widget::Graph>() {
- g2.set_network_opacity(self.network_opacity);
- g2.set_node_opacity(self.node_opacity);
- g2.set_grid_color(self.graph_grid_color);
- }
- if let Some(bc2) = self.slots.breadcrumb2.as_any_mut().downcast_mut::<cce_ui::widget::Breadcrumb>() {
- bc2.set_network_opacity(self.network_opacity);
- }
}
pub fn update_inertial_settings(&mut self) {
@@ -9430,17 +9222,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let body_h = self.body_h();
- // The second network editor exists only in the floating branch,
- // which re-lays it below; zeroing here keeps the circular and
- // detached branches (which predate it) from leaving stale rects.
- for slot in [
- crate::slots::NETWORK_PANEL2_IDX,
- crate::slots::CONTENT2_IDX,
- crate::slots::BREADCRUMB2_IDX,
- ] {
- self.positions[slot] = (0.0, 0.0, 0.0, 0.0);
- self.slots.get_dyn_mut(slot).set_visible(false);
- }
if self.is_detached_network {
let cx = self.width / 2.0;
@@ -9825,30 +9606,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.positions[SPREADSHEET_IDX].3,
);
- // The second network editor: the pane-1 arrangement against
- // its own dock rect (plate, full-plate graph, hovering
- // breadcrumb at the same lip offset). Zero when unplaced or
- // waiting as a tab — rect_for already answers that.
- let (qx, qy, qw, qh) = rect_for(crate::slots::NETWORK_PANEL2_IDX, self);
- self.positions[crate::slots::NETWORK_PANEL2_IDX] = (qx, qy, qw, qh);
- self.slots.network_panel2.set_rect(qx, qy, qw, qh);
- if let Some(plate) =
- self.slots.network_panel2.as_any_mut().downcast_mut::<PassivePlate>()
- {
- plate.set_curved_circle(None);
- }
- self.positions[crate::slots::CONTENT2_IDX] = (qx, qy, qw, qh);
- let bc2 = if qw > 0.0 { breadcrumb_h() } else { 0.0 };
- self.positions[crate::slots::BREADCRUMB2_IDX] =
- (qx + bc_pad, qy + bc_pad, (qw - 2.0 * bc_pad).max(0.0), bc2);
- for (slot, on) in [
- (crate::slots::NETWORK_PANEL2_IDX, qw > 0.0),
- (crate::slots::CONTENT2_IDX, qw > 0.0),
- (crate::slots::BREADCRUMB2_IDX, qw > 0.0),
- ] {
- self.slots.get_dyn_mut(slot).set_visible(on);
- }
-
self.positions[CANVAS_IDX] = (0.0, 0.0, self.width, body_h);
self.positions[PARAM_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
self.positions[STATUS_IDX] = (0.0, self.height - STATUS_H, self.width, STATUS_H);
@@ -10939,12 +10696,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
pub fn sync_cursor_and_selection(&mut self) {
- // The grid cursor is PANE 1's concept: both network editors share
- // the LEFT_MENUBAR focus domain, so without the param_editor gate a
- // click in the second editor ran this and forced pane 1's selection
- // to whatever sat under its cursor cell — wiping the selection a
- // pinned params pane or spreadsheet was reading.
- if self.focused_pane != LEFT_MENUBAR_IDX || self.param_editor != CONTENT_IDX {
+ if self.focused_pane != LEFT_MENUBAR_IDX {
return;
}
let dir = self.current_dir();
@@ -11746,11 +11498,11 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
return true;
}
// A plate with no context menu of its own — the
- // params pane off a row, the spreadsheet, the
- // second network editor — has its plate menu.
+ // params pane off a row, the spreadsheet — has
+ // its plate menu.
if let Some(idx) = self
.plate_at(self.cursor_x, self.cursor_y)
- .filter(|&i| matches!(i, PARAM_IDX | SPREADSHEET_IDX | crate::slots::NETWORK_PANEL2_IDX))
+ .filter(|&i| matches!(i, PARAM_IDX | SPREADSHEET_IDX))
{
self.close_node_menu();
self.close_viewport_menu();
@@ -11989,13 +11741,11 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
if click_target.is_none() {
let mut hit_order: Vec<usize> = (0..WIDGET_COUNT).collect();
hit_order.sort_by_key(|&i| {
- let z = if i == NETWORK_PANEL_IDX || i == crate::slots::NETWORK_PANEL2_IDX {
+ let z = if i == NETWORK_PANEL_IDX {
// Plates sort behind their content, or the
// stable sort hands the plate every press
// and the graph never hears a click.
-5
- } else if i == crate::slots::BREADCRUMB2_IDX {
- 1
} else if i == VIEWPORT_IDX {
// Below PARAM_IDX: the params pane floats over the viewport,
// and the old shared -4 tier let the stable sort's index order
@@ -12025,9 +11775,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
|| i == CANVAS_IDX
|| i == BREADCRUMB_IDX
|| i == NETWORK_PANEL_IDX
- || i == crate::slots::CONTENT2_IDX
- || i == crate::slots::BREADCRUMB2_IDX
- || i == crate::slots::NETWORK_PANEL2_IDX
{
new_pane = Some(LEFT_MENUBAR_IDX);
} else if i == RIGHT_MENUBAR_IDX || i == VIEWPORT_IDX {
@@ -12140,25 +11887,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.focused_widget = None;
}
}
- if i == crate::slots::CONTENT2_IDX {
- // A node click in the SECOND editor hands the
- // parameters pane (and the viewport's level)
- // to it.
- if self.param_editor != crate::slots::CONTENT2_IDX {
- self.param_editor = crate::slots::CONTENT2_IDX;
- if self.viewport_pin.is_none() {
- self.rebuild_scene_geometry();
- }
- }
- self.sync_parameters_pane();
- }
if i == CONTENT_IDX {
- if self.param_editor != CONTENT_IDX {
- self.param_editor = CONTENT_IDX;
- if self.viewport_pin.is_none() {
- self.rebuild_scene_geometry();
- }
- }
self.sync_parameters_pane();
if let Some(slot_idx) = self.graph().selected_node() {
let dir = self.current_dir();
@@ -12323,22 +12052,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.rebuild_positions();
self.apply_layout();
self.update_panel_bounds();
- } else if idx == crate::slots::CONTENT2_IDX {
- // The second editor's node drags write back to
- // ITS level, or the next sync snaps them home.
- {
- 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); }
- }
- use cce_ui::widget::GraphController as _;
- let updated_nodes = self.slots.content2.get_nodes();
- let p2 = self.current_path2.clone();
- let dir = self.dir_at_mut(&p2);
- for (i, node) in updated_nodes.iter().enumerate() {
- if let Some(child) = dir.children.get_mut(i) {
- child.position = node.position;
- }
- }
} else {
{
let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
@@ -12380,8 +12093,8 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
if let Some((i, visible)) = self.graph_mut().take_node_geom_toggle() {
self.current_dir_mut().set_child_geometry_visible(i, visible);
// The widget only flipped its own copy of the clicked node;
- // the exclusivity rule may have cleared siblings (in both
- // editors' views), so push the model back out.
+ // the exclusivity rule may have cleared siblings, so push
+ // the model back out.
self.sync_nodes();
self.rebuild_scene_geometry();
changed = true;
@@ -12418,63 +12131,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
}
- // The SECOND network editor's drains — the same handshakes,
- // against ITS OWN level (`current_path2` via the clamping
- // dir_at walk). Selection stays pane-1's affair for now: the
- // params pane follows the primary editor.
- {
- use cce_ui::widget::GraphController as _;
- if let Some((i, visible)) = self.slots.content2.take_node_geom_toggle() {
- let p2 = self.current_path2.clone();
- let dir = self.dir_at_mut(&p2);
- if i < dir.children.len() {
- dir.set_child_geometry_visible(i, visible);
- self.sync_nodes();
- self.rebuild_scene_geometry();
- changed = true;
- }
- }
- if let Some((input_node_id, output_node_name, port)) =
- self.slots.content2.take_pending_connection_to_port()
- {
- let p2 = self.current_path2.clone();
- changed |= self.connect_port(&p2, &input_node_id, output_node_name, port);
- }
- if let Some((a_id, b_id)) = self.slots.content2.take_pending_swap() {
- let p2 = self.current_path2.clone();
- if swap_places(self.dir_at_mut(&p2), &a_id, &b_id) {
- self.sync_nodes();
- self.rebuild_scene_geometry();
- self.sync_parameters_pane();
- changed = true;
- }
- }
- if let Some((mid_id, src_name, dest_id)) =
- self.slots.content2.take_pending_splice()
- {
- let p2 = self.current_path2.clone();
- if splice_into_wire(self.dir_at_mut(&p2), &mid_id, src_name, &dest_id) {
- self.sync_nodes();
- self.rebuild_scene_geometry();
- self.sync_parameters_pane();
- changed = true;
- }
- }
- if let Some(dir_idx) = self.slots.content2.double_clicked_node() {
- self.slots.content2.clear_double_clicked_node();
- let p2 = self.current_path2.clone();
- let dir = self.dir_at(&p2);
- if dir_idx < dir.children.len() && dir.children[dir_idx].is_enterable() {
- self.current_path2.push(dir_idx);
- self.sync_nodes();
- // The viewport tracks its editor's level.
- if self.viewport_editor() == crate::slots::CONTENT2_IDX {
- self.rebuild_scene_geometry();
- }
- changed = true;
- }
- }
- }
diff --git a/src/context.rs b/src/context.rs
index 4fd412e..c204622 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -226,7 +226,6 @@ pub fn wrap_root_geometry(project: &mut Project) {
} else {
view.current_path = remap(&view.current_path);
}
- view.current_path2 = remap(&view.current_path2);
}
/// One channel path as it resolved before a move.
diff --git a/src/dialog.rs b/src/dialog.rs
index 5be0176..2135e90 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -889,8 +889,7 @@ impl Dialog {
}
impl Layout for Dialog {
- /// Above every pane, and above the second network editor's plates: the
- /// dialog is modal in practice — a press inside it never reaches what it
+ /// Above every pane: the dialog is modal in practice — a press inside it never reaches what it
/// covers — so it has to be drawn that way too.
fn z_order(&self) -> i32 {
900
@@ -2404,12 +2403,11 @@ impl State {
}
}
- /// The node wires' style on both network editors — `None` follows the
- /// config's `wire_style`. The widgets hold it; the save reads it back
- /// off the first.
+ /// The node wires' style on the network editor — `None` follows the
+ /// config's `wire_style`. The widget holds it; the save reads it back
+ /// off it.
pub(crate) fn set_node_wire_style(&mut self, style: Option<cce_ui::widget::display::WireStyle>) {
self.slots.content.inner_mut().set_wire_style(style);
- self.slots.content2.inner_mut().set_wire_style(style);
}
/// Write one setting's value to whatever owns it.
diff --git a/src/edit_history.rs b/src/edit_history.rs
index aa4c4d7..193b695 100644
--- a/src/edit_history.rs
+++ b/src/edit_history.rs
@@ -38,7 +38,6 @@
//! the step names, and what that is is in the step.
use crate::app::{FsNode, State};
-use cce_ui::widget::GraphController as _;
use crate::param::{ParamDef, ParamKind};
use std::time::{Duration, Instant};
@@ -511,10 +510,9 @@ impl State {
self.update_status_text(&format!("{verb} {what}: {name}"));
}
Step::Structure(step) => {
- // Where the editors are and what is selected are slots,
- // which a node coming or going moves: held by id across it.
+ // Where the editor is and what is selected are slots, which
+ // a node coming or going moves: held by id across it.
let path = self.ids_along(&self.current_path.clone());
- let path2 = self.ids_along(&self.current_path2.clone());
let selected = self
.graph()
.selected_node()
@@ -530,7 +528,6 @@ impl State {
let inverse = restore(&mut self.fs_root, step);
self.edit_history.file(undo, Step::Structure(inverse));
self.current_path = self.slots_along(&path);
- self.current_path2 = self.slots_along(&path2);
let camera = camera
.and_then(|id| crate::viewer_state::find_node_by_id(&self.fs_root, &id))
.map(|n| n.name.clone());
@@ -539,7 +536,6 @@ impl State {
}
let slot = selected.and_then(|id| self.current_dir().children.iter().position(|n| n.id == id));
self.graph_mut().set_selected_node(slot);
- self.slots.content2.set_selected_node(None);
self.grid_cursor_expanse = None;
let at = slot.and_then(|i| self.current_dir().children.get(i)).map(|n| n.position);
if let Some((col, row)) = at {
diff --git a/src/main.rs b/src/main.rs
index 3819b28..b94a6c7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -476,20 +476,21 @@ mod tests {
/// other dock — what dragging the corner used to do.
#[test]
fn move_to_swaps_a_pane_into_another_dock() {
- use crate::app::Dock;
+ use crate::app::{Dock, NO_PANE};
use crate::plate_menu::PlateMenuAction;
- use crate::slots::{NETWORK_PANEL2_IDX, NETWORK_PANEL_IDX, PARAM_IDX, SPREADSHEET_IDX};
+ use crate::slots::{NETWORK_PANEL_IDX, PARAM_IDX, SPREADSHEET_IDX};
let mut state = State::new(false);
state.resize(1600.0, 900.0, 1.0);
state.show_spreadsheet = true;
- state.add_dock_tab(Dock::Left, NETWORK_PANEL2_IDX);
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), (0.0, 0.0));
assert_eq!(state.dock_of_pane(SPREADSHEET_IDX), Some(Dock::Left));
- assert_eq!(state.dock_of_pane(NETWORK_PANEL2_IDX), Some(Dock::Bottom));
+ 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] {
@@ -506,32 +507,27 @@ mod tests {
#[test]
fn test_collapse_shrinks_the_plate_and_restores_it() {
use crate::plate_menu::STUB_H;
- // The second network editor, docked: the network itself is in no
- // dock and does not collapse.
- use crate::slots::{CONTENT2_IDX as CONTENT_IDX, NETWORK_PANEL2_IDX as NETWORK_PANEL_IDX};
+ use crate::slots::{NETWORK_PANEL_IDX, SPREADSHEET_IDX};
let mut state = State::new(false);
state.resize(1600.0, 900.0, 1.0);
- state.set_pane_collapsed(crate::slots::NETWORK_PANEL_IDX, true);
- assert!(!state.pane_is_collapsed(crate::slots::NETWORK_PANEL_IDX), "the network does not collapse");
- state.add_dock_tab(crate::app::Dock::Left, NETWORK_PANEL_IDX);
+ state.set_pane_collapsed(NETWORK_PANEL_IDX, true);
+ assert!(!state.pane_is_collapsed(NETWORK_PANEL_IDX), "the network does not collapse");
+ state.execute_menu_action("Show Spreadsheet Pane");
- let (_, _, _, full_h) = state.slots.get_dyn(NETWORK_PANEL_IDX).rect();
- assert!(full_h > STUB_H, "network plate starts taller than a stub");
- assert!(state.slots.get_dyn(CONTENT_IDX).visible(), "graph starts visible");
+ let (_, _, _, full_h) = state.slots.get_dyn(SPREADSHEET_IDX).rect();
+ assert!(full_h > STUB_H, "the plate starts taller than a stub");
- state.set_pane_collapsed(NETWORK_PANEL_IDX, true);
- let (_, _, _, stub_h) = state.slots.get_dyn(NETWORK_PANEL_IDX).rect();
+ state.set_pane_collapsed(SPREADSHEET_IDX, true);
+ let (_, _, _, stub_h) = state.slots.get_dyn(SPREADSHEET_IDX).rect();
assert_eq!(stub_h, STUB_H, "collapsed plate is not the stub height");
- assert!(!state.slots.get_dyn(CONTENT_IDX).visible(), "graph survived the collapse");
// A right press on the stub offers Expand; a left press expands it.
- let (x, y, w, h) = state.slots.get_dyn(NETWORK_PANEL_IDX).rect();
+ let (x, y, w, h) = state.slots.get_dyn(SPREADSHEET_IDX).rect();
press_at(&mut state, x + w * 0.5, y + h * 0.5, cce_ui::widget::MouseButton::Right);
assert!(state.plate_menu_actions.contains(&crate::plate_menu::PlateMenuAction::Expand));
state.close_plate_menu();
press_at(&mut state, x + w * 0.5, y + h * 0.5, cce_ui::widget::MouseButton::Left);
- let (_, _, _, back_h) = state.slots.get_dyn(NETWORK_PANEL_IDX).rect();
+ let (_, _, _, back_h) = state.slots.get_dyn(SPREADSHEET_IDX).rect();
assert_eq!(back_h, full_h, "expanding did not restore the plate height");
- assert!(state.slots.get_dyn(CONTENT_IDX).visible(), "graph did not come back");
}
/// Both sides of a detach. The child must show ONE pane and nothing else —
@@ -1065,67 +1061,29 @@ mod tests {
assert!(s.points().has("uv") && !s.points().has("UV"));
}
- /// Dock swap: Move To another dock swaps occupants, and the dock-owned
- /// dimensions stay put — the second network editor lands in the bottom
- /// strip's rect, the spreadsheet in the right dock's.
- #[test]
- fn test_dock_swap_repositions_plates() {
- use crate::app::Dock;
- use crate::slots::{NETWORK_PANEL2_IDX as NETWORK_PANEL_IDX, SPREADSHEET_IDX};
- // The second network editor: the first spans the window, whatever
- // dock it is in, since it has no plate.
- let mut state = State::new(false);
- state.resize(1600.0, 900.0, 1.0);
- state.show_spreadsheet = true;
- state.add_dock_tab(Dock::Right, NETWORK_PANEL_IDX);
- state.rebuild_positions();
- state.apply_layout();
-
- let net_before = state.slots.get_dyn(NETWORK_PANEL_IDX).rect();
- let ss_before = state.slots.get_dyn(SPREADSHEET_IDX).rect();
- assert!(net_before.2 > 0.0 && ss_before.2 > 0.0);
- assert!(net_before.1 < ss_before.1, "network starts above the bottom strip");
-
- state.move_pane_to_dock(NETWORK_PANEL_IDX, Dock::Bottom);
- assert_eq!(state.dock_of_pane(NETWORK_PANEL_IDX), Some(Dock::Bottom));
- assert_eq!(state.dock_of_pane(SPREADSHEET_IDX), Some(Dock::Right));
-
- let net_after = state.slots.get_dyn(NETWORK_PANEL_IDX).rect();
- let ss_after = state.slots.get_dyn(SPREADSHEET_IDX).rect();
- // The network now wears (approximately) the strip geometry and the
- // spreadsheet the left column's; exact equality is not required
- // because the strip derivation reads dock occupancy, but the vertical
- // order must have inverted and both must remain visible.
- assert!(net_after.1 > ss_after.1, "network did not move below the spreadsheet");
- assert!(net_after.2 > 0.0 && ss_after.2 > 0.0, "a pane vanished in the swap");
-
- // Dropping it back restores the original arrangement.
- state.move_pane_to_dock(NETWORK_PANEL_IDX, Dock::Right);
- assert_eq!(state.dock_of_pane(SPREADSHEET_IDX), Some(Dock::Bottom));
- }
-
/// 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;
- // A plate in the right dock to tuck under: the params HUD is in none.
- state.add_dock_tab(crate::app::Dock::Right, crate::slots::NETWORK_PANEL2_IDX);
state.rebuild_positions();
- assert!(!state.spreadsheet_tucks_left() && !state.spreadsheet_tucks_right());
-
- state.set_spreadsheet_full_width(true);
- // Under the neighbour there is: the left dock is empty, the
- // network being in no dock.
- assert!(state.spreadsheet_tucks_right() && !state.spreadsheet_tucks_left(),
- "full width must tuck under the right dock's plate");
+ // 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 actually full width: x={ss_x} w={ss_w}");
-
- state.set_spreadsheet_full_width(false);
+ 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,
@@ -1295,7 +1253,6 @@ mod tests {
SPLITTER2_IDX, PARAM_IDX, CANVAS_IDX, LEFT_MENUBAR_IDX,
RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, STATUS_IDX, BREADCRUMB_IDX,
SPREADSHEET_IDX, SPREADSHEET_MENUBAR_IDX, NETWORK_PANEL_IDX, PLAYBAR_IDX,
- NETWORK_PANEL2_IDX, CONTENT2_IDX, BREADCRUMB2_IDX,
DIALOG_IDX,
];
assert_eq!(roster.len(), WIDGET_COUNT, "roster length vs WIDGET_COUNT");
@@ -1541,48 +1498,6 @@ mod tests {
assert_eq!(m.match_command(&ctrl_shift, &lower), Some("save_document_as"));
}
- /// Plates support tabs: a pane pulled into another dock rides it as a
- /// tab (one laid out, the rest waiting), switching fronts a waiting tab,
- /// splitting moves the active one back out to the empty dock, and the
- /// arrangement rides the project view state active-first.
- #[test]
- fn test_plate_tabs_share_a_dock() {
- use crate::app::{Dock, NO_PANE};
- use crate::slots::{NETWORK_PANEL2_IDX, SPREADSHEET_IDX};
- let mut state = State::new(false);
- assert_eq!(state.pane_in_dock(Dock::Right), NO_PANE, "the right dock starts empty");
-
- // The second editor takes the right dock; pulling the spreadsheet in
- // beside it fronts it, the editor waits as a tab, and the bottom
- // dock empties.
- state.add_dock_tab(Dock::Right, NETWORK_PANEL2_IDX);
- state.add_dock_tab(Dock::Right, SPREADSHEET_IDX);
- assert_eq!(state.pane_in_dock(Dock::Right), SPREADSHEET_IDX);
- assert!(state.dock_tabs[Dock::Right as usize].contains(&NETWORK_PANEL2_IDX));
- assert_eq!(state.pane_in_dock(Dock::Bottom), NO_PANE);
- // The waiting tab is laid out nowhere but keeps its home dock.
- assert_eq!(state.dock_of_pane(NETWORK_PANEL2_IDX), None);
- assert_eq!(state.tab_dock_of_pane(NETWORK_PANEL2_IDX), Some(Dock::Right));
-
- // Switching fronts the waiting tab without evicting the other.
- state.show_dock_tab(Dock::Right, NETWORK_PANEL2_IDX);
- assert_eq!(state.pane_in_dock(Dock::Right), NETWORK_PANEL2_IDX);
- assert!(state.dock_tabs[Dock::Right as usize].contains(&SPREADSHEET_IDX));
-
- // The view state carries the groups, active first.
- let vs = state.project_view_state();
- let name = crate::plate_menu::pane_name_from_slot(NETWORK_PANEL2_IDX).unwrap();
- assert_eq!(vs.dock_tabs[1][0], name);
- assert!(vs.dock_tabs[1].contains(&"spreadsheet".to_string()));
- assert!(vs.dock_tabs[2].is_empty());
-
- // Splitting moves the active pane to the first empty dock — the
- // left, which the network does not hold; the tab left behind fronts.
- state.split_dock_tab(NETWORK_PANEL2_IDX);
- assert_eq!(state.pane_in_dock(Dock::Left), NETWORK_PANEL2_IDX);
- assert_eq!(state.pane_in_dock(Dock::Right), SPREADSHEET_IDX);
- }
-
/// 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
@@ -1593,7 +1508,7 @@ mod tests {
fn the_network_is_in_no_dock() {
use crate::app::{Dock, NO_PANE, HEADER_H};
use crate::plate_menu::PlateMenuAction;
- use crate::slots::{BREADCRUMB_IDX, LEFT_MENUBAR_IDX, NETWORK_PANEL2_IDX, NETWORK_PANEL_IDX, SPREADSHEET_IDX};
+ use crate::slots::{BREADCRUMB_IDX, LEFT_MENUBAR_IDX, NETWORK_PANEL_IDX, SPREADSHEET_IDX};
use crate::window::{LocalPosition, WindowEvent};
use cce_ui::widget::{ElementState, MouseButton};
let mut state = State::new(false);
@@ -1641,8 +1556,8 @@ mod tests {
assert_ne!(state.focused_pane, LEFT_MENUBAR_IDX);
// A plate in the left dock: its right edge resizes the dock.
- state.add_dock_tab(Dock::Left, NETWORK_PANEL2_IDX);
- let (qx, qy, qw, qh) = state.positions[NETWORK_PANEL2_IDX];
+ 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);
@@ -1652,18 +1567,17 @@ mod tests {
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[NETWORK_PANEL2_IDX].2, state.left_dock_width());
+ assert_eq!(state.positions[SPREADSHEET_IDX].2, state.left_dock_width());
}
- /// An older save docked the network, the second editor sometimes waiting
- /// behind it as a tab. Both load out of sight: the network in no dock,
- /// and the second editor CLOSED, since it was hidden — fronting it would
- /// put a second graph where the network's plate was. A second editor
- /// that was in front stays where it was.
+ /// 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.
#[test]
- fn a_second_editor_hidden_behind_the_network_loads_closed() {
+ fn an_older_saves_second_network_editor_is_dropped() {
use crate::app::{Dock, NO_PANE};
- use crate::slots::{NETWORK_PANEL2_IDX, SPREADSHEET_IDX};
+ 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);
let mut a = State::new(false);
@@ -1672,157 +1586,26 @@ mod tests {
let load_with = |tabs: serde_json::Value| {
let mut json: serde_json::Value = serde_json::from_str(&fs::read_to_string(&file).unwrap()).unwrap();
json["view_state"]["dock_tabs"] = tabs;
+ json["view_state"]["current_path2"] = serde_json::json!([0]);
+ json["view_state"]["params_pin"] = serde_json::json!("network2");
fs::write(&file, serde_json::to_string(&json).unwrap()).unwrap();
let mut b = State::new(false);
b.load_from_file(&dir).expect("load");
b
};
-
- let b = load_with(serde_json::json!([["network", "network2"], [], ["spreadsheet"]]));
- assert_eq!(b.tab_dock_of_pane(NETWORK_PANEL2_IDX), None, "the hidden editor loads closed");
- assert_eq!(b.pane_in_dock(Dock::Left), NO_PANE);
- assert_eq!(b.pane_in_dock(Dock::Bottom), SPREADSHEET_IDX);
-
- let b = load_with(serde_json::json!([["network2", "network"], [], ["spreadsheet"]]));
- assert_eq!(b.pane_in_dock(Dock::Left), NETWORK_PANEL2_IDX, "a fronted editor stays");
-
+ for tabs in [
+ serde_json::json!([["network", "network2"], [], ["spreadsheet"]]),
+ serde_json::json!([["network2", "network"], [], ["spreadsheet"]]),
+ 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}");
+ }
let _ = fs::remove_dir_all(&dir);
}
- /// Move To Own Plate is offered only on a dock holding more than one
- /// tab. With neither the params pane nor the network in the docks, the
- /// two tab candidates share three docks, so a dock holding both always
- /// leaves another empty — the row is offered, and the split lands there.
- #[test]
- fn move_to_own_plate_needs_an_empty_dock() {
- use crate::app::Dock;
- use crate::plate_menu::PlateMenuAction;
- use crate::slots::{NETWORK_PANEL2_IDX, SPREADSHEET_IDX};
- let mut state = State::new(false);
- state.resize(1600.0, 900.0, 1.0);
- state.show_spreadsheet = true;
-
- // A dock of its own: no split to offer.
- state.open_plate_menu(SPREADSHEET_IDX);
- assert!(!state.plate_menu_actions.contains(&PlateMenuAction::SplitTab), "its dock is its own");
- state.close_plate_menu();
-
- // The second editor tabs in beside the spreadsheet: the row is
- // offered on the shared dock.
- state.add_dock_tab(Dock::Bottom, NETWORK_PANEL2_IDX);
- state.show_dock_tab(Dock::Bottom, SPREADSHEET_IDX);
- assert_eq!(state.first_empty_dock(), Some(Dock::Left));
- state.open_plate_menu(SPREADSHEET_IDX);
- assert!(state.plate_menu_actions.contains(&PlateMenuAction::SplitTab));
- assert!(state.plate_menu_actions.contains(&PlateMenuAction::ShowTab(NETWORK_PANEL2_IDX)));
- state.close_plate_menu();
-
- // And the split lands there.
- state.split_dock_tab(SPREADSHEET_IDX);
- assert_eq!(state.pane_in_dock(Dock::Left), SPREADSHEET_IDX);
- }
-
- /// The second network editor: joins a dock from nowhere through the tab
- /// machinery, dives on its OWN path while the primary stays put, clamps
- /// a stale path instead of panicking, and Close removes it entirely.
- #[test]
- fn test_second_network_editor_has_its_own_path() {
- use crate::app::Dock;
- use crate::slots::{NETWORK_PANEL2_IDX, NETWORK_PANEL_IDX};
- let mut state = State::new(false);
- assert_eq!(state.tab_dock_of_pane(NETWORK_PANEL2_IDX), None, "starts unplaced");
-
- state.add_dock_tab(Dock::Left, NETWORK_PANEL2_IDX);
- assert_eq!(state.pane_in_dock(Dock::Left), NETWORK_PANEL2_IDX);
- assert_eq!(state.tab_dock_of_pane(NETWORK_PANEL_IDX), None, "the network is in no dock");
-
- let start = state.current_path.clone();
- let sphere = state
- .current_dir()
- .children
- .iter()
- .position(|c| c.name == "sphere1")
- .expect("default project has sphere1");
- state.current_path2 = [start.clone(), vec![sphere]].concat();
- state.sync_nodes();
- assert_eq!(state.current_path, start, "primary path must not follow");
- assert_eq!(state.path_names_at(&state.current_path2), vec!["geometry1".to_string(), "sphere1".to_string()]);
-
- state.current_path2 = vec![99];
- state.sync_nodes();
- assert!(state.current_path2.is_empty(), "a stale path clamps, never indexes");
-
- state.close_dock_tab(NETWORK_PANEL2_IDX);
- assert_eq!(state.tab_dock_of_pane(NETWORK_PANEL2_IDX), None);
- assert_eq!(state.pane_in_dock(Dock::Left), crate::app::NO_PANE);
- }
-
-
- /// Pinning: the spreadsheet bound to pane 1 keeps reading pane 1's
- /// selection while clicks land in the second editor, and the cursor-
- /// selection sync no longer wipes pane 1's selection on second-editor
- /// interactions (the regression that made pins look broken).
- #[test]
- fn test_pane_pins_bind_selection_sources() {
- use crate::app::Dock;
- use crate::slots::{CONTENT2_IDX, CONTENT_IDX, LEFT_MENUBAR_IDX, NETWORK_PANEL2_IDX};
- use cce_ui::widget::GraphController as _;
- let mut state = State::new(false);
- state.add_dock_tab(Dock::Left, NETWORK_PANEL2_IDX);
-
- // Both editors in the bundled project's Geometry node, where the
- // sphere is; a second node there for the other editor to pick.
- state.current_path2 = state.current_path.clone();
- let mut other = state.current_dir().children.iter().find(|c| c.name == "sphere1").unwrap().clone();
- crate::app::regenerate_node_ids(&mut other);
- other.name = "sphere2".into();
- other.position.0 += 2.0;
- state.current_dir_mut().children.push(other);
- state.sync_nodes();
- let sphere = state.current_dir().children.iter().position(|c| c.name == "sphere1").unwrap();
- let camera = state.current_dir().children.iter().position(|c| c.name == "sphere2").unwrap();
-
- // Pane 1 selects the sphere; the spreadsheet pins to pane 1.
- state.graph_mut().set_selected_node(Some(sphere));
- state.spreadsheet_pin = Some(CONTENT_IDX);
- // A click in the second editor selects the camera and takes the
- // active-editor role.
- state.slots.content2.set_selected_node(Some(camera));
- state.param_editor = CONTENT2_IDX;
-
- // The params pane (unpinned) follows the second editor...
- assert_eq!(state.param_editor_selected(), Some(camera));
- // ...the spreadsheet's binding stays on pane 1's sphere.
- let se = state.spreadsheet_editor();
- assert_eq!(se, CONTENT_IDX);
- assert_eq!(state.editor_selected_of(se), Some(sphere));
-
- // The cursor-selection sync must NOT wipe pane 1's selection while
- // the second editor is active — the grid cursor is pane 1's concept.
- state.focused_pane = LEFT_MENUBAR_IDX;
- state.grid_cursor_col = -50;
- state.grid_cursor_row = -50;
- state.sync_cursor_and_selection();
- assert_eq!(
- state.graph().selected_node(),
- Some(sphere),
- "second-editor activity must not clear pane 1's selection"
- );
-
- // And the spreadsheet refresh keys off the pinned selection.
- state.show_spreadsheet = true;
- state.sync_nodes();
- let sphere_id = state.current_dir().children[sphere].id.clone();
- assert_eq!(
- state.last_spreadsheet_node_name.as_deref(),
- Some(sphere_id.as_str()),
- "spreadsheet must refresh against the PINNED editor's selection"
- );
-
- // A params pin binds the pane the other way.
- state.params_pin = Some(CONTENT_IDX);
- assert_eq!(state.param_editor_selected(), Some(sphere));
- }
/// Pane state rides save files: visibility through the meta→View subnet
/// params (synced at save, applied on load), collapse and splitter
@@ -1844,16 +1627,8 @@ 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;
- // Tab state: a second network editor in the left dock (which the
- // network does not hold), dived one level down its own path.
- a.add_dock_tab(crate::app::Dock::Left, crate::slots::NETWORK_PANEL2_IDX);
- let sphere = a
- .current_dir()
- .children
- .iter()
- .position(|c| c.name == "sphere1")
- .expect("default project has sphere1");
- a.current_path2 = [a.current_path.clone(), vec![sphere]].concat();
+ // 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);
@@ -1866,15 +1641,9 @@ 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 tab arrangement rides the file: the second editor exists,
- // fronted in the left dock, on its own path; the network is in none.
- assert_eq!(
- b.pane_in_dock(crate::app::Dock::Left),
- crate::slots::NETWORK_PANEL2_IDX,
- "the fronted second editor must load fronted"
- );
+ // 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.tab_dock_of_pane(crate::slots::NETWORK_PANEL_IDX), None, "the network loads in no dock");
- assert_eq!(b.current_path2, [a.current_path.clone(), vec![sphere]].concat(), "the second editor's path must round-trip");
// A detached pane window must ignore the same file's pane state.
let mut d = State::new(true);
@@ -1898,8 +1667,6 @@ mod tests {
a.resize(1600.0, 900.0, 1.0);
a.execute_menu_action("Show Spreadsheet Pane");
assert!(a.show_spreadsheet);
- // A plate in the left dock to tuck under (the network is in none).
- a.add_dock_tab(crate::app::Dock::Left, crate::slots::NETWORK_PANEL2_IDX);
a.floating_network_layout.2 = 520.0;
a.floating_param_width = 360.0;
a.params_hud_width = 420.0;
@@ -1923,7 +1690,6 @@ mod tests {
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);
- assert!(b.spreadsheet_tucks_left(), "the full-width tuck must load tucked");
// Half the window: the same fractions land at half the pixels.
let mut c = State::new(false);
@@ -2255,7 +2021,6 @@ mod tests {
let mut state = State::new(false);
state.apply_setting("Node Wire Style", "Bezier");
assert_eq!(state.slots.content.inner().wire_style(), WireStyle::Bezier);
- assert_eq!(state.slots.content2.inner().wire_style(), WireStyle::Bezier, "both editors");
assert_eq!(state.display_settings().viewport.node_wire_style, "bezier");
state.save_to_file(&dir).expect("save");
@@ -2374,7 +2139,7 @@ mod tests {
#[test]
fn the_params_hud_is_under_the_plates_and_stops_above_the_bottom_ones() {
use crate::app::{Dock, NO_PANE};
- use crate::slots::{NETWORK_PANEL2_IDX, PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX};
+ use crate::slots::{PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX};
let mut state = State::new(false);
state.resize(1600.0, 900.0, 1.0);
state.rebuild_positions();
@@ -2386,13 +2151,11 @@ mod tests {
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. A plate in the right dock, and the right
- // dock's width, size nothing.
+ // the higher of them. The right dock's width sizes nothing.
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.add_dock_tab(Dock::Right, NETWORK_PANEL2_IDX);
state.floating_param_width = 700.0;
state.rebuild_positions();
state.apply_layout();
@@ -2433,9 +2196,6 @@ 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");
- // A network editor in the right dock has no plate, so it covers
- // nothing of the HUD.
- assert!(!state.plates_over_params().contains(&state.positions[NETWORK_PANEL2_IDX]));
// 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);
@@ -8938,7 +8698,6 @@ mod tests {
state.resize(1600.0, 900.0, 1.0);
state.rebuild_positions();
state.apply_layout();
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
// A group of five points on the sphere, shown.
state.apply_action(McpAction::AddNode { template_name: "Group".into(), name: Some("tagged".into()), x: 7.0, y: 8.0 }, &mut redraw).unwrap();
@@ -9394,7 +9153,6 @@ mod tests {
.unwrap();
}
s.focused_pane = LEFT_MENUBAR_IDX;
- s.param_editor = crate::slots::CONTENT_IDX;
s.grid_cursor_col = 13;
s.grid_cursor_row = 7;
s.sync_cursor_and_selection();
@@ -9438,7 +9196,6 @@ mod tests {
assert_eq!(state.current_dir().children[slot].position, (3.0, 2.0));
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
state.grid_cursor_col = 3;
state.grid_cursor_row = 2;
state.sync_cursor_and_selection();
@@ -9710,7 +9467,7 @@ mod tests {
/// The command end to end, on a real project.
#[test]
fn test_the_layout_command_arranges_the_current_level() {
- use crate::slots::{CONTENT_IDX, LEFT_MENUBAR_IDX};
+ use crate::slots::LEFT_MENUBAR_IDX;
let mut state = State::new(false);
let mut redraw = false;
for (template, x, y) in
@@ -9746,7 +9503,6 @@ mod tests {
set_input(&mut state, subdiv, &names[remesh]);
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = CONTENT_IDX;
assert!(state.layout_current_level());
let pos = |state: &State, slot: usize| state.current_dir().children[slot].position;
@@ -10022,7 +9778,7 @@ mod tests {
/// that one cell alone.
#[test]
fn test_deselect_sticks_until_the_cursor_moves() {
- use crate::slots::{CONTENT_IDX, LEFT_MENUBAR_IDX};
+ use crate::slots::LEFT_MENUBAR_IDX;
let mut state = State::new(false);
let mut redraw = false;
state
@@ -10034,7 +9790,6 @@ mod tests {
let slot = state.current_dir().children.len() - 1;
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = CONTENT_IDX;
state.grid_cursor_col = 6;
state.grid_cursor_row = 6;
state.sync_cursor_and_selection();
@@ -10355,7 +10110,6 @@ mod tests {
]);
let mut proj = Project { name: "p".into(), root, view_state: Default::default(), format: 4 };
proj.view_state.selected_node = Some(4);
- proj.view_state.current_path2 = vec![6, 0];
proj.migrate_format();
assert_eq!(proj.format, PROJECT_FORMAT);
@@ -10376,11 +10130,9 @@ mod tests {
assert!(err.is_none(), "{err:?}");
assert_eq!(text(&resolved, "radius"), "1");
- // The view: the root editor is inside, on xform1; the second editor's
- // path into sub1 goes through the new node.
+ // The view: the editor is inside, on xform1.
assert_eq!(proj.view_state.current_path, vec![3]);
assert_eq!(proj.view_state.selected_node, Some(1));
- assert_eq!(proj.view_state.current_path2, vec![3, 3, 0]);
// Once.
let before = serde_json::to_string(&proj).unwrap();
@@ -10810,7 +10562,6 @@ mod tests {
assert!(wrap_words("", 10).is_empty());
let mut state = State::new(false);
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
let slot_of = |state: &State, name: &str| state.current_dir().children.iter().position(|c| c.name == name).expect(name);
// sphere1 comes from the bundled project, whose file has no
@@ -10870,7 +10621,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = crate::slots::LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
state
.apply_action(McpAction::AddNode { template_name: "Sphere".into(), name: Some("ball".into()), x: 1.0, y: 8.0 }, &mut redraw)
@@ -16745,7 +16495,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = crate::slots::LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
for (name, x, y) in [("a", 1.0, 5.0), ("b", 2.0, 7.0), ("c", 1.0, 11.0)] {
@@ -16827,7 +16576,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = crate::slots::LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
// Two nodes well inside a box drawn from (0, 3) to (3, 9).
let mut redraw = false;
@@ -16968,7 +16716,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = crate::slots::LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
for (name, x, y) in [("a", 1.0, 4.0), ("b", 2.0, 4.0), ("c", 3.0, 4.0)] {
@@ -17597,7 +17344,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = crate::slots::LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
// Clear the bundled level so only these two nodes are framed.
let existing = state.current_dir().children.len();
for slot in (0..existing).rev() {
@@ -18163,7 +17909,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let sphere = state.current_dir().children.iter().position(|c| c.name == "sphere1").expect("sphere1");
state.graph_mut().set_selected_node(Some(sphere));
state.sync_parameters_pane();
@@ -18528,7 +18273,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
state.apply_action(McpAction::AddNode { template_name: "Attribute".into(), name: Some("pull1".into()), x: 5.0, y: 8.0 }, &mut redraw).unwrap();
let pull = state.current_dir().children.iter().position(|c| c.name == "pull1").unwrap();
@@ -18579,7 +18323,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
state.apply_action(McpAction::AddNode { template_name: "Attribute".into(), name: Some("pull1".into()), x: 5.0, y: 8.0 }, &mut redraw).unwrap();
state.apply_action(McpAction::AddNode { template_name: "Group".into(), name: Some("group1".into()), x: 6.0, y: 8.0 }, &mut redraw).unwrap();
@@ -18662,7 +18405,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
state.apply_action(McpAction::AddNode { template_name: "Attribute".into(), name: Some("pull1".into()), x: 5.0, y: 8.0 }, &mut redraw).unwrap();
let pull = state.current_dir().children.iter().position(|c| c.name == "pull1").unwrap();
@@ -18716,7 +18458,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
state.apply_action(McpAction::AddNode { template_name: "Attribute".into(), name: Some("pull1".into()), x: 5.0, y: 8.0 }, &mut redraw).unwrap();
let pull = state.current_dir().children.iter().position(|c| c.name == "pull1").unwrap();
@@ -18801,7 +18542,6 @@ mod tests {
main.rebuild_positions();
main.apply_layout();
main.focused_pane = LEFT_MENUBAR_IDX;
- main.param_editor = crate::slots::CONTENT_IDX;
main.set_active_camera("Default Camera");
let mut redraw = false;
main.apply_action(McpAction::AddNode { template_name: "Attribute".into(), name: Some("pull1".into()), x: 5.0, y: 8.0 }, &mut redraw).unwrap();
@@ -18873,7 +18613,6 @@ mod tests {
state.execute_menu_action("Show Spreadsheet Pane");
state.rebuild_positions();
state.apply_layout();
- state.param_editor = crate::slots::CONTENT_IDX;
let mut redraw = false;
state.apply_action(McpAction::AddNode { template_name: "Box".into(), name: Some("rows_a".into()), x: 6.0, y: 8.0 }, &mut redraw).unwrap();
state.apply_action(McpAction::AddNode { template_name: "Box".into(), name: Some("rows_b".into()), x: 7.0, y: 8.0 }, &mut redraw).unwrap();
@@ -18938,7 +18677,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
state.show_spreadsheet = true;
let mut redraw = false;
state.apply_action(McpAction::AddNode { template_name: "Simnet".into(), name: Some("sim".into()), x: 6.0, y: 8.0 }, &mut redraw).unwrap();
@@ -18995,7 +18733,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
state.show_spreadsheet = true;
let mut redraw = false;
// sphere1 -> sim (a pull inside) -> tagged (a Group reading the sim).
@@ -19061,7 +18798,6 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
state.focused_pane = LEFT_MENUBAR_IDX;
- state.param_editor = crate::slots::CONTENT_IDX;
state.show_spreadsheet = true;
let mut redraw = false;
let slot_of = |state: &State, name: &str| state.current_dir().children.iter().position(|c| c.name == name).expect(name);
diff --git a/src/plate_menu.rs b/src/plate_menu.rs
index 43237d0..8ef947c 100644
--- a/src/plate_menu.rs
+++ b/src/plate_menu.rs
@@ -9,8 +9,7 @@
//! 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, the second network editor, and a collapsed or
-//! detached plate's stub). Moving a pane to another dock is a row too,
+//! 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.
//!
//! [`State::plate_menu_rows`] is the one list; [`State::open_plate_menu_at`]
@@ -19,21 +18,21 @@
use crate::app::{Dock, State};
use crate::slots::{
- NETWORK_PANEL2_IDX, NETWORK_PANEL_IDX, PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX, WIDGET_COUNT,
+ NETWORK_PANEL_IDX, PARAM_IDX, PLAYBAR_IDX, SPREADSHEET_IDX, WIDGET_COUNT,
};
use cce_ui::widget::plate_dock::{self, PlateDockAction, PlateDockState};
/// The plates that carry a plate menu. The viewport is deliberately absent:
/// its "plate" is the window-spanning lip, not a pane.
-pub const PLATE_SLOTS: [usize; 5] =
- [NETWORK_PANEL_IDX, PARAM_IDX, SPREADSHEET_IDX, PLAYBAR_IDX, NETWORK_PANEL2_IDX];
+pub const PLATE_SLOTS: [usize; 4] = [NETWORK_PANEL_IDX, PARAM_IDX, SPREADSHEET_IDX, PLAYBAR_IDX];
/// The panes the tab rows offer — the dockable set. The playbar's strip is
-/// not a dock, and the second network editor joins as the first CLOSABLE
-/// pane: unplaced it simply does not exist. The params pane is not one
-/// (since 2026-10-06): it is a HUD on the scene, under the plates, laid out
-/// from the viewport alone (`State::params_hud_rect`).
-pub const TAB_CANDIDATES: [usize; 2] = [SPREADSHEET_IDX, NETWORK_PANEL2_IDX];
+/// not a dock. The params pane is not one (since 2026-10-06): it is a HUD
+/// on the scene, under the plates, laid out from the viewport alone
+/// (`State::params_hud_rect`); nor is the network (since 2026-10-07), which
+/// spans the window. The second network editor, the one closable pane, was
+/// removed the same day.
+pub const TAB_CANDIDATES: [usize; 1] = [SPREADSHEET_IDX];
/// What the plate menu can do to its plate.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -63,12 +62,6 @@ pub enum PlateMenuAction {
/// Move this pane, and the tabs riding it, to another dock, swapping
/// with what is there — what dragging the plate's corner used to do.
MoveTo(Dock),
- /// Remove a closable pane (the second network editor) from the docks.
- CloseTab,
- /// Bind this pane to whichever editor takes the last node click.
- PinFollow,
- /// Bind this pane to one editor (CONTENT_IDX / CONTENT2_IDX).
- PinTo(usize),
/// A "-" row: engraved, inert — keeps the action list aligned with the
/// option rows so a click on the line dispatches nothing.
Separator,
@@ -81,7 +74,6 @@ pub fn plate_title(idx: usize) -> &'static str {
PARAM_IDX => "Parameters",
SPREADSHEET_IDX => "Spreadsheet",
PLAYBAR_IDX => "Playbar",
- NETWORK_PANEL2_IDX => "Network 2",
_ => "Pane",
}
}
@@ -164,7 +156,14 @@ impl State {
}
};
- if idx == SPREADSHEET_IDX && !self.collapsed_panes[idx] {
+ // 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.
@@ -178,30 +177,6 @@ impl State {
}
}
- // Selection binding — the params pane and spreadsheet can pin to
- // one editor (the viewport's right-click radio, on the plates that
- // follow selection). Only while a second editor exists: with one,
- // following IS pinned.
- if (idx == PARAM_IDX || idx == SPREADSHEET_IDX)
- && self.tab_dock_of_pane(NETWORK_PANEL2_IDX).is_some()
- {
- separate(&mut options, &mut actions);
- let pin = if idx == PARAM_IDX { self.params_pin } else { self.spreadsheet_pin };
- let mark = |on: bool| if on { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF };
- options.push(format!("{}Follow Active Editor", mark(pin.is_none())));
- actions.push(PlateMenuAction::PinFollow);
- options.push(format!(
- "{}Pin: Network",
- mark(pin == Some(crate::slots::CONTENT_IDX))
- ));
- actions.push(PlateMenuAction::PinTo(crate::slots::CONTENT_IDX));
- options.push(format!(
- "{}Pin: Network 2",
- mark(pin == Some(crate::slots::CONTENT2_IDX))
- ));
- actions.push(PlateMenuAction::PinTo(crate::slots::CONTENT2_IDX));
- }
-
// Tabs — only on docked plates (the playbar's strip is not a dock).
// The dock's other tabs switch to the front; panes docked elsewhere
// can be pulled in as tabs; a pane sharing its dock can move back
@@ -247,11 +222,6 @@ impl State {
actions.push(PlateMenuAction::MoveTo(other));
}
}
- if idx == NETWORK_PANEL2_IDX {
- manage_row(&mut options, &mut actions);
- options.push("Close Tab".to_string());
- actions.push(PlateMenuAction::CloseTab);
- }
}
(options, actions)
}
@@ -389,30 +359,10 @@ impl State {
}
PlateMenuAction::SplitTab => self.split_dock_tab(idx),
PlateMenuAction::MoveTo(d) => self.move_pane_to_dock(idx, d),
- PlateMenuAction::CloseTab => self.close_dock_tab(idx),
- PlateMenuAction::PinFollow => self.set_pane_pin(idx, None),
- PlateMenuAction::PinTo(e) => self.set_pane_pin(idx, Some(e)),
PlateMenuAction::Separator => {}
}
}
- /// Apply a plate's selection-binding pick and refresh what it feeds.
- fn set_pane_pin(&mut self, idx: usize, pin: Option<usize>) {
- match idx {
- PARAM_IDX => {
- self.params_pin = pin;
- self.sync_parameters_pane();
- }
- SPREADSHEET_IDX => {
- self.spreadsheet_pin = pin;
- // The spreadsheet refresh lives in sync_nodes, keyed by the
- // bound selection's node id — rebinding changes the key.
- self.sync_nodes();
- }
- _ => {}
- }
- }
-
/// 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.
@@ -541,7 +491,6 @@ pub fn pane_name_from_slot(idx: usize) -> Option<&'static str> {
PARAM_IDX => Some("parameters"),
SPREADSHEET_IDX => Some("spreadsheet"),
PLAYBAR_IDX => Some("playbar"),
- NETWORK_PANEL2_IDX => Some("network2"),
_ => None,
}
}
@@ -553,7 +502,6 @@ pub fn pane_slot_from_name(name: &str) -> Option<usize> {
"parameters" | "params" => Some(PARAM_IDX),
"spreadsheet" => Some(SPREADSHEET_IDX),
"playbar" => Some(PLAYBAR_IDX),
- "network2" => Some(NETWORK_PANEL2_IDX),
_ => None,
}
}
@@ -633,12 +581,6 @@ impl State {
self.slots.get_dyn_mut(child).set_visible(false);
}
}
- if idx == NETWORK_PANEL2_IDX {
- for child in [crate::slots::CONTENT2_IDX, crate::slots::BREADCRUMB2_IDX] {
- self.positions[child] = (0.0, 0.0, 0.0, 0.0);
- self.slots.get_dyn_mut(child).set_visible(false);
- }
- }
}
}
diff --git a/src/project.rs b/src/project.rs
index 32c56af..086baf7 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -163,10 +163,6 @@ impl State {
collapsed_panes,
splitters,
dock_tabs,
- current_path2: self.current_path2.clone(),
- viewport_pin: Self::pin_name(self.viewport_pin),
- params_pin: Self::pin_name(self.params_pin),
- spreadsheet_pin: Self::pin_name(self.spreadsheet_pin),
plates,
frame_range: {
let pb = self.slots.playbar.inner();
@@ -207,27 +203,6 @@ impl State {
vp.reset_velocity();
}
- /// A pin as its saved pane name.
- fn pin_name(pin: Option<usize>) -> Option<String> {
- match pin {
- Some(crate::slots::CONTENT2_IDX) => Some("network2".to_string()),
- Some(_) => Some("network".to_string()),
- None => None,
- }
- }
-
- /// The inverse: a saved pin name, honored only when its editor exists.
- fn pin_from_name(&self, name: Option<&str>) -> Option<usize> {
- match name {
- Some("network") => Some(crate::slots::CONTENT_IDX),
- Some("network2")
- if self.tab_dock_of_pane(crate::slots::NETWORK_PANEL2_IDX).is_some() =>
- {
- Some(crate::slots::CONTENT2_IDX)
- }
- _ => None,
- }
- }
/// The view state a DETACHED window writes into the sync channel: the
/// file's own (the main window's layout, display settings and camera,
@@ -354,10 +329,7 @@ impl State {
self.set_pane_collapsed(idx, desired);
}
// Dock tab groups: accepted only whole — three lists whose names
- // resolve, cover each CORE docked pane exactly once, and carry the
- // second network editor at most once (its presence in a list is what
- // recreates it; absent, it stays closed — including replacing a live
- // one, since the file's arrangement is the arrangement). Anything
+ // resolve and cover each CORE docked pane exactly once. Anything
// else (older saves' empty list included) keeps the current layout
// rather than loading half of one.
if vs.dock_tabs.len() == 3 {
@@ -371,39 +343,22 @@ impl State {
.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();
- let n2 = crate::slots::NETWORK_PANEL2_IDX;
- let n2_count = all.iter().filter(|&&s| s == n2).count();
// 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 fronts its next tab, or is
// empty.
let undocked = |s: usize| s == crate::slots::PARAM_IDX || s == crate::slots::NETWORK_PANEL_IDX;
- // A dock the network FRONTED showed the network alone: what
- // waited behind it was hidden. The second editor waiting there
- // is closed, not brought to the front where the network's plate
- // was — it would appear as a second graph in the old dock's
- // place, which is what a save of 2026-10-06 did on 2026-10-07.
- // (The spreadsheet cannot be closed, and fronts.)
- let resolved: Vec<Vec<usize>> = resolved
- .into_iter()
- .map(|tabs| {
- if tabs.first() == Some(&crate::slots::NETWORK_PANEL_IDX) {
- tabs.into_iter().filter(|&s| s != n2).collect()
- } else {
- tabs
- }
- })
- .collect();
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 core: Vec<usize> = all.iter().copied().filter(|&s| s != n2).collect();
let expected = vec![crate::slots::SPREADSHEET_IDX];
- if core == expected && n2_count <= 1 {
+ if all == expected {
for d in 0..3 {
self.dock_tabs[d] = resolved[d].clone();
self.dock_panes[d] =
@@ -413,15 +368,6 @@ impl State {
self.apply_layout();
}
}
- // The second editor's own path, clamped against the loaded tree —
- // a stale save must degrade to the deepest valid ancestor.
- self.current_path2 = vs.current_path2.clone();
- self.clamp_path2();
- // Pins: "network2" only holds if the loaded arrangement actually
- // carries the second editor.
- self.viewport_pin = self.pin_from_name(vs.viewport_pin.as_deref());
- self.params_pin = self.pin_from_name(vs.params_pin.as_deref());
- self.spreadsheet_pin = self.pin_from_name(vs.spreadsheet_pin.as_deref());
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;
diff --git a/src/render.rs b/src/render.rs
index 16f8b63..b84f942 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -138,9 +138,6 @@ impl State {
CONTENT_IDX | BREADCRUMB_IDX if !self.circular_network_pane => {
self.positions[NETWORK_PANEL_IDX]
}
- crate::slots::CONTENT2_IDX | crate::slots::BREADCRUMB2_IDX => {
- self.positions[crate::slots::NETWORK_PANEL2_IDX]
- }
// The plate as drawn — fitted to the rows, the circle, or on
// its way between them — so the rows are revealed as it grows.
PARAM_IDX if self.params_plate => match self.params_plate_drawn() {
@@ -210,9 +207,9 @@ impl State {
-7
} else if i == PARAM_IDX {
-6
- } else if i == NETWORK_PANEL_IDX || i == crate::slots::NETWORK_PANEL2_IDX {
+ } else if i == NETWORK_PANEL_IDX {
-5
- } else if i == CONTENT_IDX || i == crate::slots::CONTENT2_IDX {
+ } else if i == CONTENT_IDX {
-4
} else if i == HEADER_IDX
|| i == LEFT_MENUBAR_IDX
@@ -459,7 +456,7 @@ impl State {
append_widget_plate_radii(w, pc, None, self.pane_plate_radii(wx, wy, ww2, wh2));
w.paint_self(&self.ui_context, pc);
}
- } else if idx == BREADCRUMB_IDX || idx == crate::slots::BREADCRUMB2_IDX {
+ } else if idx == BREADCRUMB_IDX {
// Modern-paint control: Breadcrumb's whole look lives in its
// Paint::paint() (the cce-ui restyle — per-segment plates on the
// dropdown's relief, slanted seams) and it serves NO legacy views,
@@ -499,18 +496,7 @@ impl State {
for (cx, cy, cr, cc) in w.extra_circles() {
pc.circle(cx, cy, cr, cc);
}
- } else if idx == CONTENT_IDX || idx == crate::slots::CONTENT2_IDX {
- let second = idx == crate::slots::CONTENT2_IDX;
- // The passed-in `clip` is PANE 1's content rect (computed once,
- // before the walk) — zero whenever pane 1 waits as a tab. The
- // second editor clips to its OWN rect or its whole graph
- // vanishes with pane 1's.
- let clip = if second {
- let (cx2, cy2, cw2, ch2) = self.positions[crate::slots::CONTENT2_IDX];
- rect(cx2, cy2, cw2, ch2)
- } else {
- clip
- };
+ } else if idx == CONTENT_IDX {
// No plate here, and none behind: the network has no plate
// (since 2026-10-06), its nodes standing on the scene. Until 2026-09-20 this arm ALSO painted
// the graph widget's own background over it — the cell colour
@@ -549,16 +535,14 @@ impl State {
// Grid cells arrive tagged with their surviving corners and draw
// as superellipse tiles, like the desktop grid; everything else
- // stays a flat quad. `g` is THIS pane's graph — the second
- // editor paints its own widget's geometry through the same body.
- let g: &dyn cce_ui::widget::GraphController =
- if second { &*self.slots.content2 } else { self.graph() };
+ // stays a flat quad.
+ let g: &dyn cce_ui::widget::GraphController = self.graph();
let cell_r = g.cell_corner_radius();
// Which of this pane's nodes are bypassed, by slot: the
// widget knows a node's rect and its slot, the tree knows
// the flag.
let bypassed: Vec<bool> = {
- let level = if second { self.dir_at(&self.current_path2.clone()) } else { self.current_dir() };
+ let level = self.current_dir();
level.children.iter().map(crate::geometry::is_bypassed).collect()
};
let mut bodies: Vec<(f32, f32, f32, f32, bool, bool)> = Vec::new();
@@ -579,10 +563,8 @@ impl State {
// so the rest are recognised here, by the cell the
// body is centred on: the same `grid_cursor_covers`
// the selection itself is derived from, rather than a
- // second rect test that could disagree with it. Pane
- // 1 only, the grid cursor being pane 1's concept.
- let in_region = !second
- && self.grid_cursor_expanded()
+ // second rect test that could disagree with it.
+ let in_region = self.grid_cursor_expanded()
&& {
let (col, row) = self.cell_at(qx + qw * 0.5, qy + qh * 0.5);
self.grid_cursor_covers(col, row)
@@ -605,7 +587,7 @@ impl State {
// grid snap the glow reads as a soft aura around the dragged
// body.
if let Some(gl) = self.drop_glow {
- if !second {
+ {
pc.glow(
rect(gl.x, gl.y, gl.w, gl.h),
cell_r,
@@ -651,7 +633,7 @@ impl State {
}
}
- if show_cursor && !second {
+ if show_cursor {
// A node-sized outline centred on the cursor's intersection —
// exactly where a node placed there would sit — or, after a
// drag across the grid, the union of the region it expanded
@@ -819,9 +801,7 @@ impl State {
}
} else {
let (wx, wy, ww2, wh2) = w.rect();
- let network_panel =
- idx == NETWORK_PANEL_IDX || idx == crate::slots::NETWORK_PANEL2_IDX;
- if !network_panel {
+ if idx != NETWORK_PANEL_IDX {
append_widget_plate_radii(w, pc, self.plate_focus_tint(idx), self.pane_plate_radii(wx, wy, ww2, wh2));
}
@@ -898,10 +878,6 @@ impl State {
}
let focused = match idx {
NETWORK_PANEL_IDX => self.focused_pane == LEFT_MENUBAR_IDX && !self.circular_network_pane,
- // The second network editor shares the network focus domain —
- // whichever of the two is FRONTED wears the ring when it holds
- // (a tab waiting behind the other has a zero rect, so no plate).
- crate::slots::NETWORK_PANEL2_IDX => self.focused_pane == LEFT_MENUBAR_IDX,
VIEWPORT_IDX => self.focused_pane == RIGHT_MENUBAR_IDX,
PARAM_IDX => self.focused_pane == PARAM_MENUBAR_IDX,
SPREADSHEET_IDX => self.focused_pane == SPREADSHEET_MENUBAR_IDX,
@@ -1106,7 +1082,7 @@ impl State {
// Node text belongs to the node domain: it fades with
// node_opacity, not the pane's network_opacity.
// A name on a light floor is written dark (node_ink).
- let color = if is_node || i == crate::slots::CONTENT2_IDX {
+ let color = if is_node {
self.node_ink().unwrap_or(color)
} else {
color
@@ -1431,10 +1407,8 @@ impl State {
let mut sim_cache = std::mem::take(&mut self.sim_cache);
let geom = {
let mut sim = crate::geometry::EvalSim::new(frame, start, &mut sim_cache);
- // The viewport shows ITS editor's level — the pinned one when a
- // pin is set, else whichever editor took the last node click —
- // while name resolution stays rooted at fs_root. Navigation in
- // the bound editor re-scopes this.
+ // The viewport shows the network editor's level, while name
+ // resolution stays rooted at fs_root. Navigation re-scopes this.
network_sphere_vertices_with_errors(&self.fs_root, self.viewport_editor_dir(), &mut ocl_error, &mut sim)
};
self.sim_cache = sim_cache;
diff --git a/src/slots.rs b/src/slots.rs
index 6cdf8dc..98d1144 100644
--- a/src/slots.rs
+++ b/src/slots.rs
@@ -106,18 +106,11 @@ widget_roster! {
SPREADSHEET_MENUBAR_IDX: spreadsheet_menubar: MenuBar,
NETWORK_PANEL_IDX: network_panel: PassivePlate,
PLAYBAR_IDX: playbar: Playbar,
- // The second network editor (plate + graph + breadcrumb): an independent
- // VIEW of the same project with its own current path, created and placed
- // through the plate corner menus' tab rows. Appended so the established
- // slot indexes stay stable.
- NETWORK_PANEL2_IDX: network_panel2: PassivePlate,
- CONTENT2_IDX: content2: Graph,
- BREADCRUMB2_IDX: breadcrumb2: Breadcrumb,
+ // (The second network editor's three slots — plate, graph, breadcrumb —
+ // stood here until 2026-10-07, when the editor was removed.)
// The Alt+D dialog: plate, query line and the one list of commands and
- // settings, its controls painted from the toolkit's own stamps. Appended,
- // like every slot since the second network editor, so established
- // indexes stay stable. (A second ParametersBg slot for a Settings half
- // sat after it until 2026-09-24.)
+ // settings, its controls painted from the toolkit's own stamps. (A second
+ // ParametersBg slot for a Settings half sat after it until 2026-09-24.)
DIALOG_IDX: dialog: crate::dialog::Dialog,
}
diff --git a/src/window.rs b/src/window.rs
index 6462feb..4b2351b 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -71,22 +71,6 @@ impl State {
}
}
- // The second network editor's breadcrumb navigates ITS path.
- {
- use cce_ui::widget::PathController as _;
- if let Some(seg) = state.slots.breadcrumb2.path_click() {
- if seg < state.current_path2.len() {
- state.current_path2.truncate(seg);
- state.sync_nodes();
- // The viewport tracks its editor's level.
- if state.viewport_editor() == crate::slots::CONTENT2_IDX {
- state.rebuild_scene_geometry();
- }
- changed = true;
- }
- }
- }
-
if let Some(id) = state.pending_command.take() {
state.run_command(id);
changed = true;