workspace/layout: Resize windows/groups

This commit is contained in:
Victoria Brekenfeld 2023-07-06 00:02:29 +02:00
parent f5dcd97c42
commit 2004705080
4 changed files with 404 additions and 183 deletions

View file

@ -3,6 +3,7 @@ use std::sync::Weak;
use crate::{ use crate::{
shell::{element::CosmicMapped, CosmicSurface}, shell::{element::CosmicMapped, CosmicSurface},
utils::prelude::*, utils::prelude::*,
wayland::handlers::xdg_shell::popup::get_popup_toplevel,
}; };
use id_tree::NodeId; use id_tree::NodeId;
use smithay::{ use smithay::{
@ -50,6 +51,16 @@ impl From<KeyboardFocusTarget> for PointerFocusTarget {
} }
} }
impl KeyboardFocusTarget {
pub fn toplevel(&self) -> Option<WlSurface> {
match self {
KeyboardFocusTarget::Element(mapped) => mapped.wl_surface(),
KeyboardFocusTarget::Popup(PopupKind::Xdg(xdg)) => get_popup_toplevel(&xdg),
_ => None,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WindowGroup { pub struct WindowGroup {
pub node: NodeId, pub node: NodeId,

View file

@ -5,7 +5,7 @@ use smithay::{
element::{AsRenderElements, RenderElement}, element::{AsRenderElements, RenderElement},
ImportAll, ImportMem, Renderer, ImportAll, ImportMem, Renderer,
}, },
desktop::{layer_map_for_output, space::SpaceElement, Space}, desktop::{layer_map_for_output, space::SpaceElement, Space, WindowSurfaceType},
input::{pointer::GrabStartData as PointerGrabStartData, Seat}, input::{pointer::GrabStartData as PointerGrabStartData, Seat},
output::Output, output::Output,
utils::{Logical, Point, Rectangle, Size}, utils::{Logical, Point, Rectangle, Size},
@ -19,8 +19,9 @@ use crate::{
stack::CosmicStackRenderElement, window::CosmicWindowRenderElement, CosmicMapped, stack::CosmicStackRenderElement, window::CosmicWindowRenderElement, CosmicMapped,
CosmicMappedRenderElement, CosmicMappedRenderElement,
}, },
focus::target::KeyboardFocusTarget,
grabs::ResizeEdge, grabs::ResizeEdge,
CosmicSurface, CosmicSurface, ResizeDirection, ResizeMode,
}, },
state::State, state::State,
utils::prelude::*, utils::prelude::*,
@ -252,6 +253,73 @@ impl FloatingLayout {
} }
} }
pub fn resize(
&mut self,
focused: &KeyboardFocusTarget,
direction: ResizeDirection,
edge: ResizeEdge,
amount: i32,
) -> bool {
let Some(toplevel) = focused.toplevel() else { return false };
let Some(mapped) = self.space.elements().find(|m| m.has_surface(&toplevel, WindowSurfaceType::TOPLEVEL)) else { return false };
let Some(original_geo) = self.space.element_geometry(mapped) else {
return false // we don't have that window
};
let mut geo = original_geo.clone();
if edge.contains(ResizeEdge::RIGHT) || edge.contains(ResizeEdge::LEFT) {
if direction == ResizeDirection::Inwards {
geo.size.w -= amount;
} else {
geo.size.w += amount;
}
if edge.contains(ResizeEdge::LEFT) {
if direction == ResizeDirection::Inwards {
geo.loc.x += amount;
} else {
geo.loc.x -= amount;
}
}
}
if edge.contains(ResizeEdge::BOTTOM) || edge.contains(ResizeEdge::TOP) {
if direction == ResizeDirection::Inwards {
geo.size.h -= amount;
} else {
geo.size.h += amount;
}
if edge.contains(ResizeEdge::TOP) {
if direction == ResizeDirection::Inwards {
geo.loc.y += amount;
} else {
geo.loc.y -= amount;
}
}
}
let Some(bounding_box) = self.space.outputs()
.map(|o| self.space.output_geometry(o).unwrap())
.filter(|output_geo| output_geo.overlaps(geo))
.fold(None, |res, output_geo| match res {
None => Some(output_geo),
Some(other) => Some(other.merge(output_geo)),
}) else { return true };
geo = geo.intersection(bounding_box).unwrap();
*mapped.resize_state.lock().unwrap() = Some(ResizeState::Resizing(ResizeData {
edges: edge,
initial_window_location: original_geo.loc,
initial_window_size: original_geo.size,
}));
mapped.set_resizing(true);
mapped.set_geometry(geo);
mapped.configure();
true
}
pub fn mapped(&self) -> impl Iterator<Item = &CosmicMapped> { pub fn mapped(&self) -> impl Iterator<Item = &CosmicMapped> {
self.space.elements().rev() self.space.elements().rev()
} }

View file

@ -14,7 +14,7 @@ use crate::{
}, },
grabs::ResizeEdge, grabs::ResizeEdge,
layout::Orientation, layout::Orientation,
CosmicSurface, OutputNotMapped, OverviewMode, CosmicSurface, OutputNotMapped, OverviewMode, ResizeDirection, ResizeMode,
}, },
utils::prelude::*, utils::prelude::*,
wayland::{ wayland::{
@ -650,7 +650,8 @@ impl TilingLayout {
let queue = self.queues.get_mut(&output).unwrap(); let queue = self.queues.get_mut(&output).unwrap();
let mut tree = queue.trees.back().unwrap().0.copy_clone(); let mut tree = queue.trees.back().unwrap().0.copy_clone();
let Some((node_id, data)) = TilingLayout::currently_focused_node(&mut tree, seat) else { let Some(target) = seat.get_keyboard().unwrap().current_focus() else { return MoveResult::Done };
let Some((node_id, data)) = TilingLayout::currently_focused_node(&mut tree, &seat.active_output(), target) else {
return MoveResult::Done return MoveResult::Done
}; };
@ -971,10 +972,12 @@ impl TilingLayout {
let output = seat.active_output(); let output = seat.active_output();
let tree = &self.queues.get(&output).unwrap().trees.back().unwrap().0; let tree = &self.queues.get(&output).unwrap().trees.back().unwrap().0;
if let Some(focused) = TilingLayout::currently_focused_node(tree, seat).or_else(|| { let Some(target) = seat.get_keyboard().unwrap().current_focus() else { return FocusResult::None };
let Some(focused) = TilingLayout::currently_focused_node(tree, &seat.active_output(), target).or_else(|| {
TilingLayout::last_active_window(tree, focus_stack) TilingLayout::last_active_window(tree, focus_stack)
.map(|(id, mapped)| (id, FocusedNodeData::Window(mapped))) .map(|(id, mapped)| (id, FocusedNodeData::Window(mapped)))
}) { }) else { return FocusResult::None };
let (last_node_id, data) = focused; let (last_node_id, data) = focused;
// stacks may handle focus internally // stacks may handle focus internally
@ -1127,8 +1130,7 @@ impl TilingLayout {
_ => unreachable!(), _ => unreachable!(),
}; };
let point = point.to_f64(); let point = point.to_f64();
((point.x - center.x).powi(2) + (point.y - center.y).powi(2)) ((point.x - center.x).powi(2) + (point.y - center.y).powi(2)).sqrt()
.sqrt()
}; };
node_id = tree node_id = tree
@ -1147,7 +1149,6 @@ impl TilingLayout {
node_id = group.clone(); node_id = group.clone();
} }
} }
}
FocusResult::None FocusResult::None
} }
@ -1161,7 +1162,10 @@ impl TilingLayout {
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();
if let Some((last_active, _)) = TilingLayout::currently_focused_node(&tree, seat) { let Some(target) = seat.get_keyboard().unwrap().current_focus() else { return };
if let Some((last_active, _)) =
TilingLayout::currently_focused_node(&tree, &seat.active_output(), target)
{
if let Some(group) = tree.get(&last_active).unwrap().parent().cloned() { if let Some(group) = tree.get(&last_active).unwrap().parent().cloned() {
if let &mut Data::Group { if let &mut Data::Group {
ref mut orientation, ref mut orientation,
@ -1203,8 +1207,9 @@ impl TilingLayout {
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();
let Some(target) = seat.get_keyboard().unwrap().current_focus() else { return };
if let Some((last_active, last_active_data)) = if let Some((last_active, last_active_data)) =
TilingLayout::currently_focused_node(&tree, seat) TilingLayout::currently_focused_node(&tree, &seat.active_output(), target)
{ {
match last_active_data { match last_active_data {
FocusedNodeData::Window(mapped) => { FocusedNodeData::Window(mapped) => {
@ -1437,6 +1442,114 @@ impl TilingLayout {
None None
} }
pub fn possible_resizes(tree: &Tree<Data>, mut node_id: NodeId) -> ResizeEdge {
let mut edges = ResizeEdge::empty();
while let Some(group_id) = tree.get(&node_id).unwrap().parent().cloned() {
let orientation = tree.get(&group_id).unwrap().data().orientation();
let node_idx = tree
.children_ids(&group_id)
.unwrap()
.position(|id| id == &node_id)
.unwrap();
let total = tree.children_ids(&group_id).unwrap().count();
if orientation == Orientation::Vertical {
if node_idx > 0 {
edges.insert(ResizeEdge::LEFT);
}
if node_idx < total - 1 {
edges.insert(ResizeEdge::RIGHT);
}
} else {
if node_idx > 0 {
edges.insert(ResizeEdge::TOP);
}
if node_idx < total - 1 {
edges.insert(ResizeEdge::BOTTOM);
}
}
node_id = group_id;
}
edges
}
pub fn resize(
&mut self,
focused: &KeyboardFocusTarget,
direction: ResizeDirection,
edges: ResizeEdge,
amount: i32,
) -> bool {
let Some((output, mut node_id)) = self.queues.iter().find_map(|(output, queue)| {
let tree = &queue.trees.back().unwrap().0;
let root_id = tree.root_node_id()?;
let id = match TilingLayout::currently_focused_node(tree, &output.output, focused.clone()) {
Some((_id, FocusedNodeData::Window(mapped))) => // we need to make sure the id belongs to this tree..
tree.traverse_pre_order_ids(root_id)
.unwrap()
.find(|id| tree.get(id).unwrap().data().is_mapped(Some(&mapped))),
Some((id, FocusedNodeData::Group(_, _))) => Some(id), // in this case the output was already matched, so the id is to be trusted
_ => None,
};
id.map(|id| (output.output.clone(), id))
}) else { return false };
let queue = self.queues.get_mut(&output).unwrap();
let mut tree = queue.trees.back().unwrap().0.copy_clone();
while let Some(group_id) = tree.get(&node_id).unwrap().parent().cloned() {
let orientation = tree.get(&group_id).unwrap().data().orientation();
if !((orientation == Orientation::Vertical
&& (edges.contains(ResizeEdge::LEFT) || edges.contains(ResizeEdge::RIGHT)))
|| (orientation == Orientation::Horizontal
&& (edges.contains(ResizeEdge::TOP) || edges.contains(ResizeEdge::BOTTOM))))
{
node_id = group_id.clone();
continue;
}
let node_idx = tree
.children_ids(&group_id)
.unwrap()
.position(|id| id == &node_id)
.unwrap();
let Some(other_idx) = (match edges {
x if x.intersects(ResizeEdge::TOP_LEFT) => node_idx.checked_sub(1),
_ => if tree.children_ids(&group_id).unwrap().count() - 1 > node_idx { Some(node_idx + 1) } else { None },
}) else {
node_id = group_id.clone();
continue;
};
let data = tree.get_mut(&group_id).unwrap().data_mut();
match data {
Data::Group { sizes, .. } => {
let (shrink_idx, grow_idx) = if direction == ResizeDirection::Inwards {
(node_idx, other_idx)
} else {
(other_idx, node_idx)
};
let old_size = sizes[shrink_idx];
sizes[shrink_idx] = (old_size - amount).max(10);
let diff = old_size - sizes[shrink_idx];
sizes[grow_idx] += diff;
}
_ => unreachable!(),
}
let blocker = TilingLayout::update_positions(&output, &mut tree, self.gaps);
queue.push_tree(tree, Duration::ZERO, blocker);
return true;
}
true
}
fn last_active_window<'a>( fn last_active_window<'a>(
tree: &Tree<Data>, tree: &Tree<Data>,
mut focus_stack: impl Iterator<Item = &'a CosmicMapped>, mut focus_stack: impl Iterator<Item = &'a CosmicMapped>,
@ -1451,10 +1564,9 @@ impl TilingLayout {
fn currently_focused_node( fn currently_focused_node(
tree: &Tree<Data>, tree: &Tree<Data>,
seat: &Seat<State>, output: &Output,
mut target: KeyboardFocusTarget,
) -> Option<(NodeId, FocusedNodeData)> { ) -> Option<(NodeId, FocusedNodeData)> {
let mut target = seat.get_keyboard().unwrap().current_focus()?;
// if the focus is currently on a popup, treat it's toplevel as the target // if the focus is currently on a popup, treat it's toplevel as the target
if let KeyboardFocusTarget::Popup(popup) = target { if let KeyboardFocusTarget::Popup(popup) = target {
let toplevel_surface = match popup { let toplevel_surface = match popup {
@ -1487,7 +1599,7 @@ impl TilingLayout {
} }
} }
KeyboardFocusTarget::Group(window_group) => { KeyboardFocusTarget::Group(window_group) => {
if window_group.output == seat.active_output() { if window_group.output == *output {
let node = tree.get(&window_group.node).ok()?; let node = tree.get(&window_group.node).ok()?;
if node.data().is_group() { if node.data().is_group() {
return Some(( return Some((
@ -1978,7 +2090,14 @@ where
let alpha = alpha * transition; let alpha = alpha * transition;
let focused = seat let focused = seat
.and_then(|seat| TilingLayout::currently_focused_node(&tree, seat)) .and_then(|seat| {
seat.get_keyboard()
.unwrap()
.current_focus()
.and_then(|target| {
TilingLayout::currently_focused_node(&tree, &seat.active_output(), target)
})
})
.map(|(id, _)| id); .map(|(id, _)| id);
let has_potential_groups = if let Some(focused_id) = focused.as_ref() { let has_potential_groups = if let Some(focused_id) = focused.as_ref() {
@ -2318,7 +2437,18 @@ where
CosmicStackRenderElement<R>: RenderElement<R>, CosmicStackRenderElement<R>: RenderElement<R>,
{ {
let focused = seat let focused = seat
.and_then(|seat| TilingLayout::currently_focused_node(&target_tree, seat)) .and_then(|seat| {
seat.get_keyboard()
.unwrap()
.current_focus()
.and_then(|target| {
TilingLayout::currently_focused_node(
&target_tree,
&seat.active_output(),
target,
)
})
})
.map(|(id, _)| id); .map(|(id, _)| id);
let mut group_backdrop = None; let mut group_backdrop = None;

View file

@ -23,7 +23,6 @@ use crate::{
xwayland::XWaylandState, xwayland::XWaylandState,
}; };
use calloop::LoopHandle;
use indexmap::IndexSet; use indexmap::IndexSet;
use smithay::{ use smithay::{
backend::renderer::{ backend::renderer::{
@ -45,12 +44,13 @@ use smithay::{
}; };
use std::{collections::HashMap, time::Instant}; use std::{collections::HashMap, time::Instant};
use tracing::warn; use tracing::warn;
use wayland_backend::server::ClientId;
use super::{ use super::{
element::{stack::CosmicStackRenderElement, window::CosmicWindowRenderElement, CosmicMapped}, element::{stack::CosmicStackRenderElement, window::CosmicWindowRenderElement, CosmicMapped},
focus::{target::KeyboardFocusTarget, FocusStack, FocusStackMut}, focus::{target::KeyboardFocusTarget, FocusStack, FocusStackMut},
grabs::{ResizeEdge, ResizeGrab}, grabs::{ResizeEdge, ResizeGrab},
CosmicMappedRenderElement, CosmicSurface, ResizeDirection, CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -341,15 +341,27 @@ impl Workspace {
} }
} }
pub fn resize(&mut self, seat: &Seat<State>, direction: ResizeDirection, edge: ResizeEdge) { pub fn resize(
if let Some(KeyboardFocusTarget::Fullscreen(_)) = &mut self,
seat.get_keyboard().unwrap().current_focus() focused: &KeyboardFocusTarget,
direction: ResizeDirection,
edge: ResizeEdge,
amount: i32,
) -> bool {
if let Some(toplevel) = focused.toplevel() {
if self
.fullscreen
.values()
.any(|surface| surface.wl_surface().as_ref() == Some(&toplevel))
{ {
return; return false;
}
} }
if !self.floating_layer.resize(seat, direction, edge) { if !self.floating_layer.resize(focused, direction, edge, amount) {
self.tiling_layer.resize(seat, direction, edge); self.tiling_layer.resize(focused, direction, edge, amount)
} else {
true
} }
} }