diff --git a/config.ron b/config.ron index 3a500f40..81ded936 100644 --- a/config.ron +++ b/config.ron @@ -59,6 +59,7 @@ (modifiers: [Super], key: "s"): ToggleStacking, (modifiers: [Super], key: "y"): ToggleTiling, (modifiers: [Super], key: "g"): ToggleWindowFloating, + (modifiers: [Super], key: "x"): SwapWindow, (modifiers: [Super], key: "r"): Resizing(Outwards), (modifiers: [Super, Shift], key: "r"): Resizing(Inwards), diff --git a/resources/i18n/de/cosmic_comp.ftl b/resources/i18n/de/cosmic_comp.ftl index b7fd2548..4ea47bb0 100644 --- a/resources/i18n/de/cosmic_comp.ftl +++ b/resources/i18n/de/cosmic_comp.ftl @@ -1,4 +1,5 @@ grow-window = Vergrößern shrink-window = Verkleinern -unknown-keybinding = -stack-windows = Fenster stapeln \ No newline at end of file +swap-windows = Fenster tauschen +stack-windows = Fenster stapeln +unknown-keybinding = \ No newline at end of file diff --git a/resources/i18n/en/cosmic_comp.ftl b/resources/i18n/en/cosmic_comp.ftl index 9a2ef9e8..71e72984 100644 --- a/resources/i18n/en/cosmic_comp.ftl +++ b/resources/i18n/en/cosmic_comp.ftl @@ -1,4 +1,5 @@ grow-window = Grow shrink-window = Shrink -unknown-keybinding = -stack-windows = Stack Windows \ No newline at end of file +swap-windows = Swap Windows +stack-windows = Stack Windows +unknown-keybinding = \ No newline at end of file diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index d5dafe0f..6005a50d 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -88,18 +88,30 @@ pub static RECTANGLE_SHADER: &str = include_str!("./shaders/rounded_rectangle.fr pub struct IndicatorShader(pub GlesPixelProgram); +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum Usage { + OverviewBackdrop, + Overlay, + MoveGrabIndicator, + FocusIndicator, + PotentialGroupIndicator, +} + #[derive(Clone)] pub enum Key { Static(Id), Group(Weak<()>), - Window(CosmicMapped), + Window(Usage, CosmicMapped), } impl std::hash::Hash for Key { fn hash(&self, state: &mut H) { match self { Key::Static(id) => id.hash(state), Key::Group(arc) => (arc.as_ptr() as usize).hash(state), - Key::Window(window) => window.hash(state), + Key::Window(usage, window) => { + usage.hash(state); + window.hash(state); + } } } } @@ -108,17 +120,12 @@ impl PartialEq for Key { match (self, other) { (Key::Static(s1), Key::Static(s2)) => s1 == s2, (Key::Group(g1), Key::Group(g2)) => Weak::ptr_eq(g1, g2), - (Key::Window(w1), Key::Window(w2)) => w1 == w2, + (Key::Window(u1, w1), Key::Window(u2, w2)) => u1 == u2 && w1 == w2, _ => false, } } } impl Eq for Key {} -impl From for Key { - fn from(window: CosmicMapped) -> Self { - Key::Window(window) - } -} impl From for Key { fn from(group: WindowGroup) -> Self { Key::Group(group.alive.clone()) @@ -202,7 +209,7 @@ impl IndicatorShader { cache.retain(|k, _| match k { Key::Static(_) => true, Key::Group(w) => w.upgrade().is_some(), - Key::Window(w) => w.alive(), + Key::Window(_, w) => w.alive(), }); let key = key.into(); @@ -283,7 +290,7 @@ impl BackdropShader { cache.retain(|k, _| match k { Key::Static(_) => true, Key::Group(a) => a.upgrade().is_some(), - Key::Window(w) => w.alive(), + Key::Window(_, w) => w.alive(), }); let key = key.into(); diff --git a/src/config/key_bindings.rs b/src/config/key_bindings.rs index d8b8bd06..269a1ddb 100644 --- a/src/config/key_bindings.rs +++ b/src/config/key_bindings.rs @@ -157,9 +157,9 @@ pub enum Action { Orientation(crate::shell::layout::Orientation), ToggleStacking, - ToggleTiling, ToggleWindowFloating, + SwapWindow, Resizing(ResizeDirection), #[serde(skip)] diff --git a/src/input/mod.rs b/src/input/mod.rs index 65304e33..82044680 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -6,7 +6,7 @@ use crate::{ shell::{ focus::{target::PointerFocusTarget, FocusDirection}, grabs::{ResizeEdge, SeatMoveGrabState}, - layout::tiling::{Direction, FocusResult, MoveResult}, + layout::tiling::{Direction, FocusResult, MoveResult, SwapWindowGrab, TilingLayout}, OverviewMode, ResizeDirection, ResizeMode, Trigger, Workspace, }, state::Common, @@ -250,9 +250,9 @@ impl State { let serial = SERIAL_COUNTER.next_serial(); let time = Event::time_msec(&event); - if let Some((action, pattern)) = seat - .get_keyboard() - .unwrap() + let keyboard = seat.get_keyboard().unwrap(); + let current_focus = keyboard.current_focus(); + if let Some((action, pattern)) = keyboard .input( self, keycode, @@ -260,16 +260,58 @@ impl State { serial, time, |data, modifiers, handle| { - // Leave overview mode, if any modifier was released - if let OverviewMode::Started(Trigger::Keyboard(action_modifiers), _) = - data.common.shell.overview_mode() + // Leave move overview mode, if any modifier was released + if let OverviewMode::Started(Trigger::KeyboardMove(action_modifiers), _) = + data.common.shell.overview_mode().0 { if (action_modifiers.ctrl && !modifiers.ctrl) || (action_modifiers.alt && !modifiers.alt) || (action_modifiers.logo && !modifiers.logo) || (action_modifiers.shift && !modifiers.shift) { - data.common.shell.set_overview_mode(None); + data.common.shell.set_overview_mode(None, data.common.event_loop_handle.clone()); + } + } + // Leave swap overview mode, if any key was released + if let OverviewMode::Started(Trigger::KeyboardSwap(action_pattern, old_descriptor), _) = + data.common.shell.overview_mode().0 + { + if (action_pattern.modifiers.ctrl && !modifiers.ctrl) + || (action_pattern.modifiers.alt && !modifiers.alt) + || (action_pattern.modifiers.logo && !modifiers.logo) + || (action_pattern.modifiers.shift && !modifiers.shift) + || (handle.raw_syms().contains(&action_pattern.key) && state == KeyState::Released) + { + data.common.shell.set_overview_mode(None, data.common.event_loop_handle.clone()); + + if let Some(focus) = current_focus { + if let Some(new_descriptor) = data.common.shell.workspaces.active(¤t_output).1.node_desc(focus) { + let mut spaces = data.common.shell.workspaces.spaces_mut(); + if old_descriptor.handle != new_descriptor.handle { + let (mut old_w, mut other_w) = spaces.partition::, _>(|w| w.handle == old_descriptor.handle); + if let Some(old_tiling_layer) = old_w.get_mut(0).map(|w| &mut w.tiling_layer) { + if let Some(new_tiling_layer) = other_w.iter_mut().find(|w| w.handle == new_descriptor.handle).map(|w| &mut w.tiling_layer) { + if let Some(focus) = TilingLayout::swap_trees(old_tiling_layer, Some(new_tiling_layer), &old_descriptor, &new_descriptor) { + let seat = seat.clone(); + data.common.event_loop_handle.insert_idle(move |data| { + Common::set_focus(&mut data.state, Some(&focus), &seat, None); + }); + } + } + } + } else { + if let Some(tiling_layer) = spaces.find(|w| w.handle == new_descriptor.handle).map(|w| &mut w.tiling_layer) { + if let Some(focus) = TilingLayout::swap_trees(tiling_layer, None, &old_descriptor, &new_descriptor) { + std::mem::drop(spaces); + let seat = seat.clone(); + data.common.event_loop_handle.insert_idle(move |data| { + Common::set_focus(&mut data.state, Some(&focus), &seat, None); + }); + } + } + } + } + } } } @@ -481,7 +523,7 @@ impl State { &output, output_geometry, &self.common.shell.override_redirect_windows, - overview, + overview.0, workspace, ); @@ -551,7 +593,7 @@ impl State { &output, geometry, &self.common.shell.override_redirect_windows, - overview, + overview.0, workspace, ); @@ -670,7 +712,7 @@ impl State { }; if !done { if let Some((target, _)) = - workspace.element_under(relative_pos, overview) + workspace.element_under(relative_pos, overview.0) { under = Some(target); } else { @@ -705,10 +747,12 @@ impl State { } } else { if let OverviewMode::Started(Trigger::Pointer(action_button), _) = - self.common.shell.overview_mode() + self.common.shell.overview_mode().0 { if action_button == button { - self.common.shell.set_overview_mode(None); + self.common + .shell + .set_overview_mode(None, self.common.event_loop_handle.clone()); } } }; @@ -899,7 +943,7 @@ impl State { } } - fn handle_action( + pub fn handle_action( &mut self, action: Action, seat: &Seat, @@ -1232,11 +1276,18 @@ impl State { } Action::Focus(focus) => { let current_output = seat.active_output(); + let overview = self.common.shell.overview_mode().0; let workspace = self.common.shell.active_space_mut(¤t_output); let focus_stack = workspace.focus_stack.get(seat); - let mut result = workspace - .tiling_layer - .next_focus(focus, seat, focus_stack.iter()); + let mut result = workspace.tiling_layer.next_focus( + focus, + seat, + focus_stack.iter(), + match overview { + OverviewMode::Started(Trigger::KeyboardSwap(_, desc), _) => Some(desc), + _ => None, + }, + ); if workspace.get_fullscreen(¤t_output).is_some() { result = FocusResult::None; } @@ -1348,14 +1399,34 @@ impl State { MoveResult::Done => { if let Some(focused_window) = workspace.focus_stack.get(seat).last() { if workspace.is_tiled(focused_window) { - self.common - .shell - .set_overview_mode(Some(Trigger::Keyboard(pattern.modifiers))); + self.common.shell.set_overview_mode( + Some(Trigger::KeyboardMove(pattern.modifiers)), + self.common.event_loop_handle.clone(), + ); } } } } } + Action::SwapWindow => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + if workspace.get_fullscreen(¤t_output).is_some() { + return; // TODO, is this what we want? Maybe disengage fullscreen instead? + } + + let keyboard_handle = seat.get_keyboard().unwrap(); + if let Some(focus) = keyboard_handle.current_focus() { + if let Some(descriptor) = workspace.node_desc(focus) { + let grab = SwapWindowGrab::new(seat.clone(), descriptor.clone()); + keyboard_handle.set_grab(grab, serial); + self.common.shell.set_overview_mode( + Some(Trigger::KeyboardSwap(pattern, descriptor)), + self.common.event_loop_handle.clone(), + ); + } + } + } Action::Maximize => { let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index c4cd1777..6fb86121 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -62,6 +62,7 @@ pub mod window; pub use self::window::CosmicWindow; pub mod resize_indicator; pub mod stack_hover; +pub mod swap_indicator; #[cfg(feature = "debug")] use egui::plot::{Corner, Legend, Plot, PlotPoints, Polygon}; @@ -72,7 +73,10 @@ use tracing::debug; use super::{ focus::FocusDirection, - layout::{floating::ResizeState, tiling::Direction}, + layout::{ + floating::ResizeState, + tiling::{Direction, NodeDesc}, + }, }; space_elements! { @@ -90,7 +94,7 @@ pub struct CosmicMapped { last_cursor_position: Arc>>>, //tiling - pub(super) tiling_node_id: Arc>>, + pub tiling_node_id: Arc>>, //floating pub(super) last_geometry: Arc>>>, pub(super) resize_state: Arc>>, @@ -167,14 +171,12 @@ impl CosmicMapped { CosmicMappedInternal::Stack(stack) => { let win = stack.active(); let location = stack.offset(); - let mut size = win.geometry().size; - size -= location.to_size(); + let size = win.geometry().size; Rectangle::from_loc_and_size(location, size) } CosmicMappedInternal::Window(win) => { let location = win.offset(); - let mut size = win.geometry().size; - size -= location.to_size(); + let size = win.geometry().size; Rectangle::from_loc_and_size(location, size) } _ => unreachable!(), @@ -248,9 +250,9 @@ impl CosmicMapped { } } - pub fn handle_focus(&self, direction: FocusDirection) -> bool { + pub fn handle_focus(&self, direction: FocusDirection, swap: Option) -> bool { if let CosmicMappedInternal::Stack(stack) = &self.element { - stack.handle_focus(direction) + stack.handle_focus(direction, swap) } else { false } @@ -1100,9 +1102,13 @@ where RescaleRenderElement>>, >, ), + TiledOverlay( + RelocateRenderElement>>, + ), GrabbedStack(RescaleRenderElement>), GrabbedWindow(RescaleRenderElement>), FocusIndicator(PixelShaderElement), + Overlay(PixelShaderElement), StackHoverIndicator(MemoryRenderBufferRenderElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), @@ -1119,9 +1125,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.id(), CosmicMappedRenderElement::TiledStack(elem) => elem.id(), CosmicMappedRenderElement::TiledWindow(elem) => elem.id(), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.id(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.id(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.id(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.id(), + CosmicMappedRenderElement::Overlay(elem) => elem.id(), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.id(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.id(), @@ -1134,9 +1142,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.current_commit(), CosmicMappedRenderElement::TiledStack(elem) => elem.current_commit(), CosmicMappedRenderElement::TiledWindow(elem) => elem.current_commit(), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.current_commit(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.current_commit(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.current_commit(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.current_commit(), + CosmicMappedRenderElement::Overlay(elem) => elem.current_commit(), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.current_commit(), @@ -1149,9 +1159,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.src(), CosmicMappedRenderElement::TiledStack(elem) => elem.src(), CosmicMappedRenderElement::TiledWindow(elem) => elem.src(), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.src(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.src(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.src(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.src(), + CosmicMappedRenderElement::Overlay(elem) => elem.src(), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.src(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.src(), @@ -1164,9 +1176,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.geometry(scale), CosmicMappedRenderElement::TiledStack(elem) => elem.geometry(scale), CosmicMappedRenderElement::TiledWindow(elem) => elem.geometry(scale), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.geometry(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.geometry(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.geometry(scale), CosmicMappedRenderElement::FocusIndicator(elem) => elem.geometry(scale), + CosmicMappedRenderElement::Overlay(elem) => elem.geometry(scale), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.geometry(scale), @@ -1179,9 +1193,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.location(scale), CosmicMappedRenderElement::TiledStack(elem) => elem.location(scale), CosmicMappedRenderElement::TiledWindow(elem) => elem.location(scale), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.location(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.location(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.location(scale), CosmicMappedRenderElement::FocusIndicator(elem) => elem.location(scale), + CosmicMappedRenderElement::Overlay(elem) => elem.location(scale), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.location(scale), @@ -1194,9 +1210,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.transform(), CosmicMappedRenderElement::TiledStack(elem) => elem.transform(), CosmicMappedRenderElement::TiledWindow(elem) => elem.transform(), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.transform(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.transform(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.transform(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.transform(), + CosmicMappedRenderElement::Overlay(elem) => elem.transform(), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.transform(), @@ -1213,9 +1231,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::TiledStack(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::TiledWindow(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::GrabbedStack(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::FocusIndicator(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::Overlay(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::StackHoverIndicator(elem) => { elem.damage_since(scale, commit) } @@ -1230,9 +1250,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::TiledStack(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::TiledWindow(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::FocusIndicator(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::Overlay(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.opaque_regions(scale), @@ -1245,9 +1267,11 @@ where CosmicMappedRenderElement::Window(elem) => elem.alpha(), CosmicMappedRenderElement::TiledStack(elem) => elem.alpha(), CosmicMappedRenderElement::TiledWindow(elem) => elem.alpha(), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.alpha(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.alpha(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.alpha(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.alpha(), + CosmicMappedRenderElement::Overlay(elem) => elem.alpha(), CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.alpha(), @@ -1268,11 +1292,17 @@ impl RenderElement for CosmicMappedRenderElement { CosmicMappedRenderElement::Window(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::TiledStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::TiledWindow(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::TiledOverlay(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage) + } CosmicMappedRenderElement::GrabbedStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::FocusIndicator(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) } + CosmicMappedRenderElement::Overlay(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage) + } CosmicMappedRenderElement::StackHoverIndicator(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) } @@ -1289,9 +1319,11 @@ impl RenderElement for CosmicMappedRenderElement { CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::TiledStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::TiledWindow(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::TiledOverlay(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::FocusIndicator(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::Overlay(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::StackHoverIndicator(elem) => { elem.underlying_storage(renderer) } @@ -1316,12 +1348,20 @@ impl<'a, 'b> RenderElement> CosmicMappedRenderElement::Window(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::TiledStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::TiledWindow(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::TiledOverlay(elem) => { + RenderElement::::draw(elem, frame.glow_frame_mut(), src, dst, damage) + .map_err(|err| GlMultiError::Render(err)) + } CosmicMappedRenderElement::GrabbedStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::FocusIndicator(elem) => { RenderElement::::draw(elem, frame.glow_frame_mut(), src, dst, damage) .map_err(|err| GlMultiError::Render(err)) } + CosmicMappedRenderElement::Overlay(elem) => { + RenderElement::::draw(elem, frame.glow_frame_mut(), src, dst, damage) + .map_err(|err| GlMultiError::Render(err)) + } CosmicMappedRenderElement::StackHoverIndicator(elem) => { elem.draw(frame, src, dst, damage) } @@ -1343,11 +1383,17 @@ impl<'a, 'b> RenderElement> CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::TiledStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::TiledWindow(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::TiledOverlay(elem) => { + elem.underlying_storage(renderer.glow_renderer_mut()) + } CosmicMappedRenderElement::GrabbedStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::FocusIndicator(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } + CosmicMappedRenderElement::Overlay(elem) => { + elem.underlying_storage(renderer.glow_renderer_mut()) + } CosmicMappedRenderElement::StackHoverIndicator(elem) => { elem.underlying_storage(renderer) } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 98a680c7..80ba66a6 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,6 +1,11 @@ use super::{CosmicMapped, CosmicSurface, CosmicWindow}; use crate::{ - shell::{focus::FocusDirection, grabs::MoveGrab, layout::tiling::Direction, Shell, Trigger}, + shell::{ + focus::FocusDirection, + grabs::MoveGrab, + layout::tiling::{Direction, NodeDesc}, + Shell, Trigger, + }, state::State, utils::iced::{IcedElement, Program}, utils::prelude::SeatExt, @@ -85,6 +90,7 @@ pub struct CosmicStackInternal { previous_keyboard: Arc, pointer_entered: Arc, previous_pointer: Arc, + reenter: Arc, potential_drag: Arc>>, override_alive: Arc, last_seat: Arc, Serial)>>>, @@ -107,7 +113,7 @@ impl CosmicStackInternal { } } -const TAB_HEIGHT: i32 = 24; +pub const TAB_HEIGHT: i32 = 24; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -148,6 +154,7 @@ impl CosmicStack { previous_keyboard: Arc::new(AtomicUsize::new(0)), pointer_entered: Arc::new(AtomicU8::new(Focus::None as u8)), previous_pointer: Arc::new(AtomicUsize::new(0)), + reenter: Arc::new(AtomicBool::new(false)), potential_drag: Arc::new(Mutex::new(None)), override_alive: Arc::new(AtomicBool::new(true)), last_seat: Arc::new(Mutex::new(None)), @@ -198,6 +205,9 @@ impl CosmicStack { let Some(idx) = windows.iter().position(|w| w == window) else { return; }; + if idx == p.active.load(Ordering::SeqCst) { + p.reenter.store(true, Ordering::SeqCst); + } let window = windows.remove(idx); window.try_force_undecorated(false); window.set_tiled(false); @@ -220,7 +230,10 @@ impl CosmicStack { if windows.len() <= idx { return; } - let window = dbg!(windows.remove(idx)); + if idx == p.active.load(Ordering::SeqCst) { + p.reenter.store(true, Ordering::SeqCst); + } + let window = windows.remove(idx); window.try_force_undecorated(false); window.set_tiled(false); @@ -233,7 +246,7 @@ impl CosmicStack { self.0.with_program(|p| p.windows.lock().unwrap().len()) } - pub fn handle_focus(&self, direction: FocusDirection) -> bool { + pub fn handle_focus(&self, direction: FocusDirection, swap: Option) -> bool { let result = self.0.with_program(|p| match direction { FocusDirection::Left => { if !p.group_focused.load(Ordering::SeqCst) { @@ -278,7 +291,7 @@ impl CosmicStack { false } } - FocusDirection::Out => { + FocusDirection::Out if swap.is_none() => { if !p.group_focused.swap(true, Ordering::SeqCst) { p.windows.lock().unwrap().iter().for_each(|w| { w.set_activated(false); @@ -289,7 +302,7 @@ impl CosmicStack { false } } - FocusDirection::In => { + FocusDirection::In if swap.is_none() => { if !p.group_focused.swap(false, Ordering::SeqCst) { p.windows.lock().unwrap().iter().for_each(|w| { w.set_activated(true); @@ -360,6 +373,11 @@ impl CosmicStack { .with_program(|p| &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)] == window) } + pub fn whole_stack_focused(&self) -> bool { + self.0 + .with_program(|p| p.group_focused.load(Ordering::SeqCst)) + } + pub fn set_active(&self, window: &CosmicSurface) { self.0.with_program(|p| { if let Some(val) = p.windows.lock().unwrap().iter().position(|w| w == window) { @@ -412,10 +430,12 @@ impl CosmicStack { self.0.with_program(|p| { let active = p.active.load(Ordering::SeqCst); let previous = p.previous_keyboard.swap(active, Ordering::SeqCst); - if previous != active { + if previous != active || p.reenter.swap(false, Ordering::SeqCst) { let windows = p.windows.lock().unwrap(); - if let Some(previous) = windows.get(previous) { - KeyboardTarget::leave(previous, seat, data, serial); + if let Some(previous_surface) = windows.get(previous) { + if previous != active { + KeyboardTarget::leave(previous_surface, seat, data, serial); + } } KeyboardTarget::enter( &windows[active], @@ -476,7 +496,7 @@ impl CosmicStack { .with_program(|p| p.group_focused.store(true, Ordering::SeqCst)); } - pub(super) fn loop_handle(&self) -> LoopHandle<'static, crate::state::Data> { + pub(in super::super) fn loop_handle(&self) -> LoopHandle<'static, crate::state::Data> { self.0.loop_handle() } @@ -1051,9 +1071,10 @@ impl PointerTarget for CosmicStack { was_tiled, ); if grab.is_tiling_grab() { - data.common - .shell - .set_overview_mode(Some(Trigger::Pointer(button))); + data.common.shell.set_overview_mode( + Some(Trigger::Pointer(button)), + data.common.event_loop_handle.clone(), + ); } let seat = seat.clone(); diff --git a/src/shell/element/swap_indicator.rs b/src/shell/element/swap_indicator.rs new file mode 100644 index 00000000..1817297b --- /dev/null +++ b/src/shell/element/swap_indicator.rs @@ -0,0 +1,58 @@ +use crate::{ + fl, + utils::iced::{IcedElement, Program}, +}; + +use apply::Apply; +use calloop::LoopHandle; +use cosmic::{ + iced::widget::{container, horizontal_space, row}, + iced_core::{Alignment, Background, Color, Length}, + theme, + widget::{icon, text}, +}; +use smithay::utils::Size; + +pub type SwapIndicator = IcedElement; + +pub fn swap_indicator(evlh: LoopHandle<'static, crate::state::Data>) -> SwapIndicator { + SwapIndicator::new(SwapIndicatorInternal, Size::from((1, 1)), evlh) +} + +pub struct SwapIndicatorInternal; + +impl Program for SwapIndicatorInternal { + type Message = (); + + fn view(&self) -> crate::utils::iced::Element<'_, Self::Message> { + row(vec![ + icon("window-swap-symbolic", 32).force_svg(true).into(), + horizontal_space(16).into(), + text(fl!("swap-windows")) + .font(cosmic::font::FONT) + .size(24) + .into(), + ]) + .align_items(Alignment::Center) + .apply(container) + .center_x() + .center_y() + .padding(16) + .apply(container) + .style(theme::Container::custom(|theme| container::Appearance { + text_color: Some(Color::from(theme.cosmic().accent.on)), + background: Some(Background::Color(theme.cosmic().accent_color().into())), + border_radius: 18.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + })) + .width(Length::Shrink) + .height(Length::Shrink) + .apply(container) + .height(Length::Fill) + .width(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 69857391..441e711c 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -4,7 +4,7 @@ use crate::{ backend::render::{ cursor::{CursorShape, CursorState}, element::AsGlowRenderer, - IndicatorShader, + IndicatorShader, Key, Usage, }, shell::{ element::{ @@ -96,7 +96,7 @@ impl MoveGrabState { Some( CosmicMappedRenderElement::from(IndicatorShader::focus_element( renderer, - self.window.clone(), + Key::Window(Usage::MoveGrabIndicator, self.window.clone()), Rectangle::from_loc_and_size( render_location, self.window diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index d3171198..40b8a234 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -13,7 +13,7 @@ use smithay::{ use std::collections::HashMap; use crate::{ - backend::render::{element::AsGlowRenderer, IndicatorShader}, + backend::render::{element::AsGlowRenderer, IndicatorShader, Key, Usage}, shell::{ element::{ resize_indicator::ResizeIndicator, stack::CosmicStackRenderElement, @@ -514,7 +514,7 @@ impl FloatingLayout { if indicator_thickness > 0 { let element = IndicatorShader::focus_element( renderer, - elem.clone(), + Key::Window(Usage::FocusIndicator, elem.clone()), indicator_geometry, indicator_thickness, output_scale, diff --git a/src/shell/layout/tiling/grabs/mod.rs b/src/shell/layout/tiling/grabs/mod.rs index 1ec08053..056c6fa2 100644 --- a/src/shell/layout/tiling/grabs/mod.rs +++ b/src/shell/layout/tiling/grabs/mod.rs @@ -1,3 +1,5 @@ mod resize; +mod swap; pub use self::resize::*; +pub use self::swap::*; diff --git a/src/shell/layout/tiling/grabs/swap.rs b/src/shell/layout/tiling/grabs/swap.rs new file mode 100644 index 00000000..4272d86d --- /dev/null +++ b/src/shell/layout/tiling/grabs/swap.rs @@ -0,0 +1,93 @@ +use smithay::{ + backend::input::KeyState, + input::{ + keyboard::{ + GrabStartData as KeyboardGrabStartData, KeyboardGrab, KeyboardInnerHandle, + ModifiersState, + }, + Seat, SeatHandler, + }, + utils::Serial, +}; + +use crate::{ + config::{Action, KeyPattern}, + shell::{layout::tiling::NodeDesc, OverviewMode, Trigger}, + state::State, +}; + +pub struct SwapWindowGrab { + seat: Seat, + desc: NodeDesc, +} + +impl SwapWindowGrab { + pub fn new(seat: Seat, desc: NodeDesc) -> Self { + SwapWindowGrab { seat, desc } + } +} + +impl KeyboardGrab for SwapWindowGrab { + fn input( + &mut self, + data: &mut State, + handle: &mut KeyboardInnerHandle<'_, State>, + keycode: u32, + state: KeyState, + modifiers: Option, + serial: Serial, + time: u32, + ) { + if self.desc.output.upgrade().is_none() + || !matches!(&data.common.shell.overview_mode, OverviewMode::Started(Trigger::KeyboardSwap(_, d), _) if d == &self.desc) + { + handle.unset_grab(data, serial, false); + return; + } + + if state == KeyState::Released { + return; + } + + let syms = Vec::from(handle.keysym_handle(keycode).raw_syms()); + let focus_bindings = &data + .common + .config + .static_conf + .key_bindings + .iter() + .filter(|(_, action)| matches!(action, Action::Focus(_))) + .map(|(pattern, action)| { + let Action::Focus(direction) = action else { unreachable!() }; + (pattern.key, *direction) + }) + .collect::>(); + let Some(direction) = syms.iter().find_map(|sym| focus_bindings.iter().find_map(|(key, direction)| (sym == key).then_some(*direction))) else { return }; + + data.handle_action( + Action::Focus(direction), + &self.seat, + serial, + time, + KeyPattern { + modifiers: modifiers.map(Into::into).unwrap_or_default(), + key: keycode, + }, + None, + ); + } + + fn set_focus( + &mut self, + data: &mut State, + handle: &mut KeyboardInnerHandle<'_, State>, + focus: Option<::KeyboardFocus>, + serial: Serial, + ) { + handle.set_focus(data, focus, serial) + } + + fn start_data(&self) -> &KeyboardGrabStartData { + &KeyboardGrabStartData { focus: None } + } +} diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 2c88ac27..5c1496b2 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -2,13 +2,17 @@ use crate::{ backend::render::{ - element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, ACTIVE_GROUP_COLOR, + element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, Usage, ACTIVE_GROUP_COLOR, GROUP_COLOR, }, shell::{ element::{ resize_indicator::ResizeIndicator, - stack::{CosmicStackRenderElement, MoveResult as StackMoveResult}, + stack::{ + CosmicStackRenderElement, MoveResult as StackMoveResult, + TAB_HEIGHT as STACK_TAB_HEIGHT, + }, + swap_indicator::SwapIndicator, window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement, CosmicStack, CosmicWindow, }, @@ -20,14 +24,18 @@ use crate::{ layout::Orientation, CosmicSurface, OutputNotMapped, OverviewMode, ResizeDirection, ResizeMode, Trigger, }, - utils::prelude::*, + utils::{prelude::*, tween::EaseRectangle}, wayland::{ - handlers::xdg_shell::popup::get_popup_toplevel, protocols::toplevel_info::ToplevelInfoState, + handlers::xdg_shell::popup::get_popup_toplevel, + protocols::{toplevel_info::ToplevelInfoState, workspace::WorkspaceHandle}, }, }; use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; -use keyframe::{ease, functions::EaseInOutCubic}; +use keyframe::{ + ease, + functions::{EaseInOutCubic, Linear}, +}; use smithay::{ backend::renderer::{ element::{ @@ -39,9 +47,9 @@ use smithay::{ }, desktop::{layer_map_for_output, space::SpaceElement, PopupKind}, input::Seat, - output::Output, + output::{Output, WeakOutput}, reexports::wayland_server::Client, - utils::{IsAlive, Logical, Point, Rectangle, Scale}, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Size}, wayland::{compositor::add_blocker, seat::WaylandFocus}, }; use std::{ @@ -95,6 +103,14 @@ impl Hash for OutputData { } } +#[derive(Debug, Clone, PartialEq)] +pub struct NodeDesc { + pub handle: WorkspaceHandle, + pub output: WeakOutput, + pub node: NodeId, + pub stack_window: Option, +} + #[derive(Debug, serde::Deserialize, Clone, Copy, PartialEq, Eq)] pub enum Direction { Left, @@ -173,6 +189,7 @@ pub struct TilingLayout { standby_tree: Option>, pending_blockers: Vec, placeholder_id: Id, + swapping_stack_surface_id: Id, last_overview_hover: Option<(Option, TargetZone)>, } @@ -375,6 +392,7 @@ impl TilingLayout { standby_tree: None, pending_blockers: Vec::new(), placeholder_id: Id::new(), + swapping_stack_surface_id: Id::new(), last_overview_hover: None, } } @@ -574,6 +592,369 @@ impl TilingLayout { *window.tiling_node_id.lock().unwrap() = Some(window_id); } + pub fn replace_window(&mut self, old: &CosmicMapped, new: &CosmicMapped) { + let Some(old_id) = old.tiling_node_id.lock().unwrap().clone() else { return }; + let Some((output_data, queue)) = self.queues.iter_mut().find(|(_, queue)| queue.trees.back().unwrap().0.get(&old_id).is_ok()) else { return }; + let mut tree = queue.trees.back().unwrap().0.copy_clone(); + + let node = tree.get_mut(&old_id).unwrap(); + let data = node.data_mut(); + match data { + Data::Mapped { mapped, .. } => { + assert_eq!(mapped, old); + *mapped = new.clone(); + *new.tiling_node_id.lock().unwrap() = Some(old_id); + *old.tiling_node_id.lock().unwrap() = None; + } + _ => unreachable!("Tiling id points to group"), + } + + let blocker = TilingLayout::update_positions(&output_data.output, &mut tree, self.gaps); + queue.push_tree(tree, ANIMATION_DURATION, blocker); + } + + pub fn swap_trees( + this: &mut Self, + mut other: Option<&mut Self>, + this_desc: &NodeDesc, + other_desc: &NodeDesc, + ) -> Option { + let this_output = this_desc.output.upgrade()?; + let other_output = other_desc.output.upgrade()?; + + if this_output == other_output + && this_desc.handle == other_desc.handle + && this_desc.node == other_desc.node + && this_desc.stack_window.is_some() != other_desc.stack_window.is_some() + { + return None; + } + + let mut this_tree = this + .queues + .get(&this_output) + .map(|queue| queue.trees.back().unwrap().0.copy_clone())?; + let mut other_tree = match other.as_mut() { + Some(other) => Some( + other + .queues + .get(&other_output) + .map(|queue| queue.trees.back().unwrap().0.copy_clone())?, + ), + None => { + if this_output != other_output { + Some( + this.queues + .get(&other_output) + .map(|queue| queue.trees.back().unwrap().0.copy_clone())?, + ) + } else { + None + } + } + }; + + match (&this_desc.stack_window, &other_desc.stack_window) { + (None, None) if other_tree.is_none() => { + if this_desc.node != other_desc.node { + match this_tree.swap_nodes( + &this_desc.node, + &other_desc.node, + id_tree::SwapBehavior::TakeChildren, + ) { + Ok(_) => {} + Err(_) => return None, // Invalid node-descs, nothing to do here + } + } + } + (None, None) => { + let Some(other_tree) = other_tree.as_mut() else { unreachable!() }; + let this_node = this_tree.get_mut(&this_desc.node).ok()?; + let other_node = other_tree.get_mut(&other_desc.node).ok()?; + + // swap data + let other_data = other_node.data().clone(); + other_node.replace_data(this_node.replace_data(other_data)); + + if let Data::Mapped { mapped, .. } = this_node.data_mut() { + *mapped.tiling_node_id.lock().unwrap() = Some(this_desc.node.clone()); + } + if let Data::Mapped { mapped, .. } = other_node.data_mut() { + *mapped.tiling_node_id.lock().unwrap() = Some(other_desc.node.clone()); + } + + // swap children + let mut this_children = this_node + .children() + .into_iter() + .map(|child_id| (other_desc.node.clone(), child_id.clone())) + .collect::>(); + while !this_children.is_empty() { + for (parent_id, child_id) in std::mem::take(&mut this_children) { + let new_children = this_tree + .children_ids(&child_id) + .unwrap() + .cloned() + .collect::>(); + let child_node = this_tree + .remove_node(child_id, RemoveBehavior::OrphanChildren) + .unwrap(); + let maybe_mapped = match child_node.data() { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + _ => None, + }; + let new_id = other_tree + .insert(child_node, InsertBehavior::UnderNode(&parent_id)) + .unwrap(); + if let Some(mapped) = maybe_mapped { + *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); + } + this_children.extend( + new_children + .into_iter() + .map(|new_child| (new_id.clone(), new_child)), + ); + } + } + + let other_node = other_tree.get_mut(&other_desc.node).ok()?; + let mut other_children = other_node + .children() + .into_iter() + .map(|child_id| (this_desc.node.clone(), child_id.clone())) + .collect::>(); + while !other_children.is_empty() { + for (parent_id, child_id) in std::mem::take(&mut other_children) { + let new_children = other_tree + .children_ids(&child_id) + .unwrap() + .cloned() + .collect::>(); + let child_node = other_tree + .remove_node(child_id, RemoveBehavior::OrphanChildren) + .unwrap(); + let maybe_mapped = match child_node.data() { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + _ => None, + }; + let new_id = this_tree + .insert(child_node, InsertBehavior::UnderNode(&parent_id)) + .unwrap(); + if let Some(mapped) = maybe_mapped { + *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); + } + other_children.extend( + new_children + .into_iter() + .map(|new_child| (new_id.clone(), new_child)), + ); + } + } + } + (Some(this_surface), None) => { + if !this_surface.alive() { + return None; + } + let this_node = this_tree.get_mut(&this_desc.node).ok()?; + let Data::Mapped { mapped: this_mapped, .. } = this_node.data() else { return None }; + let this_mapped = this_mapped.clone(); + let geometry = *this_node.data().geometry(); + assert!(this_mapped.is_stack()); + + let other_tree = other_tree.as_mut().unwrap_or(&mut this_tree); + if other_tree.get(&other_desc.node).is_err() { + return None; + } + + let surfaces = other_tree + .traverse_pre_order(&other_desc.node) + .unwrap() + .flat_map(|node| match node.data() { + Data::Mapped { mapped, .. } => Some(mapped.windows().map(|(s, _)| s)), + _ => None, + }) + .flatten() + .collect::>(); + let cleanup = other_tree + .children_ids(&other_desc.node) + .unwrap() + .cloned() + .collect::>(); + for node in cleanup { + let _ = other_tree.remove_node(node, RemoveBehavior::DropChildren); + } + + let this_stack = this_mapped.stack_ref()?; + let this_idx = this_stack + .surfaces() + .position(|s| &s == this_surface) + .unwrap(); + this_stack.remove_window(&this_surface); + for (i, surface) in surfaces.into_iter().enumerate() { + this_stack.add_window(surface, Some(this_idx + i)); + } + + let mapped: CosmicMapped = + CosmicWindow::new(this_surface.clone(), this_stack.loop_handle()).into(); + *mapped.tiling_node_id.lock().unwrap() = Some(other_desc.node.clone()); + other_tree + .get_mut(&other_desc.node) + .unwrap() + .replace_data(Data::Mapped { + mapped, + last_geometry: geometry, + }); + } + (None, Some(other_surface)) => { + if !other_surface.alive() { + return None; + } + + let other_tree = other_tree.as_mut().unwrap_or(&mut this_tree); + let other_node = other_tree.get_mut(&other_desc.node).ok()?; + let Data::Mapped { mapped: other_mapped, .. } = other_node.data() else { return None }; + let other_mapped = other_mapped.clone(); + let geometry = *other_node.data().geometry(); + assert!(other_mapped.is_stack()); + + let surfaces = this_tree + .traverse_pre_order(&this_desc.node) + .unwrap() + .flat_map(|node| match node.data() { + Data::Mapped { mapped, .. } => Some(mapped.windows().map(|(s, _)| s)), + _ => None, + }) + .flatten() + .collect::>(); + let cleanup = this_tree + .children_ids(&this_desc.node) + .unwrap() + .cloned() + .collect::>(); + for node in cleanup { + let _ = this_tree.remove_node(node, RemoveBehavior::DropChildren); + } + + let other_stack = other_mapped.stack_ref()?; + let other_idx = other_stack + .surfaces() + .position(|s| &s == other_surface) + .unwrap(); + other_stack.remove_window(&other_surface); + for (i, surface) in surfaces.into_iter().enumerate() { + other_stack.add_window(surface, Some(other_idx + i)); + } + + let mapped: CosmicMapped = + CosmicWindow::new(other_surface.clone(), other_stack.loop_handle()).into(); + *mapped.tiling_node_id.lock().unwrap() = Some(this_desc.node.clone()); + this_tree + .get_mut(&this_desc.node) + .unwrap() + .replace_data(Data::Mapped { + mapped, + last_geometry: geometry, + }); + } + (Some(this_surface), Some(other_surface)) => { + if !this_surface.alive() || !other_surface.alive() { + return None; + } + + let this_mapped = + this_tree + .get(&this_desc.node) + .ok() + .and_then(|node| match node.data() { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + _ => None, + })?; + let other_tree = other_tree.as_mut().unwrap_or(&mut this_tree); + let other_mapped = + other_tree + .get(&other_desc.node) + .ok() + .and_then(|node| match node.data() { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + _ => None, + })?; + + let this_stack = this_mapped.stack_ref()?; + let other_stack = other_mapped.stack_ref()?; + + let this_idx = this_stack + .surfaces() + .position(|s| &s == this_surface) + .unwrap(); + let other_idx = other_stack + .surfaces() + .position(|s| &s == other_surface) + .unwrap(); + let this_was_active = &this_stack.active() == this_surface; + let other_was_active = &other_stack.active() == other_surface; + this_stack.remove_window(&this_surface); + this_stack.add_window(other_surface.clone(), Some(this_idx)); + other_stack.remove_window(&other_surface); + other_stack.add_window(this_surface.clone(), Some(other_idx)); + if this_was_active { + this_stack.set_active(&other_surface); + } + if other_was_active { + other_stack.set_active(&this_surface); + } + + return other + .as_ref() + .unwrap_or(&this) + .node_desc_to_focus(&other_desc); + } + } + + let this_queue = this.queues.get_mut(&this_output).unwrap(); + let blocker = TilingLayout::update_positions(&this_output, &mut this_tree, this.gaps); + this_queue.push_tree(this_tree, ANIMATION_DURATION, blocker); + + if let Some(mut other_tree) = other_tree { + let (other_queue, gaps) = if let Some(other) = other.as_mut() { + (other.queues.get_mut(&other_output).unwrap(), other.gaps) + } else { + (this.queues.get_mut(&other_output).unwrap(), this.gaps) + }; + let blocker = TilingLayout::update_positions(&other_output, &mut other_tree, gaps); + other_queue.push_tree(other_tree, ANIMATION_DURATION, blocker); + } + + match (&this_desc.stack_window, &other_desc.stack_window) { + (None, None) => this.node_desc_to_focus(&this_desc), + (None, Some(_)) => None, + _ => other + .as_ref() + .unwrap_or(&this) + .node_desc_to_focus(&other_desc), + } + } + + fn node_desc_to_focus(&self, desc: &NodeDesc) -> Option { + let output = desc.output.upgrade()?; + let queue = self.queues.get(&output)?; + let tree = &queue.trees.back().unwrap().0; + let data = tree.get(&desc.node).ok()?.data(); + match data { + Data::Mapped { mapped, .. } => Some(KeyboardFocusTarget::Element(mapped.clone())), + Data::Group { alive, .. } => Some(KeyboardFocusTarget::Group(WindowGroup { + node: desc.node.clone(), + output: desc.output.clone(), + alive: Arc::downgrade(alive), + focus_stack: tree + .children_ids(&desc.node) + .unwrap() + .cloned() + .collect::>(), + })), + _ => None, + } + } + pub fn unmap(&mut self, window: &CosmicMapped) -> Option { let output = { let node_id = window.tiling_node_id.lock().unwrap().clone()?; @@ -1062,6 +1443,7 @@ impl TilingLayout { direction: FocusDirection, seat: &Seat, focus_stack: impl Iterator + 'a, + swap_desc: Option, ) -> FocusResult { let output = seat.active_output(); let tree = &self.queues.get(&output).unwrap().trees.back().unwrap().0; @@ -1084,12 +1466,23 @@ impl TilingLayout { // stacks may handle focus internally if let FocusedNodeData::Window(window) = data.clone() { - if window.handle_focus(direction) { + if window.handle_focus( + direction, + swap_desc.clone().filter(|desc| desc.node == last_node_id), + ) { return FocusResult::Handled; } } if direction == FocusDirection::In { + if swap_desc + .as_ref() + .map(|desc| desc.node == last_node_id) + .unwrap_or(false) + { + return FocusResult::Handled; // abort + } + if let FocusedNodeData::Group(mut stack, _) = data.clone() { let maybe_id = stack.pop().unwrap(); let id = if tree @@ -1119,7 +1512,7 @@ impl TilingLayout { } .into(), ), - Data::Placeholder { .. } => return FocusResult::None, + Data::Placeholder { .. } => FocusResult::None, }; } } @@ -1133,6 +1526,18 @@ impl TilingLayout { assert!(group_data.is_group()); if direction == FocusDirection::Out { + if swap_desc + .as_ref() + .map(|desc| { + tree.traverse_pre_order_ids(group) + .unwrap() + .any(|node| node == desc.node) + }) + .unwrap_or(false) + { + return FocusResult::Handled; // abort + } + return FocusResult::Some( WindowGroup { node: group.clone(), @@ -1180,6 +1585,41 @@ impl TilingLayout { if focus_subtree.is_some() { let mut node_id = focus_subtree; while node_id.is_some() { + if let Some(desc) = swap_desc.as_ref() { + if let Some(replacement_id) = tree + .ancestor_ids(node_id.unwrap()) + .unwrap() + .find(|anchestor| *anchestor == &desc.node) + .or_else(|| { + tree.children_ids(node_id.unwrap()) + .unwrap() + .find(|child| *child == &desc.node) + }) + { + return match tree.get(replacement_id).unwrap().data() { + Data::Group { alive, .. } => { + FocusResult::Some(KeyboardFocusTarget::Group(WindowGroup { + node: replacement_id.clone(), + output: output.downgrade(), + alive: Arc::downgrade(&alive), + focus_stack: tree + .children_ids(replacement_id) + .unwrap() + .cloned() + .collect(), + })) + } + Data::Mapped { mapped, .. } => { + if mapped.is_stack() && desc.stack_window.is_none() { + mapped.stack_ref().unwrap().focus_stack(); + } + FocusResult::Some(KeyboardFocusTarget::Element(mapped.clone())) + } + _ => unreachable!(), + }; + } + } + match tree.get(node_id.unwrap()).unwrap().data() { Data::Group { orientation, .. } if orientation == &main_orientation => { // if the group is layed out in the direction we care about, @@ -1244,6 +1684,14 @@ impl TilingLayout { }); } Data::Mapped { mapped, .. } => { + if mapped.is_stack() + && swap_desc + .as_ref() + .map(|desc| desc.stack_window.is_none()) + .unwrap_or(false) + { + mapped.stack_ref().unwrap().focus_stack(); + } return FocusResult::Some(mapped.clone().into()); } Data::Placeholder { .. } => return FocusResult::None, @@ -2322,6 +2770,7 @@ impl TilingLayout { 1.0, placeholder_id, Some(None), + None, ) .map(|x| x.0) .unwrap_or_default(); @@ -2917,9 +3366,10 @@ impl TilingLayout { &self, renderer: &mut R, output: &Output, + workspace: &WorkspaceHandle, seat: Option<&Seat>, non_exclusive_zone: Rectangle, - overview: OverviewMode, + overview: (OverviewMode, Option), resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, ) -> Result< @@ -2966,14 +3416,25 @@ impl TilingLayout { } else { 1.0 }; - let draw_groups = overview.alpha(); + let draw_groups = overview.0.alpha(); let mut window_elements = Vec::new(); let mut popup_elements = Vec::new(); - let is_overview = !matches!(overview, OverviewMode::None); - let is_mouse_tiling = (matches!(overview, OverviewMode::Started(Trigger::Pointer(_), _))) + let is_overview = !matches!(overview.0, OverviewMode::None); + let is_mouse_tiling = (matches!(overview.0, OverviewMode::Started(Trigger::Pointer(_), _))) .then(|| self.last_overview_hover.as_ref().map(|x| &x.1)); + let swap_desc = match &overview.0 { + OverviewMode::Started(Trigger::KeyboardSwap(_, desc), _) + | OverviewMode::Ended(Some(Trigger::KeyboardSwap(_, desc)), _) => { + if desc.output == *output && desc.handle == *workspace { + Some(desc.clone()) + } else { + None + } + } + _ => None, + }; // all gone windows and fade them out let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { @@ -2989,6 +3450,7 @@ impl TilingLayout { output_scale, &self.placeholder_id, is_mouse_tiling, + swap_desc.clone(), ) } else { None @@ -3003,6 +3465,7 @@ impl TilingLayout { geometries.clone(), output_scale, percentage, + swap_desc.is_some(), ); window_elements.extend(w_elements); popup_elements.extend(p_elements); @@ -3023,6 +3486,7 @@ impl TilingLayout { output_scale, &self.placeholder_id, is_mouse_tiling, + swap_desc.clone(), ) } else { None @@ -3040,6 +3504,7 @@ impl TilingLayout { seat, output, percentage, + draw_groups, if let Some(transition) = draw_groups { let diff = (4u8.abs_diff(indicator_thickness) as f32 * transition).round() as u8; if 3 > indicator_thickness { @@ -3050,7 +3515,10 @@ impl TilingLayout { } else { indicator_thickness }, + overview, resize_indicator, + swap_desc.clone(), + &self.swapping_stack_surface_id, ); window_elements.extend(w_elements); popup_elements.extend(p_elements); @@ -3070,6 +3538,33 @@ const PLACEHOLDER_GAP_MOUSE: i32 = 8; const WINDOW_BACKDROP_BORDER: i32 = 4; const WINDOW_BACKDROP_GAP: i32 = 12; +const MAX_SWAP_WINDOW_SIZE: (i32, i32) = (360, 240); + +fn swap_factor(size: Size) -> f64 { + let target_w = std::cmp::min(size.w, MAX_SWAP_WINDOW_SIZE.0); + let target_h = std::cmp::min(size.h, MAX_SWAP_WINDOW_SIZE.1); + (target_w as f64 / size.w as f64).min(target_h as f64 / size.h as f64) +} + +fn swap_geometry( + size: Size, + relative_to: Rectangle, +) -> Rectangle { + let factor = swap_factor(size); + + let new_size = Size::from(( + (size.w as f64 * factor).round() as i32, + (size.h as f64 * factor).round() as i32, + )); + + let loc = Point::from(( + relative_to.loc.x + relative_to.size.w - new_size.w + 16, + relative_to.loc.y - 16, + )); + + Rectangle::from_loc_and_size(loc, new_size) +} + fn geometries_for_groupview<'a, R>( tree: &Tree, renderer: impl Into>, @@ -3080,6 +3575,7 @@ fn geometries_for_groupview<'a, R>( output_scale: f64, placeholder_id: &Id, mouse_tiling: Option>, + swap_desc: Option, ) -> Option<( HashMap>, Vec>, @@ -3103,7 +3599,7 @@ where let mut stack = vec![(non_exclusive_zone, 0)]; let mut elements = Vec::new(); - let mut geometries = HashMap::new(); + let mut geometries: HashMap> = HashMap::new(); let alpha = alpha * transition; let focused = seat @@ -3116,6 +3612,11 @@ where }) }) .map(|(id, _)| id); + let focused_geo = if let Some(focused_id) = focused.as_ref() { + Some(*tree.get(focused_id).unwrap().data().geometry()) + } else { + None + }; let has_potential_groups = if let Some(focused_id) = focused.as_ref() { let focused_node = tree.get(focused_id).unwrap(); @@ -3142,7 +3643,8 @@ where }) .unwrap_or(false); - let render_potential_group = has_potential_groups + let render_potential_group = swap_desc.is_none() + && has_potential_groups && (if let Some(focused_id) = focused.as_ref() { // `focused` can move into us directly if let Some(parent) = node.parent() { @@ -3220,6 +3722,7 @@ where } => { let render_active_child = if let Some(focused_id) = focused.as_ref() { !has_potential_groups + && swap_desc.is_none() && node .children() .iter() @@ -3404,6 +3907,33 @@ where }; } + if matches!(swap_desc, Some(ref desc) if &desc.node == &node_id) { + if let Some(focused_geo) = focused_geo { + if let Some(renderer) = renderer.as_mut() { + elements.push( + BackdropShader::element( + *renderer, + Key::Group(Arc::downgrade(alive)), + geo, + 8., + alpha * 0.15, + GROUP_COLOR, + ) + .into(), + ); + } + let swap_geo = swap_geometry(geo.size, focused_geo); + geo = ease( + Linear, + EaseRectangle(geo), + EaseRectangle(swap_geo), + transition, + ) + .unwrap(); + geometries.insert(node_id.clone(), geo); + } + }; + let previous_length = match orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, @@ -3524,7 +4054,7 @@ where elements.push( IndicatorShader::element( *renderer, - mapped.clone(), + Key::Window(Usage::PotentialGroupIndicator, mapped.clone()), geo, 4, 8, @@ -3562,7 +4092,7 @@ where elements.push( BackdropShader::element( *renderer, - mapped.clone(), + Key::Window(Usage::OverviewBackdrop, mapped.clone()), geo, 8., alpha @@ -3585,6 +4115,20 @@ where geo.size -= (WINDOW_BACKDROP_GAP * 2, WINDOW_BACKDROP_GAP * 2).into(); } + if matches!(swap_desc, Some(ref desc) if &desc.node == &node_id && desc.stack_window.is_none()) + { + if let Some(focused_geo) = focused_geo { + let swap_geo = swap_geometry(geo.size, focused_geo); + geo = ease( + Linear, + EaseRectangle(geo), + EaseRectangle(swap_geo), + transition, + ) + .unwrap(); + } + }; + geometries.insert(node_id.clone(), geo); } Data::Placeholder { .. } => { @@ -3628,6 +4172,7 @@ fn render_old_tree( geometries: Option>>, output_scale: f64, percentage: f32, + is_swap_mode: bool, ) -> ( Vec>, Vec>, @@ -3660,10 +4205,11 @@ where ) .filter(|(mapped, _, _)| { if let Some(root) = target_tree.root_node_id() { - !target_tree - .traverse_pre_order(root) - .unwrap() - .any(|node| node.data().is_mapped(Some(mapped))) + is_swap_mode + || !target_tree + .traverse_pre_order(root) + .unwrap() + .any(|node| node.data().is_mapped(Some(mapped))) } else { true } @@ -3761,8 +4307,12 @@ fn render_new_tree( seat: Option<&Seat>, output: &Output, percentage: f32, + transition: Option, indicator_thickness: u8, + overview: (OverviewMode, Option), mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>, + swap_desc: Option, + swapping_stack_surface_id: &Id, ) -> ( Vec>, Vec>, @@ -3793,8 +4343,9 @@ where let mut popup_elements = Vec::new(); let mut group_backdrop = None; - let mut indicator = None; + let mut indicators = Vec::new(); let mut resize_elements = None; + let mut swap_elements = Vec::new(); let output_geo = output.geometry(); let output_scale = output.current_scale().fractional_scale(); @@ -3848,7 +4399,7 @@ where .unwrap_or(*original_geo) }); - let crop_rect = original_geo; + let mut crop_rect = original_geo; let (scale, offset) = scaled_geo .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) .unwrap_or_else(|| (1.0.into(), (0, 0).into())); @@ -3864,33 +4415,32 @@ where }) .unwrap_or(*original_geo); - let (geo, alpha) = if let Some(old_geo) = old_geo { + let (geo, alpha) = if let Some(old_geo) = old_geo.filter(|_| { + swap_desc + .as_ref() + .map(|desc| desc.node != node_id && desc.stack_window.is_none()) + .unwrap_or(true) + }) { ( - Rectangle::from_loc_and_size( - ( - old_geo.loc.x - + ((new_geo.loc.x - old_geo.loc.x) as f32 * percentage).round() - as i32, - old_geo.loc.y - + ((new_geo.loc.y - old_geo.loc.y) as f32 * percentage).round() - as i32, - ), - ( - old_geo.size.w - + ((new_geo.size.w - old_geo.size.w) as f32 * percentage) - .round() as i32, - old_geo.size.h - + ((new_geo.size.h - old_geo.size.h) as f32 * percentage) - .round() as i32, - ), - ), + ease( + Linear, + EaseRectangle(old_geo), + EaseRectangle(new_geo), + percentage, + ) + .unwrap(), 1.0, ) } else { (new_geo, percentage) }; + if matches!(overview.0, OverviewMode::None) { + crop_rect = &geo; + } - if focused.as_ref() == Some(&node_id) { + if swap_desc.as_ref().map(|desc| &desc.node) == Some(&node_id) + || focused.as_ref() == Some(&node_id) + { if indicator_thickness > 0 || data.is_group() { let mut geo = geo.clone(); if data.is_group() { @@ -3913,22 +4463,117 @@ where )); } - indicator = Some(IndicatorShader::focus_element( - renderer, - match data { - Data::Mapped { mapped, .. } => mapped.clone().into(), - Data::Group { alive, .. } => Key::Group(Arc::downgrade(alive)), - _ => unreachable!(), - }, - geo, - if data.is_group() { - 4 - } else { - indicator_thickness - }, - output_scale, - 1.0, - )); + if swap_desc.as_ref().map(|desc| &desc.node) == Some(&node_id) + || focused.as_ref() == Some(&node_id) + { + if !swap_desc + .as_ref() + .map(|desc| desc.stack_window.is_some()) + .unwrap_or(false) + || focused.as_ref() == Some(&node_id) + { + indicators.push(IndicatorShader::focus_element( + renderer, + match data { + Data::Mapped { mapped, .. } => Key::Window( + Usage::FocusIndicator, + mapped.clone().into(), + ), + Data::Group { alive, .. } => { + Key::Group(Arc::downgrade(alive)) + } + _ => unreachable!(), + }, + geo, + if data.is_group() { + 4 + } else { + indicator_thickness + }, + output_scale, + 1.0, + )); + } + + if focused.as_ref() == Some(&node_id) { + if let Some(window) = swap_desc + .as_ref() + .and_then(|desc| desc.stack_window.clone()) + { + let window_geo = window.geometry(); + let swap_geo = ease( + Linear, + EaseRectangle({ + let mut geo = geo.clone(); + geo.loc.x += STACK_TAB_HEIGHT; + geo.size.h -= STACK_TAB_HEIGHT; + geo + }), + EaseRectangle(swap_geometry(window_geo.size, geo)), + transition.unwrap_or(1.0), + ) + .unwrap(); + + indicators.push(IndicatorShader::focus_element( + renderer, + Key::Static(swapping_stack_surface_id.clone()), + swap_geo, + 4, + output_scale, + transition.unwrap_or(1.0), + )); + + let render_loc = (swap_geo.loc - window_geo.loc) + .to_physical_precise_round(output_scale); + + swap_elements.extend( + window + .render_elements::>( + renderer, + render_loc, + output_scale.into(), + 1.0, + ) + .into_iter() + .map(|window| { + CosmicMappedRenderElement::GrabbedWindow( + RescaleRenderElement::from_element( + window, + swap_geo.loc.to_physical_precise_round( + output_scale, + ), + ease( + Linear, + 1.0, + swap_factor(window_geo.size), + transition.unwrap_or(1.0), + ), + ), + ) + }), + ) + } + } + + if focused.as_ref() == Some(&node_id) + && swap_desc.as_ref().map(|desc| &desc.node) != Some(&node_id) + { + if let Some(swap) = &overview.1 { + swap.resize(geo.size); + swap.output_enter(output, output_geo); + swap_elements.extend( + swap.render_elements::>( + renderer, + geo.loc.to_physical_precise_round(output_scale), + output_scale.into(), + alpha * overview.0.alpha().unwrap_or(1.0), + ) + .into_iter() + .map(CosmicMappedRenderElement::from), + ); + } + } + } } if let Some((mode, resize)) = resize_indicator.as_mut() { @@ -3938,7 +4583,8 @@ where resize.resize(geo.size); resize.output_enter(output, output_geo); - let possible_edges = TilingLayout::possible_resizes(target_tree, node_id); + let possible_edges = + TilingLayout::possible_resizes(target_tree, node_id.clone()); if !possible_edges.is_empty() { if resize.with_program(|internal| { let mut edges = internal.edges.lock().unwrap(); @@ -3971,69 +4617,126 @@ where let original_location = (original_geo.loc - mapped.geometry().loc) .to_physical_precise_round(output_scale); - let (w_elements, p_elements) = mapped + let (mut w_elements, p_elements) = mapped .split_render_elements::>( renderer, original_location, Scale::from(output_scale), alpha, ); + if swap_desc + .as_ref() + .map(|swap_desc| { + &swap_desc.node == &node_id && swap_desc.stack_window.is_some() + }) + .unwrap_or(false) + { + let mut geo = mapped.active_window_geometry(); + geo.loc += original_geo.loc; + w_elements.insert( + 0, + CosmicMappedRenderElement::Overlay(BackdropShader::element( + renderer, + Key::Window(Usage::Overlay, mapped.clone()), + geo, + 0.0, + 0.3, + GROUP_COLOR, + )), + ) + } - window_elements.extend(w_elements.into_iter().flat_map( - |element| match element { - CosmicMappedRenderElement::Stack(elem) => { - Some(CosmicMappedRenderElement::TiledStack({ - let cropped = CropRenderElement::from_element( - elem, - output_scale, - crop_rect.to_physical_precise_round(output_scale), - )?; - let rescaled = RescaleRenderElement::from_element( - cropped, - original_geo.loc.to_physical_precise_round(output_scale), - scale, - ); - let relocated = RelocateRenderElement::from_element( - rescaled, - (geo.loc - original_geo.loc) - .to_physical_precise_round(output_scale), - Relocate::Relative, - ); - relocated - })) - } - CosmicMappedRenderElement::Window(elem) => { - Some(CosmicMappedRenderElement::TiledWindow({ - let cropped = CropRenderElement::from_element( - elem, - output_scale, - crop_rect.to_physical_precise_round(output_scale), - )?; - let rescaled = RescaleRenderElement::from_element( - cropped, - original_geo.loc.to_physical_precise_round(output_scale), - scale, - ); - let relocated = RelocateRenderElement::from_element( - rescaled, - (geo.loc - original_geo.loc) - .to_physical_precise_round(output_scale), - Relocate::Relative, - ); - relocated - })) - } - x => Some(x), - }, - )); - popup_elements.extend(p_elements) + let w_elements = w_elements.into_iter().flat_map(|element| match element { + CosmicMappedRenderElement::Stack(elem) => { + Some(CosmicMappedRenderElement::TiledStack({ + let cropped = CropRenderElement::from_element( + elem, + output_scale, + crop_rect.to_physical_precise_round(output_scale), + )?; + let rescaled = RescaleRenderElement::from_element( + cropped, + original_geo.loc.to_physical_precise_round(output_scale), + scale, + ); + let relocated = RelocateRenderElement::from_element( + rescaled, + (geo.loc - original_geo.loc) + .to_physical_precise_round(output_scale), + Relocate::Relative, + ); + relocated + })) + } + CosmicMappedRenderElement::Window(elem) => { + Some(CosmicMappedRenderElement::TiledWindow({ + let cropped = CropRenderElement::from_element( + elem, + output_scale, + crop_rect.to_physical_precise_round(output_scale), + )?; + let rescaled = RescaleRenderElement::from_element( + cropped, + original_geo.loc.to_physical_precise_round(output_scale), + scale, + ); + let relocated = RelocateRenderElement::from_element( + rescaled, + (geo.loc - original_geo.loc) + .to_physical_precise_round(output_scale), + Relocate::Relative, + ); + relocated + })) + } + CosmicMappedRenderElement::Overlay(elem) => { + Some(CosmicMappedRenderElement::TiledOverlay({ + let cropped = CropRenderElement::from_element( + elem, + output_scale, + crop_rect.to_physical_precise_round(output_scale), + )?; + let rescaled = RescaleRenderElement::from_element( + cropped, + original_geo.loc.to_physical_precise_round(output_scale), + scale, + ); + let relocated = RelocateRenderElement::from_element( + rescaled, + (geo.loc - original_geo.loc) + .to_physical_precise_round(output_scale), + Relocate::Relative, + ); + relocated + })) + } + x => Some(x), + }); + if swap_desc + .as_ref() + .map(|swap_desc| { + (&swap_desc.node == &node_id + || target_tree + .ancestor_ids(&node_id) + .unwrap() + .any(|id| &swap_desc.node == id)) + && swap_desc.stack_window.is_none() + }) + .unwrap_or(false) + { + swap_elements.extend(w_elements); + } else { + window_elements.extend(w_elements); + popup_elements.extend(p_elements) + } } }); window_elements = resize_elements .into_iter() .flatten() - .chain(indicator.into_iter().map(Into::into)) + .chain(swap_elements) + .chain(indicators.into_iter().map(Into::into)) .chain(window_elements) .chain(group_backdrop.into_iter().map(Into::into)) .collect(); diff --git a/src/shell/mod.rs b/src/shell/mod.rs index d07046c1..f6b365ae 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -59,13 +59,14 @@ pub use self::workspace::*; use self::{ element::{ resize_indicator::{resize_indicator, ResizeIndicator}, + swap_indicator::{swap_indicator, SwapIndicator}, CosmicWindow, }, focus::target::KeyboardFocusTarget, grabs::ResizeEdge, layout::{ floating::{FloatingLayout, ResizeState}, - tiling::{Direction, TilingLayout}, + tiling::{Direction, NodeDesc, TilingLayout}, }, }; @@ -73,7 +74,8 @@ const ANIMATION_DURATION: Duration = Duration::from_millis(200); #[derive(Debug, Clone)] pub enum Trigger { - Keyboard(KeyModifiers), + KeyboardSwap(KeyPattern, NodeDesc), + KeyboardMove(KeyModifiers), Pointer(u32), } @@ -81,7 +83,7 @@ pub enum Trigger { pub enum OverviewMode { None, Started(Trigger, Instant), - Ended(Instant), + Ended(Option, Instant), } impl OverviewMode { @@ -92,7 +94,7 @@ impl OverviewMode { / ANIMATION_DURATION.as_millis() as f32; Some(ease(EaseInOutCubic, 0.0, 1.0, percentage)) } - OverviewMode::Ended(end) => { + OverviewMode::Ended(_, end) => { let percentage = Instant::now().duration_since(*end).as_millis() as f32 / ANIMATION_DURATION.as_millis() as f32; if percentage < 1.0 { @@ -160,6 +162,7 @@ pub struct Shell { gaps: (u8, u8), overview_mode: OverviewMode, + swap_indicator: Option, resize_mode: ResizeMode, resize_state: Option<( KeyboardFocusTarget, @@ -628,6 +631,7 @@ impl Shell { gaps: config.static_conf.gaps, overview_mode: OverviewMode::None, + swap_indicator: None, resize_mode: ResizeMode::None, resize_state: None, resize_indicator: None, @@ -1206,32 +1210,45 @@ impl Shell { clients } - pub fn set_overview_mode(&mut self, enabled: Option) { + pub fn set_overview_mode( + &mut self, + enabled: Option, + evlh: LoopHandle<'static, crate::state::Data>, + ) { if let Some(trigger) = enabled { if !matches!(self.overview_mode, OverviewMode::Started(_, _)) { + if matches!(trigger, Trigger::KeyboardSwap(_, _)) { + self.swap_indicator = Some(swap_indicator(evlh)); + } self.overview_mode = OverviewMode::Started(trigger, Instant::now()); } } else { - if !matches!(self.overview_mode, OverviewMode::Ended(_)) { - let reverse_duration = if let OverviewMode::Started(_, start) = self.overview_mode { - ANIMATION_DURATION - - Instant::now().duration_since(start).min(ANIMATION_DURATION) - } else { - Duration::ZERO - }; - self.overview_mode = OverviewMode::Ended(Instant::now() - reverse_duration); + if !matches!(self.overview_mode, OverviewMode::Ended(_, _)) { + let (reverse_duration, trigger) = + if let OverviewMode::Started(trigger, start) = self.overview_mode.clone() { + ( + ANIMATION_DURATION + - Instant::now().duration_since(start).min(ANIMATION_DURATION), + Some(trigger), + ) + } else { + (Duration::ZERO, None) + }; + self.overview_mode = + OverviewMode::Ended(trigger, Instant::now() - reverse_duration); } } } - pub fn overview_mode(&mut self) -> OverviewMode { - if let OverviewMode::Ended(timestamp) = self.overview_mode { + pub fn overview_mode(&mut self) -> (OverviewMode, Option) { + if let OverviewMode::Ended(_, timestamp) = self.overview_mode { if Instant::now().duration_since(timestamp) > ANIMATION_DURATION { self.overview_mode = OverviewMode::None; + self.swap_indicator = None; } } - self.overview_mode.clone() + (self.overview_mode.clone(), self.swap_indicator.clone()) } pub fn set_resize_mode( @@ -1570,10 +1587,10 @@ impl Shell { .toplevel_info_state .toplevel_leave_output(&window, &output); if grab.is_tiling_grab() { - state - .common - .shell - .set_overview_mode(Some(Trigger::Pointer(button))); + state.common.shell.set_overview_mode( + Some(Trigger::Pointer(button)), + state.common.event_loop_handle.clone(), + ); } seat.get_pointer().unwrap().set_grab( state, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 76e4af01..3dafee62 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -50,13 +50,14 @@ use wayland_backend::server::ClientId; use super::{ element::{ resize_indicator::ResizeIndicator, stack::CosmicStackRenderElement, - window::CosmicWindowRenderElement, CosmicMapped, + swap_indicator::SwapIndicator, window::CosmicWindowRenderElement, CosmicMapped, }, focus::{ - target::{KeyboardFocusTarget, PointerFocusTarget}, + target::{KeyboardFocusTarget, PointerFocusTarget, WindowGroup}, FocusStack, FocusStackMut, }, grabs::{ResizeEdge, ResizeGrab}, + layout::tiling::NodeDesc, CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode, }; @@ -482,6 +483,43 @@ impl Workspace { && self.tiling_layer.mapped().any(|(_, m, _)| m == mapped) } + pub fn node_desc(&self, focus: KeyboardFocusTarget) -> Option { + match focus { + KeyboardFocusTarget::Element(mapped) => { + self.tiling_layer.mapped().find_map(|(output, m, _)| { + (m == &mapped).then_some(output.clone()).and_then(|output| { + mapped + .tiling_node_id + .lock() + .unwrap() + .clone() + .map(|node_id| NodeDesc { + handle: self.handle.clone(), + output: output.downgrade(), + node: node_id, + stack_window: if mapped + .stack_ref() + .map(|stack| !stack.whole_stack_focused()) + .unwrap_or(false) + { + Some(mapped.active_window()) + } else { + None + }, + }) + }) + }) + } + KeyboardFocusTarget::Group(WindowGroup { output, node, .. }) => Some(NodeDesc { + handle: self.handle.clone(), + output, + node, + stack_window: None, + }), + _ => None, + } + } + pub fn render_output<'a, R>( &self, renderer: &mut R, @@ -489,7 +527,7 @@ impl Workspace { override_redirect_windows: &[X11Surface], xwm_state: Option<&'a mut XWaylandState>, draw_focus_indicator: Option<&Seat>, - overview: OverviewMode, + overview: (OverviewMode, Option), resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, ) -> Result< @@ -582,7 +620,7 @@ impl Workspace { draw_focus_indicator.and_then(|seat| self.focus_stack.get(seat).last().cloned()); // floating surfaces - let alpha = match &overview { + let alpha = match &overview.0 { OverviewMode::Started(_, started) => { (1.0 - (Instant::now().duration_since(*started).as_millis() / ANIMATION_DURATION.as_millis()) as f32) @@ -590,7 +628,7 @@ impl Workspace { * 0.4 + 0.6 } - OverviewMode::Ended(ended) => { + OverviewMode::Ended(_, ended) => { ((Instant::now().duration_since(*ended).as_millis() / ANIMATION_DURATION.as_millis()) as f32) * 0.4 @@ -614,6 +652,7 @@ impl Workspace { let (w_elements, p_elements) = self.tiling_layer.render_output::( renderer, output, + &self.handle, draw_focus_indicator, layer_map.non_exclusive_zone(), overview.clone(), @@ -635,12 +674,12 @@ impl Workspace { } } - let alpha = match overview { + let alpha = match overview.0 { OverviewMode::Started(_, start) => Some( (Instant::now().duration_since(start).as_millis() as f64 / 100.0).min(1.0) as f32, ), - OverviewMode::Ended(ended) => Some( + OverviewMode::Ended(_, ended) => Some( 1.0 - (Instant::now().duration_since(ended).as_millis() as f64 / 100.0).min(1.0) as f32, ), diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 025f45ef..ff39a61d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,3 +4,4 @@ mod ids; pub(crate) use self::ids::id_gen; pub mod iced; pub mod prelude; +pub mod tween; diff --git a/src/utils/tween.rs b/src/utils/tween.rs new file mode 100644 index 00000000..4000b7d9 --- /dev/null +++ b/src/utils/tween.rs @@ -0,0 +1,47 @@ +use keyframe::{num_traits::Float, CanTween}; +use smithay::utils::{Coordinate, Point, Rectangle, Size}; + +pub struct EasePoint(pub Point); +pub struct EaseSize(pub Size); +pub struct EaseRectangle(pub Rectangle); + +impl CanTween for EasePoint { + fn ease(from: Self, to: Self, time: impl Float) -> Self { + let x = N::from_f64(CanTween::ease(from.0.x.to_f64(), to.0.x.to_f64(), time).round()); + let y = N::from_f64(CanTween::ease(from.0.y.to_f64(), to.0.y.to_f64(), time).round()); + EasePoint((x, y).into()) + } +} + +impl CanTween for EaseSize { + fn ease(from: Self, to: Self, time: impl Float) -> Self { + let w = N::from_f64(CanTween::ease(from.0.w.to_f64(), to.0.w.to_f64(), time).round()); + let h = N::from_f64(CanTween::ease(from.0.h.to_f64(), to.0.h.to_f64(), time).round()); + EaseSize((w, h).into()) + } +} + +impl CanTween for EaseRectangle { + fn ease(from: Self, to: Self, time: impl Float) -> Self { + EaseRectangle(Rectangle::from_loc_and_size( + CanTween::ease(EasePoint(from.0.loc), EasePoint(to.0.loc), time).unwrap(), + CanTween::ease(EaseSize(from.0.size), EaseSize(to.0.size), time).unwrap(), + )) + } +} + +impl EasePoint { + pub fn unwrap(self) -> Point { + self.0 + } +} +impl EaseSize { + pub fn unwrap(self) -> Size { + self.0 + } +} +impl EaseRectangle { + pub fn unwrap(self) -> Rectangle { + self.0 + } +}