Keep drag source focused until pointer leaves release window

This commit is contained in:
Samuel Onoja committed 2026-09-29 13:50:12 +01:00
1 parent 96e1b6ac5a
commit 7b6e592cb4
2 files changed
+67 -5

No files matched your search

+50 -2
View File
@@ -25,6 +25,17 @@ use smithay::{
}; };
use crate::{Ferese, config::BindingAction}; use crate::{Ferese, config::BindingAction};
use ferese_layout::WindowId;
fn hover_blocked_after_drag(blocked: &mut Option<WindowId>, target: Option<WindowId>) -> bool {
if let Some(window) = *blocked {
if target == Some(window) {
return true;
}
*blocked = None;
}
false
}
impl Ferese { impl Ferese {
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) { pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) {
@@ -279,6 +290,12 @@ impl Ferese {
InputEvent::PointerButton { event, .. } => { InputEvent::PointerButton { event, .. } => {
let pointer = self.seat.get_pointer().expect("seat has a pointer"); let pointer = self.seat.get_pointer().expect("seat has a pointer");
let serial = SERIAL_COUNTER.next_serial(); let serial = SERIAL_COUNTER.next_serial();
let released_over_another_window = (event.state() == ButtonState::Released
&& pointer.is_grabbed())
.then(|| self.window_under_visual(pointer.current_location()))
.flatten()
.and_then(|window| self.window_ids.get(&window).copied())
.filter(|window| Some(*window) != self.focused_window);
// Native popup_done destroys Iced's window immediately. For // Native popup_done destroys Iced's window immediately. For
// effects-capable shell popups, release input now but defer // effects-capable shell popups, release input now but defer
@@ -337,6 +354,7 @@ impl Ferese {
} }
if event.state() == ButtonState::Pressed && !pointer.is_grabbed() { if event.state() == ButtonState::Pressed && !pointer.is_grabbed() {
self.hover_focus_blocked = None;
self.focus_window_at(pointer.current_location(), serial, true); self.focus_window_at(pointer.current_location(), serial, true);
} }
@@ -350,6 +368,9 @@ impl Ferese {
}, },
); );
pointer.frame(self); pointer.frame(self);
if event.state() == ButtonState::Released {
self.hover_focus_blocked = released_over_another_window;
}
} }
InputEvent::PointerAxis { event, .. } => { InputEvent::PointerAxis { event, .. } => {
let source = event.source(); let source = event.source();
@@ -624,8 +645,19 @@ impl Ferese {
{ {
return; return;
} }
if self.layer_under(position).is_some() {
self.hover_focus_blocked = None;
return;
}
let Some(window) = self.window_under_visual(position) else { let window = self.window_under_visual(position);
let target = window
.as_ref()
.and_then(|window| self.window_ids.get(window).copied());
if hover_blocked_after_drag(&mut self.hover_focus_blocked, target) {
return;
}
let Some(window) = window else {
return; return;
}; };
if self.window_ids.get(&window).copied() == self.focused_window { if self.window_ids.get(&window).copied() == self.focused_window {
@@ -797,7 +829,23 @@ fn overview_escape(symbol: u32, overview_active: bool) -> bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{emergency_shortcut_escape, overview_escape, virtual_terminal}; use super::{
WindowId, emergency_shortcut_escape, hover_blocked_after_drag, overview_escape,
virtual_terminal,
};
#[test]
fn drag_release_does_not_turn_motion_inside_the_same_window_into_hover_focus() {
let selected = WindowId(1);
let other = WindowId(2);
let mut blocked = Some(other);
assert!(hover_blocked_after_drag(&mut blocked, Some(other)));
assert!(hover_blocked_after_drag(&mut blocked, Some(other)));
assert!(!hover_blocked_after_drag(&mut blocked, Some(selected)));
assert_eq!(blocked, None);
assert!(!hover_blocked_after_drag(&mut blocked, Some(other)));
}
#[test] #[test]
fn escape_closes_only_an_active_overview() { fn escape_closes_only_an_active_overview() {
+17 -3
View File
@@ -147,6 +147,7 @@ pub struct Ferese {
viewport_coupled_widths: HashMap<WindowId, (WorkspaceId, AnimatedValue)>, viewport_coupled_widths: HashMap<WindowId, (WorkspaceId, AnimatedValue)>,
pending_column_width_cycles: HashSet<WindowId>, pending_column_width_cycles: HashSet<WindowId>,
pub focused_window: Option<WindowId>, pub focused_window: Option<WindowId>,
pub(crate) hover_focus_blocked: Option<WindowId>,
column_width_presets: Vec<ColumnWidth>, column_width_presets: Vec<ColumnWidth>,
gap_config: GapConfig, gap_config: GapConfig,
pub(crate) input_settings: InputSettings, pub(crate) input_settings: InputSettings,
@@ -360,6 +361,7 @@ impl Ferese {
viewport_coupled_widths: HashMap::new(), viewport_coupled_widths: HashMap::new(),
pending_column_width_cycles: HashSet::new(), pending_column_width_cycles: HashSet::new(),
focused_window: None, focused_window: None,
hover_focus_blocked: None,
column_width_presets: config.column_width_presets, column_width_presets: config.column_width_presets,
gap_config: config.gap_config, gap_config: config.gap_config,
input_settings: config.input_settings, input_settings: config.input_settings,
@@ -862,11 +864,23 @@ impl Ferese {
let visual = self.presented_window_rect(*id)?; let visual = self.presented_window_rect(*id)?;
let caption_height = if overview_active { 34.0 } else { 0.0 }; let caption_height = if overview_active { 34.0 } else { 0.0 };
(position.x >= visual.x let inside_visual = position.x >= visual.x
&& position.y >= visual.y && position.y >= visual.y
&& position.x < visual.x + visual.width && position.x < visual.x + visual.width
&& position.y < visual.y + visual.height + caption_height) && position.y < visual.y + visual.height + caption_height;
.then_some(window.clone()) if !inside_visual {
return None;
}
if !overview_active {
let (source_x, source_y) =
self.inverse_presented_window_point(*id, position.x, position.y)?;
let source_point =
Point::from((source_x, source_y)) + window.geometry().loc.to_f64();
window.surface_under(source_point, WindowSurfaceType::ALL)?;
}
Some(window.clone())
}; };
if overview_active { if overview_active {