Keep overview open when selecting workspace cards

This commit is contained in:
Samuel Onoja committed 2026-09-30 01:48:23 +01:00
1 parent 0ab5f2e05b
commit 61870b9b6c
2 files changed
+59 -29

No files matched your search

+59 -27
View File
@@ -104,6 +104,7 @@ type OverviewStateLabel = HashMap<
pub(crate) struct OverviewState { pub(crate) struct OverviewState {
active: bool, active: bool,
selected: Option<WindowId>, selected: Option<WindowId>,
selection_workspace: Option<WorkspaceId>,
presentations: HashMap<WindowId, AnimatedRect>, presentations: HashMap<WindowId, AnimatedRect>,
opacity: AnimatedValue, opacity: AnimatedValue,
exit_transition: Option<(f64, f64)>, exit_transition: Option<(f64, f64)>,
@@ -119,6 +120,7 @@ impl Default for OverviewState {
Self { Self {
active: false, active: false,
selected: None, selected: None,
selection_workspace: None,
presentations: HashMap::new(), presentations: HashMap::new(),
opacity: AnimatedValue::new(0.0), opacity: AnimatedValue::new(0.0),
exit_transition: None, exit_transition: None,
@@ -139,14 +141,6 @@ pub(crate) struct WorkspaceCard {
pub windows: Vec<(WindowId, Rect)>, pub windows: Vec<(WindowId, Rect)>,
} }
impl WorkspaceCard {
fn window_at(&self, point: Point<f64, Logical>) -> Option<WindowId> {
self.windows
.iter()
.find_map(|(id, rect)| contains(*rect, point).then_some(*id))
}
}
pub(crate) fn workspace_strip(bounds: Rect) -> Rect { pub(crate) fn workspace_strip(bounds: Rect) -> Rect {
let inset = 24.0_f64.min(bounds.width * 0.05); let inset = 24.0_f64.min(bounds.width * 0.05);
Rect::new( Rect::new(
@@ -291,6 +285,21 @@ impl OverviewState {
self.opacity.current.clamp(0.0, 1.0) as f32 self.opacity.current.clamp(0.0, 1.0) as f32
} }
fn select_workspace_windows(&mut self, windows: &[WindowId], preferred: Option<WindowId>) {
let visible = |id: &WindowId| windows.contains(id) && self.presentations.contains_key(id);
self.selected = self
.selected
.filter(visible)
.or_else(|| preferred.filter(visible))
.or_else(|| {
windows
.iter()
.copied()
.filter(visible)
.min_by_key(|id| id.0)
});
}
pub(crate) fn selected(&self) -> Option<WindowId> { pub(crate) fn selected(&self) -> Option<WindowId> {
self.selected self.selected
} }
@@ -322,6 +331,7 @@ impl OverviewState {
return false; return false;
}; };
self.selection_workspace = None;
self.selected = Some(next); self.selected = Some(next);
true true
} }
@@ -359,6 +369,7 @@ impl OverviewState {
} }
} }
self.exit_transition = None; self.exit_transition = None;
self.selection_workspace = None;
self.active = true; self.active = true;
self.opacity.retarget_preserving_motion(1.0); self.opacity.retarget_preserving_motion(1.0);
if !animations_enabled { if !animations_enabled {
@@ -484,6 +495,7 @@ impl Ferese {
.and_then(|w| self.window_ids.get(&w).copied()); .and_then(|w| self.window_ids.get(&w).copied());
if selected.is_some() && self.overview.selected != selected { if selected.is_some() && self.overview.selected != selected {
self.overview.selection_workspace = None;
self.overview.selected = selected; self.overview.selected = selected;
crate::backends::direct::render_all(self); crate::backends::direct::render_all(self);
} }
@@ -595,6 +607,23 @@ impl Ferese {
let targets = self.overview_targets(); let targets = self.overview_targets();
self.overview.retarget(targets, self.animations_enabled()); self.overview.retarget(targets, self.animations_enabled());
if self.overview.selection_workspace.is_some() {
let id = self.workspaces.active_id();
self.overview.selection_workspace = Some(id);
let windows = self
.workspaces
.workspace(id)
.map(|workspace| {
workspace
.layout
.window_ids()
.chain(workspace.floating.iter().copied())
.collect::<Vec<_>>()
})
.unwrap_or_default();
self.overview
.select_workspace_windows(&windows, self.focused_window);
}
} }
pub(crate) fn presented_window_rect(&self, id: WindowId) -> Option<Rect> { pub(crate) fn presented_window_rect(&self, id: WindowId) -> Option<Rect> {
@@ -732,12 +761,6 @@ impl Ferese {
.into_iter() .into_iter()
.find(|card| contains(card.rect, point)); .find(|card| contains(card.rect, point));
if let Some(card) = card { if let Some(card) = card {
if let Some(id) = card.window_at(point) {
if self.activate_managed_window(id) {
self.set_overview_active(false);
}
return true;
}
let Some(output_id) = self.output_id(&output) else { let Some(output_id) = self.output_id(&output) else {
return true; return true;
}; };
@@ -749,6 +772,7 @@ impl Ferese {
// Existing monitor ownership wins; never steal another output's workspace. // Existing monitor ownership wins; never steal another output's workspace.
self.restore_output_focus(); self.restore_output_focus();
self.overview.strip_offsets.clear(); self.overview.strip_offsets.clear();
self.overview.selection_workspace = Some(card.workspace);
self.relayout(); self.relayout();
self.restore_keyboard_focus(); self.restore_keyboard_focus();
} }
@@ -1164,19 +1188,27 @@ mod tests {
} }
#[test] #[test]
fn workspace_thumbnail_hit_selects_the_clicked_window_not_workspace_focus() { fn workspace_selection_replaces_other_output_selection_and_keeps_empty_selection() {
let card = WorkspaceCard { let old = WindowId(1);
workspace: WorkspaceId(1), let new = WindowId(2);
rect: Rect::new(0.0, 0.0, 160.0, 100.0), let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
selected: true, let targets = HashMap::from([(old, (rect, rect)), (new, (rect, rect))]);
windows: vec![ let mut overview = OverviewState::default();
(WindowId(1), Rect::new(5.0, 5.0, 70.0, 65.0)), overview.enter(targets.clone(), Some(old), false);
(WindowId(2), Rect::new(85.0, 5.0, 70.0, 65.0)),
], overview.retarget(targets.clone(), false);
}; overview.select_workspace_windows(&[new], Some(new));
assert_eq!(card.window_at((120.0, 35.0).into()), Some(WindowId(2))); assert!(overview.is_active());
assert_eq!(card.window_at((30.0, 35.0).into()), Some(WindowId(1))); assert_eq!(overview.selected(), Some(new));
assert_eq!(card.window_at((80.0, 85.0).into()), None);
overview.retarget(targets.clone(), false);
overview.select_workspace_windows(&[], None);
assert_eq!(overview.selected(), None);
assert!(overview.is_active());
overview.retarget(targets, false);
overview.select_workspace_windows(&[new], None);
assert_eq!(overview.selected(), Some(new));
} }
#[test] #[test]
-2
View File
@@ -1,5 +1,3 @@
# Backlog # Backlog
- [ ] Keep overview open when selecting a workspace and show all of that workspace’s windows. Activate a window only after the user selects it.
- [ ] Add a first-run keybinding guide modal showing important keybindings and their functions. Keep showing it until the user disables the guide in Settings. - [ ] Add a first-run keybinding guide modal showing important keybindings and their functions. Keep showing it until the user disables the guide in Settings.