From fa78d028918ee1c73277717f5344e8b63aefb9a6 Mon Sep 17 00:00:00 2001 From: boloto1979 Date: Fri, 24 Apr 2026 17:20:49 -0300 Subject: [PATCH 1/2] feat: implement drag-and-drop reordering for applets --- cosmic-panel-bin/src/main.rs | 15 ++ cosmic-panel-bin/src/space/layout.rs | 56 ++++- cosmic-panel-bin/src/space/panel_space.rs | 221 ++++++++++++++++++++ cosmic-panel-bin/src/space/wrapper_space.rs | 56 ++++- 4 files changed, 346 insertions(+), 2 deletions(-) diff --git a/cosmic-panel-bin/src/main.rs b/cosmic-panel-bin/src/main.rs index 7d55a396..5182de87 100644 --- a/cosmic-panel-bin/src/main.rs +++ b/cosmic-panel-bin/src/main.rs @@ -40,6 +40,11 @@ pub enum PanelCalloopMsg { RestartSpace(CosmicPanelConfig, WlOutput), MinimizeRect { output: String, applet_info: MinimizeApplet }, UpdateToplevel(ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1), + ReorderPlugins { + panel_name: String, + plugins_wings: Option<(Vec, Vec)>, + plugins_center: Option>, + }, } /// Access glibc malloc tunables. @@ -186,6 +191,16 @@ fn main() -> Result<()> { PanelCalloopMsg::MinimizeRect { output, applet_info } => { minimize::set_rectangles(state, output, applet_info) }, + PanelCalloopMsg::ReorderPlugins { panel_name, plugins_wings, plugins_center } => { + for entry in &mut state.space.config.config_list { + if entry.name == panel_name { + entry.plugins_wings = plugins_wings; + entry.plugins_center = plugins_center; + break; + } + } + let _ = state.space.config.write_entries(); + }, }, calloop::channel::Event::Closed => {}, }; diff --git a/cosmic-panel-bin/src/space/layout.rs b/cosmic-panel-bin/src/space/layout.rs index fa226503..f44c9004 100644 --- a/cosmic-panel-bin/src/space/layout.rs +++ b/cosmic-panel-bin/src/space/layout.rs @@ -16,7 +16,7 @@ use crate::space::Alignment; use crate::xdg_shell_wrapper::space::Visibility; use super::PanelSpace; -use super::panel_space::{ClientShrinkSize, PanelClient}; +use super::panel_space::{ClientShrinkSize, DragSection, PanelClient}; use crate::xdg_shell_wrapper::space::WrapperSpace; use anyhow::bail; use cosmic::widget::Id; @@ -194,6 +194,60 @@ impl PanelSpace { let mut windows_center = map_clients(&self.clients_center); let mut windows_right = map_clients(&self.clients_right); + if let Some(drag) = self.drag_state.as_ref().filter(|d| d.is_active) { + let drag_name = drag.applet_name.clone(); + let preview_section = drag.preview_section; + let preview_index = drag.preview_index; + + let drag_client_id = self + .clients_left + .lock() + .unwrap() + .iter() + .chain(self.clients_center.lock().unwrap().iter()) + .chain(self.clients_right.lock().unwrap().iter()) + .find(|c| c.name == drag_name) + .and_then(|c| c.client.as_ref()) + .map(|c| c.id()); + + if let Some(drag_client_id) = drag_client_id { + let dragged = if let Some(pos) = windows_left.iter().position(|(_, w, ..)| { + w.toplevel() + .and_then(|t| t.wl_surface().client()) + .is_some_and(|c| c.id() == drag_client_id) + }) { + Some(windows_left.remove(pos)) + } else if let Some(pos) = windows_center.iter().position(|(_, w, ..)| { + w.toplevel() + .and_then(|t| t.wl_surface().client()) + .is_some_and(|c| c.id() == drag_client_id) + }) { + Some(windows_center.remove(pos)) + } else if let Some(pos) = windows_right.iter().position(|(_, w, ..)| { + w.toplevel() + .and_then(|t| t.wl_surface().client()) + .is_some_and(|c| c.id() == drag_client_id) + }) { + Some(windows_right.remove(pos)) + } else { + None + }; + + if let Some(dragged_window) = dragged { + let target_list = match preview_section { + DragSection::Left => &mut windows_left, + DragSection::Center => &mut windows_center, + DragSection::Right => &mut windows_right, + }; + let insert_idx = preview_index.min(target_list.len()); + target_list.insert(insert_idx, dragged_window); + for (j, (i, ..)) in target_list.iter_mut().enumerate() { + *i = j; + } + } + } + } + let is_dock = !self.config.expand_to_edges() || self.animate_state.as_ref().is_some_and(|a| !(a.cur.expanded > 0.5)); diff --git a/cosmic-panel-bin/src/space/panel_space.rs b/cosmic-panel-bin/src/space/panel_space.rs index 3ea7e77a..e8d6ad73 100644 --- a/cosmic-panel-bin/src/space/panel_space.rs +++ b/cosmic-panel-bin/src/space/panel_space.rs @@ -54,6 +54,7 @@ use smithay::desktop::{PopupManager, Space}; use smithay::output::Output; use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; use smithay::reexports::wayland_server::backend::ClientId; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface as s_WlSurface; use smithay::reexports::wayland_server::{Client, DisplayHandle, Resource}; use smithay::utils::{Logical, Rectangle, Size}; use smithay::wayland::compositor::with_states; @@ -256,6 +257,27 @@ impl PanelColors { } } +pub const DRAG_LONG_PRESS_MS: u64 = 400; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DragSection { + Left, + Center, + Right, +} + +#[derive(Debug, Clone)] +pub struct AppletDragState { + pub applet_name: String, + pub source_section: DragSection, + pub source_index: usize, + pub cursor_pos: (i32, i32), + pub press_started: Instant, + pub is_active: bool, + pub preview_section: DragSection, + pub preview_index: usize, +} + #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] pub struct HoverTrack { pub hover_id: Option, @@ -362,6 +384,7 @@ pub struct PanelSpace { pub(crate) hover_track: HoverTrack, pub(crate) start_show_instant: Rc>>, pub shared: Rc, + pub drag_state: Option, } impl PanelSpace { @@ -442,6 +465,7 @@ impl PanelSpace { minimized_toplevels: HashSet::new(), start_show_instant: Rc::new(RefCell::new(None)), + drag_state: None, } } @@ -1624,6 +1648,203 @@ impl PanelSpace { } } + pub fn find_applet_for_surface( + &self, + wl_surface: &s_WlSurface, + ) -> Option<(String, DragSection, usize)> { + let client_id = wl_surface.client().map(|c| c.id())?; + for (i, c) in self.clients_left.lock().unwrap().iter().enumerate() { + if c.client.as_ref().is_some_and(|w| w.id() == client_id) { + return Some((c.name.clone(), DragSection::Left, i)); + } + } + for (i, c) in self.clients_center.lock().unwrap().iter().enumerate() { + if c.client.as_ref().is_some_and(|w| w.id() == client_id) { + return Some((c.name.clone(), DragSection::Center, i)); + } + } + for (i, c) in self.clients_right.lock().unwrap().iter().enumerate() { + if c.client.as_ref().is_some_and(|w| w.id() == client_id) { + return Some((c.name.clone(), DragSection::Right, i)); + } + } + None + } + + pub fn update_drag_preview(&mut self) -> bool { + let Some(drag) = self.drag_state.as_mut() else { return false }; + if !drag.is_active { + return false; + } + + let (cursor_x, cursor_y) = drag.cursor_pos; + let major_cursor = if self.config.is_horizontal() { cursor_x } else { cursor_y }; + let panel_major = if self.config.is_horizontal() { + self.dimensions.w + } else { + self.dimensions.h + }; + + let preview_section = if major_cursor < panel_major / 3 { + DragSection::Left + } else if major_cursor > 2 * panel_major / 3 { + DragSection::Right + } else { + DragSection::Center + }; + + let drag_name = drag.applet_name.clone(); + + let left_ids: Vec<_> = self + .clients_left + .lock() + .unwrap() + .iter() + .filter_map(|c| c.client.as_ref().map(|wc| wc.id())) + .collect(); + let center_ids: Vec<_> = self + .clients_center + .lock() + .unwrap() + .iter() + .filter_map(|c| c.client.as_ref().map(|wc| wc.id())) + .collect(); + let right_ids: Vec<_> = self + .clients_right + .lock() + .unwrap() + .iter() + .filter_map(|c| c.client.as_ref().map(|wc| wc.id())) + .collect(); + + let drag_client_id: Option<_> = { + let left = self.clients_left.lock().unwrap(); + let center = self.clients_center.lock().unwrap(); + let right = self.clients_right.lock().unwrap(); + left.iter() + .chain(center.iter()) + .chain(right.iter()) + .find(|c| c.name == drag_name) + .and_then(|c| c.client.as_ref()) + .map(|wc| wc.id()) + }; + + let mut left_pos: Vec = vec![]; + let mut center_pos: Vec = vec![]; + let mut right_pos: Vec = vec![]; + + for elem in self.space.elements() { + if let CosmicMappedInternal::Window(w) = elem { + let Some(client_id) = w.wl_surface().and_then(|s| s.client().map(|c| c.id())) + else { + continue; + }; + if drag_client_id.as_ref().is_some_and(|id| *id == client_id) { + continue; + } + let Some(geo) = self.space.element_geometry(elem) else { continue }; + let center = if self.config.is_horizontal() { + geo.loc.x + geo.size.w / 2 + } else { + geo.loc.y + geo.size.h / 2 + }; + if left_ids.contains(&client_id) { + left_pos.push(center); + } else if center_ids.contains(&client_id) { + center_pos.push(center); + } else if right_ids.contains(&client_id) { + right_pos.push(center); + } + } + } + + let section_pos = match preview_section { + DragSection::Left => &left_pos, + DragSection::Center => ¢er_pos, + DragSection::Right => &right_pos, + }; + let preview_index = section_pos.iter().filter(|&&c| c < major_cursor).count(); + + let changed = drag.preview_section != preview_section || drag.preview_index != preview_index; + drag.preview_section = preview_section; + drag.preview_index = preview_index; + changed + } + + pub fn commit_drag_reorder(&mut self) { + let Some(drag) = self.drag_state.take() else { return }; + if !drag.is_active { + return; + } + + let removed = { + let mut src = match drag.source_section { + DragSection::Left => self.clients_left.lock().unwrap(), + DragSection::Center => self.clients_center.lock().unwrap(), + DragSection::Right => self.clients_right.lock().unwrap(), + }; + let pos = src.iter().position(|c| c.name == drag.applet_name); + pos.map(|i| src.remove(i)) + }; + + let Some(applet_client) = removed else { return }; + + { + let mut dst = match drag.preview_section { + DragSection::Left => self.clients_left.lock().unwrap(), + DragSection::Center => self.clients_center.lock().unwrap(), + DragSection::Right => self.clients_right.lock().unwrap(), + }; + let insert_idx = drag.preview_index.min(dst.len()); + dst.insert(insert_idx, applet_client); + } + + let plugins_left: Vec = self + .clients_left + .lock() + .unwrap() + .iter() + .filter(|c| !c.name.starts_with("spacer-")) + .map(|c| c.name.clone()) + .collect(); + let plugins_right: Vec = self + .clients_right + .lock() + .unwrap() + .iter() + .filter(|c| !c.name.starts_with("spacer-")) + .map(|c| c.name.clone()) + .collect(); + let plugins_center: Vec = self + .clients_center + .lock() + .unwrap() + .iter() + .filter(|c| !c.name.starts_with("spacer-")) + .map(|c| c.name.clone()) + .collect(); + + let new_plugins_wings = + if plugins_left.is_empty() && plugins_right.is_empty() { + None + } else { + Some((plugins_left, plugins_right)) + }; + let new_plugins_center = + if plugins_center.is_empty() { None } else { Some(plugins_center) }; + + self.config.plugins_wings = new_plugins_wings.clone(); + self.config.plugins_center = new_plugins_center.clone(); + + let _ = self.shared.panel_tx.send(PanelCalloopMsg::ReorderPlugins { + panel_name: self.config.name.clone(), + plugins_wings: new_plugins_wings, + plugins_center: new_plugins_center, + }); + + self.is_dirty = true; + } + pub fn update_config( &mut self, config: CosmicPanelConfig, diff --git a/cosmic-panel-bin/src/space/wrapper_space.rs b/cosmic-panel-bin/src/space/wrapper_space.rs index ceee9b01..99a829f7 100644 --- a/cosmic-panel-bin/src/space/wrapper_space.rs +++ b/cosmic-panel-bin/src/space/wrapper_space.rs @@ -61,7 +61,7 @@ use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1; use crate::iced::elements::{CosmicMappedInternal, PanelSpaceElement}; use crate::space::AppletMsg; -use crate::space::panel_space::{AppletAutoClickAnchor, PanelClient}; +use crate::space::panel_space::{AppletAutoClickAnchor, AppletDragState, DRAG_LONG_PRESS_MS, PanelClient}; use super::PanelSpace; use super::layout::OverflowSection; @@ -890,11 +890,50 @@ impl WrapperSpace for PanelSpace { self.close_popups(|_| false); } } + + if press { + if let Some(SpaceTarget::Surface(ref srv_surface)) = target { + if let Some((name, section, idx)) = + self.find_applet_for_surface(srv_surface) + { + let cursor_pos = self + .s_hovered_surface + .iter() + .find(|h| h.seat_name == seat_name) + .map(|h| { + ( + h.c_pos.x + h.s_pos.x as i32, + h.c_pos.y + h.s_pos.y as i32, + ) + }) + .unwrap_or((0, 0)); + self.drag_state = Some(AppletDragState { + applet_name: name, + source_section: section, + source_index: idx, + cursor_pos, + press_started: Instant::now(), + is_active: false, + preview_section: section, + preview_index: idx, + }); + } + } + } else { + if self.drag_state.as_ref().is_some_and(|d| d.is_active) { + self.commit_drag_reorder(); + } else { + self.drag_state = None; + } + } + target } else { if press { self.close_popups(|_| false); } + // cancel any in-flight drag when the cursor leaves the panel + self.drag_state = None; // no hover found // if has keyboard focus remove it and close popups self.keyboard_leave(seat_name, None); @@ -910,6 +949,16 @@ impl WrapperSpace for PanelSpace { c_wl_surface: c_wl_surface::WlSurface, pointer: &WlPointer, ) -> Option { + if let Some(drag) = self.drag_state.as_mut() { + drag.cursor_pos = (x, y); + if !drag.is_active + && drag.press_started.elapsed().as_millis() as u64 >= DRAG_LONG_PRESS_MS + { + drag.is_active = true; + self.is_dirty = true; + } + } + let mut prev_hover = self.s_hovered_surface.iter_mut().enumerate().find(|(_, f)| f.seat_name == seat_name); let prev_foc = self.s_focused_surface.iter_mut().find(|f| f.1 == seat_name); @@ -1303,6 +1352,11 @@ impl WrapperSpace for PanelSpace { self.hover_track.set_hover_id(None); } } + if self.drag_state.as_ref().is_some_and(|d| d.is_active) { + if self.update_drag_preview() { + self.is_dirty = true; + } + } ret } From 3717714624e10d57e5a51998e08e6ebaeea90daa Mon Sep 17 00:00:00 2001 From: boloto1979 Date: Fri, 24 Apr 2026 21:32:56 -0300 Subject: [PATCH 2/2] feat: add drag-and-drop animation support and suppress button press during drag --- cosmic-panel-bin/src/space/layout.rs | 60 ++++++++++++++++++- cosmic-panel-bin/src/space/panel_space.rs | 36 +++++++++++ cosmic-panel-bin/src/space/wrapper_space.rs | 22 ++++++- .../src/space_container/wrapper_space.rs | 4 ++ .../client/handlers/pointer.rs | 57 +++++++++++++----- .../src/xdg_shell_wrapper/server/state.rs | 4 ++ .../src/xdg_shell_wrapper/space/space.rs | 6 ++ 7 files changed, 172 insertions(+), 17 deletions(-) diff --git a/cosmic-panel-bin/src/space/layout.rs b/cosmic-panel-bin/src/space/layout.rs index f44c9004..6f54c373 100644 --- a/cosmic-panel-bin/src/space/layout.rs +++ b/cosmic-panel-bin/src/space/layout.rs @@ -26,7 +26,7 @@ use sctk::shell::WaylandSurface; use smithay::desktop::space::SpaceElement; use smithay::desktop::{Space, Window}; use smithay::reexports::wayland_server::Resource; -use smithay::utils::{IsAlive, Physical, Rectangle, Size}; +use smithay::utils::{IsAlive, Logical, Physical, Point, Rectangle, Size}; use smithay::wayland::compositor::with_states; use smithay::wayland::fractional_scale::with_fractional_scale; use smithay::wayland::seat::WaylandFocus; @@ -267,6 +267,64 @@ impl PanelSpace { right_overflow_button, center_overflow_button, ); + + let anim_info = self.drag_state.as_ref().and_then(|drag| { + if drag.is_active && drag.anim_t < 1.0 && !drag.prev_positions.is_empty() { + Some((drag.anim_t, drag.prev_positions.clone())) + } else { + None + } + }); + if let Some((raw_t, prev_positions)) = anim_info { + // ease-out-cubic + let t = 1.0_f32 - (1.0_f32 - raw_t).powi(3); + let is_h = self.config.is_horizontal(); + let client_to_name: Vec<(_, String)> = { + let l = self.clients_left.lock().unwrap(); + let c = self.clients_center.lock().unwrap(); + let r = self.clients_right.lock().unwrap(); + l.iter() + .chain(c.iter()) + .chain(r.iter()) + .filter_map(|pc| pc.client.as_ref().map(|wc| (wc.id(), pc.name.clone()))) + .collect() + }; + let updates: Vec<(CosmicMappedInternal, Point)> = self + .space + .elements() + .filter_map(|elem| { + if let CosmicMappedInternal::Window(w) = elem { + let client_id = w + .toplevel() + .and_then(|t| t.wl_surface().client()) + .map(|c| c.id())?; + let (_, name) = + client_to_name.iter().find(|(id, _)| *id == client_id)?; + let &prev_major = prev_positions.get(name)?; + let geo = self.space.element_geometry(elem)?; + let cur_major = if is_h { geo.loc.x } else { geo.loc.y }; + let lerped = (prev_major as f32 * (1.0 - t) + + cur_major as f32 * t) + .round() as i32; + if lerped == cur_major { + return None; + } + let new_loc = if is_h { + Point::from((lerped, geo.loc.y)) + } else { + Point::from((geo.loc.x, lerped)) + }; + Some((elem.clone(), new_loc)) + } else { + None + } + }) + .collect(); + for (elem, loc) in updates { + self.space.map_element(elem, loc, false); + } + } + if let Err(e) = res.as_ref() { info!("Requires relayout: {:?}", e); } diff --git a/cosmic-panel-bin/src/space/panel_space.rs b/cosmic-panel-bin/src/space/panel_space.rs index e8d6ad73..54c84bf8 100644 --- a/cosmic-panel-bin/src/space/panel_space.rs +++ b/cosmic-panel-bin/src/space/panel_space.rs @@ -276,6 +276,9 @@ pub struct AppletDragState { pub is_active: bool, pub preview_section: DragSection, pub preview_index: usize, + pub anim_t: f32, + pub anim_start: Option, + pub prev_positions: std::collections::HashMap, } #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] @@ -1766,6 +1769,39 @@ impl PanelSpace { let preview_index = section_pos.iter().filter(|&&c| c < major_cursor).count(); let changed = drag.preview_section != preview_section || drag.preview_index != preview_index; + if changed { + let is_h = self.config.is_horizontal(); + let name_for_client: Vec<(_, String)> = { + let l = self.clients_left.lock().unwrap(); + let c = self.clients_center.lock().unwrap(); + let r = self.clients_right.lock().unwrap(); + l.iter() + .chain(c.iter()) + .chain(r.iter()) + .filter_map(|pc| pc.client.as_ref().map(|wc| (wc.id(), pc.name.clone()))) + .collect() + }; + let mut prev_pos = std::collections::HashMap::new(); + for elem in self.space.elements() { + if let CosmicMappedInternal::Window(w) = elem { + if let Some(client_id) = + w.wl_surface().and_then(|s| s.client().map(|c| c.id())) + { + if let Some((_, name)) = + name_for_client.iter().find(|(id, _)| *id == client_id) + { + if let Some(geo) = self.space.element_geometry(elem) { + let major = if is_h { geo.loc.x } else { geo.loc.y }; + prev_pos.insert(name.clone(), major); + } + } + } + } + } + drag.prev_positions = prev_pos; + drag.anim_start = Some(Instant::now()); + drag.anim_t = 0.0; + } drag.preview_section = preview_section; drag.preview_index = preview_index; changed diff --git a/cosmic-panel-bin/src/space/wrapper_space.rs b/cosmic-panel-bin/src/space/wrapper_space.rs index 99a829f7..8764b4bd 100644 --- a/cosmic-panel-bin/src/space/wrapper_space.rs +++ b/cosmic-panel-bin/src/space/wrapper_space.rs @@ -863,6 +863,10 @@ impl WrapperSpace for PanelSpace { } /// returns false to forward the button press, and true to intercept + fn is_pending_drag(&self) -> bool { + self.drag_state.as_ref().is_some_and(|d| !d.is_active) + } + fn handle_button(&mut self, seat_name: &str, press: bool) -> Option { if let Some(prev_foc) = { let c_hovered_surface: &ClientFocus = &self.shared.c_hovered_surface.borrow(); @@ -916,12 +920,17 @@ impl WrapperSpace for PanelSpace { is_active: false, preview_section: section, preview_index: idx, + anim_t: 1.0, + anim_start: None, + prev_positions: std::collections::HashMap::new(), }); } } } else { if self.drag_state.as_ref().is_some_and(|d| d.is_active) { self.commit_drag_reorder(); + + return None; } else { self.drag_state = None; } @@ -1353,7 +1362,18 @@ impl WrapperSpace for PanelSpace { } } if self.drag_state.as_ref().is_some_and(|d| d.is_active) { - if self.update_drag_preview() { + let anim_running = if let Some(drag) = self.drag_state.as_mut() { + if let Some(start) = drag.anim_start { + const ANIM_MS: f32 = 180.0; + drag.anim_t = (start.elapsed().as_millis() as f32 / ANIM_MS).min(1.0); + drag.anim_t < 1.0 + } else { + false + } + } else { + false + }; + if self.update_drag_preview() || anim_running { self.is_dirty = true; } } diff --git a/cosmic-panel-bin/src/space_container/wrapper_space.rs b/cosmic-panel-bin/src/space_container/wrapper_space.rs index 4736394a..185e2c55 100644 --- a/cosmic-panel-bin/src/space_container/wrapper_space.rs +++ b/cosmic-panel-bin/src/space_container/wrapper_space.rs @@ -601,6 +601,10 @@ impl WrapperSpace for SpaceContainer { } } + fn is_pending_drag(&self) -> bool { + self.space_list.iter().any(|s| s.is_pending_drag()) + } + fn handle_button(&mut self, seat_name: &str, press: bool) -> Option { if let Some((popup_space_i, popup_space)) = self.space_list.iter_mut().enumerate().find(|(_, s)| !s.popups.is_empty()) diff --git a/cosmic-panel-bin/src/xdg_shell_wrapper/client/handlers/pointer.rs b/cosmic-panel-bin/src/xdg_shell_wrapper/client/handlers/pointer.rs index 2a6bd4fe..b48080b8 100644 --- a/cosmic-panel-bin/src/xdg_shell_wrapper/client/handlers/pointer.rs +++ b/cosmic-panel-bin/src/xdg_shell_wrapper/client/handlers/pointer.rs @@ -275,13 +275,17 @@ impl GlobalState { let s = self.space.handle_button(&seat_name, true); kbd.set_focus(self, s, SERIAL_COUNTER.next_serial()); - ptr.button(self, &ButtonEvent { - serial: SERIAL_COUNTER.next_serial(), - time, - button, - state: ButtonState::Pressed, - }); - ptr.frame(self); + if self.space.is_pending_drag() { + self.server_state.suppressed_drag_press = Some((time, button)); + } else { + ptr.button(self, &ButtonEvent { + serial: SERIAL_COUNTER.next_serial(), + time, + button, + state: ButtonState::Pressed, + }); + ptr.frame(self); + } }, sctk::seat::pointer::PointerEventKind::Release { time, button, .. } => { self.server_state.last_button.replace(button); @@ -310,16 +314,39 @@ impl GlobalState { continue; } + let suppressed = self.server_state.suppressed_drag_press.take(); let s = self.space.handle_button(&seat_name, false); - kbd.set_focus(self, s, SERIAL_COUNTER.next_serial()); + kbd.set_focus(self, s.clone(), SERIAL_COUNTER.next_serial()); - ptr.button(self, &ButtonEvent { - serial: SERIAL_COUNTER.next_serial(), - time, - button, - state: ButtonState::Released, - }); - ptr.frame(self); + match suppressed { + Some((press_time, press_button)) if s.is_some() => { + ptr.button(self, &ButtonEvent { + serial: SERIAL_COUNTER.next_serial(), + time: press_time, + button: press_button, + state: ButtonState::Pressed, + }); + ptr.frame(self); + ptr.button(self, &ButtonEvent { + serial: SERIAL_COUNTER.next_serial(), + time, + button, + state: ButtonState::Released, + }); + ptr.frame(self); + }, + Some(_) => { + }, + None => { + ptr.button(self, &ButtonEvent { + serial: SERIAL_COUNTER.next_serial(), + time, + button, + state: ButtonState::Released, + }); + ptr.frame(self); + }, + } }, sctk::seat::pointer::PointerEventKind::Axis { time, diff --git a/cosmic-panel-bin/src/xdg_shell_wrapper/server/state.rs b/cosmic-panel-bin/src/xdg_shell_wrapper/server/state.rs index 3e444b67..38ebb3bd 100644 --- a/cosmic-panel-bin/src/xdg_shell_wrapper/server/state.rs +++ b/cosmic-panel-bin/src/xdg_shell_wrapper/server/state.rs @@ -49,6 +49,9 @@ pub struct ServerState { pub(crate) display_handle: DisplayHandle, // pub(crate) selected_data_provider: SelectedDataProvider, pub(crate) last_button: Option, + /// when a press on an applet is suppressed (awaiting drag-or-click decision), + /// stores (time_ms, button_code) to replay on release if no drag occurred. + pub(crate) suppressed_drag_press: Option<(u32, u32)>, pub(crate) seats: Vec, // Smithay State pub(crate) compositor_state: CompositorState, @@ -72,6 +75,7 @@ impl ServerState { popup_manager: PopupManager::default(), display_handle: dh.clone(), last_button: None, + suppressed_drag_press: None, seats: Vec::new(), compositor_state: CompositorState::new::(&dh), _cursor_shape: CursorShapeManagerState::new::(&dh), diff --git a/cosmic-panel-bin/src/xdg_shell_wrapper/space/space.rs b/cosmic-panel-bin/src/xdg_shell_wrapper/space/space.rs index fa390602..8bba434f 100644 --- a/cosmic-panel-bin/src/xdg_shell_wrapper/space/space.rs +++ b/cosmic-panel-bin/src/xdg_shell_wrapper/space/space.rs @@ -174,6 +174,12 @@ pub trait WrapperSpace { /// optionally returns an interacted server wl surface fn handle_button(&mut self, seat_name: &str, press: bool) -> Option; + /// returns true if a button press on an applet is pending (awaiting drag-or-click decision). + /// the caller should suppress forwarding the press event to the applet. + fn is_pending_drag(&self) -> bool { + false + } + /// keyboard focus lost handler fn keyboard_leave(&mut self, seat_name: &str, surface: Option);