From 5bb19f3ca7e75136b4c2a4e647df4b686ced3ac2 Mon Sep 17 00:00:00 2001 From: khyperia <953151+khyperia@users.noreply.github.com> Date: Wed, 24 Dec 2025 15:53:31 +0100 Subject: [PATCH] tree: allow focusing workspace nodes --- src/compositor.rs | 1 + src/ifs/wl_seat.rs | 28 +++++++++++- .../tests/t0007_subsurface/screenshot_1.qoi | Bin 8141 -> 8141 bytes .../tests/t0007_subsurface/screenshot_2.qoi | Bin 7832 -> 7832 bytes src/it/tests/t0029_double_click_float.rs | 1 + .../t0029_double_click_float/screenshot_1.qoi | Bin 10118 -> 7834 bytes .../t0029_double_click_float/screenshot_2.qoi | Bin 7834 -> 10118 bytes .../t0037_toplevel_drag/screenshot_1.qoi | Bin 9072 -> 9069 bytes src/renderer.rs | 10 ++++- src/tasks/connector.rs | 1 + src/tree/output.rs | 41 ++++++++++++------ src/tree/workspace.rs | 16 ++++--- 12 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/compositor.rs b/src/compositor.rs index 8cb3ae8b..0e333e56 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -728,6 +728,7 @@ fn create_dummy_output(state: &Rc) { bar_rect: Default::default(), bar_rect_rel: Default::default(), bar_rect_with_separator: Default::default(), + bar_separator_rect: Default::default(), bar_separator_rect_rel: Default::default(), non_exclusive_rect: Default::default(), render_data: Default::default(), diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 5a96ea29..27ced5da 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -740,7 +740,16 @@ impl WlSeatGlobal { pub fn move_focus(self: &Rc, direction: Direction) { let tl = match self.keyboard_node.get().node_toplevel() { Some(tl) => tl, - _ => return, + _ => { + if let Some(ws) = self.keyboard_node.get().node_into_workspace() + && let Some(target) = self + .state + .find_output_in_direction(&ws.output.get(), direction) + { + target.take_keyboard_navigation_focus(self, direction); + } + return; + } }; if direction == Direction::Down && tl.node_is_container() { tl.node_do_focus(self, direction); @@ -762,6 +771,13 @@ impl WlSeatGlobal { pub fn move_focused(self: &Rc, direction: Direction) { let kb_node = self.keyboard_node.get(); let Some(tl) = kb_node.node_toplevel() else { + if let Some(ws) = self.keyboard_node.get().node_into_workspace() + && let Some(target) = self + .state + .find_output_in_direction(&ws.output.get(), direction) + { + self.state.move_ws_to_output(&ws, &target); + } return; }; let data = tl.tl_data(); @@ -995,7 +1011,15 @@ impl WlSeatGlobal { NodeLayer::Layer0 => handle_layer_shell(&output.layers[0]), NodeLayer::Layer1 => handle_layer_shell(&output.layers[1]), NodeLayer::Output => None, - NodeLayer::Workspace => None, + NodeLayer::Workspace => { + if let Some(ws) = &ws + && ws.container_visible() + { + self.focus_node(ws.clone()); + return; + } + None + } NodeLayer::Tiled => ws .as_ref() .and_then(|w| w.container.get()) diff --git a/src/it/tests/t0007_subsurface/screenshot_1.qoi b/src/it/tests/t0007_subsurface/screenshot_1.qoi index eca5ddef82cd60dbbbd400fd5b1b711540368114..230c0408f7411f5ac77c386a080c686f2646c3c0 100644 GIT binary patch delta 39 mcmX?Wf7X733ZsztUnKDDpOTW&WJ9Lg%vUycY!+eKDGLDNi58sz delta 39 mcmX?Wf7X733Zw8pV`JmLNZ?z4$7DmM+e}xCHj6Orlm!6T2o}iz diff --git a/src/it/tests/t0007_subsurface/screenshot_2.qoi b/src/it/tests/t0007_subsurface/screenshot_2.qoi index ea65888db926c35557a53373bb773265383fa617..722271f61949f939e280e154ee9a514d0b14314c 100644 GIT binary patch delta 39 mcmbPXJHvK^3ZsztUnKDDpOTW&WJ9Lg%vUycY!+c^kpTeF&K3Cp delta 39 mcmbPXJHvK^3Zw8pV`JmLNZ?z4$7DmM+e}xCHj6N|$N&JxO%^2p diff --git a/src/it/tests/t0029_double_click_float.rs b/src/it/tests/t0029_double_click_float.rs index dbce7c08..a2c71b92 100644 --- a/src/it/tests/t0029_double_click_float.rs +++ b/src/it/tests/t0029_double_click_float.rs @@ -18,6 +18,7 @@ async fn test(run: Rc) -> TestResult { win1.set_color(255, 0, 0, 255); win1.map2().await?; run.cfg.set_floating(ds.seat.id(), true)?; + client.sync().await; for i in ["1", "2"] { let (x, y) = win1.tl.server.node_absolute_position().position(); diff --git a/src/it/tests/t0029_double_click_float/screenshot_1.qoi b/src/it/tests/t0029_double_click_float/screenshot_1.qoi index f49edd4d3627d804802ad5f24047b69f68aebc11..dd974ccffda932661558262ad0c65bd5f05f7bed 100644 GIT binary patch delta 68 zcmZqkpJltjl#x+$CVM$^G)Iv7m{ zpb!{M2cS?GEe}T1!Du=Fg}`V!0ENP6c`%v|M$-W(1V+;VC=^D^gOQjHKr#RKFY!|$ esOllSQF1f{MnhmU1V%$(Gz5lU2z+CJ07d}izNMo8 diff --git a/src/it/tests/t0029_double_click_float/screenshot_2.qoi b/src/it/tests/t0029_double_click_float/screenshot_2.qoi index dd974ccffda932661558262ad0c65bd5f05f7bed..f49edd4d3627d804802ad5f24047b69f68aebc11 100644 GIT binary patch literal 10118 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%d81SF9K zM%kkwFd71*Aut*OBPj%){j+!Xf_V6m3532R4WWtW9}$CVM$^G)Iv7m{ zpb!{M2cS?GEe}T1!Du=Fg}`V!0ENP6c`%v|M$-W(1V+;VC=^D^gOQjHKr#RKFY!|$ esOllSQF1f{MnhmU1V%$(Gz5lU2z+CJ07d}izNMo8 delta 68 zcmZqkpJltjl#x+*gI1f2i? delta 17 YcmaFs_Q7p~3M0!uV`JmZs*D~=07A+J9RL6T diff --git a/src/renderer.rs b/src/renderer.rs index 6f7efd46..2c94020f 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -85,6 +85,7 @@ impl Renderer<'_> { } else { render_layer!(output.layers[0]); render_layer!(output.layers[1]); + let ws = output.workspace.get(); if self.state.show_bar.get() { let non_exclusive_rect_rel = output.non_exclusive_rect_rel.get(); let (mut x, mut y) = non_exclusive_rect_rel.translate_inv(x, y); @@ -109,7 +110,12 @@ impl Renderer<'_> { self.base .fill_boxes2(slice::from_ref(&aw.rect), &c, srgb, x, y); } - let c = theme.colors.separator.get(); + let mut c = theme.colors.separator.get(); + if let Some(ws) = &ws + && ws.seat_state.is_active() + { + c = theme.colors.focused_title_background.get(); + } self.base .fill_boxes2(slice::from_ref(&rd.bar_separator), &c, srgb, x, y); let c = theme.colors.unfocused_title_background.get(); @@ -172,7 +178,7 @@ impl Renderer<'_> { } } } - if let Some(ws) = output.workspace.get() { + if let Some(ws) = &ws { let ws_rect = output.workspace_rect_rel.get(); let (x, y) = ws_rect.translate_inv(x, y); self.render_workspace(&ws, x, y); diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 4b24cba5..85753771 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -235,6 +235,7 @@ impl ConnectorHandler { bar_rect: Default::default(), bar_rect_rel: Default::default(), bar_rect_with_separator: Default::default(), + bar_separator_rect: Default::default(), bar_separator_rect_rel: Default::default(), render_data: Default::default(), state: self.state.clone(), diff --git a/src/tree/output.rs b/src/tree/output.rs index ba78da11..29027687 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -96,6 +96,7 @@ pub struct OutputNode { pub bar_rect: Cell, pub bar_rect_rel: Cell, pub bar_rect_with_separator: Cell, + pub bar_separator_rect: Cell, pub bar_separator_rect_rel: Cell, pub render_data: RefCell, pub state: Rc, @@ -774,11 +775,11 @@ impl OutputNode { let mut bar_rect = Rect::default(); let mut bar_rect_rel = Rect::default(); let mut bar_rect_with_separator = Rect::default(); + let mut bar_separator_rect = Rect::default(); let mut bar_separator_rect_rel = Rect::default(); let mut workspace_rect = non_exclusive_rect; let mut workspace_rect_rel = non_exclusive_rect_rel; if self.state.show_bar.get() { - let bar_separator_rect; match self.state.theme.bar_position.get() { BarPosition::Bottom => { workspace_rect = Rect::new_sized_saturating(x1, y1, width, height - bh - bsw); @@ -805,6 +806,7 @@ impl OutputNode { self.bar_rect.set(bar_rect); self.bar_rect_rel.set(bar_rect_rel); self.bar_rect_with_separator.set(bar_rect_with_separator); + self.bar_separator_rect.set(bar_separator_rect); self.bar_separator_rect_rel.set(bar_separator_rect_rel); self.workspace_rect.set(workspace_rect); self.workspace_rect_rel.set(workspace_rect_rel); @@ -1147,20 +1149,13 @@ impl OutputNode { set_layer_visible!(self.layers[3], visible); } - fn button(self: Rc, seat: &Rc, id: PointerType) { + fn bar_button(self: &Rc, seat: &Rc, x: i32, y: i32) -> bool { if !self.state.show_bar.get() { - return; - } - let (x, y) = match self.pointer_positions.get(&id) { - Some(p) => p, - _ => return, - }; - if let PointerType::Seat(s) = id { - self.pointer_down.set(s, (x, y)); + return false; } let bar_rect_rel = self.bar_rect_rel.get(); if bar_rect_rel.not_contains(x, y) { - return; + return false; } let (x, _) = bar_rect_rel.translate(x, y); let ws = 'ws: { @@ -1170,9 +1165,25 @@ impl OutputNode { break 'ws title.ws.clone(); } } - return; + return true; }; - self.state.show_workspace2(Some(seat), &self, &ws); + self.state.show_workspace2(Some(seat), self, &ws); + true + } + + fn button(self: Rc, seat: &Rc, id: PointerType) { + let (x, y) = match self.pointer_positions.get(&id) { + Some(p) => p, + _ => return, + }; + if let PointerType::Seat(s) = id { + self.pointer_down.set(s, (x, y)); + } + if self.bar_button(seat, x, y) { + return; + } + let ws = self.ensure_workspace(); + seat.focus_node(ws); } pub fn update_presentation_type(&self) { @@ -1489,6 +1500,10 @@ impl OutputNode { if c.node_visible() { c.node_do_focus(seat, direction); } + } else { + if ws.node_visible() { + seat.focus_node(ws); + } } } } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index df0e43cf..eb308c08 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -366,17 +366,23 @@ impl Node for WorkspaceNode { seat.focus_node(last); } else if let Some(container) = self.container.get() { container.node_do_focus(seat, direction); - } else if let Some(float) = self + } else if let Some(child) = self .stacked .rev_iter() - .find_map(|node| (*node).clone().node_into_float()) + .filter_map(|node| (*node).clone().node_into_float()) + .find_map(|float| float.child.get()) { - if let Some(child) = float.child.get() { - child.node_do_focus(seat, direction); - } + child.node_do_focus(seat, direction); + } else { + seat.focus_node(self); } } + fn node_active_changed(&self, _active: bool) { + let output = self.output.get(); + self.state.damage(output.bar_separator_rect.get()); + } + fn node_find_tree_at( &self, x: i32,