From 2e33553a318e8163ac7d0856fdae55b21b692dfd Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Sat, 23 May 2026 08:20:12 +0200 Subject: [PATCH 1/4] Simplify expansion animation by removng it and replacing it as a simple state expanded or not --- .github/copilot-instructions.md | 1 + desktop/src/desktop_system/layout_effects.rs | 1 + desktop/src/desktop_system/layout_state.rs | 3 + desktop/src/projects/launcher_presenter.rs | 61 +++++++++----------- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 61640434..b4b5ce82 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -9,6 +9,7 @@ Update it whenever you learn something new about the project's patterns, convent - Keep functions small, clear, and deterministic. - Avoid multiple exit points that return the same result; consolidate them when it improves readability. - Comment only to explain non-obvious reasoning or intent. +- Prefer concise, ideally one-line comments for conceptual or semantic blocks inside functions. - Order functions high-level first, utilities last; order types by importance (public API first, private helpers last). - When splitting large modules, extract low-coupling impl blocks first and preserve existing external imports via local re-exports in the parent module. diff --git a/desktop/src/desktop_system/layout_effects.rs b/desktop/src/desktop_system/layout_effects.rs index 1d1aa728..8a8592e2 100644 --- a/desktop/src/desktop_system/layout_effects.rs +++ b/desktop/src/desktop_system/layout_effects.rs @@ -91,6 +91,7 @@ impl DesktopSystem { focused_instance, }; + // If measurements of children are not available, push them as effects and return early. let missing_children = self .layout_state .missing_child_measures(&target, &self.aggregates.hierarchy); diff --git a/desktop/src/desktop_system/layout_state.rs b/desktop/src/desktop_system/layout_state.rs index 64bc705b..b0d495fd 100644 --- a/desktop/src/desktop_system/layout_state.rs +++ b/desktop/src/desktop_system/layout_state.rs @@ -92,6 +92,8 @@ impl DesktopLayoutState { .collect() } + // Place the children of a given target, and return a list of size changes if there are any. For + // example, for children that expand to fill their parent. pub fn place_children_of( &mut self, target: &DesktopTarget, @@ -124,6 +126,7 @@ impl DesktopLayoutState { let child_placements = self.place_children(target, children, algorithm); + // Update the placements, and see if there are size changes. let mut updates = Vec::with_capacity(children.len()); for (child, placement) in children.iter().zip(child_placements) { let Some(entry) = self.entries.get_mut(child) else { diff --git a/desktop/src/projects/launcher_presenter.rs b/desktop/src/projects/launcher_presenter.rs index 516f3df7..17457955 100644 --- a/desktop/src/projects/launcher_presenter.rs +++ b/desktop/src/projects/launcher_presenter.rs @@ -33,20 +33,23 @@ const TEXT_COLOR: Color = Color::WHITE; const FADING_DURATION: Duration = Duration::from_millis(500); const STRUCTURAL_ANIMATION_DURATION: Duration = Duration::from_millis(500); -const VISOR_EXPANSION_ANIMATION_DURATION: Duration = Duration::from_millis(500); const COLLAPSED_NON_ANCHOR_Z_OFFSET: f64 = 1.0; const CHILD_SPACING: i32 = 0; #[derive(Debug, Clone, Copy)] -struct VisorPlacementContext { +struct VisorLayoutSummary { group_center_x: f64, flat_span: f64, - focused_index: Option, anchor_index: usize, - expansion_factor: f64, instance_count: usize, } +#[derive(Debug, Clone, Copy)] +struct VisorExpansionState { + focused_index: Option, + expanded: bool, +} + #[derive(Debug)] pub struct LauncherPresenter { #[allow(unused)] @@ -65,7 +68,7 @@ pub struct LauncherPresenter { // Alpha fading of name / background. fader: Animated, - visor_expansion_animation: Animated, + visor_expanded: bool, last_focused_instance: Option, events: EventManager, @@ -126,7 +129,7 @@ impl LauncherPresenter { background, name, fader: scene.animated(1.0), - visor_expansion_animation: scene.animated(1.0), + visor_expanded: true, last_focused_instance: None, events: EventManager::default(), } @@ -190,11 +193,12 @@ impl LauncherPresenter { let offset = centered_children_offset(local_offset, child_sizes, default_panel_size.width as i32); - let expansion_factor = self.visor_expansion_animation.value(); + let expansion_state = VisorExpansionState { + focused_index, + expanded: self.visor_expanded, + }; - let Some(summary) = - visor_layout_summary(offset, child_sizes, focused_index, expansion_factor) - else { + let Some(summary) = visor_layout_summary(offset, child_sizes, focused_index) else { return place_container_children( LayoutAxis::HORIZONTAL, CHILD_SPACING, @@ -212,7 +216,7 @@ impl LauncherPresenter { } let center_y = child_center_y(offset, child_size); - let transform = visor_child_transform(child_index, center_y, summary); + let transform = visor_child_transform(child_index, center_y, summary, expansion_state); child_placements.push(Placement::new( transform, @@ -235,22 +239,13 @@ impl LauncherPresenter { matches!(self.mode, LauncherMode::Visor) && instance_count > 1 } - pub fn set_visor_expansion(&mut self, expanded: bool, animate: bool) { + pub fn set_visor_expansion(&mut self, expanded: bool, _animate: bool) { match self.mode { LauncherMode::Visor => { - let target = if expanded { 1.0 } else { 0.0 }; - if animate { - self.visor_expansion_animation.animate_if_changed( - target, - VISOR_EXPANSION_ANIMATION_DURATION, - Interpolation::Linear, - ); - } else { - self.visor_expansion_animation.set_immediately(target); - } + self.visor_expanded = expanded; } LauncherMode::Band => { - self.visor_expansion_animation.set_immediately(1.0); + self.visor_expanded = true; } } } @@ -412,8 +407,7 @@ fn visor_layout_summary( mut offset: Offset<2>, child_sizes: &[LayoutSize<2>], focused_index: Option, - expansion_factor: f64, -) -> Option { +) -> Option { let mut first_center_x = None; let mut last_center_x = None; @@ -438,12 +432,10 @@ fn visor_layout_summary( let last_center_x = last_center_x.expect("Internal error: Expected at least one instance"); let anchor_index = focused_index.unwrap_or(instance_count / 2); - Some(VisorPlacementContext { + Some(VisorLayoutSummary { group_center_x: (first_center_x + last_center_x) * 0.5, flat_span: (last_center_x - first_center_x).abs(), - focused_index, anchor_index, - expansion_factor, instance_count, }) } @@ -459,17 +451,20 @@ fn child_center_y(offset: Offset<2>, child_size: LayoutSize<2>) -> f64 { fn visor_child_transform( instance_index: usize, center_y: f64, - summary: VisorPlacementContext, + summary: VisorLayoutSummary, + expansion_state: VisorExpansionState, ) -> Transform { - let focused_index = summary + let expanded = expansion_state.expanded && expansion_state.focused_index.is_some(); + let focused_index = expansion_state .focused_index - .or((summary.expansion_factor < 1.0).then_some(summary.anchor_index)); + .or((!expanded).then_some(summary.anchor_index)); + let expansion_factor = if expanded { 1.0 } else { 0.0 }; let placement = visor_layout::placement( instance_index, summary.instance_count, summary.flat_span, focused_index, - summary.expansion_factor, + expansion_factor, ) .expect("Internal error: Visor placement requires at least two instances"); let mut transform = Transform::new( @@ -483,7 +478,7 @@ fn visor_child_transform( ); if instance_index != summary.anchor_index { - transform.translate.z += COLLAPSED_NON_ANCHOR_Z_OFFSET * (1.0 - summary.expansion_factor); + transform.translate.z += COLLAPSED_NON_ANCHOR_Z_OFFSET * if expanded { 0.0 } else { 1.0 }; } transform From 3475aae6b1d9f55b2ce25205c146596f85348dae Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Sat, 23 May 2026 08:30:12 +0200 Subject: [PATCH 2/4] visors: Remove the expanded flag, it's not needed --- desktop/src/desktop_system/effects.rs | 1 - desktop/src/desktop_system/layout_effects.rs | 32 +------------ desktop/src/projects/launcher_presenter.rs | 47 +++++++------------- 3 files changed, 16 insertions(+), 64 deletions(-) diff --git a/desktop/src/desktop_system/effects.rs b/desktop/src/desktop_system/effects.rs index d5bee2ab..52a542ed 100644 --- a/desktop/src/desktop_system/effects.rs +++ b/desktop/src/desktop_system/effects.rs @@ -6,7 +6,6 @@ use super::DesktopTarget; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum DesktopEffect { - UpdateLauncherExpansion, Measure(DesktopTarget), Place(DesktopTarget), ApplyLayout(DesktopTarget), diff --git a/desktop/src/desktop_system/layout_effects.rs b/desktop/src/desktop_system/layout_effects.rs index 8a8592e2..fb7dbbbf 100644 --- a/desktop/src/desktop_system/layout_effects.rs +++ b/desktop/src/desktop_system/layout_effects.rs @@ -14,7 +14,7 @@ use crate::projects::LaunchProfileId; impl DesktopSystem { pub(super) fn transaction_effects(&self, command_effects: Effects) -> Effects { - let mut effects = Effects::from(DesktopEffect::UpdateLauncherExpansion); + let mut effects = Effects::None; effects += command_effects; effects += DesktopEffect::Measure(DesktopTarget::Desktop); effects @@ -48,10 +48,6 @@ impl DesktopSystem { effects_mode: TransactionEffectsMode, ) -> Result { match effect { - DesktopEffect::UpdateLauncherExpansion => { - self.update_launcher_expansion_effect(effects_mode); - Ok(Effects::None) - } DesktopEffect::Measure(target) => self.measure_layout_effect(target), DesktopEffect::Place(root) => self.place_layout_effect(root), DesktopEffect::ApplyLayout(target) => self.apply_layout_effect(target, effects_mode), @@ -66,11 +62,6 @@ impl DesktopSystem { } } - fn update_launcher_expansion_effect(&mut self, effects_mode: TransactionEffectsMode) { - let focused_target = self.event_router.focused().cloned(); - self.update_launcher_visor_expansion(focused_target.as_ref(), effects_mode.animate()); - } - /// Measures one layout target in a bottom-up pass and schedules follow-up work. /// /// If any direct child is still unmeasured, this does not measure the target yet. @@ -349,27 +340,6 @@ impl DesktopSystem { self.desktop_presenter.set_hover_placement(hover_placement); } - fn update_launcher_visor_expansion( - &mut self, - focused_target: Option<&DesktopTarget>, - animate: bool, - ) { - let focused_path = self.aggregates.hierarchy.resolve_path(focused_target); - let launcher_ids: Vec<_> = self.aggregates.launchers.keys().copied().collect(); - - for launcher_id in launcher_ids { - let launcher_target = DesktopTarget::Launcher(launcher_id); - let expanded = focused_path.contains(&launcher_target); - - let launcher = self - .aggregates - .launchers - .get_mut(&launcher_id) - .expect("Launcher missing"); - launcher.set_visor_expansion(expanded, animate); - } - } - fn instance_hover_placement(&self, instance_id: InstanceId) -> Option> { let mut placement = self.placement(&DesktopTarget::Instance(instance_id))?; diff --git a/desktop/src/projects/launcher_presenter.rs b/desktop/src/projects/launcher_presenter.rs index 17457955..8d7ded54 100644 --- a/desktop/src/projects/launcher_presenter.rs +++ b/desktop/src/projects/launcher_presenter.rs @@ -44,12 +44,6 @@ struct VisorLayoutSummary { instance_count: usize, } -#[derive(Debug, Clone, Copy)] -struct VisorExpansionState { - focused_index: Option, - expanded: bool, -} - #[derive(Debug)] pub struct LauncherPresenter { #[allow(unused)] @@ -68,7 +62,6 @@ pub struct LauncherPresenter { // Alpha fading of name / background. fader: Animated, - visor_expanded: bool, last_focused_instance: Option, events: EventManager, @@ -129,7 +122,6 @@ impl LauncherPresenter { background, name, fader: scene.animated(1.0), - visor_expanded: true, last_focused_instance: None, events: EventManager::default(), } @@ -165,7 +157,7 @@ impl LauncherPresenter { child_sizes, ), LauncherMode::Visor => { - let focused_index = focused_index.or_else(|| { + let anchor_index = focused_index.or_else(|| { self.last_focused_instance.and_then(|focused| { child_instances .iter() @@ -177,6 +169,7 @@ impl LauncherPresenter { local_offset, child_sizes, focused_index, + anchor_index, default_panel_size, ) } @@ -188,17 +181,14 @@ impl LauncherPresenter { local_offset: Offset<2>, child_sizes: &[LayoutSize<2>], focused_index: Option, + anchor_index: Option, default_panel_size: SizePx, ) -> Vec> { let offset = centered_children_offset(local_offset, child_sizes, default_panel_size.width as i32); + let expanded = focused_index.is_some(); - let expansion_state = VisorExpansionState { - focused_index, - expanded: self.visor_expanded, - }; - - let Some(summary) = visor_layout_summary(offset, child_sizes, focused_index) else { + let Some(summary) = visor_layout_summary(offset, child_sizes, anchor_index) else { return place_container_children( LayoutAxis::HORIZONTAL, CHILD_SPACING, @@ -216,7 +206,13 @@ impl LauncherPresenter { } let center_y = child_center_y(offset, child_size); - let transform = visor_child_transform(child_index, center_y, summary, expansion_state); + let transform = visor_child_transform( + child_index, + center_y, + summary, + anchor_index, + expanded, + ); child_placements.push(Placement::new( transform, @@ -239,17 +235,6 @@ impl LauncherPresenter { matches!(self.mode, LauncherMode::Visor) && instance_count > 1 } - pub fn set_visor_expansion(&mut self, expanded: bool, _animate: bool) { - match self.mode { - LauncherMode::Visor => { - self.visor_expanded = expanded; - } - LauncherMode::Band => { - self.visor_expanded = true; - } - } - } - // Architecture: I don't want the launcher here to directly generate commands. may be // LauncherCommand? Not sure. pub fn process(&mut self, view_event: ViewEvent) -> Result { @@ -452,12 +437,10 @@ fn visor_child_transform( instance_index: usize, center_y: f64, summary: VisorLayoutSummary, - expansion_state: VisorExpansionState, + focused_index: Option, + expanded: bool, ) -> Transform { - let expanded = expansion_state.expanded && expansion_state.focused_index.is_some(); - let focused_index = expansion_state - .focused_index - .or((!expanded).then_some(summary.anchor_index)); + let focused_index = focused_index.or((!expanded).then_some(summary.anchor_index)); let expansion_factor = if expanded { 1.0 } else { 0.0 }; let placement = visor_layout::placement( instance_index, From 0a386b6595776626d0f377cccc03f5d394a33d17 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Sat, 23 May 2026 09:05:14 +0200 Subject: [PATCH 3/4] Review launcher focus and anchors, so that it's not done in apply_animations --- .harper-dictionary.txt | 2 + desktop/src/desktop_system.rs | 14 +--- desktop/src/desktop_system/focus_input.rs | 83 ++++++++++++++++---- desktop/src/desktop_system/layout_effects.rs | 2 +- desktop/src/projects/launcher_presenter.rs | 30 +++---- 5 files changed, 87 insertions(+), 44 deletions(-) diff --git a/.harper-dictionary.txt b/.harper-dictionary.txt index f08520a8..3a5f423b 100644 --- a/.harper-dictionary.txt +++ b/.harper-dictionary.txt @@ -1,2 +1,4 @@ pre-view relayout +teardown +unfocus diff --git a/desktop/src/desktop_system.rs b/desktop/src/desktop_system.rs index adc9d349..c5aa9ce3 100644 --- a/desktop/src/desktop_system.rs +++ b/desktop/src/desktop_system.rs @@ -42,7 +42,6 @@ use layout_state::DesktopLayoutState; use crate::event_sourcing::{self, Transaction}; use crate::focus_path::FocusPath; -use crate::focus_path::PathResolver; use crate::instance_manager::InstanceManager; use crate::instance_presenter::InstancePresenter; use crate::projects::{ @@ -130,6 +129,7 @@ pub struct DesktopSystem { event_router: EventRouter, camera: Animated, pointer_feedback_enabled: bool, + /// Launchers queued for focus-driven relayout once pointer buttons are released. deferred_focus_layout_launchers: HashSet, #[debug(skip)] @@ -242,12 +242,6 @@ impl DesktopSystem { } pub fn apply_animations(&mut self) { - let focused_instance = self - .aggregates - .hierarchy - .resolve_path(self.event_router.focused()) - .instance(); - let launcher_instance_ids: Vec<_> = self .aggregates .launchers @@ -266,11 +260,7 @@ impl DesktopSystem { .launchers .get_mut(&launcher_id) .expect("Launcher missing") - .apply_animations( - &mut self.aggregates.instances, - &child_instances, - focused_instance, - ); + .apply_animations(&mut self.aggregates.instances, &child_instances); } for project in self.aggregates.projects.values_mut() { diff --git a/desktop/src/desktop_system/focus_input.rs b/desktop/src/desktop_system/focus_input.rs index 885df979..5257dd6b 100644 --- a/desktop/src/desktop_system/focus_input.rs +++ b/desktop/src/desktop_system/focus_input.rs @@ -11,6 +11,7 @@ use super::{ Cmd, DesktopCommand, DesktopSystem, DesktopTarget, Effects, POINTER_FEEDBACK_REENABLE_MAX_DURATION, POINTER_FEEDBACK_REENABLE_MIN_DISTANCE_PX, }; +use crate::event_router::EventTransitions; use crate::focus_path::PathResolver; use crate::hit_tester::AggregateHitTester; use crate::instance_manager::InstanceManager; @@ -24,6 +25,7 @@ impl DesktopSystem { ) -> Result<(Cmd, Effects)> { let keyboard_cmd = self.preprocess_keyboard_input(event)?; let mut effects = Effects::None; + let any_buttons_pressed = event.any_buttons_pressed(); let cmd = if !keyboard_cmd.is_none() { keyboard_cmd @@ -36,17 +38,17 @@ impl DesktopSystem { ); let transitions = self.event_router.process(event, &hit_tester)?; - if event.any_buttons_pressed() { - self.defer_layout_for_focus_change(transitions.keyboard_focus_change()); - } else { - effects += - self.invalidate_layout_for_focus_change(transitions.keyboard_focus_change()); - } - self.forward_event_transitions(transitions, instance_manager)? + let (cmd, transition_effects) = self.apply_and_forward_focus_transitions( + transitions, + instance_manager, + any_buttons_pressed, + )?; + effects += transition_effects; + cmd }; self.update_pointer_feedback(event); - if !event.any_buttons_pressed() { + if !any_buttons_pressed { effects += self.flush_deferred_focus_layout(); } @@ -84,17 +86,70 @@ impl DesktopSystem { instance_manager: &InstanceManager, ) -> Result { let transitions = self.event_router.focus(target); - let effects = self.invalidate_layout_for_focus_change(transitions.keyboard_focus_change()); + let (cmd, effects) = + self.apply_and_forward_focus_transitions(transitions, instance_manager, false)?; // Invariant: Programmatic focus changes must not trigger commands. - assert!( - self.forward_event_transitions(transitions, instance_manager)? - .is_none() - ); + assert!(cmd.is_none()); Ok(effects) } + fn apply_and_forward_focus_transitions( + &mut self, + transitions: EventTransitions, + instance_manager: &InstanceManager, + defer_layout: bool, + ) -> Result<(Cmd, Effects)> { + let effects = self.apply_keyboard_focus_change_effects(&transitions, defer_layout); + let cmd = self.forward_event_transitions(transitions, instance_manager)?; + + Ok((cmd, effects)) + } + + fn apply_keyboard_focus_change_effects( + &mut self, + transitions: &EventTransitions, + defer_layout: bool, + ) -> Effects { + let keyboard_focus_change = transitions.keyboard_focus_change(); + + if !keyboard_focus_change.is_empty() { + self.update_launcher_focus_anchor_on_keyboard_focus_change(); + } + + if defer_layout { + self.defer_layout_for_focus_change(keyboard_focus_change); + Effects::None + } else { + self.invalidate_layout_for_focus_change(keyboard_focus_change) + } + } + + // Inform the launchers that are affected by the focus change. + fn update_launcher_focus_anchor_on_keyboard_focus_change(&mut self) { + let focused_instance = self + .aggregates + .hierarchy + .resolve_path(self.event_router.focused()) + .instance(); + + let Some(instance_id) = focused_instance else { + return; + }; + + let Some(launcher_id) = self.instance_launcher(instance_id) else { + return; + }; + + let launcher = self + .aggregates + .launchers + .get_mut(&launcher_id) + .expect("Launcher missing"); + launcher.set_focus_anchor_instance(Some(instance_id)); + } + pub(super) fn unfocus_pointer_if_path_contains( &mut self, target: &DesktopTarget, @@ -133,7 +188,7 @@ impl DesktopSystem { } fn preprocess_keyboard_input(&self, event: &Event) -> Result { - // Catch CMD+t and CMD+w if an instance has the keyboard focus. + // Catch `CMD+t` and `CMD+w` if an instance has the keyboard focus. if let ViewEvent::KeyboardInput { event: key_event, .. diff --git a/desktop/src/desktop_system/layout_effects.rs b/desktop/src/desktop_system/layout_effects.rs index fb7dbbbf..17d43cb4 100644 --- a/desktop/src/desktop_system/layout_effects.rs +++ b/desktop/src/desktop_system/layout_effects.rs @@ -251,7 +251,7 @@ impl DesktopSystem { } } - fn instance_launcher(&self, instance_id: InstanceId) -> Option { + pub(super) fn instance_launcher(&self, instance_id: InstanceId) -> Option { let instance_target = DesktopTarget::Instance(instance_id); match self.aggregates.hierarchy.parent(&instance_target) { Some(DesktopTarget::Launcher(id)) => Some(*id), diff --git a/desktop/src/projects/launcher_presenter.rs b/desktop/src/projects/launcher_presenter.rs index 8d7ded54..6c8a59b1 100644 --- a/desktop/src/projects/launcher_presenter.rs +++ b/desktop/src/projects/launcher_presenter.rs @@ -62,7 +62,9 @@ pub struct LauncherPresenter { // Alpha fading of name / background. fader: Animated, - last_focused_instance: Option, + /// The most recent focused instance in this launcher, used as visor anchor when nothing in + /// this launcher is currently focused. + focus_anchor_instance: Option, events: EventManager, } @@ -122,7 +124,7 @@ impl LauncherPresenter { background, name, fader: scene.animated(1.0), - last_focused_instance: None, + focus_anchor_instance: None, events: EventManager::default(), } } @@ -158,7 +160,7 @@ impl LauncherPresenter { ), LauncherMode::Visor => { let anchor_index = focused_index.or_else(|| { - self.last_focused_instance.and_then(|focused| { + self.focus_anchor_instance.and_then(|focused| { child_instances .iter() .position(|&instance| instance == focused) @@ -206,13 +208,8 @@ impl LauncherPresenter { } let center_y = child_center_y(offset, child_size); - let transform = visor_child_transform( - child_index, - center_y, - summary, - anchor_index, - expanded, - ); + let transform = + visor_child_transform(child_index, center_y, summary, anchor_index, expanded); child_placements.push(Placement::new( transform, @@ -235,6 +232,12 @@ impl LauncherPresenter { matches!(self.mode, LauncherMode::Visor) && instance_count > 1 } + pub fn set_focus_anchor_instance(&mut self, instance: Option) { + if instance.is_some() { + self.focus_anchor_instance = instance; + } + } + // Architecture: I don't want the launcher here to directly generate commands. may be // LauncherCommand? Not sure. pub fn process(&mut self, view_event: ViewEvent) -> Result { @@ -311,14 +314,7 @@ impl LauncherPresenter { &mut self, instances: &mut Map, child_instances: &[InstanceId], - focused_instance: Option, ) { - if let Some(focused) = focused_instance - && child_instances.contains(&focused) - { - self.last_focused_instance = Some(focused); - } - self.apply_presenter_animations(); self.apply_child_instance_animations(instances, child_instances); } From 30a5e69dd99d919f2311c56fc0fa483a812f7621 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Mon, 25 May 2026 15:48:01 +0200 Subject: [PATCH 4/4] Simplify chaos related to focused and anchor indices when layouting the visors --- desktop/src/desktop_system/focus_input.rs | 2 +- desktop/src/projects/launcher_presenter.rs | 49 ++++++++++------------ desktop/src/projects/visor_layout.rs | 6 +-- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/desktop/src/desktop_system/focus_input.rs b/desktop/src/desktop_system/focus_input.rs index 5257dd6b..8e7d1477 100644 --- a/desktop/src/desktop_system/focus_input.rs +++ b/desktop/src/desktop_system/focus_input.rs @@ -147,7 +147,7 @@ impl DesktopSystem { .launchers .get_mut(&launcher_id) .expect("Launcher missing"); - launcher.set_focus_anchor_instance(Some(instance_id)); + launcher.set_focus_anchor_instance(instance_id); } pub(super) fn unfocus_pointer_if_path_contains( diff --git a/desktop/src/projects/launcher_presenter.rs b/desktop/src/projects/launcher_presenter.rs index 6c8a59b1..e6640abd 100644 --- a/desktop/src/projects/launcher_presenter.rs +++ b/desktop/src/projects/launcher_presenter.rs @@ -40,7 +40,6 @@ const CHILD_SPACING: i32 = 0; struct VisorLayoutSummary { group_center_x: f64, flat_span: f64, - anchor_index: usize, instance_count: usize, } @@ -64,7 +63,7 @@ pub struct LauncherPresenter { fader: Animated, /// The most recent focused instance in this launcher, used as visor anchor when nothing in /// this launcher is currently focused. - focus_anchor_instance: Option, + most_recent_focused_instance: Option, events: EventManager, } @@ -124,7 +123,7 @@ impl LauncherPresenter { background, name, fader: scene.animated(1.0), - focus_anchor_instance: None, + most_recent_focused_instance: None, events: EventManager::default(), } } @@ -159,19 +158,22 @@ impl LauncherPresenter { child_sizes, ), LauncherMode::Visor => { - let anchor_index = focused_index.or_else(|| { - self.focus_anchor_instance.and_then(|focused| { - child_instances - .iter() - .position(|&instance| instance == focused) + let expanded = focused_index.is_some(); + let center_index = focused_index + .or_else(|| { + self.most_recent_focused_instance.and_then(|focused| { + child_instances + .iter() + .position(|&instance| instance == focused) + }) }) - }); + .unwrap_or_default(); self.place_visor_panel_children( local_offset, child_sizes, - focused_index, - anchor_index, + center_index, + expanded, default_panel_size, ) } @@ -182,15 +184,14 @@ impl LauncherPresenter { &self, local_offset: Offset<2>, child_sizes: &[LayoutSize<2>], - focused_index: Option, - anchor_index: Option, + center_index: usize, + expanded: bool, default_panel_size: SizePx, ) -> Vec> { let offset = centered_children_offset(local_offset, child_sizes, default_panel_size.width as i32); - let expanded = focused_index.is_some(); - let Some(summary) = visor_layout_summary(offset, child_sizes, anchor_index) else { + let Some(summary) = visor_layout_summary(offset, child_sizes) else { return place_container_children( LayoutAxis::HORIZONTAL, CHILD_SPACING, @@ -209,7 +210,7 @@ impl LauncherPresenter { let center_y = child_center_y(offset, child_size); let transform = - visor_child_transform(child_index, center_y, summary, anchor_index, expanded); + visor_child_transform(child_index, center_y, summary, center_index, expanded); child_placements.push(Placement::new( transform, @@ -232,10 +233,8 @@ impl LauncherPresenter { matches!(self.mode, LauncherMode::Visor) && instance_count > 1 } - pub fn set_focus_anchor_instance(&mut self, instance: Option) { - if instance.is_some() { - self.focus_anchor_instance = instance; - } + pub fn set_focus_anchor_instance(&mut self, instance: InstanceId) { + self.most_recent_focused_instance = Some(instance); } // Architecture: I don't want the launcher here to directly generate commands. may be @@ -387,7 +386,6 @@ fn children_span(child_sizes: &[LayoutSize<2>]) -> i32 { fn visor_layout_summary( mut offset: Offset<2>, child_sizes: &[LayoutSize<2>], - focused_index: Option, ) -> Option { let mut first_center_x = None; let mut last_center_x = None; @@ -411,12 +409,10 @@ fn visor_layout_summary( let first_center_x = first_center_x.expect("Internal error: Expected at least one instance"); let last_center_x = last_center_x.expect("Internal error: Expected at least one instance"); - let anchor_index = focused_index.unwrap_or(instance_count / 2); Some(VisorLayoutSummary { group_center_x: (first_center_x + last_center_x) * 0.5, flat_span: (last_center_x - first_center_x).abs(), - anchor_index, instance_count, }) } @@ -433,16 +429,15 @@ fn visor_child_transform( instance_index: usize, center_y: f64, summary: VisorLayoutSummary, - focused_index: Option, + center_index: usize, expanded: bool, ) -> Transform { - let focused_index = focused_index.or((!expanded).then_some(summary.anchor_index)); let expansion_factor = if expanded { 1.0 } else { 0.0 }; let placement = visor_layout::placement( instance_index, summary.instance_count, summary.flat_span, - focused_index, + center_index, expansion_factor, ) .expect("Internal error: Visor placement requires at least two instances"); @@ -456,7 +451,7 @@ fn visor_child_transform( 1.0, ); - if instance_index != summary.anchor_index { + if instance_index != center_index { transform.translate.z += COLLAPSED_NON_ANCHOR_Z_OFFSET * if expanded { 0.0 } else { 1.0 }; } diff --git a/desktop/src/projects/visor_layout.rs b/desktop/src/projects/visor_layout.rs index b8af2ed5..5d003893 100644 --- a/desktop/src/projects/visor_layout.rs +++ b/desktop/src/projects/visor_layout.rs @@ -16,7 +16,7 @@ pub fn placement( index: usize, instance_count: usize, flat_span: f64, - focused_index: Option, + center_index: usize, expansion_factor: f64, ) -> Option { if instance_count <= 1 { @@ -26,9 +26,7 @@ pub fn placement( let regular_arc = effective_arc(instance_count); let radius = radius_for_center_span(flat_span, regular_arc); - let focused_rotation = focused_index - .map(|focused| base_angle(focused, instance_count, regular_arc)) - .unwrap_or(0.0); + let focused_rotation = base_angle(center_index, instance_count, regular_arc); let regular_angle = base_angle(index, instance_count, regular_arc) - focused_rotation; let angle = regular_angle * expansion_factor;