Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 2 additions & 0 deletions .harper-dictionary.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pre-view
relayout
teardown
unfocus
14 changes: 2 additions & 12 deletions desktop/src/desktop_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -130,6 +129,7 @@ pub struct DesktopSystem {
event_router: EventRouter<DesktopTarget>,
camera: Animated<PixelCamera>,
pointer_feedback_enabled: bool,
/// Launchers queued for focus-driven relayout once pointer buttons are released.
deferred_focus_layout_launchers: HashSet<LaunchProfileId>,

#[debug(skip)]
Expand Down Expand Up @@ -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
Expand All @@ -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() {
Expand Down
1 change: 0 additions & 1 deletion desktop/src/desktop_system/effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use super::DesktopTarget;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DesktopEffect {
UpdateLauncherExpansion,
Measure(DesktopTarget),
Place(DesktopTarget),
ApplyLayout(DesktopTarget),
Expand Down
83 changes: 69 additions & 14 deletions desktop/src/desktop_system/focus_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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();
}

Expand Down Expand Up @@ -84,17 +86,70 @@ impl DesktopSystem {
instance_manager: &InstanceManager,
) -> Result<Effects> {
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<DesktopTarget>,
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<DesktopTarget>,
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(instance_id);
}

pub(super) fn unfocus_pointer_if_path_contains(
&mut self,
target: &DesktopTarget,
Expand Down Expand Up @@ -133,7 +188,7 @@ impl DesktopSystem {
}

fn preprocess_keyboard_input(&self, event: &Event<ViewEvent>) -> Result<Cmd> {
// 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, ..
Expand Down
35 changes: 3 additions & 32 deletions desktop/src/desktop_system/layout_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -48,10 +48,6 @@ impl DesktopSystem {
effects_mode: TransactionEffectsMode,
) -> Result<Effects> {
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),
Expand All @@ -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.
Expand All @@ -91,6 +82,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);
Expand Down Expand Up @@ -259,7 +251,7 @@ impl DesktopSystem {
}
}

fn instance_launcher(&self, instance_id: InstanceId) -> Option<LaunchProfileId> {
pub(super) fn instance_launcher(&self, instance_id: InstanceId) -> Option<LaunchProfileId> {
let instance_target = DesktopTarget::Instance(instance_id);
match self.aggregates.hierarchy.parent(&instance_target) {
Some(DesktopTarget::Launcher(id)) => Some(*id),
Expand Down Expand Up @@ -348,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<Placement<Transform, 2>> {
let mut placement = self.placement(&DesktopTarget::Instance(instance_id))?;

Expand Down
3 changes: 3 additions & 0 deletions desktop/src/desktop_system/layout_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Loading
Loading