diff --git a/compositor/src/input.rs b/compositor/src/input.rs index d1ddb0f..bbab27c 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -538,6 +538,7 @@ impl Ferese { if self.click_overview_workspace(position) { return; } + if let Some(window) = self.window_under_visual(position) && let Some(id) = self.windows.ids().get(&window).copied() { diff --git a/compositor/src/overview.rs b/compositor/src/overview.rs index 4382b83..bf280cd 100644 --- a/compositor/src/overview.rs +++ b/compositor/src/overview.rs @@ -742,14 +742,17 @@ impl Ferese { let Some(output) = self.overview_strip_at(point) else { return false; }; + let card = self .overview_workspace_cards(&output) .into_iter() .find(|card| contains(card.rect, point)); + if let Some(card) = card { let Some(output_id) = self.output_id(&output) else { return true; }; + if self .output_workspaces .switch_workspace(output_id, card.workspace) @@ -950,6 +953,98 @@ fn directional_distance(direction: Direction, current: (f64, f64), candidate: (f Some(primary + secondary * 0.35) } +impl Default for OverviewMotion { + fn default() -> Self { + Self { + active: false, + presentations: HashMap::new(), + opacity: AnimatedValue::new(0.0), + exit_transition: None, + } + } +} + +impl OverviewMotion { + pub(crate) fn is_animating(&self, spring: SpringConfig) -> bool { + self.exit_transition.is_some() + || (self.opacity.current - self.opacity.target).abs() > 0.001 + || self.opacity.velocity.abs() > 0.005 + || self.presentations.values().any(|rect| !rect.is_settled(spring)) + } + + pub(crate) fn is_presenting(&self) -> bool { + self.active || !self.presentations.is_empty() || self.opacity.current > 0.001 + } + + pub(crate) fn opacity(&self) -> f32 { + self.opacity.current.clamp(0.0, 1.0) as f32 + } + + pub(crate) fn presented_rect(&self, id: WindowId, normal: Rect) -> Rect { + let preview = self.presentations.get(&id).map_or(normal, |p| p.current); + if let Some((elapsed, _)) = self.exit_transition { + let t = 1.0 - (1.0 - (elapsed / EXIT_DURATION).clamp(0.0, 1.0)).powi(3); + return Rect::new( + preview.x + (normal.x - preview.x) * t, + preview.y + (normal.y - preview.y) * t, + preview.width + (normal.width - preview.width) * t, + preview.height + (normal.height - preview.height) * t, + ); + } + preview + } + + pub(crate) fn advance(&mut self, delta: Duration, spring: SpringConfig, animations_enabled: bool) -> bool { + if let Some((elapsed, initial_opacity)) = self.exit_transition { + let elapsed = if animations_enabled { + elapsed + delta.as_secs_f64() + } else { + EXIT_DURATION + }; + let remaining = (1.0 - elapsed / EXIT_DURATION).clamp(0.0, 1.0); + self.opacity.current = initial_opacity * remaining.powi(3); + if elapsed >= EXIT_DURATION { + self.exit_transition = None; + self.presentations.clear(); + self.opacity.snap(); + return false; + } + self.exit_transition = Some((elapsed, initial_opacity)); + return true; + } + let mut active_animation = false; + + for presentation in self.presentations.values_mut() { + if animations_enabled { + active_animation |= presentation.advance(delta, spring); + } else { + presentation.snap(); + } + } + + if !self.active && !active_animation { + self.presentations.clear(); + } + + if animations_enabled { + // Geometry's 0.1-pixel tolerance is far too coarse for opacity: + // it would abruptly drop the last ten percent of the fade. + active_animation |= self.opacity.advance( + delta, + SpringConfig { + position_tolerance: 0.001, + velocity_tolerance: 0.005, + ..spring + }, + ); + } else { + self.opacity.snap(); + } + + active_animation + } +} + #[cfg(test)] mod tests { use super::*; @@ -1276,95 +1371,3 @@ mod tests { assert!(!overview.select_direction(Direction::Right)); } } - -impl Default for OverviewMotion { - fn default() -> Self { - Self { - active: false, - presentations: HashMap::new(), - opacity: AnimatedValue::new(0.0), - exit_transition: None, - } - } -} - -impl OverviewMotion { - pub(crate) fn is_animating(&self, spring: SpringConfig) -> bool { - self.exit_transition.is_some() - || (self.opacity.current - self.opacity.target).abs() > 0.001 - || self.opacity.velocity.abs() > 0.005 - || self.presentations.values().any(|rect| !rect.is_settled(spring)) - } - - pub(crate) fn is_presenting(&self) -> bool { - self.active || !self.presentations.is_empty() || self.opacity.current > 0.001 - } - - pub(crate) fn opacity(&self) -> f32 { - self.opacity.current.clamp(0.0, 1.0) as f32 - } - - pub(crate) fn presented_rect(&self, id: WindowId, normal: Rect) -> Rect { - let preview = self.presentations.get(&id).map_or(normal, |p| p.current); - if let Some((elapsed, _)) = self.exit_transition { - let t = 1.0 - (1.0 - (elapsed / EXIT_DURATION).clamp(0.0, 1.0)).powi(3); - return Rect::new( - preview.x + (normal.x - preview.x) * t, - preview.y + (normal.y - preview.y) * t, - preview.width + (normal.width - preview.width) * t, - preview.height + (normal.height - preview.height) * t, - ); - } - preview - } - - pub(crate) fn advance(&mut self, delta: Duration, spring: SpringConfig, animations_enabled: bool) -> bool { - if let Some((elapsed, initial_opacity)) = self.exit_transition { - let elapsed = if animations_enabled { - elapsed + delta.as_secs_f64() - } else { - EXIT_DURATION - }; - let remaining = (1.0 - elapsed / EXIT_DURATION).clamp(0.0, 1.0); - self.opacity.current = initial_opacity * remaining.powi(3); - if elapsed >= EXIT_DURATION { - self.exit_transition = None; - self.presentations.clear(); - self.opacity.snap(); - return false; - } - self.exit_transition = Some((elapsed, initial_opacity)); - return true; - } - let mut active_animation = false; - - for presentation in self.presentations.values_mut() { - if animations_enabled { - active_animation |= presentation.advance(delta, spring); - } else { - presentation.snap(); - } - } - - if !self.active && !active_animation { - self.presentations.clear(); - } - - if animations_enabled { - // Geometry's 0.1-pixel tolerance is far too coarse for opacity: - // it would abruptly drop the last ten percent of the fade. - active_animation |= self.opacity.advance( - delta, - SpringConfig { - position_tolerance: 0.001, - velocity_tolerance: 0.005, - ..spring - }, - ); - } else { - self.opacity.snap(); - } - - active_animation - } -} diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 0ede6a0..b7a057a 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -393,7 +393,7 @@ pub struct Ferese { pub(crate) shell_resources: Vec>, pub(crate) shell_snapshot_serial: u32, pub(crate) last_shell_snapshot: Option, - pub(crate) overview: crate::overview::OverviewState, + pub(crate) overview: OverviewState, next_output_id: u64, last_animation_tick: Instant, pub popups: PopupManager, diff --git a/crates/ferese-core/src/workspace.rs b/crates/ferese-core/src/workspace.rs index b2d01b2..060e079 100644 --- a/crates/ferese-core/src/workspace.rs +++ b/crates/ferese-core/src/workspace.rs @@ -359,6 +359,33 @@ impl Default for WorkspaceSet { } impl WorkspaceSet { + pub fn new( + default_layout_mode: LayoutMode, + default_column_width: ColumnWidth, + scrolling_focus_strategy: ViewportFocusStrategy, + ) -> Self { + let active = WorkspaceId(1); + let workspace = Workspace { + id: active, + layout: WorkspaceLayout::new(default_layout_mode, default_column_width, scrolling_focus_strategy), + floating: Vec::new(), + last_focused: None, + fullscreen: None, + }; + let workspaces = HashMap::from([(active, workspace)]); + + Self { + active, + workspaces, + window_workspaces: HashMap::new(), + placements: HashMap::new(), + default_layout_mode, + default_column_width, + scrolling_focus_strategy, + next_id: 2, + } + } + pub fn reconfigure_live( &mut self, mode: LayoutMode, @@ -428,33 +455,6 @@ impl WorkspaceSet { } } - pub fn new( - default_layout_mode: LayoutMode, - default_column_width: ColumnWidth, - scrolling_focus_strategy: ViewportFocusStrategy, - ) -> Self { - let active = WorkspaceId(1); - let workspace = Workspace { - id: active, - layout: WorkspaceLayout::new(default_layout_mode, default_column_width, scrolling_focus_strategy), - floating: Vec::new(), - last_focused: None, - fullscreen: None, - }; - let workspaces = HashMap::from([(active, workspace)]); - - Self { - active, - workspaces, - window_workspaces: HashMap::new(), - placements: HashMap::new(), - default_layout_mode, - default_column_width, - scrolling_focus_strategy, - next_id: 2, - } - } - pub fn active_id(&self) -> WorkspaceId { self.active }