From 10902ff5432bc09a43faf58c16268ae244e896f8 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 28 Jul 2023 19:18:14 +0200 Subject: [PATCH] stack: Allow dragging tabs out --- src/shell/element/stack.rs | 103 +++++++++++++++++++++++++++++---- src/shell/element/stack/tab.rs | 41 ++++++++----- src/shell/layout/tiling/mod.rs | 24 ++++---- 3 files changed, 132 insertions(+), 36 deletions(-) diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index dc324c37..ff685fcb 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,6 +1,6 @@ -use super::CosmicSurface; +use super::{CosmicMapped, CosmicSurface, CosmicWindow}; use crate::{ - shell::{focus::FocusDirection, layout::tiling::Direction, Shell}, + shell::{focus::FocusDirection, grabs::MoveGrab, layout::tiling::Direction, Shell, Trigger}, state::State, utils::iced::{IcedElement, Program}, utils::prelude::SeatExt, @@ -31,7 +31,10 @@ use smithay::{ desktop::space::SpaceElement, input::{ keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, - pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget, RelativeMotionEvent}, + pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, + PointerTarget, RelativeMotionEvent, + }, Seat, }, output::Output, @@ -80,6 +83,8 @@ pub struct CosmicStackInternal { previous_keyboard: Arc, pointer_entered: Arc, previous_pointer: Arc, + potential_drag: Arc>>, + override_alive: Arc, last_seat: Arc, Serial)>>>, last_location: Arc, Serial, u32)>>>, geometry: Arc>>>, @@ -98,8 +103,6 @@ impl CosmicStackInternal { pub fn current_focus(&self) -> Focus { unsafe { std::mem::transmute::(self.pointer_entered.load(Ordering::SeqCst)) } } - - //pub fn offsets_for_tabs(&self) -> Vec< } const TAB_HEIGHT: i32 = 24; @@ -143,6 +146,8 @@ 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)), + potential_drag: Arc::new(Mutex::new(None)), + override_alive: Arc::new(AtomicBool::new(true)), last_seat: Arc::new(Mutex::new(None)), last_location: Arc::new(Mutex::new(None)), geometry: Arc::new(Mutex::new(None)), @@ -181,6 +186,10 @@ impl CosmicStack { self.0.with_program(|p| { let mut windows = p.windows.lock().unwrap(); if windows.len() == 1 { + p.override_alive.store(false, Ordering::SeqCst); + let window = windows.get(0).unwrap(); + window.try_force_undecorated(false); + window.set_tiled(false); return; } @@ -198,12 +207,16 @@ impl CosmicStack { self.0.with_program(|p| { let mut windows = p.windows.lock().unwrap(); if windows.len() == 1 { + p.override_alive.store(false, Ordering::SeqCst); + let window = windows.get(0).unwrap(); + window.try_force_undecorated(false); + window.set_tiled(false); return; } - if windows.len() >= idx { + if windows.len() <= idx { return; } - let window = windows.remove(idx); + let window = dbg!(windows.remove(idx)); window.try_force_undecorated(false); window.set_tiled(false); @@ -511,6 +524,7 @@ impl CosmicStack { #[derive(Debug, Clone, Copy)] pub enum Message { DragStart, + PotentialTabDragStart(usize), Activate(usize), Close(usize), ScrollForward, @@ -578,7 +592,11 @@ impl Program for CosmicStackInternal { } } } + Message::PotentialTabDragStart(idx) => { + *self.potential_drag.lock().unwrap() = Some(idx); + } Message::Activate(idx) => { + *self.potential_drag.lock().unwrap() = None; if self.windows.lock().unwrap().get(idx).is_some() { let old = self.active.swap(idx, Ordering::SeqCst); self.previous_keyboard.store(old, Ordering::SeqCst); @@ -643,6 +661,7 @@ impl Program for CosmicStackInternal { w.app_id(), user_data.get::().unwrap().clone(), ) + .on_press(Message::PotentialTabDragStart(i)) .on_close(Message::Close(i)) }), active, @@ -729,8 +748,10 @@ impl Program for CosmicStackInternal { impl IsAlive for CosmicStack { fn alive(&self) -> bool { - self.0 - .with_program(|p| p.windows.lock().unwrap().iter().any(IsAlive::alive)) + self.0.with_program(|p| { + p.override_alive.load(Ordering::SeqCst) + && p.windows.lock().unwrap().iter().any(IsAlive::alive) + }) } } @@ -945,6 +966,7 @@ impl PointerTarget for CosmicStack { event.location.y -= TAB_HEIGHT as f64; let active = self.pointer_leave_if_previous(seat, data, event.serial, event.time, event.location); + if let Some((previous, next)) = self.0.with_program(|p| { let active_window = &p.windows.lock().unwrap()[active]; if let Some(sessions) = active_window.user_data().get::() { @@ -987,7 +1009,68 @@ impl PointerTarget for CosmicStack { } (_, Focus::Header) => PointerTarget::enter(&self.0, seat, data, &event), (Focus::Header, _) => { - PointerTarget::leave(&self.0, seat, data, event.serial, event.time) + PointerTarget::leave(&self.0, seat, data, event.serial, event.time); + if let Some(dragged_out) = self + .0 + .with_program(|p| p.potential_drag.lock().unwrap().take()) + { + if let Some(surface) = self + .0 + .with_program(|p| p.windows.lock().unwrap().get(dragged_out).cloned()) + { + if let Some(stack_mapped) = + data.common.shell.element_for_surface(&surface) + { + if let Some(workspace) = data.common.shell.space_for(stack_mapped) { + // TODO: Unify this somehow with Shell::move_request/Workspace::move_request + let button = 0x110; // BTN_LEFT + let pos = event.location; + let start_data = PointerGrabStartData { + focus: None, + button, + location: pos, + }; + let mapped = CosmicMapped::from(CosmicWindow::new( + surface, + self.0.loop_handle(), + )); + let elem_geo = + workspace.element_geometry(stack_mapped).unwrap(); + let indicator_thickness = + data.common.config.static_conf.active_hint; + let was_tiled = workspace.is_tiled(stack_mapped); + + self.remove_idx(dragged_out); + mapped.configure(); + + let grab = MoveGrab::new( + start_data, + mapped, + seat, + pos, + pos.to_i32_round() - Point::from((elem_geo.size.w / 2, 24)), + indicator_thickness, + was_tiled, + ); + if grab.is_tiling_grab() { + data.common + .shell + .set_overview_mode(Some(Trigger::Pointer(button))); + } + + let seat = seat.clone(); + data.common.event_loop_handle.insert_idle(move |data| { + seat.get_pointer().unwrap().set_grab( + &mut data.state, + grab, + event.serial, + smithay::input::pointer::Focus::Clear, + ); + }); + } + } + } + } } _ => {} } diff --git a/src/shell/element/stack/tab.rs b/src/shell/element/stack/tab.rs index 3c955fdd..de5023da 100644 --- a/src/shell/element/stack/tab.rs +++ b/src/shell/element/stack/tab.rs @@ -103,7 +103,7 @@ impl Into for TabBackgroundTheme { } } -pub trait TabMessage { +pub trait TabMessage: Clone { fn activate(idx: usize) -> Self; fn is_activate(&self) -> Option; @@ -119,6 +119,7 @@ pub struct Tab<'a, Message: TabMessage> { title: String, font: Font, close_message: Option, + press_message: Option, rule_theme: TabRuleTheme, background_theme: TabBackgroundTheme, active: bool, @@ -132,12 +133,18 @@ impl<'a, Message: TabMessage> Tab<'a, Message> { title: title.into(), font: cosmic::font::FONT, close_message: None, + press_message: None, rule_theme: TabRuleTheme::Default, background_theme: TabBackgroundTheme::Default, active: false, } } + pub fn on_press(mut self, message: Message) -> Self { + self.press_message = Some(message); + self + } + pub fn on_close(mut self, message: Message) -> Self { self.close_message = Some(message); self @@ -191,7 +198,7 @@ impl<'a, Message: TabMessage> Tab<'a, Message> { close_button = close_button.on_press(close_message); } - let mut items = vec![ + let items = vec![ widget::vertical_rule(4).style(self.rule_theme).into(), self.app_icon .apply(widget::container) @@ -200,8 +207,6 @@ impl<'a, Message: TabMessage> Tab<'a, Message> { .padding([2, 4]) .center_y() .into(), - ]; - items.push( text(self.title) .size(14) .font(self.font) @@ -212,8 +217,6 @@ impl<'a, Message: TabMessage> Tab<'a, Message> { .height(Length::Fill) .width(Length::Fill) .into(), - ); - items.push( close_button .apply(widget::container) .height(Length::Fill) @@ -222,7 +225,7 @@ impl<'a, Message: TabMessage> Tab<'a, Message> { .center_y() .align_x(alignment::Horizontal::Right) .into(), - ); + ]; TabInternal { id: self.id, @@ -230,6 +233,7 @@ impl<'a, Message: TabMessage> Tab<'a, Message> { active: self.active, background: self.background_theme.into(), elements: items, + press_message: self.press_message, } } } @@ -247,6 +251,7 @@ pub(super) struct TabInternal<'a, Message: TabMessage, Renderer> { active: bool, background: theme::Container, elements: Vec>, + press_message: Option, } impl<'a, Message, Renderer> Widget for TabInternal<'a, Message, Renderer> @@ -361,15 +366,23 @@ where }) .fold(event::Status::Ignored, event::Status::merge); - if status == event::Status::Ignored - && cursor.is_over(layout.bounds()) - && matches!( + if status == event::Status::Ignored && cursor.is_over(layout.bounds()) { + if matches!( + event, + event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + ) { + if let Some(message) = self.press_message.clone() { + shell.publish(message); + return event::Status::Captured; + } + } + if matches!( event, event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - ) - { - shell.publish(Message::activate(self.idx)); - return event::Status::Captured; + ) { + shell.publish(Message::activate(self.idx)); + return event::Status::Captured; + } } status diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index d5a2c4dd..5743c0df 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -131,6 +131,7 @@ pub enum MoveResult { #[derive(Debug, Clone, PartialEq)] enum TargetZone { + InitialStackGrab, InitialPlaceholder(NodeId), WindowStack(NodeId, Rectangle), WindowSplit(NodeId, Direction), @@ -2502,18 +2503,17 @@ impl TilingLayout { last_overview_hover @ None => { *last_overview_hover = Some(( None, - TargetZone::InitialPlaceholder( - tree.traverse_pre_order_ids(root) - .unwrap() - .find(|id| match tree.get(id).unwrap().data() { - Data::Placeholder { - initial_placeholder: true, - .. - } => true, - _ => false, - }) - .unwrap(), - ), + tree.traverse_pre_order_ids(root) + .unwrap() + .find(|id| match tree.get(id).unwrap().data() { + Data::Placeholder { + initial_placeholder: true, + .. + } => true, + _ => false, + }) + .map(|node_id| TargetZone::InitialPlaceholder(node_id)) + .unwrap_or(TargetZone::InitialStackGrab), )); } Some((instant, old_target_zone)) => {