From 8ed96498de72ec4caffd59b7f2f963855b4bfd30 Mon Sep 17 00:00:00 2001 From: Luca <43602144+LucaCoduriV@users.noreply.github.com> Date: Thu, 1 Aug 2024 20:56:23 +0200 Subject: [PATCH] feat: move window by dragging them with the mouse (#635) --- .../events/handle_window_location_changed.rs | 109 ++++- .../events/handle_window_moved_or_resized.rs | 52 --- .../handle_window_moved_or_resized_end.rs | 386 ++++++++++++++++++ .../handle_window_moved_or_resized_start.rs | 21 + packages/wm/src/common/events/mod.rs | 6 +- .../wm/src/common/platform/event_listener.rs | 3 +- packages/wm/src/common/platform/platform.rs | 21 +- .../src/common/platform/window_event_hook.rs | 10 +- packages/wm/src/common/rect.rs | 8 +- packages/wm/src/windows/active_drag.rs | 11 + .../wm/src/windows/commands/manage_window.rs | 2 + packages/wm/src/windows/mod.rs | 2 + packages/wm/src/windows/non_tiling_window.rs | 8 +- packages/wm/src/windows/tiling_window.rs | 5 + .../wm/src/windows/traits/window_getters.rs | 14 +- packages/wm/src/wm.rs | 10 +- packages/wm/src/wm_state.rs | 47 ++- 17 files changed, 638 insertions(+), 77 deletions(-) delete mode 100644 packages/wm/src/common/events/handle_window_moved_or_resized.rs create mode 100644 packages/wm/src/common/events/handle_window_moved_or_resized_end.rs create mode 100644 packages/wm/src/common/events/handle_window_moved_or_resized_start.rs create mode 100644 packages/wm/src/windows/active_drag.rs diff --git a/packages/wm/src/common/events/handle_window_location_changed.rs b/packages/wm/src/common/events/handle_window_location_changed.rs index 7941a9cbc..5e7209038 100644 --- a/packages/wm/src/common/events/handle_window_location_changed.rs +++ b/packages/wm/src/common/events/handle_window_location_changed.rs @@ -2,14 +2,18 @@ use anyhow::Context; use tracing::info; use crate::{ - common::platform::NativeWindow, + common::{platform::NativeWindow, Rect}, containers::{ - commands::move_container_within_tree, traits::CommonGetters, - WindowContainer, + commands::{ + attach_container, detach_container, move_container_within_tree, + }, + traits::{CommonGetters, PositionGetters}, + Container, WindowContainer, }, - user_config::{FullscreenStateConfig, UserConfig}, + user_config::{FloatingStateConfig, FullscreenStateConfig, UserConfig}, windows::{ - commands::update_window_state, traits::WindowGetters, WindowState, + commands::update_window_state, traits::WindowGetters, + ActiveDragOperation, TilingWindow, WindowState, }, wm_state::WmState, }; @@ -23,7 +27,17 @@ pub fn handle_window_location_changed( // Update the window's state to be fullscreen or toggled from fullscreen. if let Some(window) = found_window { - let frame_position = window.native().refresh_frame_position()?; + let frame_position: Rect = window.native().refresh_frame_position()?; + let old_frame_position: Rect = window.to_rect()?; + + update_window_operation( + state, + config, + &window, + &frame_position, + &old_frame_position, + )?; + let is_minimized = window.native().refresh_is_minimized()?; let old_is_maximized = window.native().is_maximized()?; @@ -131,3 +145,86 @@ pub fn handle_window_location_changed( Ok(()) } + +/// Updates the window operation based on changes in frame position. +/// +/// This function determines whether a window is being moved or resized and +/// updates its operation state accordingly. If the window is being moved, +/// it's set to floating mode. +fn update_window_operation( + state: &mut WmState, + config: &UserConfig, + window: &WindowContainer, + frame_position: &Rect, + old_frame_position: &Rect, +) -> anyhow::Result<()> { + if let Some(tiling_window) = window.as_tiling_window() { + if let Some(mut active_drag) = tiling_window.active_drag() { + if active_drag.operation.is_none() + && frame_position != old_frame_position + { + if frame_position.height() == old_frame_position.height() + && frame_position.width() == old_frame_position.width() + { + active_drag.operation = Some(ActiveDragOperation::Moving); + tiling_window.set_active_drag(Some(active_drag)); + set_into_floating(tiling_window.clone(), state, config)?; + } else { + active_drag.operation = Some(ActiveDragOperation::Resizing); + tiling_window.set_active_drag(Some(active_drag)); + } + } + } + } + Ok(()) +} + +/// Converts a tiling window to a floating window and updates the window +/// hierarchy. +/// +/// This function handles the process of transitioning a tiling window to a +/// floating state, including necessary adjustments to the window hierarchy +/// and updating the window's state. +fn set_into_floating( + moved_window: TilingWindow, + state: &mut WmState, + config: &UserConfig, +) -> anyhow::Result<()> { + let moved_window_parent = moved_window + .parent() + .context("Tiling window has no parent")?; + + if let Some(Container::Split(split)) = moved_window.parent() { + if split.child_count() == 2 { + let split_parent = split.parent().unwrap(); + let split_index = split.index(); + let children = split.children(); + + // Looping in reversed order to reattach them in the right order + for child in children.into_iter().rev() { + detach_container(child.clone())?; + attach_container(&child, &split_parent, Some(split_index))?; + } + } + } + + if let Some(mut active_drag) = moved_window.active_drag() { + active_drag.is_from_tiling = true; + moved_window.set_active_drag(Some(active_drag)); + } + + update_window_state( + moved_window.as_window_container().unwrap(), + WindowState::Floating(FloatingStateConfig { + centered: true, + shown_on_top: true, + }), + state, + config, + )?; + state + .pending_sync + .containers_to_redraw + .push(moved_window_parent); + Ok(()) +} diff --git a/packages/wm/src/common/events/handle_window_moved_or_resized.rs b/packages/wm/src/common/events/handle_window_moved_or_resized.rs deleted file mode 100644 index e155205cc..000000000 --- a/packages/wm/src/common/events/handle_window_moved_or_resized.rs +++ /dev/null @@ -1,52 +0,0 @@ -use anyhow::Context; -use tracing::info; - -use crate::{ - common::{platform::NativeWindow, LengthValue}, - containers::{ - traits::{CommonGetters, PositionGetters}, - WindowContainer, - }, - windows::{commands::resize_window, traits::WindowGetters}, - wm_state::WmState, -}; - -/// Handles the event for when a window is finished being moved or resized -/// by the user (e.g. via the window's drag handles). -/// -/// This resizes the window if it's a tiling window. -pub fn handle_window_moved_or_resized( - native_window: NativeWindow, - state: &mut WmState, -) -> anyhow::Result<()> { - let found_window = state.window_from_native(&native_window); - - if let Some(WindowContainer::TilingWindow(window)) = found_window { - // TODO: Log window details. - info!("Tiling window moved/resized"); - - let parent = window.parent().context("No parent.")?; - - // Snap window to its original position if it's the only window in the - // workspace. - if parent.is_workspace() && window.tiling_siblings().count() == 0 { - state.pending_sync.containers_to_redraw.push(window.into()); - return Ok(()); - } - - let new_position = window.native().refresh_frame_position()?; - let old_position = window.to_rect()?; - - let width_delta = new_position.width() - old_position.width(); - let height_delta = new_position.height() - old_position.height(); - - resize_window( - window.clone().into(), - Some(LengthValue::from_px(width_delta)), - Some(LengthValue::from_px(height_delta)), - state, - )?; - } - - Ok(()) -} diff --git a/packages/wm/src/common/events/handle_window_moved_or_resized_end.rs b/packages/wm/src/common/events/handle_window_moved_or_resized_end.rs new file mode 100644 index 000000000..bae71e2d2 --- /dev/null +++ b/packages/wm/src/common/events/handle_window_moved_or_resized_end.rs @@ -0,0 +1,386 @@ +use anyhow::Context; +use tracing::{debug, info}; + +use crate::{ + common::{ + platform::{NativeWindow, Platform}, + DisplayState, LengthValue, Point, TilingDirection, + }, + containers::{ + commands::{ + attach_container, detach_container, move_container_within_tree, + wrap_in_split_container, + }, + traits::{CommonGetters, PositionGetters, TilingDirectionGetters}, + Container, SplitContainer, TilingContainer, WindowContainer, + }, + user_config::UserConfig, + windows::{ + commands::{resize_window, update_window_state}, + traits::WindowGetters, + ActiveDragOperation, NonTilingWindow, TilingWindow, WindowState, + }, + wm_state::WmState, +}; + +/// Handles the event for when a window is finished being moved or resized +/// by the user (e.g. via the window's drag handles). +/// +/// This resizes the window if it's a tiling window and attach a dragged +/// floating window. +pub fn handle_window_moved_or_resized_end( + native_window: NativeWindow, + state: &mut WmState, + config: &UserConfig, +) -> anyhow::Result<()> { + let found_window = state.window_from_native(&native_window); + if let Some(window) = found_window { + // TODO: Log window details. + + let new_rect = window.native().refresh_frame_position()?; + let old_rect = window.to_rect()?; + + let width_delta = new_rect.width() - old_rect.width(); + let height_delta = new_rect.height() - old_rect.height(); + + if let WindowContainer::NonTilingWindow(window) = window { + let has_window_moved = matches!((width_delta, height_delta), (0, 0)); + + if has_window_moved { + window_moved_end(window, state, config)?; + } + } else if let WindowContainer::TilingWindow(window) = window { + window_resized_end(window, state, width_delta, height_delta)?; + } + } + + Ok(()) +} + +/// Handles window resize events +fn window_resized_end( + window: TilingWindow, + state: &mut WmState, + width_delta: i32, + height_delta: i32, +) -> anyhow::Result<()> { + info!("Tiling window resized"); + resize_window( + window.clone().into(), + Some(LengthValue::from_px(width_delta)), + Some(LengthValue::from_px(height_delta)), + state, + ) +} + +/// Handles window move events +fn window_moved_end( + moved_window: NonTilingWindow, + state: &mut WmState, + config: &UserConfig, +) -> anyhow::Result<()> { + // We continue only if it's a temporary Floating window and if the window + // got moved and not resized + if let Some(active_drag) = moved_window.active_drag() { + if active_drag.is_from_tiling == false + || !matches!( + active_drag.operation, + Some(ActiveDragOperation::Moving) + ) + { + moved_window.set_active_drag(None); + return Ok(()); + } + } + info!("Tiling window drag end event"); + + let mouse_position = Platform::mouse_position()?; + + let window_under_cursor = match get_tiling_window_at_mouse_pos( + &moved_window, + &mouse_position, + state, + ) { + Some(value) => value, + None => { + return on_no_target_window( + &moved_window, + state, + config, + &mouse_position, + ); + } + }; + + debug!( + "Moved window: {:?} \n Target window: {:?}", + moved_window.native().process_name(), + window_under_cursor.native().process_name(), + ); + + let tiling_direction = get_split_direction(&window_under_cursor)?; + let new_window_position = get_drop_position( + &mouse_position, + &window_under_cursor, + &tiling_direction, + )?; + + let parent = window_under_cursor + .direction_container() + .context("The window has no direction container")?; + let parent_tiling_direction: TilingDirection = parent.tiling_direction(); + + move_window_to_target( + state, + config, + moved_window.clone(), + window_under_cursor.clone(), + &parent.into(), + parent_tiling_direction, + tiling_direction, + new_window_position, + )?; + moved_window.set_active_drag(None); + + state.pending_sync.containers_to_redraw.push( + window_under_cursor + .workspace() + .context("No workspace")? + .into(), + ); + + Ok(()) +} + +fn on_no_target_window( + moved_window: &NonTilingWindow, + state: &mut WmState, + config: &UserConfig, + mouse_position: &Point, +) -> anyhow::Result<()> { + let target_monitor = state + .monitor_at_position(&mouse_position) + .context("couldn't get the monitor")?; + + let target_workspace = target_monitor + .displayed_workspace() + .context("couldn't get the workspace")?; + + let visible_tiling_window_count = target_workspace.descendants().fold( + 0, + |acc, container| match container { + Container::TilingWindow(tiling_window) => { + match tiling_window.display_state() { + DisplayState::Shown | DisplayState::Showing => acc + 1, + _ => acc, + } + } + _ => acc, + }, + ); + + if visible_tiling_window_count == 0 { + move_container_within_tree( + moved_window.clone().into(), + target_workspace.into(), + 0, + state, + )?; + } + update_window_state( + moved_window.as_window_container().unwrap(), + WindowState::Tiling, + state, + config, + )?; + return Ok(()); +} + +/// Return the window under the mouse position excluding the dragged window +fn get_tiling_window_at_mouse_pos( + exclude_window: &NonTilingWindow, + mouse_position: &Point, + state: &WmState, +) -> Option { + state + .window_containers_at_position(mouse_position) + .into_iter() + .filter_map(|container| match container { + WindowContainer::TilingWindow(tiling) => Some(tiling), + _ => None, + }) + .filter(|window: &TilingWindow| window.id() != exclude_window.id()) + .next() +} + +fn move_window_to_target( + state: &mut WmState, + config: &UserConfig, + moved_window: NonTilingWindow, + target_window: TilingWindow, + target_window_parent: &Container, + current_tiling_direction: TilingDirection, + new_tiling_direction: TilingDirection, + drop_position: DropPosition, +) -> anyhow::Result<()> { + update_window_state( + moved_window.as_window_container().unwrap(), + WindowState::Tiling, + state, + config, + )?; + + let moved_window = state + .windows() + .iter() + .find(|w| w.id() == moved_window.id()) + .context("couldn't find the new tiled window")? + .as_tiling_window() + .context("window is not a tiled window")? + .clone(); + + // TODO: We can optimize that by not detaching and attaching the window + // Little trick to get the right index + detach_container(Container::TilingWindow(moved_window.clone()))?; + let target_window_index = target_window.index(); + attach_container( + &Container::TilingWindow(moved_window.clone()), + target_window_parent, + None, + )?; + + let target_index = match drop_position { + DropPosition::Start => target_window_index, + DropPosition::End => target_window_index + 1, + }; + + match (new_tiling_direction, current_tiling_direction) { + (TilingDirection::Horizontal, TilingDirection::Horizontal) + | (TilingDirection::Vertical, TilingDirection::Vertical) => { + move_container_within_tree( + Container::TilingWindow(moved_window.clone()), + target_window_parent.clone(), + target_index, + state, + )?; + } + (TilingDirection::Horizontal, TilingDirection::Vertical) => { + create_split_container( + TilingDirection::Horizontal, + config, + moved_window, + target_window, + drop_position, + &target_window_parent, + )?; + } + (TilingDirection::Vertical, TilingDirection::Horizontal) => { + create_split_container( + TilingDirection::Vertical, + config, + moved_window, + target_window, + drop_position, + &target_window_parent, + )?; + } + } + + Ok(()) +} + +/// Creates a split container and moves the target window and the moved +/// window inside at the dropped position +fn create_split_container( + tiling_direction: TilingDirection, + config: &UserConfig, + moved_window: TilingWindow, + target_window: TilingWindow, + dropped_position: DropPosition, + parent: &Container, +) -> anyhow::Result<()> { + let target_index_inside_split_container = match dropped_position { + DropPosition::Start => 0, + DropPosition::End => 1, + }; + + let split_container = SplitContainer::new( + tiling_direction, + config.value.gaps.inner_gap.clone(), + ); + + let mut split_container_children = + vec![TilingContainer::TilingWindow(target_window)]; + + split_container_children.insert( + target_index_inside_split_container, + TilingContainer::TilingWindow(moved_window), + ); + + wrap_in_split_container( + split_container, + parent.clone(), + split_container_children, + )?; + Ok(()) +} + +/// Represents where the window was dropped over another one. +/// It depends on the tiling direction. +/// +/// [DropPosition::Start] can either be the top or left side. +/// [DropPosition::Stop] can either be bottom or right side. +#[derive(Debug)] +enum DropPosition { + Start, + End, +} + +/// Determines the drop position for a window based on the mouse position +/// and tiling direction. +/// +/// This function calculates whether a window should be dropped at the +/// start or end of a tiling layout, depending on the mouse position +/// relative to the middle of the target window. +fn get_drop_position( + mouse_position: &Point, + window: &TilingWindow, + tiling_direction: &TilingDirection, +) -> anyhow::Result { + let rect = window.to_rect()?; + + match tiling_direction { + TilingDirection::Vertical => { + let middle = rect.top + (rect.height() / 2); + if mouse_position.y < middle { + Ok(DropPosition::Start) + } else { + Ok(DropPosition::End) + } + } + TilingDirection::Horizontal => { + let middle = rect.left + (rect.width() / 2); + if mouse_position.x < middle { + Ok(DropPosition::Start) + } else { + Ok(DropPosition::End) + } + } + } +} + +/// Determines the optimal split direction for a given window. +/// +/// This function decides whether a window should be split vertically or +/// horizontally based on its current dimensions. +fn get_split_direction( + window: &TilingWindow, +) -> anyhow::Result { + let rect = window.to_rect()?; + + if rect.height() > rect.width() { + Ok(TilingDirection::Vertical) + } else { + Ok(TilingDirection::Horizontal) + } +} diff --git a/packages/wm/src/common/events/handle_window_moved_or_resized_start.rs b/packages/wm/src/common/events/handle_window_moved_or_resized_start.rs new file mode 100644 index 000000000..b8a25ee87 --- /dev/null +++ b/packages/wm/src/common/events/handle_window_moved_or_resized_start.rs @@ -0,0 +1,21 @@ +use crate::{ + common::platform::NativeWindow, + containers::WindowContainer, + windows::{traits::WindowGetters, ActiveDrag}, + wm_state::WmState, +}; + +/// Handles the event for when a window is started being moved or resized +/// by the user (e.g. via the window's drag handles). +pub fn handle_window_moved_or_resized_start( + native_window: NativeWindow, + state: &mut WmState, +) -> anyhow::Result<()> { + let found_window = state.window_from_native(&native_window); + + if let Some(WindowContainer::TilingWindow(moved_window)) = found_window { + moved_window.set_active_drag(Some(ActiveDrag::default())); + } + + Ok(()) +} diff --git a/packages/wm/src/common/events/mod.rs b/packages/wm/src/common/events/mod.rs index a882022f2..0019a0f67 100644 --- a/packages/wm/src/common/events/mod.rs +++ b/packages/wm/src/common/events/mod.rs @@ -6,7 +6,8 @@ mod handle_window_hidden; mod handle_window_location_changed; mod handle_window_minimize_ended; mod handle_window_minimized; -mod handle_window_moved_or_resized; +mod handle_window_moved_or_resized_end; +mod handle_window_moved_or_resized_start; mod handle_window_shown; mod handle_window_title_changed; @@ -18,6 +19,7 @@ pub use handle_window_hidden::*; pub use handle_window_location_changed::*; pub use handle_window_minimize_ended::*; pub use handle_window_minimized::*; -pub use handle_window_moved_or_resized::*; +pub use handle_window_moved_or_resized_end::*; +pub use handle_window_moved_or_resized_start::*; pub use handle_window_shown::*; pub use handle_window_title_changed::*; diff --git a/packages/wm/src/common/platform/event_listener.rs b/packages/wm/src/common/platform/event_listener.rs index f8e0cec10..0a5dbd833 100644 --- a/packages/wm/src/common/platform/event_listener.rs +++ b/packages/wm/src/common/platform/event_listener.rs @@ -17,7 +17,8 @@ pub enum PlatformEvent { WindowLocationChanged(NativeWindow), WindowMinimized(NativeWindow), WindowMinimizeEnded(NativeWindow), - WindowMovedOrResized(NativeWindow), + WindowMovedOrResizedEnd(NativeWindow), + WindowMovedOrResizedStart(NativeWindow), WindowShown(NativeWindow), WindowTitleChanged(NativeWindow), } diff --git a/packages/wm/src/common/platform/platform.rs b/packages/wm/src/common/platform/platform.rs index eec8539f1..e23563de5 100644 --- a/packages/wm/src/common/platform/platform.rs +++ b/packages/wm/src/common/platform/platform.rs @@ -18,9 +18,9 @@ use windows::{ SHELLEXECUTEINFOW, }, WindowsAndMessaging::{ - CreateWindowExW, DispatchMessageW, GetAncestor, GetDesktopWindow, - GetForegroundWindow, GetMessageW, MessageBoxW, PeekMessageW, - PostThreadMessageW, RegisterClassW, SetCursorPos, + CreateWindowExW, DispatchMessageW, GetAncestor, GetCursorPos, + GetDesktopWindow, GetForegroundWindow, GetMessageW, MessageBoxW, + PeekMessageW, PostThreadMessageW, RegisterClassW, SetCursorPos, SystemParametersInfoW, TranslateMessage, WindowFromPoint, ANIMATIONINFO, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, GA_ROOT, MB_ICONERROR, MB_OK, MB_SYSTEMMODAL, MSG, PM_REMOVE, @@ -136,7 +136,7 @@ impl Platform { Ok(()) } - // Find the window at the specified point in screen space. + /// Find the window at the specified point in screen space. pub fn window_from_point(point: &Point) -> anyhow::Result { let point = POINT { x: point.x, @@ -147,6 +147,19 @@ impl Platform { Ok(NativeWindow::new(handle.0)) } + /// gets the mouse position in screen space. + pub fn mouse_position() -> anyhow::Result { + let mut point = POINT { x: 0, y: 0 }; + unsafe { + GetCursorPos(&mut point)?; + }; + + Ok(Point { + x: point.x, + y: point.y, + }) + } + /// Creates a hidden message window. /// /// Returns a handle to the created window. diff --git a/packages/wm/src/common/platform/window_event_hook.rs b/packages/wm/src/common/platform/window_event_hook.rs index b144f53ac..06d8f7d69 100644 --- a/packages/wm/src/common/platform/window_event_hook.rs +++ b/packages/wm/src/common/platform/window_event_hook.rs @@ -12,8 +12,8 @@ use windows::Win32::{ EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_SHOW, EVENT_OBJECT_UNCLOAKED, EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_MINIMIZEEND, EVENT_SYSTEM_MINIMIZESTART, - EVENT_SYSTEM_MOVESIZEEND, OBJID_WINDOW, WINEVENT_OUTOFCONTEXT, - WINEVENT_SKIPOWNPROCESS, + EVENT_SYSTEM_MOVESIZEEND, EVENT_SYSTEM_MOVESIZESTART, OBJID_WINDOW, + WINEVENT_OUTOFCONTEXT, WINEVENT_SKIPOWNPROCESS, }, }, }; @@ -63,6 +63,7 @@ impl WindowEventHook { (EVENT_OBJECT_DESTROY, EVENT_OBJECT_HIDE), (EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND), (EVENT_SYSTEM_MOVESIZEEND, EVENT_SYSTEM_MOVESIZEEND), + (EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZESTART), (EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND), (EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_NAMECHANGE), (EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED), @@ -122,7 +123,10 @@ impl WindowEventHook { PlatformEvent::WindowMinimizeEnded(window) } EVENT_SYSTEM_MOVESIZEEND => { - PlatformEvent::WindowMovedOrResized(window) + PlatformEvent::WindowMovedOrResizedEnd(window) + } + EVENT_SYSTEM_MOVESIZESTART => { + PlatformEvent::WindowMovedOrResizedStart(window) } EVENT_OBJECT_SHOW | EVENT_OBJECT_UNCLOAKED => { PlatformEvent::WindowShown(window) diff --git a/packages/wm/src/common/rect.rs b/packages/wm/src/common/rect.rs index 8b1da6f9a..0301b014f 100644 --- a/packages/wm/src/common/rect.rs +++ b/packages/wm/src/common/rect.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use super::{Direction, Point, RectDelta}; -#[derive(Debug, Deserialize, Clone, Serialize)] +#[derive(Debug, Deserialize, Clone, Serialize, Eq, PartialEq)] pub struct Rect { /// X-coordinate of the left edge of the rectangle. pub left: i32, @@ -148,4 +148,10 @@ impl Rect { !(self.y() + self.height() <= other.y() || other.y() + other.height() <= self.y()) } + + pub fn contains_point(&self, point: &Point) -> bool { + let is_in_x = point.x >= self.left && point.x <= self.right; + let is_in_y = point.y >= self.top && point.y <= self.bottom; + return is_in_x && is_in_y; + } } diff --git a/packages/wm/src/windows/active_drag.rs b/packages/wm/src/windows/active_drag.rs new file mode 100644 index 000000000..d54035c19 --- /dev/null +++ b/packages/wm/src/windows/active_drag.rs @@ -0,0 +1,11 @@ +#[derive(Debug, Clone, Default)] +pub struct ActiveDrag { + pub operation: Option, + pub is_from_tiling: bool, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ActiveDragOperation { + Moving, + Resizing, +} diff --git a/packages/wm/src/windows/commands/manage_window.rs b/packages/wm/src/windows/commands/manage_window.rs index 8a0f4b50b..413f2500b 100644 --- a/packages/wm/src/windows/commands/manage_window.rs +++ b/packages/wm/src/windows/commands/manage_window.rs @@ -133,6 +133,7 @@ fn create_window( floating_placement, inner_gap, Vec::new(), + None, ) .into(), _ => NonTilingWindow::new( @@ -144,6 +145,7 @@ fn create_window( None, floating_placement, Vec::new(), + None, ) .into(), }; diff --git a/packages/wm/src/windows/mod.rs b/packages/wm/src/windows/mod.rs index 218da5bc1..9d4b32447 100644 --- a/packages/wm/src/windows/mod.rs +++ b/packages/wm/src/windows/mod.rs @@ -1,3 +1,4 @@ +mod active_drag; pub mod commands; mod non_tiling_window; mod tiling_window; @@ -5,6 +6,7 @@ pub mod traits; mod window_dto; mod window_state; +pub use active_drag::*; pub use non_tiling_window::*; pub use tiling_window::*; pub use window_dto::*; diff --git a/packages/wm/src/windows/non_tiling_window.rs b/packages/wm/src/windows/non_tiling_window.rs index 2607a95dc..173c32619 100644 --- a/packages/wm/src/windows/non_tiling_window.rs +++ b/packages/wm/src/windows/non_tiling_window.rs @@ -7,7 +7,9 @@ use std::{ use anyhow::Context; use uuid::Uuid; -use super::{traits::WindowGetters, TilingWindow, WindowDto, WindowState}; +use super::{ + traits::WindowGetters, ActiveDrag, TilingWindow, WindowDto, WindowState, +}; use crate::{ common::{ platform::NativeWindow, DisplayState, LengthValue, Rect, RectDelta, @@ -38,6 +40,7 @@ struct NonTilingWindowInner { has_pending_dpi_adjustment: bool, floating_placement: Rect, done_window_rules: Vec, + active_drag: Option, } impl NonTilingWindow { @@ -50,6 +53,7 @@ impl NonTilingWindow { insertion_target: Option<(Container, usize)>, floating_placement: Rect, done_window_rules: Vec, + active_drag: Option, ) -> Self { let window = NonTilingWindowInner { id: id.unwrap_or_else(|| Uuid::new_v4()), @@ -65,6 +69,7 @@ impl NonTilingWindow { has_pending_dpi_adjustment: false, floating_placement, done_window_rules, + active_drag, }; Self(Rc::new(RefCell::new(window))) @@ -90,6 +95,7 @@ impl NonTilingWindow { self.floating_placement(), inner_gap, self.done_window_rules(), + self.active_drag(), ) } diff --git a/packages/wm/src/windows/tiling_window.rs b/packages/wm/src/windows/tiling_window.rs index a9c8193f0..c0fdd65da 100644 --- a/packages/wm/src/windows/tiling_window.rs +++ b/packages/wm/src/windows/tiling_window.rs @@ -27,6 +27,7 @@ use crate::{ impl_position_getters_as_resizable, impl_tiling_size_getters, impl_window_getters, user_config::WindowRuleConfig, + windows::active_drag::ActiveDrag, }; #[derive(Clone)] @@ -47,6 +48,7 @@ struct TilingWindowInner { floating_placement: Rect, inner_gap: LengthValue, done_window_rules: Vec, + active_drag: Option, } impl TilingWindow { @@ -58,6 +60,7 @@ impl TilingWindow { floating_placement: Rect, inner_gap: LengthValue, done_window_rules: Vec, + active_drag: Option, ) -> Self { let window = TilingWindowInner { id: id.unwrap_or_else(|| Uuid::new_v4()), @@ -74,6 +77,7 @@ impl TilingWindow { floating_placement, inner_gap, done_window_rules, + active_drag, }; Self(Rc::new(RefCell::new(window))) @@ -93,6 +97,7 @@ impl TilingWindow { insertion_target, self.floating_placement(), self.done_window_rules(), + self.active_drag(), ) } diff --git a/packages/wm/src/windows/traits/window_getters.rs b/packages/wm/src/windows/traits/window_getters.rs index c3fe190bf..c86c4761d 100644 --- a/packages/wm/src/windows/traits/window_getters.rs +++ b/packages/wm/src/windows/traits/window_getters.rs @@ -8,7 +8,7 @@ use crate::{ }, containers::WindowContainer, user_config::{UserConfig, WindowRuleConfig}, - windows::WindowState, + windows::{active_drag::ActiveDrag, WindowState}, }; #[enum_dispatch] @@ -108,6 +108,10 @@ pub trait WindowGetters { &self, done_window_rules: Vec, ); + + fn active_drag(&self) -> Option; + + fn set_active_drag(&self, active_drag: Option); } /// Implements the `WindowGetters` trait for a given struct. @@ -185,6 +189,14 @@ macro_rules! impl_window_getters { ) { self.0.borrow_mut().done_window_rules = done_window_rules; } + + fn active_drag(&self) -> Option { + self.0.borrow().active_drag.clone() + } + + fn set_active_drag(&self, active_drag: Option) { + self.0.borrow_mut().active_drag = active_drag; + } } }; } diff --git a/packages/wm/src/wm.rs b/packages/wm/src/wm.rs index 030750d6f..a5c7978a9 100644 --- a/packages/wm/src/wm.rs +++ b/packages/wm/src/wm.rs @@ -11,7 +11,8 @@ use crate::{ handle_window_destroyed, handle_window_focused, handle_window_hidden, handle_window_location_changed, handle_window_minimize_ended, handle_window_minimized, - handle_window_moved_or_resized, handle_window_shown, + handle_window_moved_or_resized_end, + handle_window_moved_or_resized_start, handle_window_shown, handle_window_title_changed, }, platform::PlatformEvent, @@ -81,8 +82,11 @@ impl WindowManager { PlatformEvent::WindowMinimizeEnded(window) => { handle_window_minimize_ended(window, state, config) } - PlatformEvent::WindowMovedOrResized(window) => { - handle_window_moved_or_resized(window, state) + PlatformEvent::WindowMovedOrResizedEnd(window) => { + handle_window_moved_or_resized_end(window, state, config) + } + PlatformEvent::WindowMovedOrResizedStart(window) => { + handle_window_moved_or_resized_start(window, state) } PlatformEvent::WindowShown(window) => { handle_window_shown(window, state, config) diff --git a/packages/wm/src/wm_state.rs b/packages/wm/src/wm_state.rs index 60ebb973d..21d7baf64 100644 --- a/packages/wm/src/wm_state.rs +++ b/packages/wm/src/wm_state.rs @@ -10,11 +10,12 @@ use crate::{ common::{ commands::platform_sync, platform::{NativeMonitor, NativeWindow, Platform}, - Direction, + Direction, Point, }, containers::{ - commands::set_focused_descendant, traits::CommonGetters, Container, - RootContainer, WindowContainer, + commands::set_focused_descendant, + traits::{CommonGetters, PositionGetters}, + Container, RootContainer, WindowContainer, }, monitors::{commands::add_monitor, Monitor}, user_config::{BindingModeConfig, UserConfig}, @@ -455,6 +456,46 @@ impl WmState { .or(descendant_focus_order.first().cloned()) .or(Some(workspace.into())) } + + /// Returns all window under the mouse position + pub fn window_containers_at_position( + &self, + position: &Point, + ) -> Vec { + self + .root_container + .descendants() + .filter_map(|container| match container { + Container::TilingWindow(tiling) => { + Some(WindowContainer::TilingWindow(tiling)) + } + Container::NonTilingWindow(non_tiling) => { + Some(WindowContainer::NonTilingWindow(non_tiling)) + } + _ => None, + }) + .filter(|c| { + let frame = c.to_rect(); + frame.unwrap().contains_point(&position) + }) + .collect() + } + + /// Returns all window under the mouse position + pub fn monitor_at_position(&self, position: &Point) -> Option { + self + .root_container + .descendants() + .filter_map(|container| match container { + Container::Monitor(monitor) => Some(monitor), + _ => None, + }) + .filter(|c| { + let frame = c.to_rect(); + frame.unwrap().contains_point(&position) + }) + .next() + } } impl Drop for WmState {