stack: Allow dragging tabs out

This commit is contained in:
Victoria Brekenfeld 2023-07-28 19:18:14 +02:00
parent 9a3bfb4bba
commit 10902ff543
3 changed files with 132 additions and 36 deletions

View file

@ -1,6 +1,6 @@
use super::CosmicSurface; use super::{CosmicMapped, CosmicSurface, CosmicWindow};
use crate::{ use crate::{
shell::{focus::FocusDirection, layout::tiling::Direction, Shell}, shell::{focus::FocusDirection, grabs::MoveGrab, layout::tiling::Direction, Shell, Trigger},
state::State, state::State,
utils::iced::{IcedElement, Program}, utils::iced::{IcedElement, Program},
utils::prelude::SeatExt, utils::prelude::SeatExt,
@ -31,7 +31,10 @@ use smithay::{
desktop::space::SpaceElement, desktop::space::SpaceElement,
input::{ input::{
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget, RelativeMotionEvent}, pointer::{
AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent,
PointerTarget, RelativeMotionEvent,
},
Seat, Seat,
}, },
output::Output, output::Output,
@ -80,6 +83,8 @@ pub struct CosmicStackInternal {
previous_keyboard: Arc<AtomicUsize>, previous_keyboard: Arc<AtomicUsize>,
pointer_entered: Arc<AtomicU8>, pointer_entered: Arc<AtomicU8>,
previous_pointer: Arc<AtomicUsize>, previous_pointer: Arc<AtomicUsize>,
potential_drag: Arc<Mutex<Option<usize>>>,
override_alive: Arc<AtomicBool>,
last_seat: Arc<Mutex<Option<(Seat<State>, Serial)>>>, last_seat: Arc<Mutex<Option<(Seat<State>, Serial)>>>,
last_location: Arc<Mutex<Option<(Point<f64, Logical>, Serial, u32)>>>, last_location: Arc<Mutex<Option<(Point<f64, Logical>, Serial, u32)>>>,
geometry: Arc<Mutex<Option<Rectangle<i32, Logical>>>>, geometry: Arc<Mutex<Option<Rectangle<i32, Logical>>>>,
@ -98,8 +103,6 @@ impl CosmicStackInternal {
pub fn current_focus(&self) -> Focus { pub fn current_focus(&self) -> Focus {
unsafe { std::mem::transmute::<u8, Focus>(self.pointer_entered.load(Ordering::SeqCst)) } unsafe { std::mem::transmute::<u8, Focus>(self.pointer_entered.load(Ordering::SeqCst)) }
} }
//pub fn offsets_for_tabs(&self) -> Vec<
} }
const TAB_HEIGHT: i32 = 24; const TAB_HEIGHT: i32 = 24;
@ -143,6 +146,8 @@ impl CosmicStack {
previous_keyboard: Arc::new(AtomicUsize::new(0)), previous_keyboard: Arc::new(AtomicUsize::new(0)),
pointer_entered: Arc::new(AtomicU8::new(Focus::None as u8)), pointer_entered: Arc::new(AtomicU8::new(Focus::None as u8)),
previous_pointer: Arc::new(AtomicUsize::new(0)), 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_seat: Arc::new(Mutex::new(None)),
last_location: Arc::new(Mutex::new(None)), last_location: Arc::new(Mutex::new(None)),
geometry: Arc::new(Mutex::new(None)), geometry: Arc::new(Mutex::new(None)),
@ -181,6 +186,10 @@ impl CosmicStack {
self.0.with_program(|p| { self.0.with_program(|p| {
let mut windows = p.windows.lock().unwrap(); let mut windows = p.windows.lock().unwrap();
if windows.len() == 1 { 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; return;
} }
@ -198,12 +207,16 @@ impl CosmicStack {
self.0.with_program(|p| { self.0.with_program(|p| {
let mut windows = p.windows.lock().unwrap(); let mut windows = p.windows.lock().unwrap();
if windows.len() == 1 { 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; return;
} }
if windows.len() >= idx { if windows.len() <= idx {
return; return;
} }
let window = windows.remove(idx); let window = dbg!(windows.remove(idx));
window.try_force_undecorated(false); window.try_force_undecorated(false);
window.set_tiled(false); window.set_tiled(false);
@ -511,6 +524,7 @@ impl CosmicStack {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Message { pub enum Message {
DragStart, DragStart,
PotentialTabDragStart(usize),
Activate(usize), Activate(usize),
Close(usize), Close(usize),
ScrollForward, ScrollForward,
@ -578,7 +592,11 @@ impl Program for CosmicStackInternal {
} }
} }
} }
Message::PotentialTabDragStart(idx) => {
*self.potential_drag.lock().unwrap() = Some(idx);
}
Message::Activate(idx) => { Message::Activate(idx) => {
*self.potential_drag.lock().unwrap() = None;
if self.windows.lock().unwrap().get(idx).is_some() { if self.windows.lock().unwrap().get(idx).is_some() {
let old = self.active.swap(idx, Ordering::SeqCst); let old = self.active.swap(idx, Ordering::SeqCst);
self.previous_keyboard.store(old, Ordering::SeqCst); self.previous_keyboard.store(old, Ordering::SeqCst);
@ -643,6 +661,7 @@ impl Program for CosmicStackInternal {
w.app_id(), w.app_id(),
user_data.get::<Id>().unwrap().clone(), user_data.get::<Id>().unwrap().clone(),
) )
.on_press(Message::PotentialTabDragStart(i))
.on_close(Message::Close(i)) .on_close(Message::Close(i))
}), }),
active, active,
@ -729,8 +748,10 @@ impl Program for CosmicStackInternal {
impl IsAlive for CosmicStack { impl IsAlive for CosmicStack {
fn alive(&self) -> bool { fn alive(&self) -> bool {
self.0 self.0.with_program(|p| {
.with_program(|p| p.windows.lock().unwrap().iter().any(IsAlive::alive)) p.override_alive.load(Ordering::SeqCst)
&& p.windows.lock().unwrap().iter().any(IsAlive::alive)
})
} }
} }
@ -945,6 +966,7 @@ impl PointerTarget<State> for CosmicStack {
event.location.y -= TAB_HEIGHT as f64; event.location.y -= TAB_HEIGHT as f64;
let active = let active =
self.pointer_leave_if_previous(seat, data, event.serial, event.time, event.location); self.pointer_leave_if_previous(seat, data, event.serial, event.time, event.location);
if let Some((previous, next)) = self.0.with_program(|p| { if let Some((previous, next)) = self.0.with_program(|p| {
let active_window = &p.windows.lock().unwrap()[active]; let active_window = &p.windows.lock().unwrap()[active];
if let Some(sessions) = active_window.user_data().get::<ScreencopySessions>() { if let Some(sessions) = active_window.user_data().get::<ScreencopySessions>() {
@ -987,7 +1009,68 @@ impl PointerTarget<State> for CosmicStack {
} }
(_, Focus::Header) => PointerTarget::enter(&self.0, seat, data, &event), (_, Focus::Header) => PointerTarget::enter(&self.0, seat, data, &event),
(Focus::Header, _) => { (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,
);
});
}
}
}
}
} }
_ => {} _ => {}
} }

View file

@ -103,7 +103,7 @@ impl Into<theme::Container> for TabBackgroundTheme {
} }
} }
pub trait TabMessage { pub trait TabMessage: Clone {
fn activate(idx: usize) -> Self; fn activate(idx: usize) -> Self;
fn is_activate(&self) -> Option<usize>; fn is_activate(&self) -> Option<usize>;
@ -119,6 +119,7 @@ pub struct Tab<'a, Message: TabMessage> {
title: String, title: String,
font: Font, font: Font,
close_message: Option<Message>, close_message: Option<Message>,
press_message: Option<Message>,
rule_theme: TabRuleTheme, rule_theme: TabRuleTheme,
background_theme: TabBackgroundTheme, background_theme: TabBackgroundTheme,
active: bool, active: bool,
@ -132,12 +133,18 @@ impl<'a, Message: TabMessage> Tab<'a, Message> {
title: title.into(), title: title.into(),
font: cosmic::font::FONT, font: cosmic::font::FONT,
close_message: None, close_message: None,
press_message: None,
rule_theme: TabRuleTheme::Default, rule_theme: TabRuleTheme::Default,
background_theme: TabBackgroundTheme::Default, background_theme: TabBackgroundTheme::Default,
active: false, 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 { pub fn on_close(mut self, message: Message) -> Self {
self.close_message = Some(message); self.close_message = Some(message);
self self
@ -191,7 +198,7 @@ impl<'a, Message: TabMessage> Tab<'a, Message> {
close_button = close_button.on_press(close_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(), widget::vertical_rule(4).style(self.rule_theme).into(),
self.app_icon self.app_icon
.apply(widget::container) .apply(widget::container)
@ -200,8 +207,6 @@ impl<'a, Message: TabMessage> Tab<'a, Message> {
.padding([2, 4]) .padding([2, 4])
.center_y() .center_y()
.into(), .into(),
];
items.push(
text(self.title) text(self.title)
.size(14) .size(14)
.font(self.font) .font(self.font)
@ -212,8 +217,6 @@ impl<'a, Message: TabMessage> Tab<'a, Message> {
.height(Length::Fill) .height(Length::Fill)
.width(Length::Fill) .width(Length::Fill)
.into(), .into(),
);
items.push(
close_button close_button
.apply(widget::container) .apply(widget::container)
.height(Length::Fill) .height(Length::Fill)
@ -222,7 +225,7 @@ impl<'a, Message: TabMessage> Tab<'a, Message> {
.center_y() .center_y()
.align_x(alignment::Horizontal::Right) .align_x(alignment::Horizontal::Right)
.into(), .into(),
); ];
TabInternal { TabInternal {
id: self.id, id: self.id,
@ -230,6 +233,7 @@ impl<'a, Message: TabMessage> Tab<'a, Message> {
active: self.active, active: self.active,
background: self.background_theme.into(), background: self.background_theme.into(),
elements: items, elements: items,
press_message: self.press_message,
} }
} }
} }
@ -247,6 +251,7 @@ pub(super) struct TabInternal<'a, Message: TabMessage, Renderer> {
active: bool, active: bool,
background: theme::Container, background: theme::Container,
elements: Vec<Element<'a, Message, Renderer>>, elements: Vec<Element<'a, Message, Renderer>>,
press_message: Option<Message>,
} }
impl<'a, Message, Renderer> Widget<Message, Renderer> for TabInternal<'a, Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer> for TabInternal<'a, Message, Renderer>
@ -361,15 +366,23 @@ where
}) })
.fold(event::Status::Ignored, event::Status::merge); .fold(event::Status::Ignored, event::Status::merge);
if status == event::Status::Ignored if status == event::Status::Ignored && cursor.is_over(layout.bounds()) {
&& cursor.is_over(layout.bounds()) if matches!(
&& 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::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
) ) {
{ shell.publish(Message::activate(self.idx));
shell.publish(Message::activate(self.idx)); return event::Status::Captured;
return event::Status::Captured; }
} }
status status

View file

@ -131,6 +131,7 @@ pub enum MoveResult {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
enum TargetZone { enum TargetZone {
InitialStackGrab,
InitialPlaceholder(NodeId), InitialPlaceholder(NodeId),
WindowStack(NodeId, Rectangle<i32, Logical>), WindowStack(NodeId, Rectangle<i32, Logical>),
WindowSplit(NodeId, Direction), WindowSplit(NodeId, Direction),
@ -2502,18 +2503,17 @@ impl TilingLayout {
last_overview_hover @ None => { last_overview_hover @ None => {
*last_overview_hover = Some(( *last_overview_hover = Some((
None, None,
TargetZone::InitialPlaceholder( tree.traverse_pre_order_ids(root)
tree.traverse_pre_order_ids(root) .unwrap()
.unwrap() .find(|id| match tree.get(id).unwrap().data() {
.find(|id| match tree.get(id).unwrap().data() { Data::Placeholder {
Data::Placeholder { initial_placeholder: true,
initial_placeholder: true, ..
.. } => true,
} => true, _ => false,
_ => false, })
}) .map(|node_id| TargetZone::InitialPlaceholder(node_id))
.unwrap(), .unwrap_or(TargetZone::InitialStackGrab),
),
)); ));
} }
Some((instant, old_target_zone)) => { Some((instant, old_target_zone)) => {