shell/tiling: semi-working stacking
This commit is contained in:
parent
fc7dd3398a
commit
b3401eb18a
7 changed files with 251 additions and 12 deletions
|
|
@ -54,6 +54,7 @@
|
||||||
(modifiers: [Super, Shift], key: "l"): Move(Right),
|
(modifiers: [Super, Shift], key: "l"): Move(Right),
|
||||||
|
|
||||||
(modifiers: [Super], key: "o"): ToggleOrientation,
|
(modifiers: [Super], key: "o"): ToggleOrientation,
|
||||||
|
(modifiers: [Super], key: "s"): ToggleStacking,
|
||||||
|
|
||||||
(modifiers: [Super], key: "y"): ToggleTiling,
|
(modifiers: [Super], key: "y"): ToggleTiling,
|
||||||
(modifiers: [Super], key: "g"): ToggleWindowFloating,
|
(modifiers: [Super], key: "g"): ToggleWindowFloating,
|
||||||
|
|
|
||||||
|
|
@ -1010,6 +1010,8 @@ pub enum Action {
|
||||||
ToggleOrientation,
|
ToggleOrientation,
|
||||||
Orientation(crate::shell::layout::Orientation),
|
Orientation(crate::shell::layout::Orientation),
|
||||||
|
|
||||||
|
ToggleStacking,
|
||||||
|
|
||||||
ToggleTiling,
|
ToggleTiling,
|
||||||
ToggleWindowFloating,
|
ToggleWindowFloating,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1120,6 +1120,12 @@ impl State {
|
||||||
.tiling_layer
|
.tiling_layer
|
||||||
.update_orientation(Some(orientation), &seat);
|
.update_orientation(Some(orientation), &seat);
|
||||||
}
|
}
|
||||||
|
Action::ToggleStacking => {
|
||||||
|
let output = seat.active_output();
|
||||||
|
let workspace = self.common.shell.active_space_mut(&output);
|
||||||
|
let focus_stack = workspace.focus_stack.get_mut(seat);
|
||||||
|
workspace.tiling_layer.toggle_stacking(seat, focus_stack);
|
||||||
|
}
|
||||||
Action::ToggleTiling => {
|
Action::ToggleTiling => {
|
||||||
let output = seat.active_output();
|
let output = seat.active_output();
|
||||||
let workspace = self.common.shell.active_space_mut(&output);
|
let workspace = self.common.shell.active_space_mut(&output);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
state::State,
|
state::State,
|
||||||
utils::prelude::SeatExt,
|
utils::prelude::SeatExt,
|
||||||
};
|
};
|
||||||
|
use calloop::LoopHandle;
|
||||||
use id_tree::NodeId;
|
use id_tree::NodeId;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
|
|
@ -228,8 +229,7 @@ impl CosmicMapped {
|
||||||
|
|
||||||
pub fn handle_focus(&self, direction: FocusDirection) -> bool {
|
pub fn handle_focus(&self, direction: FocusDirection) -> bool {
|
||||||
if let CosmicMappedInternal::Stack(stack) = &self.element {
|
if let CosmicMappedInternal::Stack(stack) = &self.element {
|
||||||
//TODO: stack.handle_focus(direction)
|
stack.handle_focus(direction)
|
||||||
false
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -458,6 +458,62 @@ impl CosmicMapped {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn convert_to_stack<'a>(
|
||||||
|
&mut self,
|
||||||
|
outputs: impl Iterator<Item = (&'a Output, Rectangle<i32, Logical>)>,
|
||||||
|
) {
|
||||||
|
match &self.element {
|
||||||
|
CosmicMappedInternal::Window(window) => {
|
||||||
|
let surface = window.surface();
|
||||||
|
let activated = surface.is_activated();
|
||||||
|
let handle = window.loop_handle();
|
||||||
|
|
||||||
|
let stack = CosmicStack::new(std::iter::once(surface), handle);
|
||||||
|
if let Some(geo) = self.last_geometry.lock().unwrap().clone() {
|
||||||
|
stack.set_geometry(geo);
|
||||||
|
}
|
||||||
|
for (output, overlap) in outputs {
|
||||||
|
stack.output_enter(output, overlap);
|
||||||
|
}
|
||||||
|
stack.set_activate(activated);
|
||||||
|
stack.active().send_configure();
|
||||||
|
stack.refresh();
|
||||||
|
|
||||||
|
self.element = CosmicMappedInternal::Stack(stack);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_to_surface<'a>(
|
||||||
|
&mut self,
|
||||||
|
surface: CosmicSurface,
|
||||||
|
outputs: impl Iterator<Item = (&'a Output, Rectangle<i32, Logical>)>,
|
||||||
|
) {
|
||||||
|
let handle = self.loop_handle();
|
||||||
|
let window = CosmicWindow::new(surface, handle);
|
||||||
|
|
||||||
|
if let Some(geo) = self.last_geometry.lock().unwrap().clone() {
|
||||||
|
window.set_geometry(geo);
|
||||||
|
}
|
||||||
|
for (output, overlap) in outputs {
|
||||||
|
window.output_enter(output, overlap);
|
||||||
|
}
|
||||||
|
window.set_activate(self.is_activated());
|
||||||
|
window.surface().send_configure();
|
||||||
|
window.refresh();
|
||||||
|
|
||||||
|
self.element = CosmicMappedInternal::Window(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn loop_handle(&self) -> LoopHandle<'static, crate::state::Data> {
|
||||||
|
match &self.element {
|
||||||
|
CosmicMappedInternal::Stack(stack) => stack.loop_handle(),
|
||||||
|
CosmicMappedInternal::Window(window) => window.loop_handle(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
pub fn set_debug(&self, flag: bool) {
|
pub fn set_debug(&self, flag: bool) {
|
||||||
let mut debug = self.debug.lock().unwrap();
|
let mut debug = self.debug.lock().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
shell::focus::FocusDirection,
|
||||||
state::State,
|
state::State,
|
||||||
utils::iced::{IcedElement, Program},
|
utils::iced::{IcedElement, Program},
|
||||||
utils::prelude::SeatExt,
|
utils::prelude::SeatExt,
|
||||||
|
|
@ -126,6 +127,10 @@ impl CosmicStack {
|
||||||
pub fn remove_window(&self, window: &CosmicSurface) {
|
pub fn remove_window(&self, window: &CosmicSurface) {
|
||||||
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 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(idx) = windows.iter().position(|w| w == window) else { return };
|
let Some(idx) = windows.iter().position(|w| w == window) else { return };
|
||||||
windows.remove(idx);
|
windows.remove(idx);
|
||||||
p.active.fetch_min(windows.len() - 1, Ordering::SeqCst);
|
p.active.fetch_min(windows.len() - 1, Ordering::SeqCst);
|
||||||
|
|
@ -135,11 +140,14 @@ impl CosmicStack {
|
||||||
pub fn remove_idx(&self, idx: usize) {
|
pub fn remove_idx(&self, idx: usize) {
|
||||||
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 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if windows.len() >= idx {
|
if windows.len() >= idx {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
windows.remove(idx);
|
windows.remove(idx);
|
||||||
p.active.fetch_min(windows.len(), Ordering::SeqCst);
|
p.active.fetch_min(windows.len() - 1, Ordering::SeqCst);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,6 +155,32 @@ impl CosmicStack {
|
||||||
self.0.with_program(|p| p.windows.lock().unwrap().len())
|
self.0.with_program(|p| p.windows.lock().unwrap().len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_focus(&self, direction: FocusDirection) -> bool {
|
||||||
|
self.0.with_program(|p| {
|
||||||
|
match direction {
|
||||||
|
FocusDirection::Left => p
|
||||||
|
.active
|
||||||
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |val| val.checked_sub(1))
|
||||||
|
.is_ok(),
|
||||||
|
FocusDirection::Right => {
|
||||||
|
let max = p.windows.lock().unwrap().len();
|
||||||
|
p.active
|
||||||
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |val| {
|
||||||
|
if val < max - 1 {
|
||||||
|
Some(val + 1)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
FocusDirection::Out => false, //TODO
|
||||||
|
FocusDirection::In => false, //TODO
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn active(&self) -> CosmicSurface {
|
pub fn active(&self) -> CosmicSurface {
|
||||||
self.0
|
self.0
|
||||||
.with_program(|p| p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].clone())
|
.with_program(|p| p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].clone())
|
||||||
|
|
@ -267,6 +301,10 @@ impl CosmicStack {
|
||||||
active
|
active
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn loop_handle(&self) -> LoopHandle<'static, crate::state::Data> {
|
||||||
|
self.0.loop_handle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Program for CosmicStackInternal {
|
impl Program for CosmicStackInternal {
|
||||||
|
|
@ -396,12 +434,19 @@ impl SpaceElement for CosmicStack {
|
||||||
fn refresh(&self) {
|
fn refresh(&self) {
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let mut windows = p.windows.lock().unwrap();
|
let mut windows = p.windows.lock().unwrap();
|
||||||
windows.retain(IsAlive::alive); // TODO: We don't handle empty stacks properly
|
|
||||||
|
// don't let the stack become empty
|
||||||
|
let active = windows[p.active.load(Ordering::SeqCst)].clone();
|
||||||
|
windows.retain(IsAlive::alive);
|
||||||
|
if windows.is_empty() {
|
||||||
|
windows.push(active);
|
||||||
|
}
|
||||||
|
|
||||||
let len = windows.len();
|
let len = windows.len();
|
||||||
let _ = p
|
let _ = p
|
||||||
.active
|
.active
|
||||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| {
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| {
|
||||||
(active > len).then_some(len - 1)
|
(active >= len).then_some(len - 1)
|
||||||
});
|
});
|
||||||
windows.iter().for_each(|w| SpaceElement::refresh(w))
|
windows.iter().for_each(|w| SpaceElement::refresh(w))
|
||||||
})
|
})
|
||||||
|
|
@ -490,11 +535,9 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
|
|
||||||
if event.location.y < TAB_HEIGHT as f64 {
|
if event.location.y < TAB_HEIGHT as f64 {
|
||||||
let focus = p.swap_focus(Focus::Header);
|
let focus = p.swap_focus(Focus::Header);
|
||||||
assert_eq!(focus, Focus::None);
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let focus = p.swap_focus(Focus::Window);
|
let focus = p.swap_focus(Focus::Window);
|
||||||
assert_eq!(focus, Focus::None);
|
|
||||||
|
|
||||||
*p.last_location.lock().unwrap() = Some((event.location, event.serial, event.time));
|
*p.last_location.lock().unwrap() = Some((event.location, event.serial, event.time));
|
||||||
let active = p.active.load(Ordering::SeqCst);
|
let active = p.active.load(Ordering::SeqCst);
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,10 @@ impl CosmicWindow {
|
||||||
Point::from((0, 0))
|
Point::from((0, 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn loop_handle(&self) -> LoopHandle<'static, crate::state::Data> {
|
||||||
|
self.0.loop_handle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::{element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, GROUP_COLOR},
|
backend::render::{element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, GROUP_COLOR},
|
||||||
shell::{
|
shell::{
|
||||||
element::{window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement},
|
element::{
|
||||||
|
window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement,
|
||||||
|
CosmicStack, CosmicWindow,
|
||||||
|
},
|
||||||
focus::{
|
focus::{
|
||||||
target::{KeyboardFocusTarget, WindowGroup},
|
target::{KeyboardFocusTarget, WindowGroup},
|
||||||
FocusDirection,
|
FocusDirection, FocusStackMut,
|
||||||
},
|
},
|
||||||
grabs::ResizeEdge,
|
grabs::ResizeEdge,
|
||||||
layout::Orientation,
|
layout::Orientation,
|
||||||
|
|
@ -393,6 +396,19 @@ impl TilingLayout {
|
||||||
let queue = self.queues.get_mut(output).expect("Output not mapped?");
|
let queue = self.queues.get_mut(output).expect("Output not mapped?");
|
||||||
let mut tree = queue.trees.back().unwrap().0.copy_clone();
|
let mut tree = queue.trees.back().unwrap().0.copy_clone();
|
||||||
|
|
||||||
|
TilingLayout::map_to_tree(&mut tree, window, output, focus_stack, direction);
|
||||||
|
|
||||||
|
let blocker = TilingLayout::update_positions(output, &mut tree, self.gaps);
|
||||||
|
queue.push_tree(tree, blocker);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_to_tree<'a>(
|
||||||
|
mut tree: &mut Tree<Data>,
|
||||||
|
window: impl Into<CosmicMapped>,
|
||||||
|
output: &Output,
|
||||||
|
focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>,
|
||||||
|
direction: Option<Direction>,
|
||||||
|
) {
|
||||||
let window = window.into();
|
let window = window.into();
|
||||||
let new_window = Node::new(Data::Mapped {
|
let new_window = Node::new(Data::Mapped {
|
||||||
mapped: window.clone(),
|
mapped: window.clone(),
|
||||||
|
|
@ -457,9 +473,6 @@ impl TilingLayout {
|
||||||
};
|
};
|
||||||
|
|
||||||
*window.tiling_node_id.lock().unwrap() = Some(window_id);
|
*window.tiling_node_id.lock().unwrap() = Some(window_id);
|
||||||
|
|
||||||
let blocker = TilingLayout::update_positions(output, &mut tree, self.gaps);
|
|
||||||
queue.push_tree(tree, blocker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unmap(&mut self, window: &CosmicMapped) -> Option<Output> {
|
pub fn unmap(&mut self, window: &CosmicMapped) -> Option<Output> {
|
||||||
|
|
@ -1052,6 +1065,120 @@ impl TilingLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_stacking<'a>(&mut self, seat: &Seat<State>, mut focus_stack: FocusStackMut) {
|
||||||
|
let output = seat.active_output();
|
||||||
|
let Some(queue) = self.queues.get_mut(&output) else { return };
|
||||||
|
let mut tree = queue.trees.back().unwrap().0.copy_clone();
|
||||||
|
|
||||||
|
if let Some((last_active, last_active_data)) =
|
||||||
|
TilingLayout::currently_focused_node(&tree, seat)
|
||||||
|
{
|
||||||
|
match last_active_data {
|
||||||
|
FocusedNodeData::Window(mapped) => {
|
||||||
|
if mapped.is_window() {
|
||||||
|
// if it is just a window
|
||||||
|
match tree.get_mut(&last_active).unwrap().data_mut() {
|
||||||
|
Data::Mapped { mapped, .. } => {
|
||||||
|
mapped.convert_to_stack(std::iter::once((&output, mapped.bbox())));
|
||||||
|
focus_stack.append(&mapped);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// if we have a stack
|
||||||
|
let mut surfaces = mapped.windows().map(|(s, _)| s);
|
||||||
|
let first = surfaces.next().expect("Stack without a window?");
|
||||||
|
|
||||||
|
let handle = match tree.get_mut(&last_active).unwrap().data_mut() {
|
||||||
|
Data::Mapped { mapped, .. } => {
|
||||||
|
let handle = mapped.loop_handle();
|
||||||
|
mapped.convert_to_surface(
|
||||||
|
first,
|
||||||
|
std::iter::once((&output, mapped.bbox())),
|
||||||
|
);
|
||||||
|
focus_stack.append(&mapped);
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// map the rest
|
||||||
|
for other in surfaces {
|
||||||
|
let window =
|
||||||
|
CosmicMapped::from(CosmicWindow::new(other, handle.clone()));
|
||||||
|
window.output_enter(&output, window.bbox());
|
||||||
|
window.set_bounds(output.geometry().size);
|
||||||
|
|
||||||
|
TilingLayout::map_to_tree(
|
||||||
|
&mut tree,
|
||||||
|
window,
|
||||||
|
&output,
|
||||||
|
Some(focus_stack.iter()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Focus the new group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FocusedNodeData::Group(_, _) => {
|
||||||
|
let mut handle = None;
|
||||||
|
let surfaces = tree
|
||||||
|
.traverse_pre_order(&last_active)
|
||||||
|
.unwrap()
|
||||||
|
.flat_map(|node| match node.data() {
|
||||||
|
Data::Mapped { mapped, .. } => {
|
||||||
|
if handle.is_none() {
|
||||||
|
handle = Some(mapped.loop_handle());
|
||||||
|
}
|
||||||
|
Some(mapped.windows().map(|(s, _)| s))
|
||||||
|
}
|
||||||
|
Data::Group { .. } => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if surfaces.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let handle = handle.unwrap();
|
||||||
|
let stack = CosmicStack::new(surfaces.into_iter(), handle);
|
||||||
|
|
||||||
|
for child in tree
|
||||||
|
.children_ids(&last_active)
|
||||||
|
.unwrap()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
{
|
||||||
|
tree.remove_node(child, RemoveBehavior::DropChildren)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let data = tree.get_mut(&last_active).unwrap().data_mut();
|
||||||
|
|
||||||
|
let geo = *data.geometry();
|
||||||
|
stack.set_geometry(geo);
|
||||||
|
stack.output_enter(&output, stack.bbox());
|
||||||
|
stack.set_activate(true);
|
||||||
|
stack.active().send_configure();
|
||||||
|
stack.refresh();
|
||||||
|
|
||||||
|
let mapped = CosmicMapped::from(stack);
|
||||||
|
*mapped.last_geometry.lock().unwrap() = Some(geo);
|
||||||
|
*mapped.tiling_node_id.lock().unwrap() = Some(last_active);
|
||||||
|
focus_stack.append(&mapped);
|
||||||
|
*data = Data::Mapped {
|
||||||
|
mapped,
|
||||||
|
last_geometry: geo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let blocker = TilingLayout::update_positions(&output, &mut tree, self.gaps);
|
||||||
|
queue.push_tree(tree, blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn recalculate(&mut self, output: &Output) {
|
pub fn recalculate(&mut self, output: &Output) {
|
||||||
let Some(queue) = self.queues.get_mut(output) else { return };
|
let Some(queue) = self.queues.get_mut(output) else { return };
|
||||||
let mut tree = queue.trees.back().unwrap().0.copy_clone();
|
let mut tree = queue.trees.back().unwrap().0.copy_clone();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue