graphic design tool
git clone https://git.lucas.co/cce-designer.git
refactor: widget slots are handles the context owns
The slot table's fields are Handle<Adapted<..>> inserted into the UiContext; accessors take the context, and calls that need a widget and the context together (the breadcrumb, the params pane, the dialog's drag events, the slot dispatch and tick loops) lend it. The per-frame registration loops are gone. Shadow A/B against the pointer-registry build: identical in the default, panes, network-pane and circular variants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
log | 2 +
src/app.rs | 480 +++++++++++++++++++++++++++---------------------------
src/dialog.rs | 203 +++++++++++------------
src/main.rs | 379 +++++++++++++++++++++---------------------
src/menu_page.rs | 2 +-
src/plate_menu.rs | 12 +-
src/project.rs | 4 +-
src/render.rs | 54 +++---
src/slots.rs | 94 ++++++-----
src/window.rs | 24 +--
10 files changed, 622 insertions(+), 632 deletions(-)
diff --git a/log b/log
new file mode 100644
index 0000000..e91e842
--- /dev/null
+++ b/log
@@ -0,0 +1,2 @@
+MCP server 'cce-designer' listening on http://127.0.0.1:3011
+Io error: Broken pipe (os error 32)
diff --git a/src/app.rs b/src/app.rs
index 240eccb..1a9e9a8 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -3539,12 +3539,12 @@ pub struct State {
}
impl State {
- pub fn viewport(&self) -> &Viewport3D { self.slots.viewport() }
+ pub fn viewport(&self) -> &Viewport3D { self.slots.viewport(&self.ui_context) }
- pub fn viewport_mut(&mut self) -> &mut Viewport3D { self.slots.viewport_mut() }
+ pub fn viewport_mut(&mut self) -> &mut Viewport3D { self.slots.viewport_mut(&mut self.ui_context) }
pub fn find_widget_index(&self, target_addr: *const ()) -> Option<usize> {
- self.slots.find_index(target_addr)
+ self.slots.find_index(&self.ui_context, target_addr)
}
pub fn has_any_open_menu(&self, idx: usize) -> bool {
@@ -3567,7 +3567,7 @@ impl State {
return true;
}
}
- let child_ptrs = self.ui_context.tree.children_ptrs(self.slots.get_dyn(idx).base().id());
+ let child_ptrs = self.ui_context.tree.children_ptrs(self.slots.get_dyn(&self.ui_context, idx).base().id());
for child_ptr in child_ptrs {
if let Some(child_idx) = self.find_widget_index(child_ptr as *const ()) {
if self.has_any_open_menu_impl(child_idx, visited) {
@@ -3580,23 +3580,23 @@ impl State {
// Typed roster accessors: the concrete-type asserts live on `WidgetSlots` (src/slots.rs);
// these forward so the ~40 call sites keep reading `self.menu(..)` / `self.graph_mut()`.
- pub fn menubar_at(&self, idx: usize) -> Option<&MenuBar> { self.slots.menubar_at(idx) }
+ pub fn menubar_at(&self, idx: usize) -> Option<&MenuBar> { self.slots.menubar_at(&self.ui_context, idx) }
- pub fn menu(&self, idx: usize) -> &dyn cce_ui::widget::MenuController { self.slots.menu(idx) }
+ pub fn menu(&self, idx: usize) -> &dyn cce_ui::widget::MenuController { self.slots.menu(&self.ui_context, idx) }
- pub fn menu_mut(&mut self, idx: usize) -> &mut dyn cce_ui::widget::MenuController { self.slots.menu_mut(idx) }
+ pub fn menu_mut(&mut self, idx: usize) -> &mut dyn cce_ui::widget::MenuController { self.slots.menu_mut(&mut self.ui_context, idx) }
- pub fn graph(&self) -> &dyn cce_ui::widget::GraphController { self.slots.graph() }
+ pub fn graph(&self) -> &dyn cce_ui::widget::GraphController { self.slots.graph(&self.ui_context) }
- pub fn graph_mut(&mut self) -> &mut dyn cce_ui::widget::GraphController { self.slots.graph_mut() }
+ pub fn graph_mut(&mut self) -> &mut dyn cce_ui::widget::GraphController { self.slots.graph_mut(&mut self.ui_context) }
- pub fn param(&self) -> &dyn cce_ui::widget::ParamController { self.slots.param() }
+ pub fn param(&self) -> &dyn cce_ui::widget::ParamController { self.slots.param(&self.ui_context) }
- pub fn param_mut(&mut self) -> &mut dyn cce_ui::widget::ParamController { self.slots.param_mut() }
+ pub fn param_mut(&mut self) -> &mut dyn cce_ui::widget::ParamController { self.slots.param_mut(&mut self.ui_context) }
- pub fn spreadsheet_mut(&mut self) -> &mut dyn cce_ui::widget::SpreadsheetController { self.slots.spreadsheet_mut() }
+ pub fn spreadsheet_mut(&mut self) -> &mut dyn cce_ui::widget::SpreadsheetController { self.slots.spreadsheet_mut(&mut self.ui_context) }
- pub fn path_mut(&mut self) -> &mut dyn cce_ui::widget::PathController { self.slots.path_mut() }
+ pub fn path_mut(&mut self) -> &mut dyn cce_ui::widget::PathController { self.slots.path_mut(&mut self.ui_context) }
pub fn has_unsaved_changes(&self) -> bool {
let tree_changed = match serde_json::to_string(&self.fs_root) {
@@ -3663,7 +3663,7 @@ impl State {
grid_thickness: self.grid_thickness,
grid_color: self.viewport().grid_color,
params_plate: self.params_plate,
- node_wire_style: self.slots.content.inner().chosen_wire_style().map(|w| w.name().to_string()).unwrap_or_default(),
+ node_wire_style: self.ui_context[self.slots.content].inner().chosen_wire_style().map(|w| w.name().to_string()).unwrap_or_default(),
show_point_markers: self.show_point_markers,
show_point_numbers: self.show_point_numbers,
show_point_normals: self.show_point_normals,
@@ -3702,9 +3702,9 @@ impl State {
render: display.render,
default_project: self.default_project_setting.clone(),
gpu: self.gpu_preference.clone(),
- playbar_repeat: self.slots.playbar.inner().repeat,
- playbar_fps: self.slots.playbar.inner().fps,
- playbar_step_buttons: self.slots.playbar.inner().step_buttons,
+ playbar_repeat: self.ui_context[self.slots.playbar].inner().repeat,
+ playbar_fps: self.ui_context[self.slots.playbar].inner().fps,
+ playbar_step_buttons: self.ui_context[self.slots.playbar].inner().step_buttons,
};
settings.save();
self.last_design_mod_time = {
@@ -3840,12 +3840,12 @@ impl State {
/// The timeline frame the graph is evaluated at — what a simnet solves up to.
pub fn sim_frame(&self) -> i32 {
- self.slots.playbar.inner().current_frame.round() as i32
+ self.ui_context[self.slots.playbar].inner().current_frame.round() as i32
}
/// The timeline's first frame: where every sim sits at its seed.
pub fn sim_start_frame(&self) -> i32 {
- self.slots.playbar.inner().start_frame.round() as i32
+ self.ui_context[self.slots.playbar].inner().start_frame.round() as i32
}
/// Hand the playbar what the simulations hold ([`playbar_cache_runs`]),
@@ -3856,7 +3856,7 @@ impl State {
/// screen reads. Returns whether the strip changed.
pub fn sync_playbar_cache(&mut self) -> bool {
let (start, end) = {
- let pb = self.slots.playbar.inner();
+ let pb = self.ui_context[self.slots.playbar].inner();
(pb.start_frame.round() as i32, pb.end_frame.round() as i32)
};
let key = (self.sim_cache.revision(), self.rt_geometry_version, start, end);
@@ -3878,7 +3878,7 @@ impl State {
walk(&self.fs_root, &solved, &mut chains);
}
let runs = playbar_cache_runs(&solved, &chains, start, end);
- let pb = self.slots.playbar.inner_mut();
+ let pb = self.ui_context[self.slots.playbar].inner_mut();
if pb.cache == runs {
return false;
}
@@ -4014,16 +4014,16 @@ impl State {
} else if i == BREADCRUMB_IDX {
self.circular_network_layout.hit_test_breadcrumb(cx, cy, 0.0, breadcrumb_h())
} else if i == NETWORK_PANEL_IDX {
- self.slots.network_panel.hit_test(cx, cy, &self.ui_context)
+ self.ui_context[self.slots.network_panel].hit_test(cx, cy, &self.ui_context)
} else {
false
}
} else {
- self.slots.get_dyn_mut(i).hit_test(cx, cy, &self.ui_context)
+ self.slots.get_dyn(&self.ui_context, i).hit_test(cx, cy, &self.ui_context)
};
let (tx, ty) = if inside { (cx, cy) } else { (-9999.0, -9999.0) };
let mv = cce_ui::widget::Event::PointerMove { x: tx, y: ty, local_x: tx, local_y: ty };
- let ptr = self.slots.get_dyn_mut(i) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, i) as *mut (dyn WidgetHost + 'static);
if unsafe { (*ptr).handle_event(&mv, &mut self.ui_context) } {
changed = true;
}
@@ -4219,7 +4219,7 @@ impl State {
/// 1). `None` with the plate off or the HUD not shown.
pub fn params_plate_target(&self) -> Option<[f32; 5]> {
let (_, _, w, h) = self.positions[PARAM_IDX];
- if !self.params_plate || w <= 0.0 || h <= 0.0 || self.pane_is_stubbed(PARAM_IDX) || !self.slots.param.visible() {
+ if !self.params_plate || w <= 0.0 || h <= 0.0 || self.pane_is_stubbed(PARAM_IDX) || !self.ui_context[self.slots.param].visible() {
return None;
}
let (cx, cy, cw, ch) = self.params_claim();
@@ -4264,12 +4264,12 @@ impl State {
/// no plate covers it, or on a dropdown it has open, which grows past
/// its rows and is drawn over everything.
pub fn params_claims(&self, px: f32, py: f32) -> bool {
- if !self.slots.param.visible() {
+ if !self.ui_context[self.slots.param].visible() {
return false;
}
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;
(inside(self.params_claim()) && !self.plate_over_params_at(px, py))
- || self.slots.param.popover_rect().is_some_and(inside)
+ || self.ui_context[self.slots.param].popover_rect().is_some_and(inside)
}
/// Whether (px, py) is under a plate drawn over the scene: a floating
@@ -4564,7 +4564,7 @@ impl State {
let mut bottom = vy + vh - gap;
for idx in [SPREADSHEET_IDX, PLAYBAR_IDX] {
let (px, py, pw, ph) = self.positions[idx];
- let below = pw > 0.0 && ph > 0.0 && self.slots.get_dyn(idx).visible() && px < x + w && px + pw > x && py > top + PARAMS_DOT_D;
+ let below = pw > 0.0 && ph > 0.0 && self.slots.get_dyn(&self.ui_context, idx).visible() && px < x + w && px + pw > x && py > top + PARAMS_DOT_D;
if below {
bottom = bottom.min(py - gap);
}
@@ -4580,7 +4580,7 @@ impl State {
let mut out = Vec::new();
let mut take = |idx: usize| {
let r = self.positions[idx];
- if r.2 > 0.0 && r.3 > 0.0 && self.slots.get_dyn(idx).visible() {
+ if r.2 > 0.0 && r.3 > 0.0 && self.slots.get_dyn(&self.ui_context, idx).visible() {
out.push(r);
}
};
@@ -4623,7 +4623,7 @@ impl State {
let ss_y_end = self.height - STATUS_H - pb_off - gap;
let ss_h = self.floating_spreadsheet_height.clamp(100.0, (ss_y_end - gap).max(100.0));
let room = (self.width - 2.0 * gap).max(Self::SPREADSHEET_MIN_W);
- let w = self.slots.spreadsheet().content_width().ceil().clamp(Self::SPREADSHEET_MIN_W, room);
+ let w = self.slots.spreadsheet(&self.ui_context).content_width().ceil().clamp(Self::SPREADSHEET_MIN_W, room);
(gap, ss_y_end - ss_h, w, ss_h)
}
@@ -5186,9 +5186,9 @@ impl State {
}
"Show Network Pane" => {
self.show_network = !self.show_network;
- self.slots.content.set_visible(self.show_network);
- self.slots.left_menubar.set_visible(self.show_network);
- self.slots.breadcrumb.set_visible(self.show_network);
+ self.ui_context[self.slots.content].set_visible(self.show_network);
+ self.ui_context[self.slots.left_menubar].set_visible(self.show_network);
+ self.ui_context[self.slots.breadcrumb].set_visible(self.show_network);
let val = self.show_network;
self.menu_mut(HEADER_IDX).set_item_checked(2, 4, val);
if !self.show_network && self.focused_pane == LEFT_MENUBAR_IDX {
@@ -5207,8 +5207,8 @@ impl State {
}
"Show Viewport Pane" => {
self.show_viewport = !self.show_viewport;
- self.slots.viewport.set_visible(self.show_viewport);
- self.slots.right_menubar.set_visible(self.show_viewport);
+ self.ui_context[self.slots.viewport].set_visible(self.show_viewport);
+ self.ui_context[self.slots.right_menubar].set_visible(self.show_viewport);
let val = self.show_viewport;
self.menu_mut(HEADER_IDX).set_item_checked(2, 5, val);
if !self.show_viewport && self.focused_pane == RIGHT_MENUBAR_IDX {
@@ -5227,8 +5227,8 @@ impl State {
}
"Show Parameters Pane" => {
self.show_parameters = !self.show_parameters;
- self.slots.param.set_visible(self.show_parameters);
- self.slots.param_menubar.set_visible(self.show_parameters);
+ self.ui_context[self.slots.param].set_visible(self.show_parameters);
+ self.ui_context[self.slots.param_menubar].set_visible(self.show_parameters);
let val = self.show_parameters;
self.menu_mut(HEADER_IDX).set_item_checked(2, 6, val);
if !self.show_parameters && self.focused_pane == PARAM_MENUBAR_IDX {
@@ -5247,8 +5247,8 @@ impl State {
}
"Show Spreadsheet Pane" => {
self.show_spreadsheet = !self.show_spreadsheet;
- self.slots.spreadsheet.set_visible(self.show_spreadsheet);
- self.slots.spreadsheet_menubar.set_visible(self.show_spreadsheet);
+ self.ui_context[self.slots.spreadsheet].set_visible(self.show_spreadsheet);
+ self.ui_context[self.slots.spreadsheet_menubar].set_visible(self.show_spreadsheet);
let val = self.show_spreadsheet;
self.menu_mut(HEADER_IDX).set_item_checked(2, 7, val);
if !self.show_spreadsheet && self.focused_pane == SPREADSHEET_MENUBAR_IDX {
@@ -5267,7 +5267,7 @@ impl State {
}
"Show Playbar Pane" => {
self.show_playbar = !self.show_playbar;
- self.slots.playbar.set_visible(self.show_playbar);
+ self.ui_context[self.slots.playbar].set_visible(self.show_playbar);
let val = self.show_playbar;
self.menu_mut(HEADER_IDX).set_item_checked(2, 8, val);
// Not in the focus-cycle roster (get_next_visible_pane): the
@@ -5287,9 +5287,9 @@ impl State {
match parent_name {
"Network" => {
self.show_network = false;
- self.slots.content.set_visible(false);
- self.slots.left_menubar.set_visible(false);
- self.slots.breadcrumb.set_visible(false);
+ self.ui_context[self.slots.content].set_visible(false);
+ self.ui_context[self.slots.left_menubar].set_visible(false);
+ self.ui_context[self.slots.breadcrumb].set_visible(false);
self.menu_mut(HEADER_IDX).set_item_checked(2, 4, false);
if self.focused_pane == LEFT_MENUBAR_IDX {
self.focused_pane = get_next_visible_pane(
@@ -5307,8 +5307,8 @@ impl State {
}
"Viewport" => {
self.show_viewport = false;
- self.slots.viewport.set_visible(false);
- self.slots.right_menubar.set_visible(false);
+ self.ui_context[self.slots.viewport].set_visible(false);
+ self.ui_context[self.slots.right_menubar].set_visible(false);
self.menu_mut(HEADER_IDX).set_item_checked(2, 5, false);
if self.focused_pane == RIGHT_MENUBAR_IDX {
self.focused_pane = get_next_visible_pane(
@@ -5326,8 +5326,8 @@ impl State {
}
"Parameters" => {
self.show_parameters = false;
- self.slots.param.set_visible(false);
- self.slots.param_menubar.set_visible(false);
+ self.ui_context[self.slots.param].set_visible(false);
+ self.ui_context[self.slots.param_menubar].set_visible(false);
self.menu_mut(HEADER_IDX).set_item_checked(2, 6, false);
if self.focused_pane == PARAM_MENUBAR_IDX {
self.focused_pane = get_next_visible_pane(
@@ -5345,8 +5345,8 @@ impl State {
}
"Spreadsheet" => {
self.show_spreadsheet = false;
- self.slots.spreadsheet.set_visible(false);
- self.slots.spreadsheet_menubar.set_visible(false);
+ self.ui_context[self.slots.spreadsheet].set_visible(false);
+ self.ui_context[self.slots.spreadsheet_menubar].set_visible(false);
self.menu_mut(HEADER_IDX).set_item_checked(2, 7, false);
if self.focused_pane == SPREADSHEET_MENUBAR_IDX {
self.focused_pane = get_next_visible_pane(
@@ -5898,7 +5898,7 @@ impl State {
if options.is_empty() {
return;
}
- let target = self.slots.get_dyn(CONTENT_IDX).base().id();
+ let target = self.slots.get_dyn(&self.ui_context, CONTENT_IDX).base().id();
self.put_up_menu(at, None, options, 0, target);
crate::menu_page::mark_page_rows(&actions, |a| a == NodeMenuAction::Rename);
self.node_menu_slot = Some(slot);
@@ -6156,7 +6156,7 @@ impl State {
/// node it shows — the one geometry the row menu's hit test and the
/// expression tint both read.
pub fn param_row_rects(&self) -> Vec<(f32, f32, f32, f32)> {
- self.slots.param.as_any().downcast_ref::<ParametersBg>().map(|pb| pb.get_param_rects()).unwrap_or_default()
+ self.ui_context[self.slots.param].as_any().downcast_ref::<ParametersBg>().map(|pb| pb.get_param_rects()).unwrap_or_default()
}
/// What the template says `pname` on `node` defaults to, where `dir` is
@@ -6310,7 +6310,7 @@ impl State {
fn open_param_context_menu(&mut self, slot: usize, pname: String) {
let node_id = self.param_editor_dir().children[slot].id.clone();
let (options, actions, headers) = self.param_menu_rows(slot, &pname);
- let target = self.slots.get_dyn(crate::slots::PARAM_IDX).base().id();
+ let target = self.slots.get_dyn(&self.ui_context, crate::slots::PARAM_IDX).base().id();
cce_ui::widget::context_menu::show(self.cursor_x, self.cursor_y, options, headers, target);
self.param_menu_active = true;
self.param_menu_actions = actions;
@@ -6391,7 +6391,7 @@ impl State {
/// The slider a playbar menu row carries, from the live value.
pub(crate) fn playbar_menu_slider(&self, action: PlaybarMenuAction) -> Option<cce_ui::widget::context_menu::MenuSlider> {
use cce_ui::widget::context_menu::MenuSlider;
- let pb = self.slots.playbar.inner();
+ let pb = self.ui_context[self.slots.playbar].inner();
Some(match action {
PlaybarMenuAction::FpsSlider => MenuSlider { value: pb.fps.clamp(1.0, 120.0).round(), min: 1.0, max: 120.0, step: 1.0, decimals: 0, suffix: " fps" },
PlaybarMenuAction::StartFrameSlider => MenuSlider { value: pb.start_frame.round().clamp(1.0, 999.0), min: 1.0, max: 999.0, step: 1.0, decimals: 0, suffix: "" },
@@ -6405,7 +6405,7 @@ impl State {
/// is a setting and saved with them; the range is the project's and
/// dirties it.
fn land_playbar_menu_slider(&mut self, action: PlaybarMenuAction, v: f32) {
- let pb = self.slots.playbar.inner_mut();
+ let pb = self.ui_context[self.slots.playbar].inner_mut();
match action {
PlaybarMenuAction::FpsSlider => pb.fps = v.round().clamp(1.0, 120.0),
PlaybarMenuAction::StartFrameSlider => {
@@ -6430,7 +6430,7 @@ impl State {
/// The playbar's menu at the pointer, or with its top-left at `at`.
pub(crate) fn open_playbar_context_menu_at(&mut self, at: Option<(f32, f32)>) {
let (options, actions) = self.playbar_menu_rows();
- let target = self.slots.get_dyn(PLAYBAR_IDX).base().id();
+ let target = self.slots.get_dyn(&self.ui_context, PLAYBAR_IDX).base().id();
self.put_up_menu(at, None, options, 0, target);
crate::menu_page::mark_page_rows(&actions, |a| a == PlaybarMenuAction::PlatePage);
for (i, a) in actions.iter().enumerate() {
@@ -7122,7 +7122,7 @@ impl State {
if self.focused_pane != LEFT_MENUBAR_IDX {
self.focused_pane = LEFT_MENUBAR_IDX;
if let Some(old) = self.focused_widget.take() {
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
+ self.ui_context.unfocus_id(self.slots.id(old));
}
self.sync_pane_focus();
}
@@ -7162,7 +7162,7 @@ impl State {
// in no dock, so its plate rows were Detach alone, which is the
// `detach_circular_window` command in the palette.
- let target = self.slots.get_dyn(CONTENT_IDX).base().id();
+ let target = self.slots.get_dyn(&self.ui_context, CONTENT_IDX).base().id();
let back = self.network_menu_from.filter(|_| at.is_some());
self.put_up_menu(at, back, options, 0, target);
crate::menu_page::mark_page_rows(&actions, |a| a.leads_to_page());
@@ -8176,49 +8176,51 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
"3: Spreadsheet".to_string(),
"Main Menu".to_string(),
];
- let mut slots = Box::new(WidgetSlots {
- header: cce_ui::widget::Owned::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Designer").with_label("Main Menu Bar").with_item("File", &["New Project", "Save", "Save As", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane", "Show Playbar Pane"]).with_item("Help", &["About"]).with_z_index(110).with_context_options(context_opts.clone(), 4)),
- content: cce_ui::widget::Owned::new(Graph::new()),
- splitter1: cce_ui::widget::Owned::new(Splitter::new(SPLITTER_W)),
- viewport: cce_ui::widget::Owned::new(Viewport3D::new()),
- splitter2: cce_ui::widget::Owned::new(Splitter::new(SPLITTER_W)),
- param: cce_ui::widget::Owned::new(ParametersBg::new()),
- canvas: cce_ui::widget::Owned::new(Canvas::new()),
- left_menubar: cce_ui::widget::Owned::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_context_options(context_opts.clone(), 0)),
- right_menubar: cce_ui::widget::Owned::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_label("Viewport Menu Bar").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["square_aspect"]).with_item("Guides", &["Show Grid", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 1)),
- param_menubar: cce_ui::widget::Owned::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_label("Parameters Menu Bar").with_item("Preset", &["Default"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 2)),
- status: cce_ui::widget::Owned::new(StatusBar::new().with_text("Ready")),
+ // The context owns the widgets; the roster keeps their handles.
+ let mut ui_context = cce_ui::context::UiContext::new();
+ let slots = Box::new(WidgetSlots {
+ header: ui_context.insert(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Designer").with_label("Main Menu Bar").with_item("File", &["New Project", "Save", "Save As", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane", "Show Playbar Pane"]).with_item("Help", &["About"]).with_z_index(110).with_context_options(context_opts.clone(), 4)),
+ content: ui_context.insert(Graph::new()),
+ splitter1: ui_context.insert(Splitter::new(SPLITTER_W)),
+ viewport: ui_context.insert(Viewport3D::new()),
+ splitter2: ui_context.insert(Splitter::new(SPLITTER_W)),
+ param: ui_context.insert(ParametersBg::new()),
+ canvas: ui_context.insert(Canvas::new()),
+ left_menubar: ui_context.insert(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_label("Network Menu Bar").with_item("File", &["New", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_context_options(context_opts.clone(), 0)),
+ right_menubar: ui_context.insert(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_label("Viewport Menu Bar").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["square_aspect"]).with_item("Guides", &["Show Grid", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 1)),
+ param_menubar: ui_context.insert(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_label("Parameters Menu Bar").with_item("Preset", &["Default"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 2)),
+ status: ui_context.insert(StatusBar::new().with_text("Ready")),
breadcrumb: {
let mut bc = Breadcrumb::new();
// Raised, not the default trough: the segments float in front
// of the network plate rather than reading as inset into it.
bc.set_raised(true);
- cce_ui::widget::Owned::new(bc)
+ ui_context.insert(bc)
},
- spreadsheet: cce_ui::widget::Owned::new(Spreadsheet::new()),
+ spreadsheet: ui_context.insert(Spreadsheet::new()),
spreadsheet_menubar: {
let mut mb = MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("3: Spreadsheet").with_label("Spreadsheet Menu Bar").with_item("View", &["Close Pane"]).with_context_options(context_opts.clone(), 3);
mb.set_visible(false);
- cce_ui::widget::Owned::new(mb)
+ ui_context.insert(mb)
},
- network_panel: cce_ui::widget::Owned::new(PassivePlate::new()),
+ network_panel: ui_context.insert(PassivePlate::new()),
playbar: {
let mut pb = Playbar::new();
pb.set_visible(false);
- cce_ui::widget::Owned::new(pb)
+ ui_context.insert(pb)
},
- dialog: cce_ui::widget::Owned::new(crate::dialog::Dialog::new()),
+ dialog: ui_context.insert(crate::dialog::Dialog::new()),
});
- slots.playbar.inner_mut().repeat = settings.playbar_repeat;
- slots.playbar.inner_mut().step_buttons = settings.playbar_step_buttons;
+ ui_context[slots.playbar].inner_mut().repeat = settings.playbar_repeat;
+ ui_context[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);
+ ui_context[slots.content].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.playbar.inner_mut().fps = settings.playbar_fps.clamp(1.0, 120.0);
- if let Some(viewport) = slots.viewport.as_any_mut().downcast_mut::<Viewport3D>() {
+ ui_context[slots.content].inner_mut().set_swap_on_drop(true);
+ ui_context[slots.playbar].inner_mut().fps = settings.playbar_fps.clamp(1.0, 120.0);
+ if let Some(viewport) = ui_context[slots.viewport].as_any_mut().downcast_mut::<Viewport3D>() {
viewport.show_grid = settings.viewport.show_grid_enabled;
viewport.show_origin = settings.viewport.show_origin_enabled;
viewport.show_camera_pivot = settings.viewport.show_camera_pivot_enabled;
@@ -8559,7 +8561,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
rt_geometry_version: 0,
last_rt_scene_key: None,
page_version: 0,
- ui_context: cce_ui::context::UiContext::new(),
+ ui_context,
};
state.update_inertial_settings();
@@ -8597,9 +8599,6 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
state.sync_pane_focus();
state.sync_cursor_and_selection();
state.sync_parameters_pane();
- for i in 0..WIDGET_COUNT {
- state.ui_context.register_host(state.slots.get_dyn_mut(i));
- }
// The layout baseline waits for the layout pass above (it clamps the
// plate fields), and the title computed earlier must be re-read
// against it or a fresh window opens starred.
@@ -8767,14 +8766,14 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
graph.set_node_size(node_w, node_h);
graph.set_grid_origin(active_node_area_x + pan_x, active_node_area_y + pan_y);
graph.set_grid_snap_enabled(grid_snap_enabled);
- if let Some(graph) = self.slots.content.as_any_mut().downcast_mut::<cce_ui::widget::Graph>() {
+ if let Some(graph) = self.ui_context[self.slots.content].as_any_mut().downcast_mut::<cce_ui::widget::Graph>() {
graph.set_network_opacity(self.network_opacity);
graph.set_node_opacity(self.node_opacity);
}
- if let Some(menubar) = self.slots.left_menubar.as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
+ if let Some(menubar) = self.ui_context[self.slots.left_menubar].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
menubar.set_network_opacity(self.network_opacity);
}
- if let Some(breadcrumb) = self.slots.breadcrumb.as_any_mut().downcast_mut::<cce_ui::widget::Breadcrumb>() {
+ if let Some(breadcrumb) = self.ui_context[self.slots.breadcrumb].as_any_mut().downcast_mut::<cce_ui::widget::Breadcrumb>() {
breadcrumb.set_network_opacity(self.network_opacity);
}
@@ -8971,15 +8970,15 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.positions[0] = (0.0, 0.0, 0.0, 0.0);
self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
- if let Some(menubar) = self.slots.left_menubar.as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
+ if let Some(menubar) = self.ui_context[self.slots.left_menubar].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
menubar.set_curved_circle(None);
}
let bc_h = breadcrumb_h();
self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0, 2.0 * r, bc_h);
self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + bc_h, 2.0 * r, 2.0 * r - (45.0 + bc_h));
self.positions[NETWORK_PANEL_IDX] = (cx - r, cy - r, 2.0 * r, 2.0 * r);
- self.slots.network_panel.set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
- if let Some(plate) = self.slots.network_panel.as_any_mut().downcast_mut::<PassivePlate>() {
+ self.ui_context[self.slots.network_panel].set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
+ if let Some(plate) = self.ui_context[self.slots.network_panel].as_any_mut().downcast_mut::<PassivePlate>() {
plate.set_curved_circle(Some((cx, cy, r)));
}
self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
@@ -8994,21 +8993,21 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.positions[STATUS_IDX] = (0.0, 0.0, 0.0, 0.0);
self.positions[PLAYBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
- self.slots.header.set_visible(false);
- self.slots.status.set_visible(false);
- self.slots.playbar.set_visible(false);
- self.slots.content.set_visible(true);
- self.slots.network_panel.set_visible(true);
- self.slots.left_menubar.set_visible(false);
- self.slots.breadcrumb.set_visible(true);
- self.slots.splitter1.set_visible(false);
- self.slots.splitter2.set_visible(false);
- self.slots.viewport.set_visible(false);
- self.slots.right_menubar.set_visible(false);
- self.slots.param.set_visible(false);
- self.slots.param_menubar.set_visible(false);
- self.slots.spreadsheet.set_visible(false);
- self.slots.spreadsheet_menubar.set_visible(false);
+ self.ui_context[self.slots.header].set_visible(false);
+ self.ui_context[self.slots.status].set_visible(false);
+ self.ui_context[self.slots.playbar].set_visible(false);
+ self.ui_context[self.slots.content].set_visible(true);
+ self.ui_context[self.slots.network_panel].set_visible(true);
+ self.ui_context[self.slots.left_menubar].set_visible(false);
+ self.ui_context[self.slots.breadcrumb].set_visible(true);
+ self.ui_context[self.slots.splitter1].set_visible(false);
+ self.ui_context[self.slots.splitter2].set_visible(false);
+ self.ui_context[self.slots.viewport].set_visible(false);
+ self.ui_context[self.slots.right_menubar].set_visible(false);
+ self.ui_context[self.slots.param].set_visible(false);
+ self.ui_context[self.slots.param_menubar].set_visible(false);
+ self.ui_context[self.slots.spreadsheet].set_visible(false);
+ self.ui_context[self.slots.spreadsheet_menubar].set_visible(false);
} else if self.detached_circular_network {
// Parent process: Network pane is detached (hidden from main window)
let pb_h = if self.show_playbar { PLAYBAR_H } else { 0.0 };
@@ -9072,7 +9071,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.positions[BREADCRUMB_IDX] = (0.0, 0.0, 0.0, 0.0);
self.positions[CONTENT_IDX] = (0.0, 0.0, 0.0, 0.0);
self.positions[NETWORK_PANEL_IDX] = (0.0, 0.0, 0.0, 0.0);
- self.slots.network_panel.set_rect(0.0, 0.0, 0.0, 0.0);
+ self.ui_context[self.slots.network_panel].set_rect(0.0, 0.0, 0.0, 0.0);
self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
self.positions[SPLITTER2_IDX] = (s2_x, HEADER_H, s2_w, body_h);
self.positions[PARAM_IDX] = (col_r_x, HEADER_H, col_r_w, body_h);
@@ -9087,23 +9086,23 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.positions[PARAM_MENUBAR_IDX] = (col_r_x, HEADER_H, col_r_w, if right_visible { MENUBAR_H } else { 0.0 });
self.positions[STATUS_IDX] = (0.0, self.height - STATUS_H, self.width, STATUS_H);
self.positions[PLAYBAR_IDX] = (0.0, HEADER_H + body_h, self.width, pb_h);
- self.slots.playbar.inner_mut().frame = 0.0;
-
- self.slots.header.set_visible(true);
- self.slots.status.set_visible(false);
- self.slots.content.set_visible(false);
- self.slots.network_panel.set_visible(false);
- self.slots.left_menubar.set_visible(false);
- self.slots.breadcrumb.set_visible(false);
- self.slots.splitter1.set_visible(false);
- self.slots.splitter2.set_visible(s2_w > 0.0);
- self.slots.viewport.set_visible(viewport_visible);
- self.slots.right_menubar.set_visible(viewport_visible);
- self.slots.param.set_visible(right_visible);
- self.slots.param_menubar.set_visible(right_visible);
- self.slots.spreadsheet.set_visible(spreadsheet_visible);
- self.slots.spreadsheet_menubar.set_visible(spreadsheet_visible);
- self.slots.playbar.set_visible(self.show_playbar);
+ self.ui_context[self.slots.playbar].inner_mut().frame = 0.0;
+
+ self.ui_context[self.slots.header].set_visible(true);
+ self.ui_context[self.slots.status].set_visible(false);
+ self.ui_context[self.slots.content].set_visible(false);
+ self.ui_context[self.slots.network_panel].set_visible(false);
+ self.ui_context[self.slots.left_menubar].set_visible(false);
+ self.ui_context[self.slots.breadcrumb].set_visible(false);
+ self.ui_context[self.slots.splitter1].set_visible(false);
+ self.ui_context[self.slots.splitter2].set_visible(s2_w > 0.0);
+ self.ui_context[self.slots.viewport].set_visible(viewport_visible);
+ self.ui_context[self.slots.right_menubar].set_visible(viewport_visible);
+ self.ui_context[self.slots.param].set_visible(right_visible);
+ self.ui_context[self.slots.param_menubar].set_visible(right_visible);
+ self.ui_context[self.slots.spreadsheet].set_visible(spreadsheet_visible);
+ self.ui_context[self.slots.spreadsheet_menubar].set_visible(spreadsheet_visible);
+ self.ui_context[self.slots.playbar].set_visible(self.show_playbar);
} else {
let paginator_w = 0.0;
let body_h = self.height - STATUS_H;
@@ -9176,18 +9175,18 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let cy = self.circular_network_layout.y;
self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
- if let Some(menubar) = self.slots.left_menubar.as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
+ if let Some(menubar) = self.ui_context[self.slots.left_menubar].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
menubar.set_curved_circle(None);
}
let bc_h = breadcrumb_h();
self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0, 2.0 * r, bc_h);
self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + bc_h, 2.0 * r, 2.0 * r - (45.0 + bc_h));
self.positions[NETWORK_PANEL_IDX] = (cx - r, cy - r, 2.0 * r, 2.0 * r);
- self.slots.network_panel.set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
- if let Some(plate) = self.slots.network_panel.as_any_mut().downcast_mut::<PassivePlate>() {
+ self.ui_context[self.slots.network_panel].set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
+ if let Some(plate) = self.ui_context[self.slots.network_panel].as_any_mut().downcast_mut::<PassivePlate>() {
plate.set_curved_circle(Some((cx, cy, r)));
}
- self.slots.network_panel.set_drag_bounds(0.0, 0.0, self.width, self.height);
+ self.ui_context[self.slots.network_panel].set_drag_bounds(0.0, 0.0, self.width, self.height);
self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
self.positions[SPLITTER2_IDX] = (s2_x, 0.0, s2_w, body_h);
self.positions[PARAM_IDX] = (col_r_x, 0.0, col_r_w, body_h);
@@ -9202,23 +9201,23 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
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);
self.positions[PLAYBAR_IDX] = (0.0, body_h, self.width, pb_h);
- self.slots.playbar.inner_mut().frame = 0.0;
-
- self.slots.header.set_visible(false);
- self.slots.status.set_visible(false);
- self.slots.content.set_visible(self.show_network);
- self.slots.network_panel.set_visible(self.show_network);
- self.slots.left_menubar.set_visible(false);
- self.slots.breadcrumb.set_visible(self.show_network);
- self.slots.splitter1.set_visible(false);
- self.slots.splitter2.set_visible(s2_w > 0.0);
- self.slots.viewport.set_visible(viewport_visible);
- self.slots.right_menubar.set_visible(false);
- self.slots.param.set_visible(right_visible);
- self.slots.param_menubar.set_visible(false);
- self.slots.spreadsheet.set_visible(spreadsheet_visible);
- self.slots.spreadsheet_menubar.set_visible(false);
- self.slots.playbar.set_visible(self.show_playbar);
+ self.ui_context[self.slots.playbar].inner_mut().frame = 0.0;
+
+ self.ui_context[self.slots.header].set_visible(false);
+ self.ui_context[self.slots.status].set_visible(false);
+ self.ui_context[self.slots.content].set_visible(self.show_network);
+ self.ui_context[self.slots.network_panel].set_visible(self.show_network);
+ self.ui_context[self.slots.left_menubar].set_visible(false);
+ self.ui_context[self.slots.breadcrumb].set_visible(self.show_network);
+ self.ui_context[self.slots.splitter1].set_visible(false);
+ self.ui_context[self.slots.splitter2].set_visible(s2_w > 0.0);
+ self.ui_context[self.slots.viewport].set_visible(viewport_visible);
+ self.ui_context[self.slots.right_menubar].set_visible(false);
+ self.ui_context[self.slots.param].set_visible(right_visible);
+ self.ui_context[self.slots.param_menubar].set_visible(false);
+ self.ui_context[self.slots.spreadsheet].set_visible(spreadsheet_visible);
+ self.ui_context[self.slots.spreadsheet_menubar].set_visible(false);
+ self.ui_context[self.slots.playbar].set_visible(self.show_playbar);
} else {
let viewport_visible = self.show_viewport;
let spreadsheet_visible = self.show_spreadsheet;
@@ -9242,7 +9241,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
(0.0, 0.0, 0.0, 0.0)
};
- if let Some(menubar) = self.slots.left_menubar.as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
+ if let Some(menubar) = self.ui_context[self.slots.left_menubar].as_any_mut().downcast_mut::<cce_ui::widget::MenuBar>() {
menubar.set_curved_circle(None);
}
@@ -9256,8 +9255,8 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.positions[CONTENT_IDX] = (px, py + mb_h, pw, content_h);
self.positions[NETWORK_PANEL_IDX] = (px, py, pw, ph);
- self.slots.network_panel.set_rect(px, py, pw, ph);
- if let Some(plate) = self.slots.network_panel.as_any_mut().downcast_mut::<PassivePlate>() {
+ self.ui_context[self.slots.network_panel].set_rect(px, py, pw, ph);
+ if let Some(plate) = self.ui_context[self.slots.network_panel].as_any_mut().downcast_mut::<PassivePlate>() {
plate.set_curved_circle(None);
}
self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
@@ -9267,7 +9266,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// out from the viewport alone, under every plate.
let p_rect = if right_visible { self.params_hud_rect() } else { (0.0, 0.0, 0.0, 0.0) };
self.positions[PARAM_IDX] = p_rect;
- self.slots.param.set_rect(p_rect.0, p_rect.1, p_rect.2, p_rect.3);
+ self.ui_context[self.slots.param].set_rect(p_rect.0, p_rect.1, p_rect.2, p_rect.3);
self.positions[RIGHT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
// The raised run floats clear of the plate's edge: offset past
// the plate's rolled lip (bevel_width) plus the run's own boss
@@ -9284,7 +9283,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// docks since 2026-10-07).
self.positions[SPREADSHEET_IDX] =
if spreadsheet_visible { self.floating_spreadsheet_rect() } else { (0.0, 0.0, 0.0, 0.0) };
- self.slots.spreadsheet.set_rect(
+ self.ui_context[self.slots.spreadsheet].set_rect(
self.positions[SPREADSHEET_IDX].0,
self.positions[SPREADSHEET_IDX].1,
self.positions[SPREADSHEET_IDX].2,
@@ -9309,23 +9308,23 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
} else {
(0.0, 0.0, 0.0, 0.0)
};
- self.slots.playbar.inner_mut().frame = cce_ui::layout::bevel_width();
-
- self.slots.header.set_visible(false);
- self.slots.status.set_visible(false);
- self.slots.playbar.set_visible(self.show_playbar);
- self.slots.content.set_visible(self.show_network);
- self.slots.network_panel.set_visible(self.show_network);
- self.slots.left_menubar.set_visible(false);
- self.slots.breadcrumb.set_visible(self.show_network);
- self.slots.splitter1.set_visible(false);
- self.slots.splitter2.set_visible(false);
- self.slots.viewport.set_visible(viewport_visible);
- self.slots.right_menubar.set_visible(false);
- self.slots.param.set_visible(right_visible);
- self.slots.param_menubar.set_visible(false);
- self.slots.spreadsheet.set_visible(spreadsheet_visible);
- self.slots.spreadsheet_menubar.set_visible(false);
+ self.ui_context[self.slots.playbar].inner_mut().frame = cce_ui::layout::bevel_width();
+
+ self.ui_context[self.slots.header].set_visible(false);
+ self.ui_context[self.slots.status].set_visible(false);
+ self.ui_context[self.slots.playbar].set_visible(self.show_playbar);
+ self.ui_context[self.slots.content].set_visible(self.show_network);
+ self.ui_context[self.slots.network_panel].set_visible(self.show_network);
+ self.ui_context[self.slots.left_menubar].set_visible(false);
+ self.ui_context[self.slots.breadcrumb].set_visible(self.show_network);
+ self.ui_context[self.slots.splitter1].set_visible(false);
+ self.ui_context[self.slots.splitter2].set_visible(false);
+ self.ui_context[self.slots.viewport].set_visible(viewport_visible);
+ self.ui_context[self.slots.right_menubar].set_visible(false);
+ self.ui_context[self.slots.param].set_visible(right_visible);
+ self.ui_context[self.slots.param_menubar].set_visible(false);
+ self.ui_context[self.slots.spreadsheet].set_visible(spreadsheet_visible);
+ self.ui_context[self.slots.spreadsheet_menubar].set_visible(false);
}
}
@@ -9357,7 +9356,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
];
for &idx in &menubars {
self.positions[idx] = (0.0, 0.0, 0.0, 0.0);
- self.slots.get_dyn_mut(idx).set_visible(false);
+ self.slots.get_dyn_mut(&mut self.ui_context, idx).set_visible(false);
}
}
@@ -9375,7 +9374,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
{
let r = self.params_hud_rect();
self.positions[PARAM_IDX] = r;
- self.slots.param.set_rect(r.0, r.1, r.2, r.3);
+ self.ui_context[self.slots.param].set_rect(r.0, r.1, r.2, r.3);
}
// Last of all: the dialog floats over whatever the branches above
// produced, so its rect depends on the window and nothing else.
@@ -9386,8 +9385,8 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
pub fn apply_layout(&mut self) {
for (i, pos) in self.positions.iter().enumerate() {
if i < WIDGET_COUNT {
- if self.slots.is_dragging(i) { continue; }
- let widget = self.slots.get_dyn_mut(i);
+ if self.slots.is_dragging(&self.ui_context, i) { continue; }
+ let widget = self.slots.get_dyn_mut(&mut self.ui_context, i);
let (x, y, w, h) = *pos;
widget.set_rect(x, y, w, h);
}
@@ -9418,13 +9417,13 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// 6bd value shrink: `set_selected` left `WidgetHost` — the five menubar slots are
// concrete `Adapted<MenuBar>` fields, selected-state sync goes to them directly.
let f = self.focused_pane;
- self.slots.header.set_selected(HEADER_IDX == f);
- self.slots.left_menubar.set_selected(LEFT_MENUBAR_IDX == f);
- self.slots.right_menubar.set_selected(RIGHT_MENUBAR_IDX == f);
- self.slots.param_menubar.set_selected(PARAM_MENUBAR_IDX == f);
- self.slots.spreadsheet_menubar.set_selected(SPREADSHEET_MENUBAR_IDX == f);
+ self.ui_context[self.slots.header].set_selected(HEADER_IDX == f);
+ self.ui_context[self.slots.left_menubar].set_selected(LEFT_MENUBAR_IDX == f);
+ self.ui_context[self.slots.right_menubar].set_selected(RIGHT_MENUBAR_IDX == f);
+ self.ui_context[self.slots.param_menubar].set_selected(PARAM_MENUBAR_IDX == f);
+ self.ui_context[self.slots.spreadsheet_menubar].set_selected(SPREADSHEET_MENUBAR_IDX == f);
if self.focused_pane != PARAM_MENUBAR_IDX {
- self.ui_context.unfocus_widget(&mut self.slots.param);
+ self.ui_context.unfocus_id(self.slots.param.id());
self.sync_parameters_to_project();
}
self.sync_context_dropdowns();
@@ -9435,11 +9434,11 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let center_visible = self.show_viewport || self.show_spreadsheet;
let right_visible = self.show_parameters;
- if left_visible && center_visible && self.slots.splitter1.visible() && self.slots.splitter1.rect().2 > 0.0 {
- self.splitter_layout.splitter1_x = self.slots.splitter1.rect().0;
+ if left_visible && center_visible && self.ui_context[self.slots.splitter1].visible() && self.ui_context[self.slots.splitter1].rect().2 > 0.0 {
+ self.splitter_layout.splitter1_x = self.ui_context[self.slots.splitter1].rect().0;
}
- if right_visible && (center_visible || left_visible) && self.slots.splitter2.visible() && self.slots.splitter2.rect().2 > 0.0 {
- self.splitter_layout.splitter2_x = self.slots.splitter2.rect().0;
+ if right_visible && (center_visible || left_visible) && self.ui_context[self.slots.splitter2].visible() && self.ui_context[self.slots.splitter2].rect().2 > 0.0 {
+ self.splitter_layout.splitter2_x = self.ui_context[self.slots.splitter2].rect().0;
}
self.rebuild_positions();
self.apply_layout();
@@ -10221,8 +10220,8 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
Action::ToggleSpreadsheet => {
self.show_spreadsheet = !self.show_spreadsheet;
- self.slots.spreadsheet.set_visible(self.show_spreadsheet);
- self.slots.spreadsheet_menubar.set_visible(self.show_spreadsheet);
+ self.ui_context[self.slots.spreadsheet].set_visible(self.show_spreadsheet);
+ self.ui_context[self.slots.spreadsheet_menubar].set_visible(self.show_spreadsheet);
let val = self.show_spreadsheet;
self.menu_mut(HEADER_IDX).set_item_checked(2, 7, val);
if !self.show_spreadsheet && self.focused_pane == SPREADSHEET_MENUBAR_IDX {
@@ -10313,7 +10312,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// was tried and rejected: a moving timeline should always stop on
// the first transport press.)
Action::PlayPause | Action::PlayPauseReverse => {
- let pb = self.slots.playbar.inner_mut();
+ let pb = self.ui_context[self.slots.playbar].inner_mut();
if pb.playing {
pb.playing = false;
} else {
@@ -10323,12 +10322,12 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// Repeat is a setting, not a transport press: the timeline keeps
// doing whatever it is doing, and the next run-off honours it.
Action::TogglePlaybarRepeat => {
- let pb = self.slots.playbar.inner_mut();
+ let pb = self.ui_context[self.slots.playbar].inner_mut();
pb.repeat = !pb.repeat;
settings_changed = true;
}
Action::TogglePlaybarStepButtons => {
- let pb = self.slots.playbar.inner_mut();
+ let pb = self.ui_context[self.slots.playbar].inner_mut();
pb.step_buttons = !pb.step_buttons;
settings_changed = true;
}
@@ -10337,13 +10336,13 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// tick_frame's last_sim_frame diff.
Action::FrameNext | Action::FramePrev => {
let step = if action == Action::FrameNext { 1.0 } else { -1.0 };
- self.slots.playbar.inner_mut().step(step);
+ self.ui_context[self.slots.playbar].inner_mut().step(step);
}
// Rewind: pause whatever is playing and land on the start frame.
// The scene rebuild follows from tick_frame's last_sim_frame
// diff, as for the steps above.
Action::FrameStart => {
- let pb = self.slots.playbar.inner_mut();
+ let pb = self.ui_context[self.slots.playbar].inner_mut();
pb.playing = false;
pb.current_frame = pb.start_frame;
}
@@ -10463,8 +10462,8 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let r = self.width / old_width;
self.splitter_layout.scale(r);
let body_h = self.body_h();
- self.slots.splitter1.set_rect(self.splitter_layout.splitter1_x, HEADER_H, SPLITTER_W, body_h);
- self.slots.splitter2.set_rect(self.splitter_layout.splitter2_x, HEADER_H, SPLITTER_W, body_h);
+ self.ui_context[self.slots.splitter1].set_rect(self.splitter_layout.splitter1_x, HEADER_H, SPLITTER_W, body_h);
+ self.ui_context[self.slots.splitter2].set_rect(self.splitter_layout.splitter2_x, HEADER_H, SPLITTER_W, body_h);
}
self.window_configured = true;
@@ -10620,13 +10619,13 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
if i == PARAM_IDX && !self.params_claims(self.cursor_x, self.cursor_y) {
continue;
}
- let root = self.slots.get_dyn_mut(i).base().id();
+ let root = self.slots.get_dyn_mut(&mut self.ui_context, i).base().id();
if self.ui_context.propagate_event(&wheel_ev, root) {
handled = true;
if i == CONTENT_IDX {
let active_node_area_y = self.positions[CONTENT_IDX].1;
let active_node_area_x = self.positions[CONTENT_IDX].0;
- let w = self.slots.get_dyn(i);
+ let w = self.slots.get_dyn(&self.ui_context, i);
let (gx, gy) = cce_ui::widget::GraphController::grid_origin(
w.as_any().downcast_ref::<Graph>().expect("CONTENT_IDX must be a Graph"),
);
@@ -10710,7 +10709,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// hand the event to the viewport widget here — its ctrl
// branch is the camera zoom. (Trackpad pinches take the
// direct `handle_pinch` path and never reach this.)
- self.slots.get_dyn_mut(VIEWPORT_IDX).set_modifiers(
+ self.slots.get_dyn_mut(&mut self.ui_context, VIEWPORT_IDX).set_modifiers(
self.modifiers.control_key(),
self.modifiers.shift_key(),
self.modifiers.alt_key(),
@@ -10837,10 +10836,10 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
} else if let Some(idx) = self.drag_widget {
// A widget drag: the slot's own Input drag hooks are driving.
- self.slots.get_dyn_mut(idx).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+ self.slots.get_dyn_mut(&mut self.ui_context, idx).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
if {
let ev = cce_ui::widget::Event::DragUpdate { dx: 0.0, dy: 0.0, x: self.cursor_x, y: self.cursor_y, local_x: self.cursor_x, local_y: self.cursor_y };
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&ev, &mut self.ui_context) }
} {
changed = true;
@@ -10995,7 +10994,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// The HUD is under every plate, and without its own
// plate it is its rows: a press anywhere else is the
// plate's or the scene's.
- state.params_claims(x, y) && state.slots.get_dyn(i).hit_test(x, y, &state.ui_context)
+ state.params_claims(x, y) && state.slots.get_dyn(&state.ui_context, i).hit_test(x, y, &state.ui_context)
} else if state.network_overlay() && i == CONTENT_IDX {
// The graph's RECT spans the window in overlay mode,
// so the widget's own hit test would claim every press
@@ -11006,7 +11005,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// same way.
state.overlay_claims(x, y)
} else {
- state.slots.get_dyn(i).hit_test(x, y, &state.ui_context)
+ state.slots.get_dyn(&state.ui_context, i).hit_test(x, y, &state.ui_context)
}
};
@@ -11150,10 +11149,10 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
if on_border {
self.focused_pane = LEFT_MENUBAR_IDX;
if let Some(old) = self.focused_widget {
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
+ self.ui_context.unfocus_id(self.slots.id(old));
self.focused_widget = None;
}
- self.ui_context.unfocus_widget(&mut self.slots.param);
+ self.ui_context.unfocus_id(self.slots.param.id());
self.sync_parameters_to_project();
return true;
}
@@ -11168,7 +11167,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let (bx, by, bw, bh) = self.positions[BREADCRUMB_IDX];
let (cx, cy) = (self.cursor_x, self.cursor_y);
if cx >= bx && cx < bx + bw && cy >= by && cy < by + bh
- && self.slots.breadcrumb.mouse_input(*button, *btn_state, cx, cy, &mut self.ui_context)
+ && self.ui_context.lend_h(self.slots.breadcrumb, |b, ui| b.mouse_input(*button, *btn_state, cx, cy, ui)).unwrap_or(false)
{
return true;
}
@@ -11185,7 +11184,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
});
self.focused_pane = PARAM_MENUBAR_IDX;
if let Some(old) = self.focused_widget {
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
+ self.ui_context.unfocus_id(self.slots.id(old));
self.focused_widget = None;
}
return true;
@@ -11204,7 +11203,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
});
self.focused_pane = SPREADSHEET_MENUBAR_IDX;
if let Some(old) = self.focused_widget {
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
+ self.ui_context.unfocus_id(self.slots.id(old));
self.focused_widget = None;
}
return true;
@@ -11253,7 +11252,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.grid_cursor_drag = Some((col, row));
self.focused_pane = LEFT_MENUBAR_IDX;
if let Some(old) = self.focused_widget.take() {
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
+ self.ui_context.unfocus_id(self.slots.id(old));
}
self.sync_pane_focus();
return true;
@@ -11280,7 +11279,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
self.focused_pane = RIGHT_MENUBAR_IDX;
if let Some(old) = self.focused_widget {
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
+ self.ui_context.unfocus_id(self.slots.id(old));
self.focused_widget = None;
}
self.sync_pane_focus();
@@ -11351,7 +11350,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// priority never shadows the graph elsewhere in the strip.
1
} else {
- self.slots.get_dyn_mut(i).z_index()
+ self.slots.get_dyn_mut(&mut self.ui_context, i).z_index()
};
-z
});
@@ -11419,12 +11418,12 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
if let Some(old) = self.focused_widget {
if click_target != Some(old) && click_target != Some(PARAM_IDX) {
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
+ self.ui_context.unfocus_id(self.slots.id(old));
self.focused_widget = None;
}
}
if click_target != Some(PARAM_IDX) {
- self.ui_context.unfocus_widget(&mut self.slots.param);
+ self.ui_context.unfocus_id(self.slots.param.id());
self.sync_parameters_to_project();
}
if click_target.is_none() && in_circle_network_pane {
@@ -11434,7 +11433,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
changed = true;
}
if let Some(i) = click_target {
- self.slots.get_dyn_mut(i).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+ self.slots.get_dyn_mut(&mut self.ui_context, i).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
// Kept as a binding: the network's cursor-expansion
// drag must not arm on a press the graph already
// took. A press on a PORT is the case that bites —
@@ -11444,7 +11443,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// motion the rubber-band line is drawn from.
let widget_took = {
let ev = cce_ui::widget::Event::MouseButton { button: *button, state: *btn_state, x: self.cursor_x, y: self.cursor_y, local_x: self.cursor_x, local_y: self.cursor_y };
- let ptr = self.slots.get_dyn_mut(i) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, i) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&ev, &mut self.ui_context) }
};
if widget_took {
@@ -11459,23 +11458,23 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
}
- if self.slots.draggable(i) {
- self.slots.get_dyn_mut(i).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+ if self.slots.draggable(&self.ui_context, i) {
+ self.slots.get_dyn_mut(&mut self.ui_context, i).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
{
let ev = cce_ui::widget::Event::DragStart { start_x: self.cursor_x, start_y: self.cursor_y };
- let ptr = self.slots.get_dyn_mut(i) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, i) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&ev, &mut self.ui_context); }
}
self.drag_widget = Some(i);
self.drag_press_cursor = Some((self.cursor_x, self.cursor_y));
}
if i != PARAM_IDX {
- self.ui_context.focus_widget(self.slots.get_dyn_mut(i));
+ self.ui_context.focus_id(self.slots.id(i));
self.focused_widget = Some(i);
if self.menubar_at(i).map(|m| m.is_menu_bar()).unwrap_or(false)
- && !self.slots.get_dyn_mut(i).focused(&self.ui_context)
+ && !self.slots.get_dyn(&self.ui_context, i).focused(&self.ui_context)
{
- self.ui_context.unfocus_widget(self.slots.get_dyn_mut(i));
+ self.ui_context.unfocus_id(self.slots.id(i));
self.focused_widget = None;
}
}
@@ -11513,9 +11512,9 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// it trading places would leave
// the rest where the offset put
// them, the selection scattered.
- self.slots.content.inner_mut().set_swap_on_drop(false);
+ self.ui_context[self.slots.content].inner_mut().set_swap_on_drop(false);
} else {
- self.slots.content.inner_mut().set_swap_on_drop(true);
+ self.ui_context[self.slots.content].inner_mut().set_swap_on_drop(true);
self.grid_cursor_col = pos.0 as i32;
self.grid_cursor_row = pos.1 as i32;
}
@@ -11572,7 +11571,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
AppDrag::SpreadsheetResize { .. } => SPREADSHEET_IDX,
};
{
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&cce_ui::widget::Event::DragEnd, &mut self.ui_context); }
}
self.sync_layout();
@@ -11582,19 +11581,19 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let idx = self.drag_widget.unwrap();
if idx == PARAM_IDX {
{
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&cce_ui::widget::Event::DragEnd, &mut self.ui_context); }
}
self.sync_layout();
} else if idx == SPREADSHEET_IDX {
{
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&cce_ui::widget::Event::DragEnd, &mut self.ui_context); }
}
self.sync_layout();
} else if idx == CONTENT_IDX {
{
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&cce_ui::widget::Event::DragEnd, &mut self.ui_context); }
}
let updated_nodes = self.graph().get_nodes();
@@ -11633,7 +11632,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.update_panel_bounds();
} else {
{
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&cce_ui::widget::Event::DragEnd, &mut self.ui_context); }
}
}
@@ -11649,12 +11648,14 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
let mut sync_params = false;
{
- let ctx = &mut self.ui_context;
+ let (ctrl, shift, alt) = (self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+ let ev = cce_ui::widget::Event::MouseButton { button: *button, state: *btn_state, x: self.cursor_x, y: self.cursor_y, local_x: self.cursor_x, local_y: self.cursor_y };
for i in 0..WIDGET_COUNT {
- let w = self.slots.get_dyn_mut(i);
- w.set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
- let ev = cce_ui::widget::Event::MouseButton { button: *button, state: *btn_state, x: self.cursor_x, y: self.cursor_y, local_x: self.cursor_x, local_y: self.cursor_y };
- if w.handle_event(&ev, ctx) {
+ let handled = self.ui_context.lend(self.slots.id(i), |w, ui| {
+ w.set_modifiers(ctrl, shift, alt);
+ w.handle_event(&ev, ui)
+ });
+ if handled == Some(true) {
changed = true;
if i == PARAM_IDX {
sync_params = true;
@@ -11744,7 +11745,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
}
}
- if self.slots.param.keyboard_input(event, &mut self.ui_context) {
+ if self.ui_context.lend_h(self.slots.param, |p, ui| p.keyboard_input(event, ui)).unwrap_or(false) {
self.sync_parameters_to_project();
return true;
}
@@ -11956,7 +11957,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
} else if let Some(idx) = self.focused_widget {
{
let kev = cce_ui::widget::Event::KeyInput(event.clone());
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&kev, &mut self.ui_context) }
}
} else { false }
@@ -11973,7 +11974,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
if std::mem::take(&mut self.title_dirty) {
self.update_window_title();
}
- if self.settings_save_pending && !self.slots.dialog.slider_dragging() {
+ if self.settings_save_pending && !self.ui_context[self.slots.dialog].slider_dragging() {
self.settings_save_pending = false;
self.save_settings();
}
@@ -12073,9 +12074,9 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let settings = DesignSettings::load();
self.default_project_setting = settings.default_project.clone();
self.gpu_preference = settings.gpu.clone();
- self.slots.playbar.inner_mut().repeat = settings.playbar_repeat;
- self.slots.playbar.inner_mut().step_buttons = settings.playbar_step_buttons;
- self.slots.playbar.inner_mut().fps = settings.playbar_fps.clamp(1.0, 120.0);
+ self.ui_context[self.slots.playbar].inner_mut().repeat = settings.playbar_repeat;
+ self.ui_context[self.slots.playbar].inner_mut().step_buttons = settings.playbar_step_buttons;
+ self.ui_context[self.slots.playbar].inner_mut().fps = settings.playbar_fps.clamp(1.0, 120.0);
self.square_viewport = settings.viewport.square;
self.grid_thickness = settings.viewport.grid_thickness;
self.viewport_mut().show_grid = settings.viewport.show_grid_enabled;
@@ -12114,9 +12115,8 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
let mut tick_changed = false;
let mut param_ticked = false;
let mut graph_ticked = false;
- let ctx = &mut self.ui_context;
for i in 0..WIDGET_COUNT {
- if self.slots.get_dyn_mut(i).tick(dt, ctx) {
+ if self.ui_context.lend(self.slots.id(i), |w, ui| w.tick(dt, ui)) == Some(true) {
tick_changed = true;
if i == PARAM_IDX {
param_ticked = true;
@@ -12182,9 +12182,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// reveal window, and syncing the whole parameter list on each of
// those (clone + compare every param) made the params scroll choppy.
if param_ticked {
- let streamed = self
- .slots
- .param
+ let streamed = self.ui_context[self.slots.param]
.as_any_mut()
.downcast_mut::<cce_ui::widget::ParametersBg>()
.map_or(false, |p| p.take_tick_value_change());
@@ -12284,10 +12282,10 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
self.pan_velocity_y = 0.0;
self.sync_grid_settings();
if let Some(idx) = self.drag_widget {
- self.slots.get_dyn_mut(idx).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+ self.slots.get_dyn_mut(&mut self.ui_context, idx).set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
{
let ev = cce_ui::widget::Event::DragUpdate { dx: 0.0, dy: 0.0, x: self.cursor_x, y: self.cursor_y, local_x: self.cursor_x, local_y: self.cursor_y };
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn WidgetHost + 'static);
unsafe { (*ptr).handle_event(&ev, &mut self.ui_context) }
};
}
@@ -12586,7 +12584,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
// scene: x right, y up, z back toward the camera (a right-handed
// view looks down its own -Z).
let row = |i: usize| [m.x_axis[i], m.y_axis[i], m.z_axis[i]];
- self.slots.param.inner_mut().set_trackball_view([row(0), row(1), row(2)])
+ self.ui_context[self.slots.param].inner_mut().set_trackball_view([row(0), row(1), row(2)])
}
pub fn stage_frame(&mut self, renderer: &mut cce_ui::vk::VkRenderer) -> bool {
diff --git a/src/dialog.rs b/src/dialog.rs
index 6de93ea..431441e 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -1665,11 +1665,11 @@ pub fn dialog_row_leads(mode: Mode, id: &str) -> bool {
impl State {
pub fn dialog_visible(&self) -> bool {
- self.slots.dialog.visible()
+ self.ui_context[self.slots.dialog].visible()
}
pub fn toggle_dialog(&mut self) {
- if self.dialog_visible() && self.slots.dialog.mode == Mode::Commands {
+ if self.dialog_visible() && self.ui_context[self.slots.dialog].mode == Mode::Commands {
self.close_dialog();
} else {
self.open_dialog();
@@ -1691,7 +1691,7 @@ impl State {
/// with the app, and put a second filterable list in front of the user
/// that looked nothing like the first.
pub fn open_node_palette(&mut self) {
- if self.dialog_visible() && self.slots.dialog.mode == Mode::AddNode {
+ if self.dialog_visible() && self.ui_context[self.slots.dialog].mode == Mode::AddNode {
self.close_dialog();
return;
}
@@ -1720,7 +1720,7 @@ impl State {
self.close_dialog();
self.open_dialog_anchored(mode, Some(at));
self.dialog_from = Some(origin);
- self.slots.dialog.turning = Some(DialogTurn { from: Some(from), start: std::time::Instant::now(), dir: 1.0 });
+ self.ui_context[self.slots.dialog].turning = Some(DialogTurn { from: Some(from), start: std::time::Instant::now(), dir: 1.0 });
}
/// Turn the open dialog to `mode`, as the row or key that leads there
@@ -1764,7 +1764,7 @@ impl State {
Some((origin, at)) => self.open_dialog_from(Mode::Rename, origin, at),
None => self.open_dialog_in(Mode::Rename),
}
- self.slots.dialog.query = name;
+ self.ui_context[self.slots.dialog].query = name;
self.refresh_dialog_rows();
}
@@ -1794,8 +1794,8 @@ impl State {
// the trail a swipe back follows — or, when the mode it turns to IS
// the trail's last, it is going back, and comes off. Opened afresh
// it has neither, and whoever opened it from a menu says so after.
- let was = self.dialog_visible().then_some(self.slots.dialog.mode);
- self.slots.dialog.turning = None;
+ let was = self.dialog_visible().then_some(self.ui_context[self.slots.dialog].mode);
+ self.ui_context[self.slots.dialog].turning = None;
match was {
Some(prev) if anchor.is_none() => {
// The same plate turned to another list: the rows slide in,
@@ -1809,10 +1809,10 @@ impl State {
} else {
None
};
- self.slots.dialog.turning = dir.map(|dir| DialogTurn { from: None, start: std::time::Instant::now(), dir });
+ self.ui_context[self.slots.dialog].turning = dir.map(|dir| DialogTurn { from: None, start: std::time::Instant::now(), dir });
}
_ => {
- self.slots.dialog.anchor = anchor;
+ self.ui_context[self.slots.dialog].anchor = anchor;
self.dialog_trail.clear();
self.dialog_from = None;
}
@@ -1820,9 +1820,9 @@ impl State {
// Always with an empty query: a dialog that reopens holding the last
// search has to be cleared before it can be used, which is a step
// every single time to save one occasionally.
- self.slots.dialog.mode = mode;
- self.slots.dialog.query.clear();
- self.slots.dialog.set_visible(true);
+ self.ui_context[self.slots.dialog].mode = mode;
+ self.ui_context[self.slots.dialog].query.clear();
+ self.ui_context[self.slots.dialog].set_visible(true);
self.refresh_dialog_rows();
self.rebuild_positions();
self.apply_layout();
@@ -1843,10 +1843,10 @@ impl State {
// Shut, not animated: with the dialog gone there is nothing to
// shrink into, and a plate still reporting itself open would keep
// covering presses meant for the panes.
- self.slots.dialog.dropdown.inner_mut().open = false;
- self.slots.dialog.dropdown_row = None;
- self.slots.dialog.dropdown_armed = false;
- self.slots.dialog.set_visible(false);
+ self.ui_context[self.slots.dialog].dropdown.inner_mut().open = false;
+ self.ui_context[self.slots.dialog].dropdown_row = None;
+ self.ui_context[self.slots.dialog].dropdown_armed = false;
+ self.ui_context[self.slots.dialog].set_visible(false);
self.dialog_from = None;
self.dialog_trail.clear();
if self.focused_widget == Some(DIALOG_IDX) {
@@ -1869,8 +1869,8 @@ impl State {
/// thing in every list; they carry no chord, so the column is simply
/// empty for them.
pub fn refresh_dialog_rows(&mut self) {
- let query = self.slots.dialog.query.clone();
- let rows: Vec<Row> = match self.slots.dialog.mode {
+ let query = self.ui_context[self.slots.dialog].query.clone();
+ let rows: Vec<Row> = match self.ui_context[self.slots.dialog].mode {
// One row, which is what Enter will do: the name as it will be
// written, or why it will not be.
Mode::Rename => match self.rename_outcome(&query) {
@@ -2032,7 +2032,7 @@ impl State {
};
// A row that turns the dialog into another list says so where a
// menu's page row does, at its right end.
- let mode = self.slots.dialog.mode;
+ let mode = self.ui_context[self.slots.dialog].mode;
let rows = rows
.into_iter()
.map(|mut r| {
@@ -2042,7 +2042,7 @@ impl State {
r
})
.collect();
- self.slots.dialog.set_rows(rows);
+ self.ui_context[self.slots.dialog].set_rows(rows);
}
/// The open project's path and its file name, for the palette's path row
@@ -2119,8 +2119,8 @@ impl State {
/// Re-read the zoom row from the live zoom, in place.
fn refresh_dialog_zoom(&mut self) {
let c = self.zoom_control();
- if self.slots.dialog.rows.iter().any(|r| r.id == ZOOM_ROW_ID) {
- self.slots.dialog.set_control(ZOOM_ROW_ID, Some(c));
+ if self.ui_context[self.slots.dialog].rows.iter().any(|r| r.id == ZOOM_ROW_ID) {
+ self.ui_context[self.slots.dialog].set_control(ZOOM_ROW_ID, Some(c));
}
}
@@ -2162,8 +2162,8 @@ impl State {
"show_viewport_pane" => self.show_viewport,
"show_parameters_pane" => self.show_parameters,
"show_playbar_pane" => self.show_playbar,
- "toggle_playbar_repeat" => self.slots.playbar.inner().repeat,
- "toggle_playbar_step_buttons" => self.slots.playbar.inner().step_buttons,
+ "toggle_playbar_repeat" => self.ui_context[self.slots.playbar].inner().repeat,
+ "toggle_playbar_step_buttons" => self.ui_context[self.slots.playbar].inner().step_buttons,
"toggle_snap" => self.viewer_tool.as_ref()?.snap.is_some(),
_ => return None,
})
@@ -2176,20 +2176,20 @@ impl State {
/// moved, and re-ranking would throw the selection back to the top of a
/// list the user is still working down.
pub(crate) fn refresh_dialog_controls(&mut self) {
- if self.slots.dialog.mode == Mode::Groups {
- let ids: Vec<String> = self.slots.dialog.rows.iter().map(|r| r.id.clone()).collect();
+ if self.ui_context[self.slots.dialog].mode == Mode::Groups {
+ let ids: Vec<String> = self.ui_context[self.slots.dialog].rows.iter().map(|r| r.id.clone()).collect();
for id in ids {
if let Some(name) = id.strip_prefix(GROUP_ROW_PREFIX) {
let on = self.group_marked(name);
- self.slots.dialog.set_control(&id, Some(Control::Toggle(on)));
+ self.ui_context[self.slots.dialog].set_control(&id, Some(Control::Toggle(on)));
}
}
return;
}
- if self.slots.dialog.mode != Mode::Commands {
+ if self.ui_context[self.slots.dialog].mode != Mode::Commands {
return;
}
- let ids: Vec<String> = self.slots.dialog.rows.iter().map(|r| r.id.clone()).collect();
+ let ids: Vec<String> = self.ui_context[self.slots.dialog].rows.iter().map(|r| r.id.clone()).collect();
for id in ids {
let control = if let Some(s) = setting_of_row(&id) {
Some(self.setting_control(s))
@@ -2200,7 +2200,7 @@ impl State {
} else {
continue;
};
- self.slots.dialog.set_control(&id, control);
+ self.ui_context[self.slots.dialog].set_control(&id, control);
}
}
@@ -2317,7 +2317,7 @@ impl State {
match key {
"world_unit" => self.world_unit.suffix().to_string(),
"gpu" => self.gpu_preference.clone(),
- "node_wire_style" => self.slots.content.inner().wire_style().label().to_string(),
+ "node_wire_style" => self.ui_context[self.slots.content].inner().wire_style().label().to_string(),
_ => String::new(),
}
}
@@ -2407,7 +2407,7 @@ impl State {
/// 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.ui_context[self.slots.content].inner_mut().set_wire_style(style);
}
/// Write one setting's value to whatever owns it.
@@ -2436,7 +2436,7 @@ impl State {
self.rebuild_scene_geometry();
self.sync_nodes();
// A slider drag saves once, when it ends (`tick_frame`).
- if self.slots.dialog.slider_dragging() {
+ if self.ui_context[self.slots.dialog].slider_dragging() {
self.settings_save_pending = true;
} else {
self.save_settings();
@@ -2473,12 +2473,12 @@ impl State {
self.positions[DIALOG_IDX] = (0.0, 0.0, 0.0, 0.0);
return;
}
- let (x, y, w, h) = match self.slots.dialog.anchor {
+ let (x, y, w, h) = match self.ui_context[self.slots.dialog].anchor {
Some((ax, ay)) => layout_at(self.width, self.height, ax, ay),
None => layout_in(self.width, self.height),
};
self.positions[DIALOG_IDX] = (x, y, w, h);
- self.slots.dialog.set_page(visible_rows(x, y, w, h));
+ self.ui_context[self.slots.dialog].set_page(visible_rows(x, y, w, h));
}
/// Every key, while the dialog is open.
@@ -2495,7 +2495,7 @@ impl State {
// A colour row's hex well, while it is being typed into, has the
// keyboard ahead of everything — Escape and Enter included, which
// end the edit rather than the dialog.
- if let Some(sel) = self.slots.dialog.editing_color() {
+ if let Some(sel) = self.ui_context[self.slots.dialog].editing_color() {
let ptr = sel as *mut cce_ui::widget::Adapted<ColorSelector>;
unsafe {
(*ptr).keyboard_input(event, &mut self.ui_context);
@@ -2524,7 +2524,7 @@ impl State {
Key::Named(NamedKey::Tab) => {
// Tab is what opened the add-node palette, so the same key
// closes it again. In the commands list it means nothing.
- if self.slots.dialog.mode == Mode::AddNode {
+ if self.ui_context[self.slots.dialog].mode == Mode::AddNode {
self.close_dialog();
}
return true;
@@ -2533,27 +2533,27 @@ impl State {
}
match &event.logical_key {
- Key::Named(NamedKey::ArrowDown) => self.slots.dialog.move_selection(1),
- Key::Named(NamedKey::ArrowUp) => self.slots.dialog.move_selection(-1),
+ Key::Named(NamedKey::ArrowDown) => self.ui_context[self.slots.dialog].move_selection(1),
+ Key::Named(NamedKey::ArrowUp) => self.ui_context[self.slots.dialog].move_selection(-1),
Key::Named(NamedKey::PageDown) => {
- let page = self.slots.dialog.page_len() as i32;
- self.slots.dialog.move_selection(page);
+ let page = self.ui_context[self.slots.dialog].page_len() as i32;
+ self.ui_context[self.slots.dialog].move_selection(page);
}
Key::Named(NamedKey::PageUp) => {
- let page = self.slots.dialog.page_len() as i32;
- self.slots.dialog.move_selection(-page);
+ let page = self.ui_context[self.slots.dialog].page_len() as i32;
+ self.ui_context[self.slots.dialog].move_selection(-page);
}
Key::Named(NamedKey::Home) => {
- self.slots.dialog.selected = 0;
- self.slots.dialog.scroll_to_selected();
+ self.ui_context[self.slots.dialog].selected = 0;
+ self.ui_context[self.slots.dialog].scroll_to_selected();
}
Key::Named(NamedKey::End) => {
- let last = self.slots.dialog.rows.len().saturating_sub(1);
- self.slots.dialog.selected = last;
- self.slots.dialog.scroll_to_selected();
+ let last = self.ui_context[self.slots.dialog].rows.len().saturating_sub(1);
+ self.ui_context[self.slots.dialog].selected = last;
+ self.ui_context[self.slots.dialog].scroll_to_selected();
}
Key::Named(NamedKey::Enter) => {
- if let Some(id) = self.slots.dialog.selected_id().map(str::to_string) {
+ if let Some(id) = self.ui_context[self.slots.dialog].selected_id().map(str::to_string) {
self.take_dialog_pick(id);
}
}
@@ -2566,12 +2566,12 @@ impl State {
self.nudge_dialog_selection(dir);
}
Key::Named(NamedKey::Backspace) => {
- if self.slots.dialog.query.pop().is_some() {
+ if self.ui_context[self.slots.dialog].query.pop().is_some() {
self.refresh_dialog_rows();
}
}
Key::Named(NamedKey::Space) => {
- self.slots.dialog.query.push(' ');
+ self.ui_context[self.slots.dialog].query.push(' ');
self.refresh_dialog_rows();
}
Key::Character(c) => {
@@ -2581,7 +2581,7 @@ impl State {
&& !self.modifiers.alt_key()
&& !self.modifiers.super_key()
{
- self.slots.dialog.query.push_str(c);
+ self.ui_context[self.slots.dialog].query.push_str(c);
self.refresh_dialog_rows();
}
}
@@ -2593,13 +2593,13 @@ impl State {
/// Step the selected row's control by `dir` (-1 or 1) and land the
/// value: a slider by its step, a choice to its neighbouring option.
fn nudge_dialog_selection(&mut self, dir: i32) {
- let Some(id) = self.slots.dialog.selected_id().map(str::to_string) else { return };
- let Some(control) = self.slots.dialog.selected_control().cloned() else { return };
+ let Some(id) = self.ui_context[self.slots.dialog].selected_id().map(str::to_string) else { return };
+ let Some(control) = self.ui_context[self.slots.dialog].selected_control().cloned() else { return };
match control {
Control::Slider { value, step, .. } => {
- let i = self.slots.dialog.selected;
- self.slots.dialog.set_slider_value(i, value + dir as f32 * step);
- let Some(v) = self.slots.dialog.rows[i].slider_value() else { return };
+ let i = self.ui_context[self.slots.dialog].selected;
+ self.ui_context[self.slots.dialog].set_slider_value(i, value + dir as f32 * step);
+ let Some(v) = self.ui_context[self.slots.dialog].rows[i].slider_value() else { return };
self.land_dialog_slider(&id, v);
}
Control::Choice { options, index } => {
@@ -2629,26 +2629,26 @@ impl State {
/// into the list, the current option highlighted. A pick lands through
/// [`Self::land_dialog_choice`]; the dialog stays up.
pub(crate) fn open_dialog_dropdown(&mut self, id: &str) {
- let Some(i) = self.slots.dialog.rows.iter().position(|r| r.id == id) else { return };
- let Some(Control::Choice { options, index }) = self.slots.dialog.rows[i].control.clone() else { return };
+ let Some(i) = self.ui_context[self.slots.dialog].rows.iter().position(|r| r.id == id) else { return };
+ let Some(Control::Choice { options, index }) = self.ui_context[self.slots.dialog].rows[i].control.clone() else { return };
if options.is_empty() {
return;
}
- self.slots.dialog.selected = i;
- self.slots.dialog.scroll_to_selected();
+ self.ui_context[self.slots.dialog].selected = i;
+ self.ui_context[self.slots.dialog].scroll_to_selected();
{
- let dd = self.slots.dialog.dropdown.inner_mut();
+ let dd = self.ui_context[self.slots.dialog].dropdown.inner_mut();
dd.options = options;
dd.selected = index;
}
- self.slots.dialog.dropdown_row = Some(id.to_string());
+ self.ui_context[self.slots.dialog].dropdown_row = Some(id.to_string());
if !self.sync_dialog_dropdown() {
- self.slots.dialog.dropdown_row = None;
+ self.ui_context[self.slots.dialog].dropdown_row = None;
return;
}
// Opened as the toolkit opens one from the keyboard: focused, then
// Enter, which unfolds it with the current option highlighted.
- let dd_id = self.slots.dialog.dropdown.base().id();
+ let dd_id = self.ui_context[self.slots.dialog].dropdown.base().id();
self.ui_context.set_focused_id(dd_id);
let enter = Event::KeyInput(KeyEvent {
state: ElementState::Pressed,
@@ -2659,33 +2659,33 @@ impl State {
shift: false,
alt: false,
});
- let ptr = &mut *self.slots.dialog.dropdown as *mut Adapted<Dropdown>;
+ let ptr = &mut *self.ui_context[self.slots.dialog].dropdown as *mut Adapted<Dropdown>;
unsafe {
(*ptr).handle_event(&enter, &mut self.ui_context);
}
- self.slots.dialog.dropdown_armed = true;
+ self.ui_context[self.slots.dialog].dropdown_armed = true;
}
/// Lay the live dropdown out on its row's band, as the dialog now
/// stands. False when there is no such row in view.
pub(crate) fn sync_dialog_dropdown(&mut self) -> bool {
let (x, y, w, h) = self.positions[DIALOG_IDX];
- let Some(band) = self.slots.dialog.dropdown_trigger(Rect { x, y, width: w, height: h }) else { return false };
- WidgetHost::set_rect(&mut *self.slots.dialog.dropdown, band.x, band.y, band.width, band.height);
+ let Some(band) = self.ui_context[self.slots.dialog].dropdown_trigger(Rect { x, y, width: w, height: h }) else { return false };
+ WidgetHost::set_rect(&mut *self.ui_context[self.slots.dialog].dropdown, band.x, band.y, band.width, band.height);
true
}
/// Whether the dropdown is open and taking input — not while it
/// shrinks closed, when the dialog under it has the pointer again.
pub(crate) fn dialog_dropdown_open(&self) -> bool {
- self.dialog_visible() && self.slots.dialog.dropdown_row.is_some() && self.slots.dialog.dropdown.is_expanded()
+ self.dialog_visible() && self.ui_context[self.slots.dialog].dropdown_row.is_some() && self.ui_context[self.slots.dialog].dropdown.is_expanded()
}
/// Close the dropdown — animated, as an outside press closes it.
pub(crate) fn close_dialog_dropdown(&mut self) {
- self.slots.dialog.dropdown_armed = false;
- if self.slots.dialog.dropdown.open {
- let ptr = &mut *self.slots.dialog.dropdown as *mut Adapted<Dropdown>;
+ self.ui_context[self.slots.dialog].dropdown_armed = false;
+ if self.ui_context[self.slots.dialog].dropdown.open {
+ let ptr = &mut *self.ui_context[self.slots.dialog].dropdown as *mut Adapted<Dropdown>;
unsafe {
(*ptr).handle_event(&Event::FocusOut, &mut self.ui_context);
}
@@ -2700,7 +2700,7 @@ impl State {
return false;
}
self.sync_dialog_dropdown();
- let ptr = &mut *self.slots.dialog.dropdown as *mut Adapted<Dropdown>;
+ let ptr = &mut *self.ui_context[self.slots.dialog].dropdown as *mut Adapted<Dropdown>;
let taken = unsafe { (*ptr).handle_event(ev, &mut self.ui_context) };
self.land_dialog_dropdown_pick();
taken
@@ -2710,11 +2710,11 @@ impl State {
/// reached it, and note whether it is still expanded.
fn land_dialog_dropdown_pick(&mut self) {
let picked = {
- let dd = self.slots.dialog.dropdown.inner_mut();
+ let dd = self.ui_context[self.slots.dialog].dropdown.inner_mut();
dd.take_change().then(|| dd.options.get(dd.selected).cloned()).flatten()
};
- self.slots.dialog.dropdown_armed = self.slots.dialog.dropdown.is_expanded();
- if let (Some(value), Some(id)) = (picked, self.slots.dialog.dropdown_row.clone()) {
+ self.ui_context[self.slots.dialog].dropdown_armed = self.ui_context[self.slots.dialog].dropdown.is_expanded();
+ if let (Some(value), Some(id)) = (picked, self.ui_context[self.slots.dialog].dropdown_row.clone()) {
self.land_dialog_choice(&id, &value);
}
}
@@ -2723,7 +2723,7 @@ impl State {
/// until this very press — which the runner then handed it first (see
/// `Dialog::dropdown_armed`).
pub(crate) fn dialog_dropdown_takes_press(&self) -> bool {
- self.dialog_dropdown_open() || (self.dialog_visible() && self.slots.dialog.dropdown_armed)
+ self.dialog_dropdown_open() || (self.dialog_visible() && self.ui_context[self.slots.dialog].dropdown_armed)
}
/// A press while the dropdown is open: on a row it picks it, on the
@@ -2735,7 +2735,7 @@ impl State {
// The runner handed it the press already: a pick, or the close
// a press anywhere else makes. Land it, and the press is spent.
self.land_dialog_dropdown_pick();
- self.slots.dialog.dropdown_armed = false;
+ self.ui_context[self.slots.dialog].dropdown_armed = false;
return true;
}
if button != MouseButton::Left {
@@ -2787,7 +2787,7 @@ impl State {
};
if landed {
self.refresh_dialog_controls();
- if !self.slots.dialog.slider_dragging() {
+ if !self.ui_context[self.slots.dialog].slider_dragging() {
self.save_settings();
}
return;
@@ -2815,7 +2815,7 @@ impl State {
/// does nothing; the controls re-read, and the selection stays where it
/// was — by Enter or by a click, since both arrive here.
pub(crate) fn take_dialog_pick(&mut self, id: String) {
- let mode = self.slots.dialog.mode;
+ let mode = self.ui_context[self.slots.dialog].mode;
if mode == Mode::Commands && id == ZOOM_ROW_ID {
return;
}
@@ -2831,7 +2831,7 @@ impl State {
}
if mode == Mode::Commands {
if let Some(s) = setting_of_row(&id) {
- let control = self.slots.dialog.rows.iter().find(|r| r.id == id).and_then(|r| r.control.clone());
+ let control = self.ui_context[self.slots.dialog].rows.iter().find(|r| r.id == id).and_then(|r| r.control.clone());
match control {
Some(Control::Toggle(on)) => {
let v = if on { "false" } else { "true" };
@@ -2898,7 +2898,7 @@ impl State {
// relays the panes.
Mode::Rename => {
let Some(target) = self.rename_target.take() else { return };
- let typed = self.slots.dialog.query.clone();
+ let typed = self.ui_context[self.slots.dialog].query.clone();
match self.rename_node(&target, &typed) {
Ok(said) | Err(said) => self.update_status_text(&said),
}
@@ -2939,15 +2939,15 @@ impl State {
/// dialog. Returns whether anything changed.
pub(crate) fn drain_dialog_clicks(&mut self) -> bool {
let mut changed = false;
- if let Some(id) = self.slots.dialog.take_activated() {
+ if let Some(id) = self.ui_context[self.slots.dialog].take_activated() {
self.take_dialog_pick(id);
changed = true;
}
- if let Some((id, v)) = self.slots.dialog.take_slider_change() {
+ if let Some((id, v)) = self.ui_context[self.slots.dialog].take_slider_change() {
self.land_dialog_slider(&id, v);
changed = true;
}
- for (id, hex) in self.slots.dialog.take_color_changes() {
+ for (id, hex) in self.ui_context[self.slots.dialog].take_color_changes() {
if let Some(s) = setting_of_row(&id) {
self.apply_setting(s.label, &hex);
changed = true;
@@ -2987,14 +2987,10 @@ impl State {
// below, or dragging a value past the dialog's edge and letting go
// would close the dialog instead of committing.
if state == ElementState::Released && self.drag_widget == Some(DIALOG_IDX) {
- let ptr = &mut *self.slots.dialog as *mut cce_ui::widget::Adapted<Dialog>;
- unsafe {
- (*ptr).handle_event(&cce_ui::widget::Event::DragEnd, &mut self.ui_context);
- (*ptr).handle_event(
- &cce_ui::widget::Event::MouseButton { button, state, x, y, local_x: x, local_y: y },
- &mut self.ui_context,
- );
- }
+ self.ui_context.lend_h(self.slots.dialog, |d, ui| {
+ d.handle_event(&cce_ui::widget::Event::DragEnd, ui);
+ d.handle_event(&cce_ui::widget::Event::MouseButton { button, state, x, y, local_x: x, local_y: y }, ui);
+ });
self.drag_widget = None;
self.drag_press_cursor = None;
self.drain_dialog_clicks();
@@ -3017,12 +3013,9 @@ impl State {
// A press that took a slider's band arms the same widget drag the
// params pane's sliders arm, so the value follows the pointer
// wherever it goes until the release.
- if state == ElementState::Pressed && self.slots.dialog.slider_dragging() {
+ if state == ElementState::Pressed && self.ui_context[self.slots.dialog].slider_dragging() {
let ev = cce_ui::widget::Event::DragStart { start_x: x, start_y: y };
- let ptr = &mut *self.slots.dialog as *mut cce_ui::widget::Adapted<Dialog>;
- unsafe {
- (*ptr).handle_event(&ev, &mut self.ui_context);
- }
+ self.ui_context.lend_h(self.slots.dialog, |d, ui| d.handle_event(&ev, ui));
self.drag_widget = Some(DIALOG_IDX);
self.drag_press_cursor = Some((x, y));
}
@@ -3042,8 +3035,8 @@ impl State {
Some(cce_ui::widget::SwipeDir::Forward) => {
let (rx, ry, rw, rh) = self.positions[DIALOG_IDX];
let rect = Rect { x: rx, y: ry, width: rw, height: rh };
- let mode = self.slots.dialog.mode;
- let row = self.slots.dialog.row_index_at(rect, x, y).and_then(|i| self.slots.dialog.rows.get(i)).map(|r| r.id.clone());
+ let mode = self.ui_context[self.slots.dialog].mode;
+ let row = self.ui_context[self.slots.dialog].row_index_at(rect, x, y).and_then(|i| self.ui_context[self.slots.dialog].rows.get(i)).map(|r| r.id.clone());
if let Some(id) = row.filter(|id| dialog_row_leads(mode, id)) {
self.take_dialog_pick(id);
return true;
@@ -3072,7 +3065,7 @@ impl State {
/// covered — by the dialog's own plate. Inside a modal the geometry IS
/// the answer.
pub(crate) fn in_dialog_slot(&self, idx: usize, x: f32, y: f32) -> bool {
- if !self.slots.get_dyn(idx).visible() {
+ if !self.slots.get_dyn(&self.ui_context, idx).visible() {
return false;
}
let (rx, ry, rw, rh) = self.positions[idx];
@@ -3090,7 +3083,7 @@ impl State {
/// decided who gets this event, so the claim comes down for the dispatch
/// and goes straight back up.
pub(crate) fn dispatch_uncovered(&mut self, idx: usize, ev: &cce_ui::widget::Event) -> bool {
- self.slots.dialog.set_occluding(false);
+ self.ui_context[self.slots.dialog].set_occluding(false);
// The coverage answer is memoized per point, so lowering the claim is
// not enough — a query from earlier this frame is served from the
// cache, and the engine makes one on every left press
@@ -3102,9 +3095,9 @@ impl State {
if matches!(ev, cce_ui::widget::Event::MouseWheel { .. }) {
self.ui_context.note_scroll_event();
}
- let ptr = self.slots.get_dyn_mut(idx) as *mut (dyn cce_ui::widget::WidgetHost + 'static);
+ let ptr = self.slots.get_dyn_mut(&mut self.ui_context, idx) as *mut (dyn cce_ui::widget::WidgetHost + 'static);
let taken = unsafe { (*ptr).handle_event(ev, &mut self.ui_context) };
- self.slots.dialog.set_occluding(true);
+ self.ui_context[self.slots.dialog].set_occluding(true);
self.ui_context.invalidate_coverage_cache();
taken
}
diff --git a/src/main.rs b/src/main.rs
index 7141819..a6bb6af 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -416,7 +416,7 @@ mod tests {
let collapse = "Collapse".to_string();
// The spreadsheet: the plate menu alone.
- let (x, y, w, h) = state.slots.get_dyn(SPREADSHEET_IDX).rect();
+ let (x, y, w, h) = state.slots.get_dyn(&state.ui_context, SPREADSHEET_IDX).rect();
press_at(&mut state, x + w - 6.0, y + h - 6.0, MouseButton::Right);
assert_eq!(state.plate_menu_slot, Some(SPREADSHEET_IDX));
assert!(state.plate_menu_actions.contains(&PlateMenuAction::Collapse));
@@ -431,7 +431,7 @@ mod tests {
state.apply_action(crate::app::McpAction::Select { slot }, &mut redraw).unwrap();
state.apply_layout();
state.params_plate = true;
- let (x, y, w, h) = state.slots.get_dyn(PARAM_IDX).rect();
+ let (x, y, w, h) = state.slots.get_dyn(&state.ui_context, PARAM_IDX).rect();
let free = (0..(h as i32))
.rev()
.map(|dy| (x + w - 6.0, y + dy as f32))
@@ -489,7 +489,7 @@ mod tests {
state.execute_menu_action("Show Playbar Pane");
}
let (x, y, w, h) = state.positions[SPREADSHEET_IDX];
- let table = state.slots.spreadsheet().content_width().ceil();
+ let table = state.slots.spreadsheet(&state.ui_context).content_width().ceil();
assert_eq!((x, w), (18.0, table.clamp(State::SPREADSHEET_MIN_W, 1600.0 - 36.0)), "as wide as its table, within the window");
assert_eq!(y + h, state.positions[PLAYBAR_IDX].1 - 18.0, "a gap above the playbar");
@@ -544,19 +544,19 @@ mod tests {
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(SPREADSHEET_IDX).rect();
+ let (_, _, _, full_h) = state.slots.get_dyn(&state.ui_context, SPREADSHEET_IDX).rect();
assert!(full_h > STUB_H, "the plate starts taller than a stub");
state.set_pane_collapsed(SPREADSHEET_IDX, true);
- let (_, _, _, stub_h) = state.slots.get_dyn(SPREADSHEET_IDX).rect();
+ let (_, _, _, stub_h) = state.slots.get_dyn(&state.ui_context, SPREADSHEET_IDX).rect();
assert_eq!(stub_h, STUB_H, "collapsed plate is not the stub height");
// A right press on the stub offers Expand; a left press expands it.
- let (x, y, w, h) = state.slots.get_dyn(SPREADSHEET_IDX).rect();
+ let (x, y, w, h) = state.slots.get_dyn(&state.ui_context, 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(SPREADSHEET_IDX).rect();
+ let (_, _, _, back_h) = state.slots.get_dyn(&state.ui_context, SPREADSHEET_IDX).rect();
assert_eq!(back_h, full_h, "expanding did not restore the plate height");
}
@@ -573,10 +573,10 @@ mod tests {
child.detached_pane = Some(PARAM_IDX);
child.resize(600.0, 400.0, 1.0);
for i in 0..WIDGET_COUNT {
- let visible = child.slots.get_dyn(i).visible();
+ let visible = child.slots.get_dyn(&child.ui_context, i).visible();
assert_eq!(visible, i == PARAM_IDX, "slot {i} visibility in a detached window");
}
- let (x, y, w, h) = child.slots.get_dyn(PARAM_IDX).rect();
+ let (x, y, w, h) = child.slots.get_dyn(&child.ui_context, PARAM_IDX).rect();
assert_eq!((x, y), (DETACHED_MARGIN, DETACHED_MARGIN));
assert_eq!(w, 600.0 - 2.0 * DETACHED_MARGIN);
assert_eq!(h, 400.0 - 2.0 * DETACHED_MARGIN);
@@ -586,17 +586,17 @@ mod tests {
use crate::plate_menu::STUB_H;
let mut parent = State::new(false);
parent.resize(1600.0, 900.0, 1.0);
- assert!(parent.slots.get_dyn(PARAM_IDX).visible(), "params starts in the parent");
- let (_, _, _, full_h) = parent.slots.get_dyn(PARAM_IDX).rect();
+ assert!(parent.slots.get_dyn(&parent.ui_context, PARAM_IDX).visible(), "params starts in the parent");
+ let (_, _, _, full_h) = parent.slots.get_dyn(&parent.ui_context, PARAM_IDX).rect();
parent.detached_panes[PARAM_IDX] = true;
parent.rebuild_positions();
parent.apply_layout();
- let (_, _, _, stub_h) = parent.slots.get_dyn(PARAM_IDX).rect();
+ let (_, _, _, stub_h) = parent.slots.get_dyn(&parent.ui_context, PARAM_IDX).rect();
assert!(full_h > stub_h, "detaching did not shrink the pane in the parent");
assert_eq!(stub_h, STUB_H, "the parent's leftover is not a stub");
- let (sx, sy, sw, sh) = parent.slots.get_dyn(PARAM_IDX).rect();
+ let (sx, sy, sw, sh) = parent.slots.get_dyn(&parent.ui_context, PARAM_IDX).rect();
press_at(&mut parent, sx + sw * 0.5, sy + sh * 0.5, cce_ui::widget::MouseButton::Right);
assert_eq!(parent.plate_menu_actions, vec![crate::plate_menu::PlateMenuAction::Reattach],
"the stub's right press offers Reattach — nothing else can reattach the pane");
@@ -604,7 +604,7 @@ mod tests {
// Collapsed and detached stubs must not read the same.
let label = parent.pane_stub_label(PARAM_IDX).expect("a detached pane is stubbed");
assert!(label.contains("detached"), "stub does not say the pane is detached: {label}");
- assert!(parent.slots.get_dyn(VIEWPORT_IDX).visible(), "the rest of the parent survived");
+ assert!(parent.slots.get_dyn(&parent.ui_context, VIEWPORT_IDX).visible(), "the rest of the parent survived");
}
/// The network has no plate, so a right press on empty
@@ -1096,7 +1096,7 @@ mod tests {
use crate::slots::PARAM_IDX;
let mut state = State::new(false);
state.resize(1600.0, 900.0, 1.0);
- let (_, _, _, full_h) = state.slots.get_dyn(PARAM_IDX).rect();
+ let (_, _, _, full_h) = state.slots.get_dyn(&state.ui_context, PARAM_IDX).rect();
state.detached_panes[PARAM_IDX] = true;
state.rebuild_positions();
@@ -1110,7 +1110,7 @@ mod tests {
state.reattach_plate(PARAM_IDX);
assert!(!state.pane_is_detached(PARAM_IDX), "still marked detached after reattach");
assert!(state.pane_stub_label(PARAM_IDX).is_none(), "still a stub after reattach");
- let (_, _, _, back_h) = state.slots.get_dyn(PARAM_IDX).rect();
+ let (_, _, _, back_h) = state.slots.get_dyn(&state.ui_context, PARAM_IDX).rect();
assert_eq!(back_h, full_h, "reattach did not restore the pane height");
}
@@ -1180,7 +1180,7 @@ mod tests {
// which are on the scene and not plates of a dock.
let idx = PLATE_SLOTS.iter().copied()
.filter(|&i| i != crate::slots::NETWORK_PANEL_IDX && i != crate::slots::PARAM_IDX)
- .find(|&i| state.slots.get_dyn(i).visible())
+ .find(|&i| state.slots.get_dyn(&state.ui_context, i).visible())
.expect("some plate is shown at this size");
state.open_plate_menu(idx);
@@ -1516,7 +1516,7 @@ mod tests {
state.resize(1600.0, 900.0, 1.0);
state.execute_menu_action("Show Spreadsheet Pane");
assert_eq!(state.positions[NETWORK_PANEL_IDX], (0.0, HEADER_H, 1600.0, state.body_h()));
- assert!(state.slots.get_dyn(crate::slots::CONTENT_IDX).visible(), "shown");
+ assert!(state.slots.get_dyn(&state.ui_context, crate::slots::CONTENT_IDX).visible(), "shown");
assert_eq!(state.positions[SPREADSHEET_IDX].0, 18.0, "the spreadsheet flush left, a gap in");
state.set_pane_collapsed(NETWORK_PANEL_IDX, true);
@@ -1539,7 +1539,7 @@ mod tests {
let x = (0..40)
.map(|k| bx + 200.0 + k as f32 * 5.0)
.find(|&x| {
- !state.slots.get_dyn(BREADCRUMB_IDX).hit_test(x, y, &state.ui_context)
+ !state.slots.get_dyn(&state.ui_context, BREADCRUMB_IDX).hit_test(x, y, &state.ui_context)
&& state.graph().node_at(x, y).is_none()
&& !state.over_floating_pane_at(x, y)
})
@@ -1980,14 +1980,14 @@ mod tests {
let _ = fs::remove_dir_all(&dir);
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.ui_context[state.slots.content].inner().wire_style(), WireStyle::Bezier);
assert_eq!(state.display_settings().viewport.node_wire_style, "bezier");
state.save_to_file(&dir).expect("save");
state.apply_setting("Node Wire Style", "Straight");
assert!(state.has_unsaved_changes(), "a wire style is an edit to the file");
state.load_from_file(&dir).expect("load");
- assert_eq!(state.slots.content.inner().wire_style(), WireStyle::Bezier, "the file's style comes back");
+ assert_eq!(state.ui_context[state.slots.content].inner().wire_style(), WireStyle::Bezier, "the file's style comes back");
// A file that names no style hands the choice back to the config.
let state_json = dir.join("state.json");
@@ -1995,8 +1995,8 @@ mod tests {
v["view_state"]["display"]["viewport"].as_object_mut().unwrap().remove("node_wire_style");
fs::write(&state_json, serde_json::to_string(&v).unwrap()).unwrap();
state.load_from_file(&dir).expect("load an older save");
- assert_eq!(state.slots.content.inner().chosen_wire_style(), None);
- assert_eq!(state.slots.content.inner().wire_style(), WireStyle::configured());
+ assert_eq!(state.ui_context[state.slots.content].inner().chosen_wire_style(), None);
+ assert_eq!(state.ui_context[state.slots.content].inner().wire_style(), WireStyle::configured());
let _ = fs::remove_dir_all(dir.parent().unwrap());
}
@@ -2034,7 +2034,7 @@ mod tests {
state.apply_action(McpAction::Select { slot }, &mut redraw).unwrap();
state.params_plate = true;
state.positions[crate::slots::PARAM_IDX] = (800.0, 200.0, 200.0, 690.0);
- state.slots.get_dyn_mut(crate::slots::PARAM_IDX).set_rect(800.0, 200.0, 200.0, 690.0);
+ state.slots.get_dyn_mut(&mut state.ui_context, crate::slots::PARAM_IDX).set_rect(800.0, 200.0, 200.0, 690.0);
let plate = state.params_claim();
assert!(plate.1 == 200.0 && plate.3 > 100.0 && plate.3 < 690.0, "the plate fits the rows: {plate:?}");
let in_plate = |x: f32, y: f32| x >= 800.0 && x < 1000.0 && y + 6.0 >= plate.1 && y + 6.0 < plate.1 + plate.3;
@@ -2049,7 +2049,7 @@ mod tests {
assert!(state.point_number_labels().len() >= fewer.len());
state.params_plate = true;
state.positions[crate::slots::PARAM_IDX] = (px, py, pw, ph);
- state.slots.get_dyn_mut(crate::slots::PARAM_IDX).set_rect(px, py, pw, ph);
+ state.slots.get_dyn_mut(&mut state.ui_context, crate::slots::PARAM_IDX).set_rect(px, py, pw, ph);
assert_eq!(state.point_number_labels().len(), all.len());
}
@@ -2075,9 +2075,9 @@ mod tests {
assert_eq!(flags, (false, false, true, true), "its bottom corners are the window's");
// A shelf of the window's edge: the transport stands clear of the
// window's lip, which is drawn over the shelf's sides and bottom.
- assert_eq!(state.slots.playbar.inner().frame, lip);
+ assert_eq!(state.ui_context[state.slots.playbar].inner().frame, lip);
let pb_rect = cce_ui::scene::layout::Rect { x: pb.0, y: pb.1, width: pb.2, height: pb.3 };
- let prev = state.slots.playbar.inner().transport_button_rect(pb_rect, -1).expect("the step buttons are on");
+ let prev = state.ui_context[state.slots.playbar].inner().transport_button_rect(pb_rect, -1).expect("the step buttons are on");
assert!(prev.x >= lip, "clear of the left lip: {prev:?}");
assert!(prev.y + prev.height <= 900.0 - STATUS_H - lip, "clear of the bottom lip: {prev:?}");
// (The network has no plate: it spans the window, under the shelf.)
@@ -2161,7 +2161,7 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
let (hx, hy, hw, hh) = state.positions[PARAM_IDX];
- let pb = state.slots.get_dyn(PARAM_IDX).as_any().downcast_ref::<cce_ui::widget::ParametersBg>().unwrap();
+ let pb = state.slots.get_dyn(&state.ui_context, PARAM_IDX).as_any().downcast_ref::<cce_ui::widget::ParametersBg>().unwrap();
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");
@@ -3064,7 +3064,7 @@ mod tests {
state.cursor_x = context_menu::x() + 20.0;
state.cursor_y = context_menu::row_y(i) + context_menu::ROW_H * 0.5;
state.handle_event(&WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(0.0, 6.0) });
- assert_eq!(state.slots.playbar.inner().fps, 30.0);
+ assert_eq!(state.ui_context[state.slots.playbar].inner().fps, 30.0);
assert!(state.playbar_menu_open(), "a slider row keeps the menu up");
let kdl = fs::read_to_string(crate::app::DesignSettings::file_path()).expect("saved");
assert_eq!(crate::app::DesignSettings::from_kdl_str(&kdl).playbar_fps, 30.0, "persisted");
@@ -3072,18 +3072,18 @@ mod tests {
// The range: an end moved past the other carries it along, and the
// playhead stays inside.
- state.slots.playbar.inner_mut().current_frame = 200.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 200.0;
let i = actions.iter().position(|a| *a == A::EndFrameSlider).unwrap();
state.cursor_y = context_menu::row_y(i) + context_menu::ROW_H * 0.5;
state.handle_event(&WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(0.0, -100.0) });
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert_eq!(pb.end_frame, 140.0, "{}", pb.end_frame);
assert_eq!(pb.current_frame, 140.0, "the playhead is kept inside");
assert!(state.has_unsaved_changes(), "the range is the project's");
let i = actions.iter().position(|a| *a == A::StartFrameSlider).unwrap();
state.cursor_y = context_menu::row_y(i) + context_menu::ROW_H * 0.5;
state.handle_event(&WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(0.0, 150.0) });
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert_eq!((pb.start_frame, pb.end_frame), (151.0, 152.0), "the far end is carried a frame ahead of the near");
// A command row runs and closes.
@@ -3091,7 +3091,7 @@ mod tests {
state.cursor_y = context_menu::row_y(i) + context_menu::ROW_H * 0.5;
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
assert!(!state.playbar_menu_open());
- assert!(!state.slots.playbar.inner().repeat, "Repeat was flipped");
+ assert!(!state.ui_context[state.slots.playbar].inner().repeat, "Repeat was flipped");
// The range rides the project file.
state.save_to_file(&dir).expect("save");
@@ -3099,7 +3099,7 @@ mod tests {
let mut again = State::new(false);
again.resize(1600.0, 900.0, 1.0);
again.load_from_file(&dir).expect("load");
- let pb = again.slots.playbar.inner();
+ let pb = again.ui_context[again.slots.playbar].inner();
assert_eq!((pb.start_frame, pb.end_frame), (151.0, 152.0));
let _ = fs::remove_dir_all(&dir);
}
@@ -3122,29 +3122,29 @@ mod tests {
state.apply_layout();
let (px, py, pw, ph) = state.positions[PLAYBAR_IDX];
let rect = Rect { x: px, y: py, width: pw, height: ph };
- assert!(state.slots.playbar.inner().step_buttons, "on by default");
+ assert!(state.ui_context[state.slots.playbar].inner().step_buttons, "on by default");
let press = |state: &mut State, r: Rect| {
let (x, y) = (r.x + r.width * 0.5, r.y + r.height * 0.5);
state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } });
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Released, button: MouseButton::Left });
};
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
let prev = pb.transport_button_rect(rect, -1).expect("a Previous Frame button");
let play = pb.transport_button_rect(rect, 0).expect("a play button");
let next = pb.transport_button_rect(rect, 1).expect("a Next Frame button");
assert!(prev.x + prev.width < play.x && play.x + play.width < next.x, "|< > >| left to right");
- state.slots.playbar.inner_mut().current_frame = 10.4;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 10.4;
press(&mut state, next);
- assert_eq!(state.slots.playbar.inner().current_frame, 11.0, "a whole frame on, off the rounded one");
+ assert_eq!(state.ui_context[state.slots.playbar].inner().current_frame, 11.0, "a whole frame on, off the rounded one");
press(&mut state, prev);
press(&mut state, prev);
- assert_eq!(state.slots.playbar.inner().current_frame, 9.0);
- assert!(!state.slots.playbar.inner().playing, "a step does not start playback");
- state.slots.playbar.inner_mut().current_frame = 1.0;
+ assert_eq!(state.ui_context[state.slots.playbar].inner().current_frame, 9.0);
+ assert!(!state.ui_context[state.slots.playbar].inner().playing, "a step does not start playback");
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 1.0;
press(&mut state, prev);
- assert_eq!(state.slots.playbar.inner().current_frame, 1.0, "held inside the range");
+ assert_eq!(state.ui_context[state.slots.playbar].inner().current_frame, 1.0, "held inside the range");
// The menu's switch hides them, and is saved.
let (options, actions) = state.playbar_menu_rows();
@@ -3157,7 +3157,7 @@ mod tests {
state.cursor_y = context_menu::row_y(i) + context_menu::ROW_H * 0.5;
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
assert!(!state.playbar_menu_open());
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert!(!pb.step_buttons);
assert!(pb.transport_button_rect(rect, -1).is_none() && pb.transport_button_rect(rect, 1).is_none());
assert_eq!(pb.transport_button_rect(rect, 0).unwrap().x, prev.x, "the play button takes the first place");
@@ -3165,11 +3165,11 @@ mod tests {
assert!(!crate::app::DesignSettings::from_kdl_str(&kdl).playbar_step_buttons, "persisted");
// Where Next Frame stood is the track now: a press there scrubs.
- state.slots.playbar.inner_mut().current_frame = 50.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 50.0;
press(&mut state, next);
- assert_ne!(state.slots.playbar.inner().current_frame, 51.0, "no step button there any more");
+ assert_ne!(state.ui_context[state.slots.playbar].inner().current_frame, 51.0, "no step button there any more");
state.run_command("toggle_playbar_step_buttons");
- assert!(state.slots.playbar.inner().step_buttons);
+ assert!(state.ui_context[state.slots.playbar].inner().step_buttons);
}
/// Camera Pivot Size is a slider under Show Camera Pivot, over 0–1:
@@ -3503,7 +3503,7 @@ mod tests {
let mut state = State::new(false);
{
- let pb = state.slots.playbar.inner_mut();
+ let pb = state.ui_context[state.slots.playbar].inner_mut();
pb.start_frame = 1.0;
pb.end_frame = 48.0;
pb.current_frame = 17.5;
@@ -3512,20 +3512,20 @@ mod tests {
}
assert!(state.run_command("frame_start"));
{
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert!(!pb.playing, "a moving timeline stops");
assert_eq!(pb.current_frame, 1.0, "and lands on the start frame");
}
// Stopped, mid-timeline: a plain jump.
- state.slots.playbar.inner_mut().current_frame = 30.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 30.0;
state.run_command("frame_start");
- assert_eq!(state.slots.playbar.inner().current_frame, 1.0);
- assert!(!state.slots.playbar.inner().playing);
+ assert_eq!(state.ui_context[state.slots.playbar].inner().current_frame, 1.0);
+ assert!(!state.ui_context[state.slots.playbar].inner().playing);
// Whatever the start frame is.
- state.slots.playbar.inner_mut().start_frame = 5.0;
- state.slots.playbar.inner_mut().current_frame = 30.0;
+ state.ui_context[state.slots.playbar].inner_mut().start_frame = 5.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 30.0;
state.run_command("frame_start");
- assert_eq!(state.slots.playbar.inner().current_frame, 5.0);
+ assert_eq!(state.ui_context[state.slots.playbar].inner().current_frame, 5.0);
}
/// Either play toggle pauses a moving timeline; direction only chooses
@@ -3537,25 +3537,25 @@ mod tests {
state.execute_action(Action::PlayPauseReverse);
{
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert!(pb.playing && pb.reversed, "Down from stopped plays in reverse");
}
state.execute_action(Action::PlayPause);
- assert!(!state.slots.playbar.inner().playing, "Up while reverse-playing pauses");
+ assert!(!state.ui_context[state.slots.playbar].inner().playing, "Up while reverse-playing pauses");
state.execute_action(Action::PlayPause);
{
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert!(pb.playing && !pb.reversed, "Up from stopped plays forward");
}
state.execute_action(Action::PlayPauseReverse);
- assert!(!state.slots.playbar.inner().playing, "Down while forward-playing pauses");
+ assert!(!state.ui_context[state.slots.playbar].inner().playing, "Down while forward-playing pauses");
state.execute_action(Action::PlayPauseReverse);
- assert!(state.slots.playbar.inner().reversed, "Down from stopped is reverse again");
+ assert!(state.ui_context[state.slots.playbar].inner().reversed, "Down from stopped is reverse again");
state.execute_action(Action::PlayPauseReverse);
- assert!(!state.slots.playbar.inner().playing, "same-direction press pauses");
+ assert!(!state.ui_context[state.slots.playbar].inner().playing, "same-direction press pauses");
{
- let pb = state.slots.playbar.inner_mut();
+ let pb = state.ui_context[state.slots.playbar].inner_mut();
pb.playing = true;
pb.reversed = true;
pb.repeat = true;
@@ -3563,13 +3563,13 @@ mod tests {
}
let rect = cce_ui::scene::layout::Rect { x: 0.0, y: 0.0, width: 100.0, height: 30.0 };
let moved =
- cce_ui::widget::Input::tick(state.slots.playbar.inner_mut(), 0.1, rect);
+ cce_ui::widget::Input::tick(state.ui_context[state.slots.playbar].inner_mut(), 0.1, rect);
assert!(moved, "reverse playback advances the frame");
- assert_eq!(state.slots.playbar.inner().current_frame, 1.0, "the start frame is played, not stepped over");
- cce_ui::widget::Input::tick(state.slots.playbar.inner_mut(), 0.1, rect);
- let f = state.slots.playbar.inner().current_frame;
+ assert_eq!(state.ui_context[state.slots.playbar].inner().current_frame, 1.0, "the start frame is played, not stepped over");
+ cce_ui::widget::Input::tick(state.ui_context[state.slots.playbar].inner_mut(), 0.1, rect);
+ let f = state.ui_context[state.slots.playbar].inner().current_frame;
assert_eq!(f.round(), 240.0, "running off the start wraps to the end, got {f}");
- assert!(state.slots.playbar.inner().playing, "the wrap does not stop playback");
+ assert!(state.ui_context[state.slots.playbar].inner().playing, "the wrap does not stop playback");
}
/// Playback plays every frame: a tick that came late moves the shown
@@ -6962,9 +6962,7 @@ mod tests {
state.sync_parameters_pane();
{
- let dropdown = state
- .slots
- .param
+ let dropdown = state.ui_context[state.slots.param]
.inner_mut()
.choices
.iter_mut()
@@ -7657,9 +7655,7 @@ mod tests {
state.wire_color = [0.2, 0.6, 0.9];
state.run_command("command_palette");
let id = setting_row_id("Wireframe Color");
- let row = state
- .slots
- .dialog
+ let row = state.ui_context[state.slots.dialog]
.rows
.iter()
.find(|r| r.id == id)
@@ -7670,7 +7666,7 @@ mod tests {
// selector stands behind it at the same value.
let hex = crate::project::color_to_hex([0.2, 0.6, 0.9]);
assert_eq!(row.control, Some(Control::Color { hex: hex.clone() }));
- let sel = state.slots.dialog.color_selector(&id).expect("a colour selector behind the row");
+ let sel = state.ui_context[state.slots.dialog].color_selector(&id).expect("a colour selector behind the row");
assert_eq!(sel.get_value_string().as_deref(), Some(hex.as_str()));
let s = SETTINGS.iter().find(|s| s.label == "Wireframe Color").unwrap();
assert_eq!(s.owner, Owner::Field("wire_color"));
@@ -7684,7 +7680,7 @@ mod tests {
assert_eq!(state.wire_color, [0.0, 0.0, 0.0], "the colour row writes the live wire colour");
assert!(state.wire_single_color, "a colour edit turns single-colour mode on");
assert!(state.dialog_visible());
- let row = state.slots.dialog.rows.iter().find(|r| r.id == id).unwrap();
+ let row = state.ui_context[state.slots.dialog].rows.iter().find(|r| r.id == id).unwrap();
assert_eq!(row.control, Some(Control::Color { hex: "#000000".into() }));
// And the switch is a command row of its own, flipped in place.
@@ -8018,12 +8014,12 @@ mod tests {
// A camera node is a row of the palette, found by its name.
state.open_dialog();
- state.slots.dialog.query = "camera1".to_string();
+ state.ui_context[state.slots.dialog].query = "camera1".to_string();
state.refresh_dialog_rows();
let id = format!("{CAMERA_ROW_PREFIX}camera1");
- let row = state.slots.dialog.rows.iter().position(|r| r.id == id).expect("no row for camera1");
- assert_eq!(state.slots.dialog.rows[row].label, "Camera: camera1");
- state.slots.dialog.selected = row;
+ let row = state.ui_context[state.slots.dialog].rows.iter().position(|r| r.id == id).expect("no row for camera1");
+ assert_eq!(state.ui_context[state.slots.dialog].rows[row].label, "Camera: camera1");
+ state.ui_context[state.slots.dialog].selected = row;
state.dialog_key_input(&key_press(Key::Named(NamedKey::Enter)));
assert!(!state.dialog_visible());
assert_eq!(state.active_camera, "camera1");
@@ -8656,19 +8652,19 @@ mod tests {
// From the palette: the command turns it into the groups list.
state.run_command("command_palette");
- assert_eq!(state.slots.dialog.mode, Mode::Commands);
+ assert_eq!(state.ui_context[state.slots.dialog].mode, Mode::Commands);
assert!(state.run_command("group_markers"));
assert!(state.dialog_visible());
- assert_eq!(state.slots.dialog.mode, Mode::Groups);
- let row = state.slots.dialog.rows.iter().position(|r| r.id == format!("{GROUP_ROW_PREFIX}five")).expect("a row for the group");
- assert_eq!(state.slots.dialog.rows[row].chord, "5 points");
- assert_eq!(state.slots.dialog.rows[row].toggle(), Some(false));
+ assert_eq!(state.ui_context[state.slots.dialog].mode, Mode::Groups);
+ let row = state.ui_context[state.slots.dialog].rows.iter().position(|r| r.id == format!("{GROUP_ROW_PREFIX}five")).expect("a row for the group");
+ assert_eq!(state.ui_context[state.slots.dialog].rows[row].chord, "5 points");
+ assert_eq!(state.ui_context[state.slots.dialog].rows[row].toggle(), Some(false));
// Enter on the row marks the group, and the list stays up.
- state.slots.dialog.selected = row;
+ state.ui_context[state.slots.dialog].selected = row;
state.dialog_key_input(&key_press(Key::Named(NamedKey::Enter)));
assert!(state.dialog_visible(), "a switch is worked in place");
- assert_eq!(state.slots.dialog.rows[row].toggle(), Some(true));
+ assert_eq!(state.ui_context[state.slots.dialog].rows[row].toggle(), Some(true));
assert!(state.group_marked("five"));
assert!(!state.marked_group_instances.is_empty() && state.marked_groups_dirty, "the markers are staged");
let one = state.marked_group_instances.len();
@@ -8694,7 +8690,7 @@ mod tests {
assert!(!state.group_marked("five"));
assert!(state.marked_group_instances.is_empty());
state.dialog_key_input(&typed("z"));
- assert!(state.slots.dialog.rows.is_empty(), "no group matches");
+ assert!(state.ui_context[state.slots.dialog].rows.is_empty(), "no group matches");
state.dialog_key_input(&key_press(Key::Named(NamedKey::Escape)));
assert!(!state.dialog_visible());
@@ -8717,9 +8713,9 @@ mod tests {
let sibling = state.current_dir().children.iter().position(|c| c.node_type == "box").unwrap();
let old = state.current_dir().children[sphere].name.clone();
let other = state.current_dir().children[sibling].name.clone();
- let row = |state: &State| state.slots.dialog.rows.iter().map(|r| r.label.clone()).collect::<Vec<_>>();
+ let row = |state: &State| state.ui_context[state.slots.dialog].rows.iter().map(|r| r.label.clone()).collect::<Vec<_>>();
let retype = |state: &mut State, name: &str| {
- while !state.slots.dialog.query.is_empty() {
+ while !state.ui_context[state.slots.dialog].query.is_empty() {
state.dialog_key_input(&key_press(Key::Named(NamedKey::Backspace)));
}
for c in name.chars() {
@@ -8733,9 +8729,9 @@ mod tests {
state.run_node_menu_action(sphere, crate::app::NodeMenuAction::Rename);
assert!(state.dialog_visible());
- assert_eq!(state.slots.dialog.mode, Mode::Rename);
- assert_eq!(state.slots.dialog.query, old, "the dialog opens holding the name");
- assert_eq!(state.slots.dialog.rows[0].id, RENAME_ROW_ID);
+ assert_eq!(state.ui_context[state.slots.dialog].mode, Mode::Rename);
+ assert_eq!(state.ui_context[state.slots.dialog].query, old, "the dialog opens holding the name");
+ assert_eq!(state.ui_context[state.slots.dialog].rows[0].id, RENAME_ROW_ID);
// Its own name, a sibling's, and none: each is said, and Enter on
// it writes nothing.
@@ -8761,8 +8757,8 @@ mod tests {
// The command renames the selection, and says so when there is none.
state.graph_mut().set_selected_node(Some(sphere));
assert!(state.run_command("rename_node"));
- assert_eq!(state.slots.dialog.mode, Mode::Rename);
- assert_eq!(state.slots.dialog.query, old);
+ assert_eq!(state.ui_context[state.slots.dialog].mode, Mode::Rename);
+ assert_eq!(state.ui_context[state.slots.dialog].query, old);
state.dialog_key_input(&key_press(Key::Named(NamedKey::Escape)));
assert_eq!(state.current_dir().children[sphere].name, old, "Escape renames nothing");
@@ -15282,7 +15278,7 @@ mod tests {
// rows, and the network pane's zoom slider row when that pane is
// focused (it is by default).
assert_eq!(
- state.slots.dialog.rows.iter().filter(|r| !r.id.starts_with(crate::dialog::SETTING_ROW_PREFIX) && r.id != crate::dialog::ZOOM_ROW_ID && !r.id.starts_with(crate::dialog::CAMERA_ROW_PREFIX)).count(),
+ state.ui_context[state.slots.dialog].rows.iter().filter(|r| !r.id.starts_with(crate::dialog::SETTING_ROW_PREFIX) && r.id != crate::dialog::ZOOM_ROW_ID && !r.id.starts_with(crate::dialog::CAMERA_ROW_PREFIX)).count(),
crate::command::COMMANDS.len(),
"an empty query lists everything"
);
@@ -15301,14 +15297,14 @@ mod tests {
for c in ["d", "e", "s", "e", "l"] {
state.dialog_key_input(&typed(c));
}
- assert_eq!(state.slots.dialog.query, "desel");
+ assert_eq!(state.ui_context[state.slots.dialog].query, "desel");
assert_eq!(
- state.slots.dialog.selected_id(),
+ state.ui_context[state.slots.dialog].selected_id(),
Some("deselect"),
"rows: {:?}",
- state.slots.dialog.rows.iter().map(|r| r.label.as_str()).collect::<Vec<_>>()
+ state.ui_context[state.slots.dialog].rows.iter().map(|r| r.label.as_str()).collect::<Vec<_>>()
);
- let row = &state.slots.dialog.rows[state.slots.dialog.selected];
+ let row = &state.ui_context[state.slots.dialog].rows[state.ui_context[state.slots.dialog].selected];
assert_eq!(row.toggle(), None, "Deselect runs and is done; it draws no switch");
state.dialog_key_input(&key_press(Key::Named(NamedKey::Enter)));
@@ -15327,24 +15323,24 @@ mod tests {
for c in ["s", "q", "u", "a"] {
state.dialog_key_input(&typed(c));
}
- assert_eq!(state.slots.dialog.selected_id(), Some("toggle_square_viewport"));
+ assert_eq!(state.ui_context[state.slots.dialog].selected_id(), Some("toggle_square_viewport"));
let before = state.square_viewport;
- let row = state.slots.dialog.rows[state.slots.dialog.selected].clone();
+ let row = state.ui_context[state.slots.dialog].rows[state.ui_context[state.slots.dialog].selected].clone();
assert_eq!(row.toggle(), Some(before), "the switch shows the live value");
state.dialog_key_input(&key_press(Key::Named(NamedKey::Enter)));
assert_eq!(state.square_viewport, !before, "Enter ran the command");
assert!(state.dialog_visible(), "and the dialog stayed up");
- assert_eq!(state.slots.dialog.query, "squa", "with its query intact");
- assert_eq!(state.slots.dialog.selected_id(), Some("toggle_square_viewport"), "and its selection");
- let row = &state.slots.dialog.rows[state.slots.dialog.selected];
+ assert_eq!(state.ui_context[state.slots.dialog].query, "squa", "with its query intact");
+ assert_eq!(state.ui_context[state.slots.dialog].selected_id(), Some("toggle_square_viewport"), "and its selection");
+ let row = &state.ui_context[state.slots.dialog].rows[state.ui_context[state.slots.dialog].selected];
assert_eq!(row.toggle(), Some(!before), "the switch moved with the value");
// And back again, without leaving.
state.dialog_key_input(&key_press(Key::Named(NamedKey::Enter)));
assert_eq!(state.square_viewport, before);
assert!(state.dialog_visible());
- assert_eq!(state.slots.dialog.rows[state.slots.dialog.selected].toggle(), Some(before));
+ assert_eq!(state.ui_context[state.slots.dialog].rows[state.ui_context[state.slots.dialog].selected].toggle(), Some(before));
// A click on the row is the same pick as Enter.
state.take_dialog_pick("toggle_square_viewport".to_string());
@@ -15394,7 +15390,7 @@ mod tests {
state.focused_pane = crate::slots::LEFT_MENUBAR_IDX;
state.run_command("command_palette");
assert!(state.dialog_visible());
- let rows = &state.slots.dialog.rows;
+ let rows = &state.ui_context[state.slots.dialog].rows;
assert_eq!(rows[0].id, ZOOM_ROW_ID, "the zoom row heads the network list");
assert!((rows[0].slider_value().unwrap() - state.zoom_percent()).abs() < 1e-3);
assert!(rows.iter().filter(|r| r.id == ZOOM_ROW_ID).count() == 1);
@@ -15404,7 +15400,7 @@ mod tests {
state.dialog_key_input(&key_press(Key::Named(NamedKey::ArrowRight)));
assert!(state.dialog_visible());
assert!(state.zoom_percent() > before, "right arrow zooms in");
- assert!((state.slots.dialog.rows[0].slider_value().unwrap() - state.zoom_percent()).abs() < 1e-3);
+ assert!((state.ui_context[state.slots.dialog].rows[0].slider_value().unwrap() - state.zoom_percent()).abs() < 1e-3);
state.dialog_key_input(&key_press(Key::Named(NamedKey::ArrowLeft)));
assert!((state.zoom_percent() - before).abs() < 0.5, "left arrow zooms back out");
@@ -15418,20 +15414,20 @@ mod tests {
assert!((state.zoom_percent() - 150.0).abs() < 0.5);
state.set_zoom_percent(100_000.0);
assert!((state.grid_pitch_x - crate::app::MAX_PITCH_X).abs() < 0.5, "clamped to the max pitch");
- assert!((state.slots.dialog.rows[0].slider_value().unwrap() - state.zoom_percent()).abs() < 1e-3);
+ assert!((state.ui_context[state.slots.dialog].rows[0].slider_value().unwrap() - state.zoom_percent()).abs() < 1e-3);
state.set_zoom_percent(100.0);
// A query that does not match "Zoom" drops the row.
for c in ["s", "a", "v"] {
state.dialog_key_input(&key_press(Key::Character(c.into())));
}
- assert!(state.slots.dialog.rows.iter().all(|r| r.id != ZOOM_ROW_ID));
+ assert!(state.ui_context[state.slots.dialog].rows.iter().all(|r| r.id != ZOOM_ROW_ID));
state.close_dialog();
// Another pane focused: no slider row at all.
state.focused_pane = crate::slots::RIGHT_MENUBAR_IDX;
state.run_command("command_palette");
- assert!(state.slots.dialog.rows.iter().all(|r| r.id != ZOOM_ROW_ID));
+ assert!(state.ui_context[state.slots.dialog].rows.iter().all(|r| r.id != ZOOM_ROW_ID));
}
/// At the widget: a press on the slider row's band takes hold, jumps the
@@ -15568,7 +15564,7 @@ mod tests {
let (x, y, w, h) = state.positions[NETWORK_PANEL_IDX];
assert!(w > 0.0 && h > 0.0, "the network plate is laid out");
let (cx, cy) = (x + w * 0.5, y + h * 0.5);
- let hovered = |state: &State| state.slots.get_dyn(NETWORK_PANEL_IDX).base().hovered;
+ let hovered = |state: &State| state.slots.get_dyn(&state.ui_context, NETWORK_PANEL_IDX).base().hovered;
let moved = |state: &mut State, x: f32, y: f32| {
state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } });
};
@@ -15628,11 +15624,11 @@ mod tests {
assert!((saved(&path) - 1.0).abs() < 1e-3);
state.open_dialog();
- state.slots.dialog.query = "geometry opacity".into();
+ state.ui_context[state.slots.dialog].query = "geometry opacity".into();
state.refresh_dialog_rows();
let row = setting_row_id("Geometry Opacity");
- assert_eq!(state.slots.dialog.rows.first().map(|r| r.id.as_str()), Some(row.as_str()), "the setting row ranks first");
- let has_toggle = state.slots.dialog.rows.iter().any(|r| matches!(r.control, Some(Control::Toggle(_))));
+ assert_eq!(state.ui_context[state.slots.dialog].rows.first().map(|r| r.id.as_str()), Some(row.as_str()), "the setting row ranks first");
+ let has_toggle = state.ui_context[state.slots.dialog].rows.iter().any(|r| matches!(r.control, Some(Control::Toggle(_))));
let (x, y, w, _) = state.positions[DIALOG_IDX];
let row_y = y + 12.0 + 30.0 + 8.0 + ROW_H * 0.5;
let band_right = x + w - 12.0 - 8.0 - if has_toggle { TOGGLE_W + 12.0 } else { 0.0 };
@@ -15647,7 +15643,7 @@ mod tests {
// and nothing is written.
at(&mut state, 0.5);
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
- assert!(state.slots.dialog.slider_dragging(), "the press took the band");
+ assert!(state.ui_context[state.slots.dialog].slider_dragging(), "the press took the band");
assert!((state.geo_opacity - 0.5).abs() < 0.02, "{}", state.geo_opacity);
assert_eq!(state.rt_geometry_version, version, "a draw-time value re-evaluated the graph");
assert!((saved(&path) - 1.0).abs() < 1e-3, "written mid-drag");
@@ -15655,14 +15651,14 @@ mod tests {
// A motion lands the value live and re-reads the row in place.
at(&mut state, 0.25);
assert!((state.geo_opacity - 0.25).abs() < 0.02, "{}", state.geo_opacity);
- let shown = state.slots.dialog.rows[0].slider_value().expect("a slider row");
+ let shown = state.ui_context[state.slots.dialog].rows[0].slider_value().expect("a slider row");
assert!((shown - state.geo_opacity).abs() < 1e-3, "the row shows {shown}, the field holds {}", state.geo_opacity);
assert_eq!(state.rt_geometry_version, version, "a drag motion re-evaluated the graph");
assert!((saved(&path) - 1.0).abs() < 1e-3, "written mid-drag");
// The release writes the file once.
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Released, button: MouseButton::Left });
- assert!(!state.slots.dialog.slider_dragging());
+ assert!(!state.ui_context[state.slots.dialog].slider_dragging());
assert!((saved(&path) - state.geo_opacity).abs() < 1e-3, "the release did not save");
assert_eq!(state.rt_geometry_version, version);
@@ -15747,13 +15743,13 @@ mod tests {
for c in ["z", "z", "z"] {
state.dialog_key_input(&typed(c));
}
- assert!(state.slots.dialog.rows.is_empty(), "nothing matches 'zzz'");
+ assert!(state.ui_context[state.slots.dialog].rows.is_empty(), "nothing matches 'zzz'");
for _ in 0..3 {
state.dialog_key_input(&key_press(Key::Named(NamedKey::Backspace)));
}
- assert_eq!(state.slots.dialog.query, "");
+ assert_eq!(state.ui_context[state.slots.dialog].query, "");
assert_eq!(
- state.slots.dialog.rows.iter().filter(|r| !r.id.starts_with(crate::dialog::SETTING_ROW_PREFIX) && r.id != crate::dialog::ZOOM_ROW_ID && !r.id.starts_with(crate::dialog::CAMERA_ROW_PREFIX)).count(),
+ state.ui_context[state.slots.dialog].rows.iter().filter(|r| !r.id.starts_with(crate::dialog::SETTING_ROW_PREFIX) && r.id != crate::dialog::ZOOM_ROW_ID && !r.id.starts_with(crate::dialog::CAMERA_ROW_PREFIX)).count(),
crate::command::COMMANDS.len()
);
}
@@ -15775,7 +15771,7 @@ mod tests {
// "l" is Cursor Right in the network pane and a plain letter here.
state.handle_event(&WindowEvent::KeyboardInput { event: typed("l") });
assert_eq!(state.grid_cursor_col, col, "the grid cursor must not move");
- assert_eq!(state.slots.dialog.query, "l");
+ assert_eq!(state.ui_context[state.slots.dialog].query, "l");
}
/// The settings are rows of the one list, each carrying its control —
@@ -15833,15 +15829,15 @@ mod tests {
};
press(&mut state, vx + 2.0, vy + 4.0);
assert!(state.dialog_dropdown_open(), "the press opened the dropdown");
- let units = state.slots.dialog.dropdown.options.clone();
- assert_eq!(units[state.slots.dialog.dropdown.selected], "mm");
- let (tx, ty, tw, th) = state.slots.dialog.dropdown.rect();
+ let units = state.ui_context[state.slots.dialog].dropdown.options.clone();
+ assert_eq!(units[state.ui_context[state.slots.dialog].dropdown.selected], "mm");
+ let (tx, ty, tw, th) = state.ui_context[state.slots.dialog].dropdown.rect();
assert!(vx >= tx && vx < tx + tw && vy >= ty - 4.0 && vy < ty + th, "laid out on the band its value was drawn in");
// The plate grows out of the trigger into the list. Judged by where
// it ends, not by a reading taken as it opens: the growth runs on
// the wall clock, and a slow press had already finished it.
- let grown = |state: &State| state.slots.dialog.dropdown.popover_rect().map(|r| r.3).unwrap_or(0.0);
+ let grown = |state: &State| state.ui_context[state.slots.dialog].dropdown.popover_rect().map(|r| r.3).unwrap_or(0.0);
std::thread::sleep(std::time::Duration::from_millis(250));
state.tick_frame(0.25);
assert!(grown(&state) > th + 24.0, "the trigger {th} grew to {}", grown(&state));
@@ -15849,8 +15845,8 @@ mod tests {
// and its labels are not.
let _ = state.collect_display_list();
let pops = &state.ui_context.active_popovers;
- let dialog_at = pops.iter().position(|&p| p == state.slots.dialog.base().id()).expect("the dialog");
- let dd_id = state.slots.dialog.dropdown.base().id();
+ let dialog_at = pops.iter().position(|&p| p == state.ui_context[state.slots.dialog].base().id()).expect("the dialog");
+ let dd_id = state.ui_context[state.slots.dialog].dropdown.base().id();
let dd_at = pops.iter().position(|&p| p == dd_id).expect("the dropdown");
assert!(dd_at > dialog_at);
// And resolvable, which is what the engine's clamp walks: an id the
@@ -15866,7 +15862,7 @@ mod tests {
// A press on a row of the list picks that row.
state.open_dialog_dropdown(&setting_row_id("World Unit"));
let k = units.iter().position(|u| u == "in").expect("inches");
- let (rx, ry, _, _) = state.slots.dialog.dropdown.popover_geom(cce_ui::scene::layout::Rect { x: tx, y: ty, width: tw, height: th });
+ let (rx, ry, _, _) = state.ui_context[state.slots.dialog].dropdown.popover_geom(cce_ui::scene::layout::Rect { x: tx, y: ty, width: tw, height: th });
press(&mut state, rx + 10.0, ry + k as f32 * 24.0 + 12.0);
assert_eq!(state.world_unit.suffix(), "in");
assert!(state.dialog_visible());
@@ -15880,7 +15876,7 @@ mod tests {
assert!(!state.dialog_dropdown_open() && state.dialog_visible(), "a press off the list closes it alone");
assert_eq!(state.world_unit.suffix(), "in", "and picks nothing");
state.close_dialog();
- assert!(!state.slots.dialog.dropdown.open, "a closed dialog leaves no plate behind");
+ assert!(!state.ui_context[state.slots.dialog].dropdown.open, "a closed dialog leaves no plate behind");
}
/// A control in the palette lifts under the pointer: the row whose
@@ -15900,14 +15896,14 @@ mod tests {
for c in ["s", "h", "o", "w", "g", "r", "i"] {
state.dialog_key_input(&typed(c));
}
- let rows = &state.slots.dialog.rows;
+ let rows = &state.ui_context[state.slots.dialog].rows;
let toggle = rows.iter().position(|r| r.id == "toggle_grid").expect("a Show Grid row");
assert!(matches!(rows[toggle].control, Some(Control::Toggle(_))));
let (dx, dy, dw, dh) = state.positions[DIALOG_IDX];
assert!(dw > 0.0 && dh > 0.0);
let at = |state: &mut State, x: f32, y: f32| {
state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } });
- state.slots.dialog.hovered_control()
+ state.ui_context[state.slots.dialog].hovered_control()
};
// Walk down the switch column until the pointer is over the row's
// switch.
@@ -15929,13 +15925,13 @@ mod tests {
assert_eq!(at(&mut state, dx - 50.0, y), None);
// A slider row: its whole control, readout lane included.
- while !state.slots.dialog.query.is_empty() {
+ while !state.ui_context[state.slots.dialog].query.is_empty() {
state.dialog_key_input(&key_press(Key::Named(NamedKey::Backspace)));
}
for c in ["g", "e", "o", "m", "e", "t", "r", "y"] {
state.dialog_key_input(&typed(c));
}
- let slider = state.slots.dialog.rows.iter().position(|r| r.id == setting_row_id("Geometry Opacity")).expect("a slider row");
+ let slider = state.ui_context[state.slots.dialog].rows.iter().position(|r| r.id == setting_row_id("Geometry Opacity")).expect("a slider row");
let mut found = None;
let mut y = dy;
while y < dy + dh {
@@ -15957,12 +15953,12 @@ mod tests {
use crate::dialog::{setting_row_id, Control, SETTINGS};
let mut state = State::new(false);
state.run_command("toggle_dialog");
- let ids: Vec<String> = state.slots.dialog.rows.iter().map(|r| r.id.clone()).collect();
+ let ids: Vec<String> = state.ui_context[state.slots.dialog].rows.iter().map(|r| r.id.clone()).collect();
for s in SETTINGS {
assert!(ids.contains(&setting_row_id(s.label)), "'{}' has no row", s.label);
}
let control = |label: &str| {
- state.slots.dialog.rows.iter().find(|r| r.id == setting_row_id(label)).and_then(|r| r.control.clone())
+ state.ui_context[state.slots.dialog].rows.iter().find(|r| r.id == setting_row_id(label)).and_then(|r| r.control.clone())
};
assert!(matches!(control("Grid Color"), Some(Control::Color { .. })));
assert!(matches!(control("Grid Thickness"), Some(Control::Slider { dec: 0, .. })), "a spin is a whole-number slider");
@@ -15972,7 +15968,7 @@ mod tests {
// A command's switch is its own row; the settings table lists none
// of them twice.
assert!(control("Show Grid").is_none());
- assert!(state.slots.dialog.rows.iter().any(|r| r.id == "toggle_grid" && r.toggle().is_some()));
+ assert!(state.ui_context[state.slots.dialog].rows.iter().any(|r| r.id == "toggle_grid" && r.toggle().is_some()));
// Tab does not move anywhere, and the dialog stays.
state.dialog_key_input(&key_press(Key::Named(NamedKey::Tab)));
@@ -15983,7 +15979,7 @@ mod tests {
for c in ["g", "r", "i", "d", "c"] {
state.dialog_key_input(&typed(c));
}
- assert_eq!(state.slots.dialog.selected_id(), Some(setting_row_id("Grid Color").as_str()));
+ assert_eq!(state.ui_context[state.slots.dialog].selected_id(), Some(setting_row_id("Grid Color").as_str()));
}
/// A Settings row writes to whatever OWNS its value.
@@ -16011,13 +16007,13 @@ mod tests {
// A Field row: Grid Thickness is a whole number in thousandths.
state.apply_setting("Grid Thickness", "40");
assert!((state.grid_thickness - 0.04).abs() < 1e-6, "{}", state.grid_thickness);
- let row = state.slots.dialog.rows.iter().find(|r| r.id == setting_row_id("Grid Thickness")).unwrap();
+ let row = state.ui_context[state.slots.dialog].rows.iter().find(|r| r.id == setting_row_id("Grid Thickness")).unwrap();
assert!(matches!(row.control, Some(Control::Slider { value, .. }) if (value - 40.0).abs() < 1e-6), "the row re-read the value");
// The arrows work the selected row's control in place: a choice
// steps, a slider nudges, each landing on the live state.
- let unit_row = state.slots.dialog.rows.iter().position(|r| r.id == setting_row_id("World Unit")).unwrap();
- state.slots.dialog.selected = unit_row;
+ let unit_row = state.ui_context[state.slots.dialog].rows.iter().position(|r| r.id == setting_row_id("World Unit")).unwrap();
+ state.ui_context[state.slots.dialog].selected = unit_row;
let before = state.world_unit;
state.dialog_key_input(&key_press(Key::Named(NamedKey::ArrowRight)));
assert_ne!(state.world_unit, before, "right arrow steps the unit");
@@ -16031,8 +16027,8 @@ mod tests {
assert!(!state.dialog_dropdown_open());
assert!(state.dialog_visible(), "and keeps the dialog up");
- let scale_row = state.slots.dialog.rows.iter().position(|r| r.id == setting_row_id("Group Marker Size")).unwrap();
- state.slots.dialog.selected = scale_row;
+ let scale_row = state.ui_context[state.slots.dialog].rows.iter().position(|r| r.id == setting_row_id("Group Marker Size")).unwrap();
+ state.ui_context[state.slots.dialog].selected = scale_row;
let before = state.group_marker_size;
state.dialog_key_input(&key_press(Key::Named(NamedKey::ArrowRight)));
assert!(state.group_marker_size > before, "right arrow grows the markers");
@@ -16065,7 +16061,7 @@ mod tests {
state.recent_files = vec![a.clone(), b.clone()];
state.open_dialog();
- let rows: Vec<String> = state.slots.dialog.rows.iter().map(|r| r.id.clone()).collect();
+ let rows: Vec<String> = state.ui_context[state.slots.dialog].rows.iter().map(|r| r.id.clone()).collect();
let id_a = format!("{RECENT_ROW_PREFIX}{}", a.display());
let id_b = format!("{RECENT_ROW_PREFIX}{}", b.display());
let ia = rows.iter().position(|r| *r == id_a).expect("no row for the newest recent project");
@@ -16073,21 +16069,21 @@ mod tests {
assert!(ia < ib, "the recent list is not in most-recent-first order");
// The row shows the path, truncated from the LEFT — the tail is what
// identifies a project — with the file name in the chord column.
- let row = &state.slots.dialog.rows[ia];
+ let row = &state.ui_context[state.slots.dialog].rows[ia];
assert_eq!(row.label, a.display().to_string());
assert_eq!(row.chord, "cce-recent-alpha");
assert!(row.truncate_head);
// And it ranks against the path text like any other row.
- state.slots.dialog.query = "beta".to_string();
+ state.ui_context[state.slots.dialog].query = "beta".to_string();
state.refresh_dialog_rows();
- let rows: Vec<String> = state.slots.dialog.rows.iter().map(|r| r.id.clone()).collect();
+ let rows: Vec<String> = state.ui_context[state.slots.dialog].rows.iter().map(|r| r.id.clone()).collect();
assert!(rows.contains(&id_b) && !rows.contains(&id_a), "{rows:?}");
state.close_dialog();
// The project already open is not offered a second time.
state.loaded_project_path = Some(a.clone());
state.open_dialog();
- let rows: Vec<String> = state.slots.dialog.rows.iter().map(|r| r.id.clone()).collect();
+ let rows: Vec<String> = state.ui_context[state.slots.dialog].rows.iter().map(|r| r.id.clone()).collect();
assert!(!rows.contains(&id_a), "the open project is listed as a recent one");
assert!(rows.contains(&id_b));
}
@@ -16133,7 +16129,7 @@ mod tests {
assert!(state.command_toggle_state(id).is_some(), "the toggle '{id}' draws no switch");
}
state.run_command("command_palette");
- assert!(state.slots.dialog.rows.iter().any(|r| r.id == "toggle_grid" && r.toggle().is_some()));
+ assert!(state.ui_context[state.slots.dialog].rows.iter().any(|r| r.id == "toggle_grid" && r.toggle().is_some()));
// The Main node's buttons are commands, and the active camera keeps
// the viewport menubar's own menu — neither is a row here.
for id in [
@@ -16155,7 +16151,7 @@ mod tests {
assert!(!state.dialog_visible());
state.run_command("toggle_dialog");
- assert_eq!(state.slots.dialog.query, "");
+ assert_eq!(state.ui_context[state.slots.dialog].query, "");
}
/// A ctrl+left press on empty grid puts the cursor on the pressed cell — on
@@ -16371,11 +16367,11 @@ mod tests {
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
let (ax, ay) = state.cell_center(1, 5);
state.handle_event(&WindowEvent::CursorMoved { position: LocalPosition { x: (ax + 23.0) as f64, y: (ay + 11.0) as f64 } });
- let ghost = state.slots.content.inner().node_rect(slot(&state, "b")).unwrap();
+ let ghost = state.ui_context[state.slots.content].inner().node_rect(slot(&state, "b")).unwrap();
let cell = state.cell_rect(1, 5);
assert!((ghost.0 - cell.0).abs() < 0.5 && (ghost.1 - cell.1).abs() < 0.5, "the dragged node sits on a's cell: {ghost:?} vs {cell:?}");
move_to(&mut state, (1, 5));
- assert_eq!(state.slots.content.inner().swap_target_idx(), Some(slot(&state, "a")), "a is the swap target");
+ assert_eq!(state.ui_context[state.slots.content].inner().swap_target_idx(), Some(slot(&state, "a")), "a is the swap target");
state.handle_event(&WindowEvent::MouseInput { state: ElementState::Released, button: MouseButton::Left });
assert_eq!((at(&state, "b"), at(&state, "a")), ((1.0, 5.0), (1.0, 6.0)), "the two traded places");
@@ -16883,7 +16879,7 @@ mod tests {
// A real port centre, off the widget's own geometry — the ports sit
// outside the node body, so nothing but this gets one right.
let (px, py) = {
- let g = state.slots.content.inner();
+ let g = state.ui_context[state.slots.content].inner();
(0..state.current_dir().children.len())
.find_map(|i| {
g.port_center(i, cce_ui::widget::display::graph::PortType::Output, 0)
@@ -16970,7 +16966,7 @@ mod tests {
assert!(state.project_path_readout().is_none());
state.run_command("command_palette");
assert!(
- !state.slots.dialog.rows.iter().any(|r| r.id == PATH_ROW_ID),
+ !state.ui_context[state.slots.dialog].rows.iter().any(|r| r.id == PATH_ROW_ID),
"no project, no row"
);
state.close_dialog();
@@ -16980,7 +16976,7 @@ mod tests {
assert_eq!(state.loaded_project_path.as_deref(), Some(dir.as_path()));
state.run_command("command_palette");
- let row = state.slots.dialog.rows.first().expect("rows").clone();
+ let row = state.ui_context[state.slots.dialog].rows.first().expect("rows").clone();
assert_eq!(row.id, PATH_ROW_ID, "it heads the list");
assert_eq!(row.label, dir.to_string_lossy(), "the label is the whole path");
assert_eq!(row.chord, "my_project", "the file name reads in the chord column");
@@ -16991,11 +16987,11 @@ mod tests {
for c in ["m", "y", "_", "p"] {
state.dialog_key_input(&typed(c));
}
- assert!(state.slots.dialog.rows.iter().any(|r| r.id == PATH_ROW_ID));
+ assert!(state.ui_context[state.slots.dialog].rows.iter().any(|r| r.id == PATH_ROW_ID));
for c in ["z", "z", "z"] {
state.dialog_key_input(&typed(c));
}
- assert!(!state.slots.dialog.rows.iter().any(|r| r.id == PATH_ROW_ID));
+ assert!(!state.ui_context[state.slots.dialog].rows.iter().any(|r| r.id == PATH_ROW_ID));
// Picking it copies the path and leaves, the way a command does.
state.close_dialog();
@@ -17020,12 +17016,12 @@ mod tests {
state.open_node_palette();
assert!(state.dialog_visible());
- assert_eq!(state.slots.dialog.mode, Mode::AddNode);
+ assert_eq!(state.ui_context[state.slots.dialog].mode, Mode::AddNode);
// Inside the bundled project's Geometry node: every template but the
// three that stand at the root.
- assert_eq!(state.slots.dialog.rows.len(), state.node_templates.len() - 3);
+ assert_eq!(state.ui_context[state.slots.dialog].rows.len(), state.node_templates.len() - 3);
assert!(
- state.slots.dialog.rows.iter().all(|r| r.chord.is_empty()),
+ state.ui_context[state.slots.dialog].rows.iter().all(|r| r.chord.is_empty()),
"a template has no chord to teach"
);
@@ -17050,10 +17046,10 @@ mod tests {
state.dialog_key_input(&typed(c));
}
assert_eq!(
- state.slots.dialog.selected_id(),
+ state.ui_context[state.slots.dialog].selected_id(),
Some("Box"),
"rows: {:?}",
- state.slots.dialog.rows.iter().map(|r| r.label.as_str()).collect::<Vec<_>>()
+ state.ui_context[state.slots.dialog].rows.iter().map(|r| r.label.as_str()).collect::<Vec<_>>()
);
state.dialog_key_input(&key_press(Key::Named(NamedKey::Enter)));
@@ -17174,7 +17170,7 @@ mod tests {
#[test]
fn the_add_node_list_offers_what_belongs_at_the_level() {
let mut state = State::new(false);
- let labels = |state: &State| state.slots.dialog.rows.iter().map(|r| r.label.clone()).collect::<Vec<_>>();
+ let labels = |state: &State| state.ui_context[state.slots.dialog].rows.iter().map(|r| r.label.clone()).collect::<Vec<_>>();
let inside = state.current_path.clone();
assert_eq!(state.path_names_at(&inside), ["geometry1"]);
@@ -17246,8 +17242,8 @@ mod tests {
state.run_command("command_palette");
assert!(state.dialog_visible(), "it lands, it does not toggle");
- assert_eq!(state.slots.dialog.mode, Mode::Commands);
- assert_eq!(state.slots.dialog.query, "", "landing starts a fresh query");
+ assert_eq!(state.ui_context[state.slots.dialog].mode, Mode::Commands);
+ assert_eq!(state.ui_context[state.slots.dialog].query, "", "landing starts a fresh query");
state.run_command("toggle_dialog");
assert!(!state.dialog_visible(), "Alt+D toggles");
@@ -17910,8 +17906,8 @@ mod tests {
assert!(crate::geometry::contains_simnet(&state.fs_root));
state.tick_frame(1.0 / 60.0);
assert_eq!(state.last_sim_frame, state.sim_frame());
- state.slots.playbar.inner_mut().playing = true;
- state.slots.playbar.inner_mut().fps = 60.0;
+ state.ui_context[state.slots.playbar].inner_mut().playing = true;
+ state.ui_context[state.slots.playbar].inner_mut().fps = 60.0;
let start = state.sim_frame();
for i in 1..=3 {
assert!(state.tick_frame(1.0 / 60.0), "a playing tick asks for a redraw");
@@ -18062,11 +18058,11 @@ mod tests {
let slot = state.current_dir().children.iter().position(|c| c.name == "sphere1").unwrap();
state.apply_action(McpAction::Select { slot }, &mut redraw).unwrap();
state.sync_nodes();
- let table = state.slots.spreadsheet().content_width();
+ let table = state.slots.spreadsheet(&state.ui_context).content_width();
let (x, _, w, _) = state.positions[SPREADSHEET_IDX];
assert!(table > State::SPREADSHEET_MIN_W && table < 1600.0 - 36.0, "a sphere's table: {table}");
assert_eq!((x, w), (18.0, table.ceil()), "the plate is the table's width, on the left");
- assert_eq!(cce_ui::widget::WidgetHost::rect(&state.slots.spreadsheet).2, w, "and the widget has it");
+ assert_eq!(cce_ui::widget::WidgetHost::rect(&state.ui_context[state.slots.spreadsheet]).2, w, "and the widget has it");
assert_eq!(state.param_editor_selected(), Some(slot), "the selection is as it was");
state.spreadsheet_mut().set_spreadsheet_data(vec![], vec![]);
@@ -18207,10 +18203,10 @@ mod tests {
state.apply_action(McpAction::ToggleGeometry { slot }, &mut redraw).unwrap();
}
let go = |state: &mut State, frame: f32| {
- state.slots.playbar.inner_mut().current_frame = frame;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = frame;
state.tick_frame(1.0 / 60.0);
};
- let strip = |state: &State| state.slots.playbar.inner().cache.clone();
+ let strip = |state: &State| state.ui_context[state.slots.playbar].inner().cache.clone();
for f in 1..=20 {
go(&mut state, f as f32);
}
@@ -18249,43 +18245,44 @@ mod tests {
// file and another's save could follow.
let kdl = fs::read_to_string(DesignSettings::file_path()).expect("state.kdl was written");
assert!(!DesignSettings::from_kdl_str(&kdl).playbar_repeat, "{kdl}");
- assert!(!State::new(false).slots.playbar.inner().repeat, "a new State seeds the playbar from the setting");
+ let fresh = State::new(false);
+ assert!(!fresh.ui_context[fresh.slots.playbar].inner().repeat, "a new State seeds the playbar from the setting");
// Forward: run off the end, land on it, stop.
{
- let pb = state.slots.playbar.inner_mut();
+ let pb = state.ui_context[state.slots.playbar].inner_mut();
pb.current_frame = pb.end_frame - 0.5;
pb.begin(false);
}
- assert!(Input::tick(state.slots.playbar.inner_mut(), 0.1, rect));
+ assert!(Input::tick(state.ui_context[state.slots.playbar].inner_mut(), 0.1, rect));
{
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert_eq!(pb.current_frame, pb.end_frame, "stopped on the last frame");
assert!(!pb.playing, "and playback ended");
}
// Play again from the end: restarts from the start frame.
state.execute_action(Action::PlayPause);
{
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert!(pb.playing && !pb.reversed);
assert_eq!(pb.current_frame, pb.start_frame, "a play press at the far end rewinds");
}
// Reverse: run off the start, stop there; Down restarts from the end.
{
- let pb = state.slots.playbar.inner_mut();
+ let pb = state.ui_context[state.slots.playbar].inner_mut();
pb.playing = false;
pb.current_frame = pb.start_frame + 0.5;
pb.begin(true);
}
- assert!(Input::tick(state.slots.playbar.inner_mut(), 0.1, rect));
+ assert!(Input::tick(state.ui_context[state.slots.playbar].inner_mut(), 0.1, rect));
{
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert_eq!(pb.current_frame, pb.start_frame);
assert!(!pb.playing);
}
state.execute_action(Action::PlayPauseReverse);
{
- let pb = state.slots.playbar.inner();
+ let pb = state.ui_context[state.slots.playbar].inner();
assert!(pb.playing && pb.reversed);
assert_eq!(pb.current_frame, pb.end_frame);
}
@@ -18464,7 +18461,7 @@ mod tests {
// The Y band of the Value row, from the pane's own float3 group.
let (bx, by) = {
// The slot is statically an `Adapted<ParametersBg>`.
- let pane: &ParametersBg = state.slots.param.inner();
+ let pane: &ParametersBg = state.ui_context[state.slots.param].inner();
let f = pane.float3s.iter().flatten().next().expect("the Value row is a float3");
let (rx, ry, rw, rh) = f.get_row_rects()[1];
(rx + (rw - 68.0) * 0.5, ry + rh * 0.5)
@@ -18554,7 +18551,7 @@ mod tests {
// the viewer, swings onto +X at the length it had.
show(&mut state, pull);
let (cx, cy, r) = {
- let pane: &ParametersBg = state.slots.param.inner();
+ let pane: &ParametersBg = state.ui_context[state.slots.param].inner();
pane.float3s.iter().flatten().next().expect("the Value row").ball_circle().expect("its ball")
};
let at = |x: f32, y: f32| WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } };
@@ -18593,7 +18590,7 @@ mod tests {
state.rebuild_positions();
state.apply_layout();
let (cx, cy, _) = {
- let pane: &ParametersBg = state.slots.param.inner();
+ let pane: &ParametersBg = state.ui_context[state.slots.param].inner();
pane.float3s.iter().flatten().next().expect("the Value row").ball_circle().expect("its ball")
};
let value = |state: &State| -> Vec<f32> {
@@ -18653,7 +18650,7 @@ mod tests {
assert!(state.sync_trackball_view(eye, Vec3::ZERO, Vec3::ZERO), "the view moved off the identity");
assert!(!state.sync_trackball_view(eye, Vec3::ZERO, Vec3::ZERO), "the same camera again moves nothing");
let ball_view = |state: &State| {
- let pane: &ParametersBg = state.slots.param.inner();
+ let pane: &ParametersBg = state.ui_context[state.slots.param].inner();
pane.float3s.iter().flatten().next().expect("the Value row").view()
};
let view = ball_view(&state);
@@ -18665,7 +18662,7 @@ mod tests {
// Roll the ball a quarter turn right: the pull, which pointed at
// the camera, now points along the camera's right.
let (cx, cy, r) = {
- let pane: &ParametersBg = state.slots.param.inner();
+ let pane: &ParametersBg = state.ui_context[state.slots.param].inner();
pane.float3s.iter().flatten().next().unwrap().ball_circle().unwrap()
};
let at = |x: f32, y: f32| WindowEvent::CursorMoved { position: LocalPosition { x: x as f64, y: y as f64 } };
@@ -18709,7 +18706,7 @@ mod tests {
fs::create_dir_all(&dir).unwrap();
let channel = dir.join("default_project.json");
let ball_view = |state: &State| {
- let pane: &ParametersBg = state.slots.param.inner();
+ let pane: &ParametersBg = state.ui_context[state.slots.param].inner();
pane.float3s.iter().flatten().next().expect("a float3 row").view()
};
let same = |a: [[f32; 3]; 3], b: [[f32; 3]; 3]| (0..3).all(|i| (0..3).all(|k| (a[i][k] - b[i][k]).abs() < 1e-4));
@@ -18876,7 +18873,7 @@ mod tests {
state.sync_nodes();
let pull = state.current_dir().children.iter().position(|c| c.name == "pull1").unwrap();
state.apply_action(McpAction::Select { slot: pull }, &mut redraw).unwrap();
- state.slots.playbar.inner_mut().current_frame = 5.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 5.0;
state.tick_frame(1.0 / 60.0);
state.sync_nodes();
assert!(!state.spreadsheet_points.is_empty());
@@ -18889,7 +18886,7 @@ mod tests {
let (row_at, marker_at) = (state.spreadsheet_points[3][0], middle(&state));
assert!((row_at - marker_at).abs() < 1e-3);
- state.slots.playbar.inner_mut().current_frame = 15.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 15.0;
state.tick_frame(1.0 / 60.0);
let moved = state.spreadsheet_points[3][0] - row_at;
assert!(moved > 0.3, "ten frames of the pull moved the row's point {moved}");
@@ -18934,7 +18931,7 @@ mod tests {
let output = simnet.children.iter_mut().find(|c| c.node_type == "output").expect("a simnet has an output");
output.params.iter_mut().find(|p| p.name == "input").unwrap().set_text("pull1".to_string());
}
- state.slots.playbar.inner_mut().current_frame = 61.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 61.0;
state.sync_nodes();
state.rebuild_scene_geometry();
@@ -18990,7 +18987,7 @@ mod tests {
state.tick_frame(1.0 / 60.0);
let opened_at = state.last_spreadsheet_read_at;
assert_eq!(opened_at.0, state.sim_frame());
- state.slots.playbar.inner_mut().current_frame = 7.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 7.0;
state.tick_frame(1.0 / 60.0);
assert_eq!(state.last_spreadsheet_read_at.0, 7, "read again at the new frame");
// The same frame again reads nothing again.
@@ -19034,11 +19031,11 @@ mod tests {
}
state.apply_action(McpAction::SetParam { slot: tagged, name: "input".into(), value: "sim".into() }, &mut redraw).unwrap();
state.apply_action(McpAction::Select { slot: tagged }, &mut redraw).unwrap();
- state.slots.playbar.inner_mut().current_frame = 10.0;
+ state.ui_context[state.slots.playbar].inner_mut().current_frame = 10.0;
state.tick_frame(1.0 / 60.0);
let mut last = marks(&state);
- state.slots.playbar.inner_mut().playing = true;
- state.slots.playbar.inner_mut().fps = 60.0;
+ state.ui_context[state.slots.playbar].inner_mut().playing = true;
+ state.ui_context[state.slots.playbar].inner_mut().fps = 60.0;
for _ in 0..3 {
state.tick_frame(1.0 / 60.0);
assert_eq!(state.last_spreadsheet_read_at.0, state.sim_frame(), "the rows are this frame's");
diff --git a/src/menu_page.rs b/src/menu_page.rs
index 6faa7c4..a66b2c5 100644
--- a/src/menu_page.rs
+++ b/src/menu_page.rs
@@ -241,7 +241,7 @@ impl State {
// The menu turns back out of the plate as it is drawn: it shrinks
// from the dialog's size into its own.
let (x, y, w, h) = self.positions[DIALOG_IDX];
- let drawn = self.slots.dialog.drawn_rect(cce_ui::scene::layout::Rect { x, y, width: w, height: h });
+ let drawn = self.ui_context[self.slots.dialog].drawn_rect(cce_ui::scene::layout::Rect { x, y, width: w, height: h });
self.close_dialog();
self.close_open_menu();
self.reopen_menu(origin, drawn.x, drawn.y);
diff --git a/src/plate_menu.rs b/src/plate_menu.rs
index 1487262..8808d76 100644
--- a/src/plate_menu.rs
+++ b/src/plate_menu.rs
@@ -58,7 +58,7 @@ impl State {
/// right press there is about.
pub fn plate_at(&self, px: f32, py: f32) -> Option<usize> {
PLATE_SLOTS.iter().rev().copied().find(|&idx| {
- let w = self.slots.get_dyn(idx);
+ let w = self.slots.get_dyn(&self.ui_context, idx);
if !w.visible() {
return false;
}
@@ -126,7 +126,7 @@ impl State {
if options.is_empty() {
return;
}
- let target = self.slots.get_dyn(idx).base().id();
+ let target = self.slots.get_dyn(&self.ui_context, idx).base().id();
self.put_up_menu(at, None, options, 0, target);
self.plate_menu_slot = Some(idx);
self.plate_menu_actions = actions;
@@ -140,7 +140,7 @@ impl State {
if options.is_empty() {
return;
}
- let target = self.slots.get_dyn(idx).base().id();
+ let target = self.slots.get_dyn(&self.ui_context, idx).base().id();
self.put_up_menu(Some(at), Some(from), options, 0, target);
self.plate_menu_slot = Some(idx);
self.plate_menu_actions = actions;
@@ -356,7 +356,7 @@ impl State {
continue;
}
self.positions[i] = (0.0, 0.0, 0.0, 0.0);
- self.slots.get_dyn_mut(i).set_visible(false);
+ self.slots.get_dyn_mut(&mut self.ui_context, i).set_visible(false);
}
let m = DETACHED_MARGIN;
self.positions[idx] = (
@@ -365,7 +365,7 @@ impl State {
(self.width - 2.0 * m).max(0.0),
(self.height - 2.0 * m).max(0.0),
);
- self.slots.get_dyn_mut(idx).set_visible(true);
+ self.slots.get_dyn_mut(&mut self.ui_context, idx).set_visible(true);
return;
}
@@ -391,7 +391,7 @@ impl State {
if idx == NETWORK_PANEL_IDX {
for child in [crate::slots::CONTENT_IDX, crate::slots::BREADCRUMB_IDX] {
self.positions[child] = (0.0, 0.0, 0.0, 0.0);
- self.slots.get_dyn_mut(child).set_visible(false);
+ self.slots.get_dyn_mut(&mut self.ui_context, child).set_visible(false);
}
}
}
diff --git a/src/project.rs b/src/project.rs
index 2938b7f..5060321 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -142,7 +142,7 @@ impl State {
splitters,
plates,
frame_range: {
- let pb = self.slots.playbar.inner();
+ let pb = self.ui_context[self.slots.playbar].inner();
Some((pb.start_frame.round() as i32, pb.end_frame.round() as i32))
},
default_view: Some(crate::app::DefaultCameraView {
@@ -284,7 +284,7 @@ impl State {
// list and the splitters follow.
if let Some((start, end)) = vs.frame_range {
if start >= 1 && end > start {
- let pb = self.slots.playbar.inner_mut();
+ let pb = self.ui_context[self.slots.playbar].inner_mut();
pb.start_frame = start as f32;
pb.end_frame = end as f32;
pb.current_frame = pb.current_frame.clamp(pb.start_frame, pb.end_frame);
diff --git a/src/render.rs b/src/render.rs
index bac59c0..f4367ac 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -171,8 +171,8 @@ impl State {
// walk must do it explicitly or open dropdowns get no occlusion.
self.ui_context.clear_popovers();
for i in 0..WIDGET_COUNT {
- if self.slots.get_dyn(i).visible() && self.slots.get_dyn(i).popover_rect().is_some() {
- self.ui_context.register_popover(self.slots.get_dyn_mut(i));
+ if self.slots.get_dyn(&self.ui_context, i).visible() && self.slots.get_dyn(&self.ui_context, i).popover_rect().is_some() {
+ self.ui_context.register_popover_id(self.slots.id(i));
}
}
@@ -220,7 +220,7 @@ impl State {
{
-3
} else {
- self.slots.get_dyn(i).z_index()
+ self.slots.get_dyn(&self.ui_context, i).z_index()
};
(self.has_any_open_menu(i), base_key)
});
@@ -228,16 +228,11 @@ impl State {
// root plate container DISSOLVED (Phase 6as): register the widgets (registry consumers:
// coverage/parent walks) and paint each top-level widget directly in sorted order.
self.ui_context.clear_hierarchy();
- // Register ALL slots, visible or not (id-rooted router): the wheel loop and the
- // hidden-widget broadcasts dispatch by id over the whole roster, and visibility
- // gates behavior inside the widget — an unregistered hidden root would drop the
- // event before that gate. Before the paint pointers below are taken, so the
- // `&mut` each registration borrows does not outlive them.
- for i in 0..WIDGET_COUNT {
- self.ui_context.register_host(self.slots.get_dyn_mut(i));
- }
+ // Every slot stays registered through the wipe (the context owns them, and
+ // `clear_hierarchy` keeps what it owns): the wheel loop and the hidden-widget
+ // broadcasts dispatch by id over the whole roster, visible or not.
let widget_ptrs: Vec<*mut (dyn WidgetHost + 'static)> = (0..WIDGET_COUNT)
- .map(|i| self.slots.get_dyn(i) as *const (dyn WidgetHost + 'static) as *mut (dyn WidgetHost + 'static))
+ .map(|i| self.slots.get_dyn(&self.ui_context, i) as *const (dyn WidgetHost + 'static) as *mut (dyn WidgetHost + 'static))
.collect();
// The dialog's open dropdown, AFTER the dialog and after the wipe
// above (which would drop it from the tree, leaving an id the
@@ -245,15 +240,16 @@ impl State {
// labels through only past the occluders registered before it, so
// the dialog's labels under the list are clamped and the list's are
// not.
- if self.dialog_visible() && self.slots.dialog.dropdown.open && self.sync_dialog_dropdown() {
- let dd: &mut (dyn cce_ui::widget::WidgetHost + 'static) = &mut *self.slots.dialog.dropdown;
- self.ui_context.register_popover(dd);
+ if self.dialog_visible() && self.ui_context[self.slots.dialog].dropdown.open && self.sync_dialog_dropdown() {
+ // The dialog's dropdown is the dialog's own (an embedded widget): lent with the
+ // dialog, it is registered from inside, where the context is free to take it.
+ self.ui_context.lend_h(self.slots.dialog, |d, ui| ui.register_popover(&mut *d.dropdown));
}
let mut pc = PaintCtx::new();
let mut visited = vec![false; WIDGET_COUNT];
for &i in &draw_order {
- if !self.slots.get_dyn(i).visible() {
+ if !self.slots.get_dyn(&self.ui_context, i).visible() {
continue;
}
// The dialog is painted after the overlay passes below, not in the
@@ -307,7 +303,7 @@ impl State {
}
visited[idx] = true;
- let w = self.slots.get_dyn(idx);
+ let w = self.slots.get_dyn(&self.ui_context, idx);
if !w.visible() {
return;
}
@@ -378,7 +374,7 @@ impl State {
// opacity as the hosted menus must.
let (x, y, ww, h) = w.rect();
let full = rect(x, y, ww, h);
- match (self.slots.dialog.turn_progress(), self.slots.dialog.turning) {
+ match (self.ui_context[self.slots.dialog].turn_progress(), self.ui_context[self.slots.dialog].turning) {
(Some(e), Some(turn)) => {
// Turning (see `dialog::DialogTurn`): the plate on its way
// from the one it replaced, and what it shows sliding in
@@ -386,7 +382,7 @@ impl State {
// and replayed moved; the text is cut at the plate as it
// is drawn, which is the occluder the dialog claims
// meanwhile, so the clamp still lets it through.
- let now = self.slots.dialog.drawn_rect(full);
+ let now = self.ui_context[self.slots.dialog].drawn_rect(full);
cce_ui::widget::context_menu::paint_menu_plate(pc, now, false);
let mut scratch = PaintCtx::new();
w.paint_self(&self.ui_context, &mut scratch);
@@ -423,7 +419,7 @@ impl State {
// text (a subtree painter; append_frame_text skips this slot so the
// text isn't doubled).
let (wx, wy, ww2, wh2) = w.rect();
- if self.slots.playbar.inner().frame > 0.0 {
+ if self.ui_context[self.slots.playbar].inner().frame > 0.0 {
// A SHELF of the window's bottom edge, not a plate laid on
// it: a plate turned inside out (cce-ui's `frame`), whose
// face is everything below the scene's opening and whose
@@ -481,7 +477,7 @@ impl State {
// The idle copy fades OUT as the fore copy fades in: left at full
// strength it showed through the plate under the raised bar and
// the two stacked, so a raised bar read nearly opaque.
- let sheet = self.slots.spreadsheet();
+ let sheet = self.slots.spreadsheet(&self.ui_context);
let sheet_rect = rect(wx, wy, ww2, wh2);
if sheet.scrollbars_shown(sheet_rect) {
sheet.paint_scrollbars(sheet_rect, pc, 1.0 - sheet.scrollbar_fade());
@@ -691,9 +687,7 @@ impl State {
// translucent plate every frame, its fore copy over the content
// at the raise's fade, so a raise and a sink are a fade.
let param_scrollbar = {
- let pb = self
- .slots
- .param
+ let pb = self.ui_context[self.slots.param]
.as_any()
.downcast_ref::<cce_ui::widget::ParametersBg>()
.expect("PARAM_IDX must be a ParametersBg");
@@ -1025,7 +1019,7 @@ impl State {
let ncr = self.circular_network_layout.r;
for i in 0..WIDGET_COUNT {
- let w = self.slots.get_dyn(i);
+ let w = self.slots.get_dyn(&self.ui_context, i);
if !w.visible() {
continue;
}
@@ -1114,7 +1108,7 @@ impl State {
visited: &mut [bool],
clip: Rect,
) {
- if !self.slots.dialog.visible() {
+ if !self.ui_context[self.slots.dialog].visible() {
return;
}
self.paint_widget(DIALOG_IDX, pc, show_cursor, visited, clip, None);
@@ -1127,7 +1121,7 @@ impl State {
/// pass would sit under every label.
fn append_popovers(&self, pc: &mut PaintCtx) {
for i in 0..WIDGET_COUNT {
- let w = self.slots.get_dyn(i);
+ let w = self.slots.get_dyn(&self.ui_context, i);
if !w.visible() {
continue;
}
@@ -1411,7 +1405,7 @@ impl State {
// params pane shows, the pane flags that line in its code row. Cleared
// whenever the evaluation says nothing about that node.
let flagged = ocl_error.as_deref().and_then(|e| self.code_error_line_for_pane(e));
- self.slots.param_bg_mut().set_code_error_line(flagged);
+ self.slots.param_bg_mut(&mut self.ui_context).set_code_error_line(flagged);
if let Some(e) = ocl_error {
self.update_status_text(&format!("Node error: {}", e));
} else {
@@ -1607,7 +1601,7 @@ impl State {
/// editor, when one is open. False otherwise, so the caller's own
/// handling runs.
pub(crate) fn code_editor_action(&mut self, action: cce_ui::widget::ContextAction) -> bool {
- let pane = self.slots.param_bg_mut();
+ let pane = self.slots.param_bg_mut(&mut self.ui_context);
pane.code_editing() && pane.code_action(action)
}
@@ -1621,7 +1615,7 @@ impl State {
pub(crate) fn update_status_text(&mut self, text: &str) {
if self.last_status_text != text {
self.last_status_text = text.to_string();
- self.slots.status.set_text(text);
+ self.ui_context[self.slots.status].set_text(text);
}
}
}
diff --git a/src/slots.rs b/src/slots.rs
index 1265604..7e48304 100644
--- a/src/slots.rs
+++ b/src/slots.rs
@@ -8,9 +8,10 @@
//! the struct fields and every index→field dispatch are generated. The typed accessors
//! that assert each slot's concrete type are hand-written, below the macro.
+use cce_ui::context::UiContext;
use cce_ui::widget::{
Adapted, Breadcrumb, Graph, MenuBar, ParametersBg, Splitter, Spreadsheet,
- StatusBar, WidgetHost,
+ StatusBar, WidgetHost, WidgetId,
};
use crate::playbar::Playbar;
@@ -29,46 +30,51 @@ macro_rules! widget_roster {
/// The roster, concretely typed (Phase 6bb): every slot's type is statically known — the
/// old `Vec<Box<dyn WidgetHost>>` erased that and pinned `WidgetHost`'s full surface through the
- /// broadcast loops. Boxed as a whole so registered widget pointers stay stable while the
- /// containing `State` moves. The `*_IDX` constants keep addressing the same slots through
+ /// broadcast loops. The widgets live in the `UiContext`; the roster holds their handles
+ /// (`docs/rfc-owning-registry.md` in cce-ui). The `*_IDX` constants keep addressing the same slots through
/// `get_dyn`/`get_dyn_mut` for the genuinely index-driven paths (draw order, focus cycling,
/// broadcast loops); everything else reaches the concrete field.
pub struct WidgetSlots {
- $(pub $field: cce_ui::widget::Owned<Adapted<$ty>>,)+
+ $(pub $field: cce_ui::widget::Handle<Adapted<$ty>>,)+
}
impl WidgetSlots {
// Per-slot drag queries (the ControlPanel endgame took `draggable`/`is_dragging`
// off `WidgetHost`): the roster routes an index to the concrete slot's inherent
// `Adapted` read, like the other value drains.
- pub fn draggable(&self, idx: usize) -> bool {
+ pub fn draggable(&self, ui: &UiContext, idx: usize) -> bool {
match idx {
- $($idx => self.$field.draggable(),)+
+ $($idx => ui[self.$field].draggable(),)+
_ => slot_out_of_range(idx),
}
}
- pub fn is_dragging(&self, idx: usize) -> bool {
+ pub fn is_dragging(&self, ui: &UiContext, idx: usize) -> bool {
match idx {
- $($idx => self.$field.is_dragging(),)+
+ $($idx => ui[self.$field].is_dragging(),)+
_ => slot_out_of_range(idx),
}
}
- /// The slot's widget itself — the address the registry holds for it, which is what
- /// `find_index` compares — not the `Owned` box around it.
- pub fn get_dyn(&self, idx: usize) -> &(dyn WidgetHost + 'static) {
+ /// The slot's widget id: what focus, popovers and dispatch are keyed by.
+ pub fn id(&self, idx: usize) -> WidgetId {
match idx {
- $($idx => &*self.$field,)+
+ $($idx => self.$field.id(),)+
_ => slot_out_of_range(idx),
}
}
- /// The slot's `Owned` box, so registering it records the stable address and the
- /// box's liveness rather than the widget's own.
- pub fn get_dyn_mut(&mut self, idx: usize) -> &mut (dyn WidgetHost + 'static) {
+ /// The slot's widget, which the context owns.
+ pub fn get_dyn<'a>(&self, ui: &'a UiContext, idx: usize) -> &'a (dyn WidgetHost + 'static) {
match idx {
- $($idx => &mut self.$field,)+
+ $($idx => &ui[self.$field],)+
+ _ => slot_out_of_range(idx),
+ }
+ }
+
+ pub fn get_dyn_mut<'a>(&self, ui: &'a mut UiContext, idx: usize) -> &'a mut (dyn WidgetHost + 'static) {
+ match idx {
+ $($idx => &mut ui[self.$field],)+
_ => slot_out_of_range(idx),
}
}
@@ -121,9 +127,9 @@ widget_roster! {
impl WidgetSlots {
/// Roster index of the slot at `target_addr` (a thin widget address — the comparison
/// never dereferences; callers pass `ptr as *const ()`).
- pub fn find_index(&self, target_addr: *const ()) -> Option<usize> {
+ pub fn find_index(&self, ui: &UiContext, target_addr: *const ()) -> Option<usize> {
(0..WIDGET_COUNT).position(|i| {
- let w_ptr = self.get_dyn(i) as *const dyn WidgetHost as *const ();
+ let w_ptr = self.get_dyn(ui, i) as *const dyn WidgetHost as *const ();
w_ptr == target_addr
})
}
@@ -133,15 +139,15 @@ impl WidgetSlots {
// Deref instead of WidgetHost's deleted as_*_controller discovery hooks. Signatures keep
// returning the narrow trait objects so the ~40 call sites stay unchanged. The dynamic
// `idx` of menu()/menu_mut() only ever receives the five menubar indexes.
- pub fn viewport(&self) -> &Viewport3D {
- self.viewport
+ pub fn viewport<'a>(&self, ui: &'a UiContext) -> &'a Viewport3D {
+ ui[self.viewport]
.as_any()
.downcast_ref::<Viewport3D>()
.expect("VIEWPORT_IDX must be a Viewport3D")
}
- pub fn viewport_mut(&mut self) -> &mut Viewport3D {
- self.viewport
+ pub fn viewport_mut<'a>(&self, ui: &'a mut UiContext) -> &'a mut Viewport3D {
+ ui[self.viewport]
.as_any_mut()
.downcast_mut::<Viewport3D>()
.expect("VIEWPORT_IDX must be a Viewport3D")
@@ -149,53 +155,53 @@ impl WidgetSlots {
/// The menu-capable roster entries are exactly the `Adapted<MenuBar>` bars (Phase 6aw
/// concrete typing); `None` for everything else.
- pub fn menubar_at(&self, idx: usize) -> Option<&MenuBar> {
+ pub fn menubar_at<'a>(&self, ui: &'a UiContext, idx: usize) -> Option<&'a MenuBar> {
// Adapted::as_any exposes the INNER widget, so the downcast targets MenuBar itself.
- self.get_dyn(idx).as_any().downcast_ref::<MenuBar>()
+ self.get_dyn(ui, idx).as_any().downcast_ref::<MenuBar>()
}
- pub fn menu(&self, idx: usize) -> &dyn cce_ui::widget::MenuController {
- self.get_dyn(idx).as_any().downcast_ref::<MenuBar>().expect("not a MenuBar")
+ pub fn menu<'a>(&self, ui: &'a UiContext, idx: usize) -> &'a dyn cce_ui::widget::MenuController {
+ self.get_dyn(ui, idx).as_any().downcast_ref::<MenuBar>().expect("not a MenuBar")
}
- pub fn menu_mut(&mut self, idx: usize) -> &mut dyn cce_ui::widget::MenuController {
- self.get_dyn_mut(idx).as_any_mut().downcast_mut::<MenuBar>().expect("not a MenuBar")
+ pub fn menu_mut<'a>(&self, ui: &'a mut UiContext, idx: usize) -> &'a mut dyn cce_ui::widget::MenuController {
+ self.get_dyn_mut(ui, idx).as_any_mut().downcast_mut::<MenuBar>().expect("not a MenuBar")
}
- pub fn graph(&self) -> &dyn cce_ui::widget::GraphController {
- self.content.as_any().downcast_ref::<Graph>().expect("CONTENT_IDX must be a Graph")
+ pub fn graph<'a>(&self, ui: &'a UiContext) -> &'a dyn cce_ui::widget::GraphController {
+ ui[self.content].as_any().downcast_ref::<Graph>().expect("CONTENT_IDX must be a Graph")
}
- pub fn graph_mut(&mut self) -> &mut dyn cce_ui::widget::GraphController {
- self.content.as_any_mut().downcast_mut::<Graph>().expect("CONTENT_IDX must be a Graph")
+ pub fn graph_mut<'a>(&self, ui: &'a mut UiContext) -> &'a mut dyn cce_ui::widget::GraphController {
+ ui[self.content].as_any_mut().downcast_mut::<Graph>().expect("CONTENT_IDX must be a Graph")
}
- pub fn param(&self) -> &dyn cce_ui::widget::ParamController {
- self.param.as_any().downcast_ref::<ParametersBg>().expect("PARAM_IDX must be a ParametersBg")
+ pub fn param<'a>(&self, ui: &'a UiContext) -> &'a dyn cce_ui::widget::ParamController {
+ ui[self.param].as_any().downcast_ref::<ParametersBg>().expect("PARAM_IDX must be a ParametersBg")
}
- pub fn param_mut(&mut self) -> &mut dyn cce_ui::widget::ParamController {
- self.param.as_any_mut().downcast_mut::<ParametersBg>().expect("PARAM_IDX must be a ParametersBg")
+ pub fn param_mut<'a>(&self, ui: &'a mut UiContext) -> &'a mut dyn cce_ui::widget::ParamController {
+ ui[self.param].as_any_mut().downcast_mut::<ParametersBg>().expect("PARAM_IDX must be a ParametersBg")
}
/// The pane as its concrete type, for what `ParamController` does not
/// carry — the code row's error line.
- pub fn param_bg_mut(&mut self) -> &mut ParametersBg {
- self.param.as_any_mut().downcast_mut::<ParametersBg>().expect("PARAM_IDX must be a ParametersBg")
+ pub fn param_bg_mut<'a>(&self, ui: &'a mut UiContext) -> &'a mut ParametersBg {
+ ui[self.param].as_any_mut().downcast_mut::<ParametersBg>().expect("PARAM_IDX must be a ParametersBg")
}
/// The spreadsheet as itself, for what its controller trait does not
/// carry — the scrollbars the render arm straddles around the plate.
- pub fn spreadsheet(&self) -> &Spreadsheet {
- self.spreadsheet.as_any().downcast_ref::<Spreadsheet>().expect("SPREADSHEET_IDX must be a Spreadsheet")
+ pub fn spreadsheet<'a>(&self, ui: &'a UiContext) -> &'a Spreadsheet {
+ ui[self.spreadsheet].as_any().downcast_ref::<Spreadsheet>().expect("SPREADSHEET_IDX must be a Spreadsheet")
}
- pub fn spreadsheet_mut(&mut self) -> &mut dyn cce_ui::widget::SpreadsheetController {
- self.spreadsheet.as_any_mut().downcast_mut::<Spreadsheet>().expect("SPREADSHEET_IDX must be a Spreadsheet")
+ pub fn spreadsheet_mut<'a>(&self, ui: &'a mut UiContext) -> &'a mut dyn cce_ui::widget::SpreadsheetController {
+ ui[self.spreadsheet].as_any_mut().downcast_mut::<Spreadsheet>().expect("SPREADSHEET_IDX must be a Spreadsheet")
}
- pub fn path_mut(&mut self) -> &mut dyn cce_ui::widget::PathController {
- self.breadcrumb.as_any_mut().downcast_mut::<Breadcrumb>().expect("BREADCRUMB_IDX must be a Breadcrumb")
+ pub fn path_mut<'a>(&self, ui: &'a mut UiContext) -> &'a mut dyn cce_ui::widget::PathController {
+ ui[self.breadcrumb].as_any_mut().downcast_mut::<Breadcrumb>().expect("BREADCRUMB_IDX must be a Breadcrumb")
}
}
diff --git a/src/window.rs b/src/window.rs
index 4b2351b..a37df1c 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -95,33 +95,33 @@ impl State {
LEFT_MENUBAR_IDX => {
if !state.show_network {
state.show_network = true;
- state.slots.content.set_visible(true);
- state.slots.left_menubar.set_visible(true);
- state.slots.breadcrumb.set_visible(true);
+ state.ui_context[state.slots.content].set_visible(true);
+ state.ui_context[state.slots.left_menubar].set_visible(true);
+ state.ui_context[state.slots.breadcrumb].set_visible(true);
state.menu_mut(HEADER_IDX).set_item_checked(2, 4, true);
}
}
RIGHT_MENUBAR_IDX => {
if !state.show_viewport {
state.show_viewport = true;
- state.slots.viewport.set_visible(true);
- state.slots.right_menubar.set_visible(true);
+ state.ui_context[state.slots.viewport].set_visible(true);
+ state.ui_context[state.slots.right_menubar].set_visible(true);
state.menu_mut(HEADER_IDX).set_item_checked(2, 5, true);
}
}
PARAM_MENUBAR_IDX => {
if !state.show_parameters {
state.show_parameters = true;
- state.slots.param.set_visible(true);
- state.slots.param_menubar.set_visible(true);
+ state.ui_context[state.slots.param].set_visible(true);
+ state.ui_context[state.slots.param_menubar].set_visible(true);
state.menu_mut(HEADER_IDX).set_item_checked(2, 6, true);
}
}
SPREADSHEET_MENUBAR_IDX => {
if !state.show_spreadsheet {
state.show_spreadsheet = true;
- state.slots.spreadsheet.set_visible(true);
- state.slots.spreadsheet_menubar.set_visible(true);
+ state.ui_context[state.slots.spreadsheet].set_visible(true);
+ state.ui_context[state.slots.spreadsheet_menubar].set_visible(true);
state.menu_mut(HEADER_IDX).set_item_checked(2, 7, true);
}
}
@@ -162,7 +162,7 @@ impl State {
state.read_panel_offsets();
state.sync_cursor_and_selection();
- if state.drag_widget == Some(PARAM_IDX) && state.slots.param.is_dragging() {
+ if state.drag_widget == Some(PARAM_IDX) && state.ui_context[state.slots.param].is_dragging() {
state.sync_parameters_to_project();
}
@@ -243,7 +243,7 @@ impl State {
// Additive sibling of the project fields: the playbar is app
// state, not project state, so it must not enter the Project
// struct (the save format) — but automation needs to read it.
- let pb = self.slots.playbar.inner();
+ let pb = self.ui_context[self.slots.playbar].inner();
v["playbar"] = serde_json::json!({
"frame": pb.current_frame.round() as i64,
"playing": pb.playing,
@@ -590,7 +590,7 @@ impl State {
}
McpAction::SetFrame { frame } => {
let clamped = {
- let pb = state.slots.playbar.inner_mut();
+ let pb = state.ui_context[state.slots.playbar].inner_mut();
pb.current_frame = frame.clamp(pb.start_frame, pb.end_frame).round();
pb.current_frame
};