tiling: Allow selecting groups

This commit is contained in:
Victoria Brekenfeld 2023-05-30 21:02:19 +02:00
parent 6d270dec14
commit 56131b13ae
4 changed files with 200 additions and 153 deletions

View file

@ -1049,7 +1049,7 @@ impl State {
} }
if let Some(_move_further) = if let Some(_move_further) =
workspace.tiling_layer.move_current_window(direction, seat) workspace.tiling_layer.move_current_node(direction, seat)
{ {
// TODO: Being able to move Groups (move_further should be KeyboardFocusTarget instead) // TODO: Being able to move Groups (move_further should be KeyboardFocusTarget instead)
match (direction, self.common.config.static_conf.workspace_layout) { match (direction, self.common.config.static_conf.workspace_layout) {
@ -1111,20 +1111,14 @@ impl State {
Action::ToggleOrientation => { Action::ToggleOrientation => {
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);
let focus_stack = workspace.focus_stack.get(seat); workspace.tiling_layer.update_orientation(None, &seat);
workspace
.tiling_layer
.update_orientation(None, &seat, focus_stack.iter());
} }
Action::Orientation(orientation) => { Action::Orientation(orientation) => {
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);
let focus_stack = workspace.focus_stack.get(seat); workspace
workspace.tiling_layer.update_orientation( .tiling_layer
Some(orientation), .update_orientation(Some(orientation), &seat);
&seat,
focus_stack.iter(),
);
} }
Action::ToggleTiling => { Action::ToggleTiling => {
let output = seat.active_output(); let output = seat.active_output();

View file

@ -55,6 +55,7 @@ pub struct WindowGroup {
pub node: NodeId, pub node: NodeId,
pub output: WeakOutput, pub output: WeakOutput,
pub alive: Weak<()>, pub alive: Weak<()>,
pub focus_stack: Vec<NodeId>,
} }
impl PartialEq for WindowGroup { impl PartialEq for WindowGroup {

View file

@ -42,7 +42,7 @@ use std::{
borrow::Borrow, borrow::Borrow,
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
hash::Hash, hash::Hash,
sync::Arc, sync::{Arc, Weak},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use tracing::trace; use tracing::trace;
@ -281,6 +281,12 @@ impl Data {
} }
} }
#[derive(Debug, Clone)]
enum FocusedNodeData {
Group(Vec<NodeId>, Weak<()>),
Window(CosmicMapped),
}
impl TilingLayout { impl TilingLayout {
pub fn new(gaps: (u8, u8)) -> TilingLayout { pub fn new(gaps: (u8, u8)) -> TilingLayout {
TilingLayout { TilingLayout {
@ -290,9 +296,7 @@ impl TilingLayout {
pending_blockers: Vec::new(), pending_blockers: Vec::new(),
} }
} }
}
impl TilingLayout {
pub fn map_output(&mut self, output: &Output, location: Point<i32, Logical>) { pub fn map_output(&mut self, output: &Output, location: Point<i32, Logical>) {
if !self.queues.contains_key(output) { if !self.queues.contains_key(output) {
self.queues.insert( self.queues.insert(
@ -423,7 +427,7 @@ impl TilingLayout {
let last_active = focus_stack let last_active = focus_stack
.and_then(|focus_stack| TilingLayout::last_active_window(&mut tree, focus_stack)); .and_then(|focus_stack| TilingLayout::last_active_window(&mut tree, focus_stack));
if let Some((_last_active_window, ref node_id)) = last_active { if let Some((ref node_id, _last_active_window)) = last_active {
let orientation = { let orientation = {
let window_size = tree.get(node_id).unwrap().data().geometry().size; let window_size = tree.get(node_id).unwrap().data().geometry().size;
if window_size.w > window_size.h { if window_size.w > window_size.h {
@ -577,31 +581,29 @@ impl TilingLayout {
None None
} }
pub fn move_current_window<'a>( pub fn move_current_node<'a>(
&mut self, &mut self,
direction: Direction, direction: Direction,
seat: &Seat<State>, seat: &Seat<State>,
) -> Option<CosmicMapped /*TODO move window groups across screens?*/> { ) -> Option<KeyboardFocusTarget> {
let output = seat.active_output(); let output = seat.active_output();
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 node_id = match TilingLayout::currently_focused_node(&mut tree, seat) { let (node_id, data) = TilingLayout::currently_focused_node(&mut tree, seat)?;
Some(node_id) => node_id,
None => {
return None;
}
};
let mut child_id = node_id.clone(); let mut child_id = node_id.clone();
// Without a parent to start with, just return // Without a parent to start with, just return
let Some(og_parent) = tree.get(&node_id).unwrap().parent().cloned() else { let Some(og_parent) = tree.get(&node_id).unwrap().parent().cloned() else {
let data = tree.get(&node_id).unwrap().data();
assert!(data.is_mapped(None));
return match data { return match data {
Data::Mapped { mapped, .. } => Some(mapped.clone()), FocusedNodeData::Window(window) => Some(window.into()),
_ => unreachable!(), FocusedNodeData::Group(focus_stack, alive) => Some(WindowGroup {
}; node: node_id,
output: output.downgrade(),
alive,
focus_stack,
}.into()),
}
}; };
let og_idx = tree let og_idx = tree
.children_ids(&og_parent) .children_ids(&og_parent)
@ -852,9 +854,17 @@ impl TilingLayout {
child_id = parent.clone(); child_id = parent.clone();
} }
match tree.get(&node_id).unwrap().data() { match data {
Data::Mapped { mapped, .. } => Some(mapped.clone()), FocusedNodeData::Window(window) => Some(window.into()),
Data::Group { .. } => None, // TODO move groups to other screens FocusedNodeData::Group(focus_stack, alive) => Some(
WindowGroup {
node: node_id,
output: output.downgrade(),
alive,
focus_stack,
}
.into(),
),
} }
} }
@ -867,14 +877,47 @@ 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;
// TODO: Rather use something like seat.current_keyboard_focus if let Some(focused) = TilingLayout::currently_focused_node(tree, seat).or_else(|| {
// TODO https://github.com/Smithay/smithay/pull/777 TilingLayout::last_active_window(tree, focus_stack)
if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { .map(|(id, mapped)| (id, FocusedNodeData::Window(mapped)))
let (last_window, last_node_id) = last_active; }) {
let (last_node_id, data) = focused;
// stacks may handle focus internally // stacks may handle focus internally
if last_window.handle_focus(direction) { if let FocusedNodeData::Window(window) = data.clone() {
return FocusResult::Handled; if window.handle_focus(direction) {
return FocusResult::Handled;
}
}
if direction == FocusDirection::In {
if let FocusedNodeData::Group(mut stack, _) = data.clone() {
let maybe_id = stack.pop().unwrap();
let id = if tree
.children_ids(&last_node_id)
.unwrap()
.any(|id| id == &maybe_id)
{
Some(maybe_id)
} else {
tree.children_ids(&last_node_id).unwrap().next().cloned()
};
if let Some(id) = id {
return match tree.get(&id).unwrap().data() {
Data::Mapped { mapped, .. } => FocusResult::Some(mapped.clone().into()),
Data::Group { alive, .. } => FocusResult::Some(
WindowGroup {
node: id,
output: output.downgrade(),
alive: Arc::downgrade(alive),
focus_stack: stack,
}
.into(),
),
};
}
}
} }
let mut node_id = last_node_id.clone(); let mut node_id = last_node_id.clone();
@ -893,6 +936,13 @@ impl TilingLayout {
&Data::Group { ref alive, .. } => Arc::downgrade(alive), &Data::Group { ref alive, .. } => Arc::downgrade(alive),
_ => unreachable!(), _ => unreachable!(),
}, },
focus_stack: match data {
FocusedNodeData::Group(mut stack, _) => {
stack.push(child);
stack
}
_ => vec![child],
},
} }
.into(), .into(),
); );
@ -1007,13 +1057,12 @@ impl TilingLayout {
&mut self, &mut self,
new_orientation: Option<Orientation>, new_orientation: Option<Orientation>,
seat: &Seat<State>, seat: &Seat<State>,
focus_stack: impl Iterator<Item = &'a CosmicMapped> + 'a,
) { ) {
let output = seat.active_output(); let output = seat.active_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();
if let Some((_, last_active)) = TilingLayout::last_active_window(&tree, focus_stack) { if let Some((last_active, _)) = TilingLayout::currently_focused_node(&tree, seat) {
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,
@ -1169,16 +1218,19 @@ impl TilingLayout {
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>,
) -> Option<(CosmicMapped, NodeId)> { ) -> Option<(NodeId, CosmicMapped)> {
focus_stack focus_stack
.find_map(|mapped| tree.root_node_id() .find_map(|mapped| tree.root_node_id()
.and_then(|root| tree.traverse_pre_order_ids(root).unwrap() .and_then(|root| tree.traverse_pre_order_ids(root).unwrap()
.find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Mapped { mapped: m, .. }) if m == mapped)) .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Mapped { mapped: m, .. }) if m == mapped))
).map(|id| (mapped.clone(), id)) ).map(|id| (id, mapped.clone()))
) )
} }
fn currently_focused_node(tree: &mut Tree<Data>, seat: &Seat<State>) -> Option<NodeId> { fn currently_focused_node(
tree: &Tree<Data>,
seat: &Seat<State>,
) -> Option<(NodeId, FocusedNodeData)> {
let mut target = seat.get_keyboard().unwrap().current_focus()?; 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
@ -1209,14 +1261,17 @@ impl TilingLayout {
let node = tree.get(&node_id).ok()?; let node = tree.get(&node_id).ok()?;
let data = node.data(); let data = node.data();
if data.is_mapped(Some(&mapped)) { if data.is_mapped(Some(&mapped)) {
return Some(node_id); return Some((node_id, FocusedNodeData::Window(mapped)));
} }
} }
KeyboardFocusTarget::Group(window_group) => { KeyboardFocusTarget::Group(window_group) => {
if window_group.output == seat.active_output() { if window_group.output == seat.active_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(window_group.node); return Some((
window_group.node,
FocusedNodeData::Group(window_group.focus_stack, window_group.alive),
));
} }
} }
} }
@ -1523,7 +1578,7 @@ impl TilingLayout {
&self, &self,
renderer: &mut R, renderer: &mut R,
output: &Output, output: &Output,
focused: Option<&CosmicMapped>, seat: Option<&Seat<State>>,
non_exclusive_zone: Rectangle<i32, Logical>, non_exclusive_zone: Rectangle<i32, Logical>,
overview: OverviewMode, overview: OverviewMode,
indicator_thickness: u8, indicator_thickness: u8,
@ -1594,7 +1649,7 @@ impl TilingLayout {
reference_tree, reference_tree,
renderer, renderer,
non_exclusive_zone, non_exclusive_zone,
focused, // TODO: Would be better to be an old focus, seat, // TODO: Would be better to be an old focus,
// but for that we have to associate focus with a tree (and animate focus changes properly) // but for that we have to associate focus with a tree (and animate focus changes properly)
1.0 - percentage, 1.0 - percentage,
transition, transition,
@ -1624,7 +1679,7 @@ impl TilingLayout {
target_tree, target_tree,
renderer, renderer,
non_exclusive_zone, non_exclusive_zone,
focused, seat,
percentage, percentage,
transition, transition,
) )
@ -1640,7 +1695,7 @@ impl TilingLayout {
renderer, renderer,
geometries, geometries,
old_geometries, old_geometries,
focused, seat,
output_scale, output_scale,
percentage, percentage,
if let Some(transition) = draw_groups { if let Some(transition) = draw_groups {
@ -1668,7 +1723,7 @@ fn geometries_for_groupview<R>(
tree: &Tree<Data>, tree: &Tree<Data>,
renderer: &mut R, renderer: &mut R,
non_exclusive_zone: Rectangle<i32, Logical>, non_exclusive_zone: Rectangle<i32, Logical>,
focused: Option<&CosmicMapped>, seat: Option<&Seat<State>>,
alpha: f32, alpha: f32,
transition: f32, transition: f32,
) -> Option<( ) -> Option<(
@ -1691,6 +1746,10 @@ where
let gap: i32 = (GAP as f32 * transition).round() as i32; let gap: i32 = (GAP as f32 * transition).round() as i32;
let alpha = alpha * transition; let alpha = alpha * transition;
let focused = seat
.and_then(|seat| TilingLayout::currently_focused_node(&tree, seat))
.map(|(id, _)| id);
for node_id in tree.traverse_pre_order_ids(root).unwrap() { for node_id in tree.traverse_pre_order_ids(root).unwrap() {
if let Some(mut geo) = stack.pop() { if let Some(mut geo) = stack.pop() {
// zoom in windows // zoom in windows
@ -1700,7 +1759,7 @@ where
let node: &Node<Data> = tree.get(&node_id).unwrap(); let node: &Node<Data> = tree.get(&node_id).unwrap();
let data = node.data(); let data = node.data();
let is_potential_group = if let Some(focused) = focused { let is_potential_group = if let Some(focused_id) = focused.as_ref() {
// 1. focused can move into us directly // 1. focused can move into us directly
if let Some(parent) = node.parent() { if let Some(parent) = node.parent() {
let parent_data = tree.get(parent).unwrap().data(); let parent_data = tree.get(parent).unwrap().data();
@ -1710,13 +1769,10 @@ where
.unwrap() .unwrap()
.position(|id| id == &node_id) .position(|id| id == &node_id)
.unwrap(); .unwrap();
if let Some((focused_idx, _focused_id)) = tree if let Some(focused_idx) = tree
.children_ids(parent) .children_ids(parent)
.unwrap() .unwrap()
.enumerate() .position(|id| id == focused_id)
.find(|(_, child_id)| {
tree.get(child_id).unwrap().data().is_mapped(Some(focused))
})
{ {
// only direct neighbors // only direct neighbors
focused_idx.abs_diff(idx) == 1 focused_idx.abs_diff(idx) == 1
@ -1730,14 +1786,11 @@ where
} }
// 2. focused can move out into us // 2. focused can move out into us
else { else {
tree.children_ids(&node_id) tree.children_ids(&node_id).unwrap().any(|child_id| {
.unwrap() tree.children_ids(child_id)
.find(|child_id| { .unwrap()
tree.children(child_id) .any(|child_id| child_id == focused_id)
.unwrap() })
.any(|child| child.data().is_mapped(Some(focused)))
})
.is_some()
} }
} else { } else {
false false
@ -1750,10 +1803,10 @@ where
sizes, sizes,
alive, alive,
} => { } => {
let has_active_child = if let Some(focused) = focused { let has_active_child = if let Some(focused_id) = focused.as_ref() {
tree.children(&node_id) tree.children_ids(&node_id)
.unwrap() .unwrap()
.any(|child| child.data().is_mapped(Some(focused))) .any(|child_id| child_id == focused_id)
} else { } else {
false false
}; };
@ -1985,7 +2038,7 @@ fn render_new_tree<R>(
renderer: &mut R, renderer: &mut R,
geometries: Option<HashMap<NodeId, Rectangle<i32, Logical>>>, geometries: Option<HashMap<NodeId, Rectangle<i32, Logical>>>,
old_geometries: Option<HashMap<NodeId, Rectangle<i32, Logical>>>, old_geometries: Option<HashMap<NodeId, Rectangle<i32, Logical>>>,
focused: Option<&CosmicMapped>, seat: Option<&Seat<State>>,
output_scale: f64, output_scale: f64,
percentage: f32, percentage: f32,
indicator_thickness: u8, indicator_thickness: u8,
@ -1996,43 +2049,33 @@ where
CosmicMappedRenderElement<R>: RenderElement<R>, CosmicMappedRenderElement<R>: RenderElement<R>,
CosmicWindowRenderElement<R>: RenderElement<R>, CosmicWindowRenderElement<R>: RenderElement<R>,
{ {
let focused = seat
.and_then(|seat| TilingLayout::currently_focused_node(&target_tree, seat))
.map(|(id, _)| id);
if let Some(root) = target_tree.root_node_id() { if let Some(root) = target_tree.root_node_id() {
let old_geometries = old_geometries.unwrap_or_default(); let old_geometries = old_geometries.unwrap_or_default();
let geometries = geometries.unwrap_or_default(); let geometries = geometries.unwrap_or_default();
target_tree target_tree
.traverse_pre_order_ids(root) .traverse_pre_order_ids(root)
.unwrap() .unwrap()
.filter(|node_id| target_tree.get(node_id).unwrap().data().is_mapped(None)) .flat_map(|node_id| {
.map(|node_id| match target_tree.get(&node_id).unwrap().data() { let data = target_tree.get(&node_id).unwrap().data();
Data::Mapped { let (original_geo, scaled_geo) = (data.geometry(), geometries.get(&node_id));
mapped,
last_geometry,
..
} => (mapped, last_geometry, geometries.get(&node_id)),
_ => unreachable!(),
})
.flat_map(|(mapped, original_geo, scaled_geo)| {
let (old_original_geo, old_scaled_geo) = let (old_original_geo, old_scaled_geo) =
if let Some(reference_tree) = reference_tree.as_ref() { if let Some(reference_tree) = reference_tree.as_ref() {
if let Some(root) = reference_tree.root_node_id() { if let Some(root) = reference_tree.root_node_id() {
reference_tree reference_tree
.traverse_pre_order_ids(root) .traverse_pre_order_ids(root)
.unwrap() .unwrap()
.find(|node_id| { .find(|id| &node_id == id)
reference_tree .map(|node_id| {
.get(node_id) (
.unwrap() reference_tree.get(&node_id).unwrap().data().geometry(),
.data() old_geometries.get(&node_id),
.is_mapped(Some(mapped)) )
}) })
.map(
|node_id| match reference_tree.get(&node_id).unwrap().data() {
Data::Mapped { last_geometry, .. } => {
(last_geometry, old_geometries.get(&node_id))
}
_ => unreachable!(),
},
)
} else { } else {
None None
} }
@ -2101,79 +2144,88 @@ where
(new_geo, percentage) (new_geo, percentage)
}; };
let original_location = (original_geo.loc - mapped.geometry().loc) let mut elements = Vec::new();
.to_physical_precise_round(output_scale);
let mut elements = if focused == Some(node_id) {
AsRenderElements::<R>::render_elements::<CosmicMappedRenderElement<R>>(
mapped,
renderer,
original_location,
Scale::from(output_scale),
alpha,
)
.into_iter()
.flat_map(|element| match element {
CosmicMappedRenderElement::Stack(elem) => {
Some(CosmicMappedRenderElement::TiledStack({
let cropped = CropRenderElement::from_element(
elem,
output_scale,
crop_rect.to_physical_precise_round(output_scale),
)?;
let rescaled = RescaleRenderElement::from_element(
cropped,
original_geo.loc.to_physical_precise_round(output_scale),
scale,
);
let relocated = RelocateRenderElement::from_element(
rescaled,
(geo.loc - original_geo.loc)
.to_physical_precise_round(output_scale),
Relocate::Relative,
);
relocated
}))
}
CosmicMappedRenderElement::Window(elem) => {
Some(CosmicMappedRenderElement::TiledWindow({
let cropped = CropRenderElement::from_element(
elem,
output_scale,
crop_rect.to_physical_precise_round(output_scale),
)?;
let rescaled = RescaleRenderElement::from_element(
cropped,
original_geo.loc.to_physical_precise_round(output_scale),
scale,
);
let relocated = RelocateRenderElement::from_element(
rescaled,
(geo.loc - original_geo.loc)
.to_physical_precise_round(output_scale),
Relocate::Relative,
);
relocated
}))
}
x => Some(x),
})
.collect::<Vec<_>>();
if focused == Some(mapped) {
if indicator_thickness > 0 { if indicator_thickness > 0 {
let element = IndicatorShader::focus_element( let element = IndicatorShader::focus_element(
renderer, renderer,
mapped.clone(), match data {
Data::Mapped { mapped, .. } => mapped.clone().into(),
Data::Group { alive, .. } => {
IndicatorKey::Group(Arc::downgrade(alive))
}
},
geo, geo,
indicator_thickness, indicator_thickness,
1.0, 1.0,
FOCUS_INDICATOR_COLOR, FOCUS_INDICATOR_COLOR,
); );
elements.insert(0, element.into()); elements.push(element.into());
} }
} }
if let Data::Mapped { mapped, .. } = data {
let original_location = (original_geo.loc - mapped.geometry().loc)
.to_physical_precise_round(output_scale);
elements.extend(
AsRenderElements::<R>::render_elements::<CosmicMappedRenderElement<R>>(
mapped,
renderer,
original_location,
Scale::from(output_scale),
alpha,
)
.into_iter()
.flat_map(|element| match element {
CosmicMappedRenderElement::Stack(elem) => {
Some(CosmicMappedRenderElement::TiledStack({
let cropped = CropRenderElement::from_element(
elem,
output_scale,
crop_rect.to_physical_precise_round(output_scale),
)?;
let rescaled = RescaleRenderElement::from_element(
cropped,
original_geo.loc.to_physical_precise_round(output_scale),
scale,
);
let relocated = RelocateRenderElement::from_element(
rescaled,
(geo.loc - original_geo.loc)
.to_physical_precise_round(output_scale),
Relocate::Relative,
);
relocated
}))
}
CosmicMappedRenderElement::Window(elem) => {
Some(CosmicMappedRenderElement::TiledWindow({
let cropped = CropRenderElement::from_element(
elem,
output_scale,
crop_rect.to_physical_precise_round(output_scale),
)?;
let rescaled = RescaleRenderElement::from_element(
cropped,
original_geo.loc.to_physical_precise_round(output_scale),
scale,
);
let relocated = RelocateRenderElement::from_element(
rescaled,
(geo.loc - original_geo.loc)
.to_physical_precise_round(output_scale),
Relocate::Relative,
);
relocated
}))
}
x => Some(x),
}),
);
}
elements elements
}) })
.collect() .collect()

View file

@ -594,7 +594,7 @@ impl Workspace {
.render_output::<R>( .render_output::<R>(
renderer, renderer,
output, output,
focused.as_ref(), draw_focus_indicator,
layer_map.non_exclusive_zone(), layer_map.non_exclusive_zone(),
overview.clone(), overview.clone(),
indicator_thickness, indicator_thickness,