From 40154983cc86ac0a45b062d7381124f262adbb3d Mon Sep 17 00:00:00 2001 From: "sorokin.oleg.edu" Date: Sat, 12 Sep 2026 15:06:14 -0700 Subject: [PATCH] Remove obsolete canvas panel focus mode --- crates/oa-desktop/src/app.rs | 28 ++++--------------- crates/oa-desktop/src/app/process_canvas.rs | 25 ++++++++--------- .../src/behavior/canvas_workspace.rs | 5 ++-- .../src/behavior/canvas_workspace/focus.rs | 1 - crates/oa-desktop/src/behavior/scenario.rs | 10 ------- .../src/behavior/scenario/workspace.rs | 1 - .../src/behavior/scenario/workspace/tests.rs | 21 +++++++------- crates/oa-desktop/src/behavior/topology.rs | 19 +++++++------ .../src/behavior/topology/direct.rs | 2 -- .../src/behavior/topology/layout_tests.rs | 7 +---- crates/oa-desktop/src/guide.rs | 4 +-- crates/oa-desktop/src/views.rs | 25 +---------------- 12 files changed, 44 insertions(+), 104 deletions(-) diff --git a/crates/oa-desktop/src/app.rs b/crates/oa-desktop/src/app.rs index 9da62536..9b83bb7e 100644 --- a/crates/oa-desktop/src/app.rs +++ b/crates/oa-desktop/src/app.rs @@ -1181,27 +1181,14 @@ impl App { // This whitelist contains only the production presentation controls. // A stamped toolbar cannot smuggle an arbitrary authoring command. match &mut message { - Message::ScenarioCanvas(Scenario::Camera(c, lifetime, Camera::Focus)) - if valid_scenario(*c) && *lifetime == scope.epoch => - { - return canvas_workspace::focus_task(scope, Camera::Focus); - } - Message::TopologyCanvas(Process::Camera(c, lifetime, Camera::Focus)) - if valid_process(*c) && *lifetime == scope.epoch => - { - return canvas_workspace::focus_task(scope, Camera::Focus); - } Message::ScenarioCanvas( - Scenario::Zoom(c, _) - | Scenario::Choose(c, _) - | Scenario::Focus(c) - | Scenario::Shelf(c, _), + Scenario::Zoom(c, _) | Scenario::Choose(c, _) | Scenario::Shelf(c, _), ) if valid_scenario(*c) => { c.epoch = self.canvas_epoch; } - Message::TopologyCanvas( - Process::Zoom(c, _) | Process::Choose(c, _) | Process::Focus(c), - ) if valid_process(*c) => { + Message::TopologyCanvas(Process::Zoom(c, _) | Process::Choose(c, _)) + if valid_process(*c) => + { c.epoch = self.canvas_epoch; } Message::Behavior(behavior::Event::Topology(behavior::topology::Event::Shelf(_))) @@ -1642,8 +1629,7 @@ impl App { return Task::none(); } match input { - Event::Focus - | Event::Traverse(_) + Event::Traverse(_) | Event::ControlFocus(_) | Event::ToggleFocus | Event::Leave => { @@ -1678,10 +1664,6 @@ impl App { self.begin_model_delete(None); } } - CanvasEvent::Focus(_) => { - self.behavior.scenario.toggle_focus(); - self.canvas_epoch = self.canvas_epoch.wrapping_add(1); - } CanvasEvent::Shelf(_, shelf) => { self.behavior.scenario.toggle_shelf(shelf); self.canvas_epoch = self.canvas_epoch.wrapping_add(1); diff --git a/crates/oa-desktop/src/app/process_canvas.rs b/crates/oa-desktop/src/app/process_canvas.rs index 110f8fae..b578f377 100644 --- a/crates/oa-desktop/src/app/process_canvas.rs +++ b/crates/oa-desktop/src/app/process_canvas.rs @@ -74,11 +74,7 @@ impl App { return Task::none(); } match input { - Event::Focus - | Event::Traverse(_) - | Event::ControlFocus(_) - | Event::ToggleFocus - | Event::Leave => { + Event::Traverse(_) | Event::ControlFocus(_) | Event::ToggleFocus | Event::Leave => { return canvas_workspace::focus_task(scope, input); } Event::CancelGesture => self.canvas_epoch = self.canvas_epoch.wrapping_add(1), @@ -163,10 +159,6 @@ impl App { self.behavior.topology.select_context(resolved.address); } } - CanvasEvent::Focus(_) => { - self.behavior.topology.focused = !self.behavior.topology.focused; - self.canvas_epoch = self.canvas_epoch.wrapping_add(1); - } CanvasEvent::Select(_, id) => { if !self.behavior.has_pending() && crate::behavior::selected_behavior(self.project(), id) == Some(context.owner) @@ -349,7 +341,7 @@ mod tests { } } #[test] - fn process_focus_retains_rejected_draft_and_guards_selection_until_panels_restore() { + fn process_zoom_retains_rejected_draft_and_guards_selection() { let (mut app, f) = setup(); let original = app.document.snapshot(); let initial = app.selected; @@ -359,9 +351,14 @@ mod tests { &mut app, Event::Name(" unfinished contextual work ".into()), ); + let camera = app.behavior.topology.camera_boundary(); let context = app.topology_context().unwrap(); - let _ = app.update(Message::TopologyCanvas(CanvasEvent::Focus(context))); - assert!(app.behavior.topology.focused); + let _ = app.update(Message::TopologyCanvas(CanvasEvent::Zoom( + context, + Some(0.75), + ))); + assert_ne!(app.canvas_epoch, context.epoch); + assert!(app.behavior.topology.camera_boundary() != camera); let raw = app.behavior.recovery_snapshot().unwrap(); let context = app.topology_context().unwrap(); let _ = app.update(Message::TopologyCanvas(CanvasEvent::Select( @@ -373,8 +370,8 @@ mod tests { assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw); assert!(Arc::ptr_eq(&original, &app.document.snapshot())); let context = app.topology_context().unwrap(); - let _ = app.update(Message::TopologyCanvas(CanvasEvent::Focus(context))); - assert!(!app.behavior.topology.focused); + let _ = app.update(Message::TopologyCanvas(CanvasEvent::Zoom(context, None))); + assert_ne!(app.canvas_epoch, context.epoch); assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw); } #[test] diff --git a/crates/oa-desktop/src/behavior/canvas_workspace.rs b/crates/oa-desktop/src/behavior/canvas_workspace.rs index 671f87e5..b6f4a97a 100644 --- a/crates/oa-desktop/src/behavior/canvas_workspace.rs +++ b/crates/oa-desktop/src/behavior/canvas_workspace.rs @@ -52,7 +52,9 @@ pub fn controls<'a>( text(format!("{:.0}%", scale * 100.)).size(12), control("ZoomIn", "+", Some(zoom(Some(scale * 1.25)))), control("ActualSize", "100%", Some(zoom(Some(1.)))), - ].spacing(4).align_y(iced::Center); + ] + .spacing(4) + .align_y(iced::Center); tooltip( controls, container(text("F6: enter/leave canvas keyboard controls · Tab: next · Enter/Space: activate · Esc: leave").size(12)).padding(7).style(crate::style::card), @@ -125,7 +127,6 @@ pub enum Event { CancelGesture, Pan(Vector), Zoom { factor: f32, at: Point, base: f32 }, - Focus, Traverse(bool), ControlFocus(&'static str), ToggleFocus, diff --git a/crates/oa-desktop/src/behavior/canvas_workspace/focus.rs b/crates/oa-desktop/src/behavior/canvas_workspace/focus.rs index 52ce9dd2..e93a139a 100644 --- a/crates/oa-desktop/src/behavior/canvas_workspace/focus.rs +++ b/crates/oa-desktop/src/behavior/canvas_workspace/focus.rs @@ -70,7 +70,6 @@ impl Operation for Collect { return operation::Outcome::None; } let target = match self.event { - Event::Focus => Some(canvas), Event::ControlFocus(name) => Some(self.scope.control_id(name)), Event::ToggleFocus if self.focused.is_none() => Some(canvas), Event::ToggleFocus | Event::Leave if self.focused.is_some() => None, diff --git a/crates/oa-desktop/src/behavior/scenario.rs b/crates/oa-desktop/src/behavior/scenario.rs index 25477bc6..c624137c 100644 --- a/crates/oa-desktop/src/behavior/scenario.rs +++ b/crates/oa-desktop/src/behavior/scenario.rs @@ -130,7 +130,6 @@ pub enum CanvasEvent { FormShown(Context, Id), Zoom(Context, Option), Camera(Context, u64, super::canvas_workspace::Event), - Focus(Context), Shelf(Context, Shelf), OpenUse(Context, Id), Back(Context), @@ -170,7 +169,6 @@ impl CanvasEvent { | Self::FormShown(context, _) | Self::Zoom(context, _) | Self::Camera(context, ..) - | Self::Focus(context) | Self::Shelf(context, _) | Self::OpenUse(context, _) | Self::Back(context) @@ -320,7 +318,6 @@ impl State { pub(crate) fn camera_boundary(&self) -> impl PartialEq + use<> { ( self.canvas_tool, - self.presentation.focused, self.presentation.shelf, self.presentation.viewport, self.presentation.pan, @@ -359,7 +356,6 @@ impl State { if self.presentation.identity != Some((document, owner)) { self.presentation = workspace::Presentation { identity: Some((document, owner)), - focused: self.presentation.focused, ..Default::default() }; self.details_offset = iced::Vector::ZERO; @@ -394,12 +390,6 @@ impl State { true } } - pub(crate) fn focused(&self) -> bool { - self.presentation.focused - } - pub(crate) fn toggle_focus(&mut self) { - self.presentation.focused = !self.presentation.focused; - } pub(crate) fn toggle_shelf(&mut self, shelf: Shelf) { self.presentation.shelf = (self.presentation.shelf != Some(shelf)).then_some(shelf); } diff --git a/crates/oa-desktop/src/behavior/scenario/workspace.rs b/crates/oa-desktop/src/behavior/scenario/workspace.rs index dd4c1d3f..6a9001f5 100644 --- a/crates/oa-desktop/src/behavior/scenario/workspace.rs +++ b/crates/oa-desktop/src/behavior/scenario/workspace.rs @@ -14,7 +14,6 @@ pub enum Shelf { pub(super) struct Presentation { pub(super) identity: Option<(Id, Id)>, pub(super) viewport: Option, - pub(super) focused: bool, pub(super) shelf: Option, pub(super) pan: iced::Vector, pub(super) zoom: Option, diff --git a/crates/oa-desktop/src/behavior/scenario/workspace/tests.rs b/crates/oa-desktop/src/behavior/scenario/workspace/tests.rs index 11a96c98..aa77d685 100644 --- a/crates/oa-desktop/src/behavior/scenario/workspace/tests.rs +++ b/crates/oa-desktop/src/behavior/scenario/workspace/tests.rs @@ -1040,7 +1040,9 @@ fn scenario_workspace_viewport_events_are_passive_guarded_and_do_not_apply_raw_f } app.busy = false; navigation_event(&mut app, |ctx| CanvasEvent::Shelf(ctx, Shelf::Selection)); - navigation_event(&mut app, CanvasEvent::Focus); + let epoch = app.canvas_epoch; + navigation_event(&mut app, |ctx| CanvasEvent::Zoom(ctx, Some(0.8))); + assert_ne!(app.canvas_epoch, epoch); navigation_event(&mut app, |ctx| CanvasEvent::Zoom(ctx, None)); assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw); assert!(std::sync::Arc::ptr_eq(&accepted, &app.document.snapshot())); @@ -1341,7 +1343,7 @@ async fn production_scenario_camera_external_controls_bounds_and_retained_deltas let mut mounted = Mounted::new(&mut app).await; let snapshot = app.document.snapshot(); let source = app.document.bundle().clone(); - for boundary in ["Fit", "A", "Details", "Panels", "Bounds"] { + for boundary in ["Fit", "A", "Details", "ZoomIn", "Bounds"] { let toolbar = capture_toolbar(&mut mounted, &app, "Fit"); let bounds = mounted.geometry(&app).bounds("scenario-viewport"); let point = bounds.position() + iced::Vector::new(95., 90.); @@ -1371,11 +1373,6 @@ async fn production_scenario_camera_external_controls_bounds_and_retained_deltas let size = Size::new(app.window_size.width + 90., app.window_size.height + 35.); let task = app.update(Message::WindowResized(size)); mounted.perform(&mut app, task).await; - } else if boundary == "Panels" { - // Panel layout remains a presentation command, not canvas activation. - let context = app.scenario_context().unwrap(); - let task = app.update(Message::ScenarioCanvas(CanvasEvent::Focus(context))); - mounted.perform(&mut app, task).await; } else { let id = scope(&app).control_id(boundary); let at = mounted.geometry(&app).containers[&id].center(); @@ -1860,7 +1857,6 @@ async fn scenario_workspace_open_and_back_restore_owner_and_mounted_camera_after CanvasEvent::Choose(ctx, CanvasTool::Message(MessageStyle::Timer)) }); navigation_event(&mut app, |ctx| CanvasEvent::Shelf(ctx, Shelf::Selection)); - navigation_event(&mut app, CanvasEvent::Focus); let mut mounted = Mounted::new(&mut app).await; navigation_event(&mut app, |ctx| CanvasEvent::Zoom(ctx, Some(0.75))); let canvas = mounted.geometry(&app).bounds("scenario-viewport"); @@ -1903,7 +1899,13 @@ async fn scenario_workspace_open_and_back_restore_owner_and_mounted_camera_after mounted.settle(&mut app).await; assert_eq!(app.behavior.scenario.presentation.pan, Vector::ZERO); navigation_event(&mut app, |ctx| CanvasEvent::Zoom(ctx, Some(2.))); - navigation_event(&mut app, CanvasEvent::Focus); + navigation_event(&mut app, |ctx| { + CanvasEvent::Choose(ctx, CanvasTool::Add(Tool::Action)) + }); + assert_eq!( + app.behavior.scenario.canvas_tool(b), + CanvasTool::Add(Tool::Action) + ); let _ = app.update(Message::Command(Command::Rename( reference, "Renamed caller use".into(), @@ -1921,7 +1923,6 @@ async fn scenario_workspace_open_and_back_restore_owner_and_mounted_camera_after app.behavior.scenario.canvas_tool(a), CanvasTool::Message(MessageStyle::Timer) ); - assert!(app.behavior.scenario.focused()); assert_eq!( app.behavior.scenario.presentation.shelf, Some(Shelf::Selection) diff --git a/crates/oa-desktop/src/behavior/topology.rs b/crates/oa-desktop/src/behavior/topology.rs index 5d973bd0..b994b588 100644 --- a/crates/oa-desktop/src/behavior/topology.rs +++ b/crates/oa-desktop/src/behavior/topology.rs @@ -86,7 +86,6 @@ pub struct State { pan: iced::Vector, zoom: Option, fit: bool, - pub(crate) focused: bool, contextual_expanded: BTreeSet, selected_context: Option, shelf: Option, @@ -128,7 +127,6 @@ impl State { pub(crate) fn camera_boundary(&self) -> impl PartialEq + use<> { ( self.tool, - self.focused, self.shelf, self.viewport, self.pan, @@ -2148,7 +2146,7 @@ mod tests { } #[test] - fn process_fit_disclosures_and_focus_preserve_rejected_raw_draft_and_accepted_source() { + fn process_zoom_and_disclosures_preserve_rejected_raw_draft_and_accepted_source() { let (p, owner, _, _, _, _) = fixture(); let mut app = App::with_project(p); let _ = app.update(Message::Select(owner)); @@ -2163,16 +2161,19 @@ mod tests { let before = app.behavior.recovery_snapshot().unwrap(); let accepted = app.document.snapshot(); assert!(app.behavior.prepare_pending(app.project()).is_err()); - for (shelf, expected) in [ - (Shelf::Selection, Some(Shelf::Selection)), - (Shelf::Selection, None), + for (shelf, expected, zoom) in [ + (Shelf::Selection, Some(Shelf::Selection), 0.6), + (Shelf::Selection, None, 1.2), ] { - let focused = app.behavior.topology.focused; let _ = app.update(event(Event::Shelf(shelf))); assert_eq!(app.behavior.topology.shelf, expected); let context = app.topology_context().unwrap(); - let _ = app.update(Message::TopologyCanvas(CanvasEvent::Focus(context))); - assert_eq!(app.behavior.topology.focused, !focused); + let _ = app.update(Message::TopologyCanvas(CanvasEvent::Zoom( + context, + Some(zoom), + ))); + assert_ne!(app.canvas_epoch, context.epoch); + assert_eq!(app.behavior.topology.zoom, Some(zoom)); assert_eq!(app.behavior.recovery_snapshot().unwrap(), before); assert!(app.behavior.prepare_pending(app.project()).is_err()); assert!(std::sync::Arc::ptr_eq(&app.document.snapshot(), &accepted)); diff --git a/crates/oa-desktop/src/behavior/topology/direct.rs b/crates/oa-desktop/src/behavior/topology/direct.rs index 9f18ca74..1b37e0bd 100644 --- a/crates/oa-desktop/src/behavior/topology/direct.rs +++ b/crates/oa-desktop/src/behavior/topology/direct.rs @@ -62,7 +62,6 @@ pub enum CanvasEvent { Back(Context), Zoom(Context, Option), Camera(Context, u64, super::super::canvas_workspace::Event), - Focus(Context), Viewport(Context, Size), } impl CanvasEvent { @@ -81,7 +80,6 @@ impl CanvasEvent { | Self::AppendExchange(c) | Self::Zoom(c, ..) | Self::Camera(c, ..) - | Self::Focus(c) | Self::Viewport(c, ..) => *c, Self::Begin { context, .. } | Self::Connect { context, .. } diff --git a/crates/oa-desktop/src/behavior/topology/layout_tests.rs b/crates/oa-desktop/src/behavior/topology/layout_tests.rs index 9a3c92ed..eba7f2a1 100644 --- a/crates/oa-desktop/src/behavior/topology/layout_tests.rs +++ b/crates/oa-desktop/src/behavior/topology/layout_tests.rs @@ -218,7 +218,7 @@ async fn production_process_camera_external_controls_bounds_and_retained_deltas_ let mut mounted = Mounted::new(&mut app).await; let snapshot = app.document.snapshot(); let source = app.document.bundle().clone(); - for boundary in ["Fit", "OR", "Details", "Panels", "Bounds"] { + for boundary in ["Fit", "OR", "Details", "ZoomIn", "Bounds"] { let toolbar = capture_toolbar(&mut mounted, &app, "Fit"); let bounds = mounted.geometry(&app).bounds("process-viewport"); let point = bounds.position() + iced::Vector::new(95., 90.); @@ -248,11 +248,6 @@ async fn production_process_camera_external_controls_bounds_and_retained_deltas_ let size = Size::new(app.window_size.width + 90., app.window_size.height + 35.); let task = app.update(Message::WindowResized(size)); mounted.perform(&mut app, task).await; - } else if boundary == "Panels" { - // Panel layout remains a presentation command, not canvas activation. - let context = app.topology_context().unwrap(); - let task = app.update(Message::TopologyCanvas(CanvasEvent::Focus(context))); - mounted.perform(&mut app, task).await; } else { let id = scope(&app).control_id(boundary); let at = mounted.geometry(&app).containers[&id].center(); diff --git a/crates/oa-desktop/src/guide.rs b/crates/oa-desktop/src/guide.rs index b36a8f01..cbec975f 100644 --- a/crates/oa-desktop/src/guide.rs +++ b/crates/oa-desktop/src/guide.rs @@ -463,7 +463,7 @@ fn article(topic: Topic) -> Article { ), explain( "Navigate the canvas", - "The header and palette stay available while Details and creation forms overlay the canvas. Fit frames the contents; −/+ changes zoom and 100% restores working scale. Focus the canvas to zoom with the wheel or pan with middle-button drag or Space + left drag. F6 enters or leaves canvas controls; Tab moves to the next control, Enter/Space activates it, and Escape leaves canvas controls or cancels a gesture.", + "The header and palette stay available while Details and creation forms overlay the canvas. Fit frames the contents; −/+ changes zoom and 100% restores working scale. Click in the canvas to give it wheel and keyboard focus; pan with middle-button drag or Space + left drag. F6 enters or leaves canvas keyboard controls; Tab moves to the next control, Enter/Space activates it, and Escape leaves canvas controls or cancels a gesture.", ), explain( "Choose the right behavioral description", @@ -476,7 +476,7 @@ fn article(topic: Topic) -> Article { walkthrough( "Connect a particular use of a procedure", &[ - "Choose Focus canvas for more working space; Restore panels brings the explorer and properties back. Select a contribution inside a reusable preview, then open Details to read its complete caller and reference identity.", + "Select a contribution directly inside a reusable preview, then open Details to read its complete caller and reference identity. The explorer and properties remain available alongside the canvas.", "In Details, use Expand nested reference or Fold nested reference to inspect that instance. Open source or Open contribution definition visits the shared definition; Back to caller returns to the original caller context.", "Open Details → Qualified link / assembly... and choose its source and target. Use Enter reference to open an instance's contributions, Up one reference for its caller scope, or Root for the link owner. Search and page through all contributions in that scope, then select the exact endpoint. The first and second uses of a procedure remain different choices even when their work has the same name.", "Choose Precedence to describe order and its guard, Exchange assembly to identify a work exchange between the contributions, or Common work incidence to state that both use the same work. Select the engineering identity when required, then Apply link.", diff --git a/crates/oa-desktop/src/views.rs b/crates/oa-desktop/src/views.rs index 0e0af3a1..832feb57 100644 --- a/crates/oa-desktop/src/views.rs +++ b/crates/oa-desktop/src/views.rs @@ -227,35 +227,12 @@ pub fn view(app: &App) -> View<'_, Message> { Page::Validation => checks(app), Page::Guide => crate::guide::view(&app.guide), }; - let process_layout = app.topology_context().is_some(); - let scenario_layout = app.scenario_context().is_some(); - let focus_behavior = (process_layout && app.behavior.topology.focused) - || (scenario_layout && app.behavior.scenario.focused()); let mut body = if app.page == Page::Home { row![container(main).width(Fill).height(Fill)].height(Fill) - } else if process_layout || scenario_layout { - // Keep main at the same widget-tree path across Focus toggles. The - // collapsed slot has no controls, so it cannot paint over or hit the canvas. - let process_sidebar: View<'_, Message> = if focus_behavior { - Space::new().width(0).into() - } else { - sidebar.into() - }; - // Row::push would discard the zero-width slot and shift main again. - widget::Row::from_vec(vec![ - container(process_sidebar) - .width(if focus_behavior { 0 } else { 260 }) - .height(Fill) - .clip(true) - .into(), - container(main).width(Fill).height(Fill).into(), - ]) - .width(Fill) - .height(Fill) } else { row![sidebar, container(main).width(Fill).height(Fill)].height(Fill) }; - if !focus_behavior && !matches!(app.page, Page::Home | Page::Text | Page::Guide) { + if !matches!(app.page, Page::Home | Page::Text | Page::Guide) { body = body.push(inspector(app)); } let footer = container(