stack: Allow dragging tabs out
This commit is contained in:
parent
9a3bfb4bba
commit
10902ff543
3 changed files with 132 additions and 36 deletions
|
|
@ -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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue