From 17a0dfed5ec9922c11fb165f7f535bc8c39273d0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 18 Apr 2024 13:43:44 +0200 Subject: [PATCH] seat: add framework to select toplevels --- jay-config/src/theme.rs | 4 + src/config/handler.rs | 1 + src/ifs/wl_seat.rs | 8 +- src/ifs/wl_seat/pointer_owner.rs | 332 ++++++++++++++---- .../wl_surface/ext_session_lock_surface_v1.rs | 10 +- src/ifs/wl_surface/x_surface/xwindow.rs | 25 +- src/ifs/wl_surface/xdg_surface/xdg_popup.rs | 16 +- .../wl_surface/xdg_surface/xdg_toplevel.rs | 26 +- src/ifs/wl_surface/zwlr_layer_surface_v1.rs | 10 +- src/it/tests.rs | 2 + src/it/tests/t0042_toplevel_select.rs | 68 ++++ .../t0042_toplevel_select/screenshot_1.qoi | Bin 0 -> 10802 bytes .../t0042_toplevel_select/screenshot_2.qoi | Bin 0 -> 10800 bytes .../t0042_toplevel_select/screenshot_3.qoi | Bin 0 -> 10800 bytes .../t0042_toplevel_select/screenshot_4.qoi | Bin 0 -> 9671 bytes src/renderer.rs | 44 ++- src/theme.rs | 13 +- src/tree.rs | 19 +- src/tree/container.rs | 22 +- src/tree/display.rs | 13 +- src/tree/float.rs | 14 +- src/tree/output.rs | 45 ++- src/tree/placeholder.rs | 24 +- src/tree/toplevel.rs | 5 + src/tree/workspace.rs | 14 +- toml-config/src/config.rs | 1 + toml-config/src/config/parsers/theme.rs | 3 + toml-config/src/lib.rs | 1 + toml-spec/spec/spec.generated.json | 4 + toml-spec/spec/spec.generated.md | 6 + toml-spec/spec/spec.yaml | 4 + 31 files changed, 603 insertions(+), 131 deletions(-) create mode 100644 src/it/tests/t0042_toplevel_select.rs create mode 100644 src/it/tests/t0042_toplevel_select/screenshot_1.qoi create mode 100644 src/it/tests/t0042_toplevel_select/screenshot_2.qoi create mode 100644 src/it/tests/t0042_toplevel_select/screenshot_3.qoi create mode 100644 src/it/tests/t0042_toplevel_select/screenshot_4.qoi diff --git a/jay-config/src/theme.rs b/jay-config/src/theme.rs index 3cbc2331..38e3a19e 100644 --- a/jay-config/src/theme.rs +++ b/jay-config/src/theme.rs @@ -257,6 +257,10 @@ pub mod colors { /// /// Default: `#23092c`. const 14 => ATTENTION_REQUESTED_BACKGROUND_COLOR, + /// Color used to highlight parts of the UI. + /// + /// Default: `#9d28c67f`. + const 15 => HIGHLIGHT_COLOR, } /// Sets the color of GUI element. diff --git a/src/config/handler.rs b/src/config/handler.rs index c778d0a0..b1f2f263 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1430,6 +1430,7 @@ impl ConfigProxyHandler { FOCUSED_INACTIVE_TITLE_TEXT_COLOR => &colors.focused_inactive_title_text, BAR_STATUS_TEXT_COLOR => &colors.bar_text, ATTENTION_REQUESTED_BACKGROUND_COLOR => &colors.attention_requested_background, + HIGHLIGHT_COLOR => &colors.highlight, _ => return Err(CphError::UnknownColor(colorable.0)), }; Ok(colorable) diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 029f082a..56f90451 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -11,7 +11,6 @@ pub mod zwp_relative_pointer_v1; pub mod zwp_virtual_keyboard_manager_v1; pub mod zwp_virtual_keyboard_v1; -pub use event_handling::NodeSeatState; use { crate::{ async_engine::SpawnedFuture, @@ -84,6 +83,7 @@ use { thiserror::Error, uapi::OwnedFd, }; +pub use {event_handling::NodeSeatState, pointer_owner::ToplevelSelector}; pub const POINTER: u32 = 1; const KEYBOARD: u32 = 2; @@ -94,6 +94,7 @@ const TOUCH: u32 = 4; const MISSING_CAPABILITY: u32 = 0; pub const BTN_LEFT: u32 = 0x110; +pub const BTN_RIGHT: u32 = 0x111; pub const SEAT_NAME_SINCE: Version = Version(2); @@ -1151,6 +1152,11 @@ impl WlSeatGlobal { pub fn set_forward(&self, forward: bool) { self.forward.set(forward); } + + #[allow(dead_code)] + pub fn select_toplevel(self: &Rc, selector: impl ToplevelSelector) { + self.pointer_owner.select_toplevel(self, selector); + } } global_base!(WlSeatGlobal, WlSeat, WlSeatError); diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index 7d4490c2..f2c8fe9f 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -1,35 +1,46 @@ use { crate::{ backend::{AxisSource, KeyState, ScrollAxis, AXIS_120}, + cursor::KnownCursor, fixed::Fixed, ifs::{ ipc, ipc::wl_data_source::WlDataSource, wl_seat::{ - wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal, - CHANGE_CURSOR_MOVED, + wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal, BTN_LEFT, + BTN_RIGHT, CHANGE_CURSOR_MOVED, }, wl_surface::WlSurface, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, state::DeviceHandlerData, - tree::{FoundNode, Node}, + tree::{FindTreeUsecase, FoundNode, Node, ToplevelNode}, utils::{clonecell::CloneCell, smallmap::SmallMap}, }, - std::{cell::Cell, rc::Rc}, + std::{ + cell::Cell, + rc::{Rc, Weak}, + }, }; pub struct PointerOwnerHolder { - default: Rc, + default: Rc>, owner: CloneCell>, pending_scroll: PendingScroll, } +pub trait ToplevelSelector: 'static { + fn set(&self, toplevel: Rc); +} + impl Default for PointerOwnerHolder { fn default() -> Self { + let default = Rc::new(SimplePointerOwner { + usecase: DefaultPointerUsecase, + }); Self { - default: Rc::new(DefaultPointerOwner), - owner: CloneCell::new(Rc::new(DefaultPointerOwner)), + default: default.clone(), + owner: CloneCell::new(default.clone()), pending_scroll: Default::default(), } } @@ -145,6 +156,20 @@ impl PointerOwnerHolder { seat.pointer_owner.owner.set(self.default.clone()); seat.changes.or_assign(CHANGE_CURSOR_MOVED); } + + pub fn select_toplevel(&self, seat: &Rc, selector: impl ToplevelSelector) { + self.revert_to_default(seat); + let usecase = Rc::new(SelectToplevelUsecase { + seat: Rc::downgrade(seat), + selector, + latest: Default::default(), + }); + if let Some(node) = seat.pointer_stack.borrow().last() { + usecase.node_focus(seat, node); + } + self.owner.set(Rc::new(SimplePointerOwner { usecase })); + seat.trigger_tree_changed(); + } } trait PointerOwner { @@ -167,9 +192,12 @@ trait PointerOwner { fn remove_dnd_icon(&self); } -struct DefaultPointerOwner; +struct SimplePointerOwner { + usecase: T, +} -struct GrabPointerOwner { +struct SimpleGrabPointerOwner { + usecase: T, buttons: SmallMap, node: Rc, serial: u32, @@ -184,7 +212,16 @@ struct DndPointerOwner { pos_y: Cell, } -impl PointerOwner for DefaultPointerOwner { +#[derive(Copy, Clone)] +struct DefaultPointerUsecase; + +struct SelectToplevelUsecase { + seat: Weak, + latest: CloneCell>>, + selector: S, +} + +impl PointerOwner for SimplePointerOwner { fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: KeyState) { if state != KeyState::Pressed { return; @@ -193,12 +230,18 @@ impl PointerOwner for DefaultPointerOwner { Some(n) => n, _ => return, }; + if self.usecase.default_button(self, seat, button, &pn) { + return; + } let serial = seat.state.next_serial(pn.node_client().as_deref()); - seat.pointer_owner.owner.set(Rc::new(GrabPointerOwner { - buttons: SmallMap::new_with(button, ()), - node: pn.clone(), - serial, - })); + seat.pointer_owner + .owner + .set(Rc::new(SimpleGrabPointerOwner { + usecase: self.usecase.clone(), + buttons: SmallMap::new_with(button, ()), + node: pn.clone(), + serial, + })); pn.node_seat_state().add_pointer_grab(seat); pn.node_on_button(seat, time_usec, button, state, serial); } @@ -220,7 +263,7 @@ impl PointerOwner for DefaultPointerOwner { }); seat.state .root - .node_find_tree_at(x_int, y_int, &mut found_tree); + .node_find_tree_at(x_int, y_int, &mut found_tree, T::FIND_TREE_USECASE); let mut divergence = found_tree.len().min(stack.len()); for (i, (found, stack)) in found_tree.iter().zip(stack.iter()).enumerate() { if found.node.node_id() != stack.node_id() { @@ -266,6 +309,7 @@ impl PointerOwner for DefaultPointerOwner { } if let Some(node) = stack.last() { node.node_on_pointer_focus(seat); + self.usecase.node_focus(seat, node); } } found_tree.clear(); @@ -289,8 +333,12 @@ impl PointerOwner for DefaultPointerOwner { seat.dropped_dnd.borrow_mut().take(); } - fn revert_to_default(&self, _seat: &Rc) { - // nothing + fn revert_to_default(&self, seat: &Rc) { + if !T::IS_DEFAULT { + seat.pointer_owner.set_default_pointer_owner(seat); + seat.trigger_tree_changed(); + seat.state.damage(); + } } fn dnd_target_removed(&self, seat: &Rc) { @@ -310,7 +358,7 @@ impl PointerOwner for DefaultPointerOwner { } } -impl PointerOwner for GrabPointerOwner { +impl PointerOwner for SimpleGrabPointerOwner { fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: KeyState) { match state { KeyState::Released => { @@ -318,7 +366,7 @@ impl PointerOwner for GrabPointerOwner { if self.buttons.is_empty() { self.node.node_seat_state().remove_pointer_grab(seat); // log::info!("button"); - seat.pointer_owner.set_default_pointer_owner(seat); + self.usecase.release_grab(seat); seat.tree_changed.trigger(); } } @@ -354,57 +402,8 @@ impl PointerOwner for GrabPointerOwner { icon: Option>, serial: u32, ) -> Result<(), WlSeatError> { - let button = match self.buttons.iter().next() { - Some((b, _)) => b, - None => return Ok(()), - }; - if self.buttons.len() != 1 { - return Ok(()); - } - if serial != self.serial { - return Ok(()); - } - if self.node.node_id() != origin.node_id { - return Ok(()); - } - if let Some(icon) = &icon { - icon.set_dnd_icon_seat(seat.id, Some(seat)); - } - if let Some(new) = &src { - ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?; - if let Some(drag) = new.toplevel_drag.get() { - drag.start_drag(); - } - } - *seat.dropped_dnd.borrow_mut() = None; - let pointer_owner = Rc::new(DndPointerOwner { - button, - dnd: Dnd { - seat: seat.clone(), - client: origin.client.clone(), - src, - }, - target: CloneCell::new(seat.state.root.clone()), - icon: CloneCell::new(icon), - pos_x: Cell::new(Fixed::from_int(0)), - pos_y: Cell::new(Fixed::from_int(0)), - }); - { - let mut stack = seat.pointer_stack.borrow_mut(); - for node in stack.drain(1..).rev() { - node.node_on_leave(seat); - node.node_seat_state().leave(seat); - } - } - self.node.node_seat_state().remove_pointer_grab(seat); - // { - // let old = seat.keyboard_node.set(seat.state.root.clone()); - // old.seat_state().unfocus(seat); - // old.unfocus(seat); - // } - seat.pointer_owner.owner.set(pointer_owner.clone()); - pointer_owner.apply_changes(seat); - Ok(()) + self.usecase + .start_drag(self, seat, origin, src, icon, serial) } fn cancel_dnd(&self, seat: &Rc) { @@ -482,7 +481,7 @@ impl PointerOwner for DndPointerOwner { }); seat.state .root - .node_find_tree_at(x_int, y_int, &mut found_tree); + .node_find_tree_at(x_int, y_int, &mut found_tree, FindTreeUsecase::None); let FoundNode { node, x, y } = found_tree.pop().unwrap(); found_tree.clear(); (node, x, y) @@ -562,3 +561,192 @@ impl PointerOwner for DndPointerOwner { self.icon.set(None); } } + +trait SimplePointerOwnerUsecase: Sized + Clone + 'static { + const FIND_TREE_USECASE: FindTreeUsecase; + const IS_DEFAULT: bool; + + fn default_button( + &self, + spo: &SimplePointerOwner, + seat: &Rc, + button: u32, + pn: &Rc, + ) -> bool; + + fn start_drag( + &self, + grab: &SimpleGrabPointerOwner, + seat: &Rc, + origin: &Rc, + src: Option>, + icon: Option>, + serial: u32, + ) -> Result<(), WlSeatError>; + + fn release_grab(&self, seat: &Rc); + + fn node_focus(&self, seat: &Rc, node: &Rc); +} + +impl SimplePointerOwnerUsecase for DefaultPointerUsecase { + const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::None; + const IS_DEFAULT: bool = true; + + fn default_button( + &self, + _spo: &SimplePointerOwner, + _seat: &Rc, + _button: u32, + _pn: &Rc, + ) -> bool { + false + } + + fn start_drag( + &self, + grab: &SimpleGrabPointerOwner, + seat: &Rc, + origin: &Rc, + src: Option>, + icon: Option>, + serial: u32, + ) -> Result<(), WlSeatError> { + let button = match grab.buttons.iter().next() { + Some((b, _)) => b, + None => return Ok(()), + }; + if grab.buttons.len() != 1 { + return Ok(()); + } + if serial != grab.serial { + return Ok(()); + } + if grab.node.node_id() != origin.node_id { + return Ok(()); + } + if let Some(icon) = &icon { + icon.set_dnd_icon_seat(seat.id, Some(seat)); + } + if let Some(new) = &src { + ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?; + if let Some(drag) = new.toplevel_drag.get() { + drag.start_drag(); + } + } + *seat.dropped_dnd.borrow_mut() = None; + let pointer_owner = Rc::new(DndPointerOwner { + button, + dnd: Dnd { + seat: seat.clone(), + client: origin.client.clone(), + src, + }, + target: CloneCell::new(seat.state.root.clone()), + icon: CloneCell::new(icon), + pos_x: Cell::new(Fixed::from_int(0)), + pos_y: Cell::new(Fixed::from_int(0)), + }); + { + let mut stack = seat.pointer_stack.borrow_mut(); + for node in stack.drain(1..).rev() { + node.node_on_leave(seat); + node.node_seat_state().leave(seat); + } + } + grab.node.node_seat_state().remove_pointer_grab(seat); + // { + // let old = seat.keyboard_node.set(seat.state.root.clone()); + // old.seat_state().unfocus(seat); + // old.unfocus(seat); + // } + seat.pointer_owner.owner.set(pointer_owner.clone()); + pointer_owner.apply_changes(seat); + Ok(()) + } + + fn release_grab(&self, seat: &Rc) { + seat.pointer_owner.set_default_pointer_owner(seat); + } + + fn node_focus(&self, _seat: &Rc, _node: &Rc) { + // nothing + } +} + +impl SimplePointerOwnerUsecase for Rc> { + const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::SelectToplevel; + const IS_DEFAULT: bool = false; + + fn default_button( + &self, + spo: &SimplePointerOwner, + seat: &Rc, + button: u32, + pn: &Rc, + ) -> bool { + let Some(tl) = pn.clone().node_into_toplevel() else { + return false; + }; + let selected_toplevel = + button == BTN_RIGHT || (button == BTN_LEFT && !tl.tl_admits_children()); + if !selected_toplevel { + return false; + } + self.selector.set(tl); + spo.revert_to_default(seat); + true + } + + fn start_drag( + &self, + _grab: &SimpleGrabPointerOwner, + seat: &Rc, + _origin: &Rc, + src: Option>, + _icon: Option>, + _serial: u32, + ) -> Result<(), WlSeatError> { + if let Some(src) = src { + src.send_cancelled(seat); + } + Ok(()) + } + + fn release_grab(&self, seat: &Rc) { + seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner { + usecase: self.clone(), + })); + seat.changes.or_assign(CHANGE_CURSOR_MOVED); + } + + fn node_focus(&self, seat: &Rc, node: &Rc) { + let mut damage = false; + let tl = node.clone().node_into_toplevel(); + if let Some(tl) = &tl { + tl.tl_data().render_highlight.fetch_add(1); + if !tl.tl_admits_children() { + seat.set_known_cursor(KnownCursor::Pointer); + } + damage = true; + } + if let Some(prev) = self.latest.set(tl) { + prev.tl_data().render_highlight.fetch_sub(1); + damage = true; + } + if damage { + seat.state.damage(); + } + } +} + +impl Drop for SelectToplevelUsecase { + fn drop(&mut self) { + if let Some(prev) = self.latest.take() { + prev.tl_data().render_highlight.fetch_sub(1); + if let Some(seat) = self.seat.upgrade() { + seat.state.damage(); + } + } + } +} diff --git a/src/ifs/wl_surface/ext_session_lock_surface_v1.rs b/src/ifs/wl_surface/ext_session_lock_surface_v1.rs index b708b0db..dcc36b78 100644 --- a/src/ifs/wl_surface/ext_session_lock_surface_v1.rs +++ b/src/ifs/wl_surface/ext_session_lock_surface_v1.rs @@ -9,7 +9,7 @@ use { leaks::Tracker, object::{Object, Version}, rect::Rect, - tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, OutputNode}, + tree::{FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode}, utils::numcell::NumCell, wire::{ext_session_lock_surface_v1::*, ExtSessionLockSurfaceV1Id, WlSurfaceId}, }, @@ -122,7 +122,13 @@ impl Node for ExtSessionLockSurfaceV1 { self.surface.node_absolute_position() } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + _usecase: FindTreeUsecase, + ) -> FindTreeResult { self.surface.find_tree_at_(x, y, tree) } diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index cb06db52..28b8c391 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -11,8 +11,8 @@ use { renderer::Renderer, state::State, tree::{ - Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, StackedNode, - ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, + Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, + StackedNode, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, }, utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode}, wire::WlSurfaceId, @@ -326,7 +326,16 @@ impl Node for Xwindow { self.toplevel_data.update_self_active(self, active); } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { + if usecase == FindTreeUsecase::SelectToplevel { + return FindTreeResult::AcceptsInput; + } let rect = self.x.surface.buffer_abs_pos.get(); if x < rect.width() && y < rect.height() { tree.push(FoundNode { @@ -340,7 +349,7 @@ impl Node for Xwindow { } fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) { - renderer.render_surface(&self.x.surface, x, y, bounds) + renderer.render_xwindow(self, x, y, bounds) } fn node_client(&self) -> Option> { @@ -359,6 +368,10 @@ impl Node for Xwindow { // log::info!("wl-surface focus"); seat.set_known_cursor(KnownCursor::Default); } + + fn node_into_toplevel(self: Rc) -> Option> { + Some(self) + } } impl ToplevelNodeBase for Xwindow { @@ -428,6 +441,10 @@ impl ToplevelNodeBase for Xwindow { fn tl_scanout_surface(&self) -> Option> { Some(self.x.surface.clone()) } + + fn tl_admits_children(&self) -> bool { + false + } } impl StackedNode for Xwindow { diff --git a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs index 56dac416..96bb64a1 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_popup.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_popup.rs @@ -15,7 +15,10 @@ use { object::Object, rect::Rect, renderer::Renderer, - tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, StackedNode, WorkspaceNode}, + tree::{ + FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, StackedNode, + WorkspaceNode, + }, utils::{clonecell::CloneCell, linkedlist::LinkedNode}, wire::{xdg_popup::*, XdgPopupId}, }, @@ -314,7 +317,16 @@ impl Node for XdgPopup { self.xdg.absolute_desired_extents.get() } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { + if usecase == FindTreeUsecase::SelectToplevel { + return FindTreeResult::Other; + } self.xdg.find_tree_at(x, y, tree) } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 54bc9815..48b81111 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -20,8 +20,9 @@ use { renderer::Renderer, state::State, tree::{ - Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, OutputNode, - ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId, WorkspaceNode, + Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, + OutputNode, ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelNodeId, + WorkspaceNode, }, utils::clonecell::CloneCell, wire::{xdg_toplevel::*, XdgToplevelId}, @@ -492,12 +493,21 @@ impl Node for XdgToplevel { self.toplevel_data.update_self_active(self, active); } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { + if usecase == FindTreeUsecase::SelectToplevel { + return FindTreeResult::AcceptsInput; + } self.xdg.find_tree_at(x, y, tree) } fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) { - renderer.render_xdg_surface(&self.xdg, x, y, bounds) + renderer.render_xdg_toplevel(self, x, y, bounds) } fn node_client(&self) -> Option> { @@ -516,6 +526,10 @@ impl Node for XdgToplevel { // log::info!("xdg-toplevel focus"); seat.set_known_cursor(KnownCursor::Default); } + + fn node_into_toplevel(self: Rc) -> Option> { + Some(self) + } } impl ToplevelNodeBase for XdgToplevel { @@ -619,6 +633,10 @@ impl ToplevelNodeBase for XdgToplevel { fn tl_restack_popups(&self) { self.xdg.restack_popups(); } + + fn tl_admits_children(&self) -> bool { + false + } } impl XdgSurfaceExt for XdgToplevel { diff --git a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs index 17d0aa4c..ed231aa9 100644 --- a/src/ifs/wl_surface/zwlr_layer_surface_v1.rs +++ b/src/ifs/wl_surface/zwlr_layer_surface_v1.rs @@ -10,7 +10,7 @@ use { object::Object, rect::Rect, renderer::Renderer, - tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, OutputNode}, + tree::{FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, OutputNode}, utils::{ bitflags::BitflagsExt, cell_ext::CellExt, linkedlist::LinkedNode, numcell::NumCell, option_ext::OptionExt, @@ -424,7 +424,13 @@ impl Node for ZwlrLayerSurfaceV1 { self.pos.get() } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + _usecase: FindTreeUsecase, + ) -> FindTreeResult { self.surface.find_tree_at_(x, y, tree) } diff --git a/src/it/tests.rs b/src/it/tests.rs index acc40f31..c32fe489 100644 --- a/src/it/tests.rs +++ b/src/it/tests.rs @@ -73,6 +73,7 @@ mod t0038_subsurface_parent_state; mod t0039_alpha_modifier; mod t0040_virtual_keyboard; mod t0041_input_method; +mod t0042_toplevel_select; pub trait TestCase: Sync { fn name(&self) -> &'static str; @@ -133,5 +134,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> { t0039_alpha_modifier, t0040_virtual_keyboard, t0041_input_method, + t0042_toplevel_select, } } diff --git a/src/it/tests/t0042_toplevel_select.rs b/src/it/tests/t0042_toplevel_select.rs new file mode 100644 index 00000000..332e60e4 --- /dev/null +++ b/src/it/tests/t0042_toplevel_select.rs @@ -0,0 +1,68 @@ +use { + crate::{ + ifs::wl_seat::{ToplevelSelector, BTN_LEFT}, + it::{test_error::TestResult, testrun::TestRun}, + tree::{Node, ToplevelNode}, + utils::clonecell::CloneCell, + }, + std::rc::Rc, +}; + +testcase!(); + +async fn test(run: Rc) -> TestResult { + let ds = run.create_default_setup().await?; + + let client = run.create_client().await?; + let win1 = client.create_window().await?; + win1.map2().await?; + let win2 = client.create_window().await?; + win2.map2().await?; + client.sync().await; + + let win1pos = win1.tl.server.node_absolute_position().position(); + let win2pos = win2.tl.server.node_absolute_position().position(); + ds.mouse.abs( + &ds.connector, + win1pos.0 as f64 + 2.0, + win1pos.1 as f64 + 2.0, + ); + run.sync().await; + + struct Selector(CloneCell>>); + impl ToplevelSelector for Rc { + fn set(&self, toplevel: Rc) { + self.0.set(Some(toplevel)); + } + } + let selector = Rc::new(Selector(Default::default())); + ds.seat.select_toplevel(selector.clone()); + + client.compare_screenshot("1", false).await?; + + ds.mouse.abs( + &ds.connector, + win2pos.0 as f64 + 2.0, + win2pos.1 as f64 + 2.0, + ); + run.sync().await; + + client.compare_screenshot("2", false).await?; + + ds.kb.press(1); + run.sync().await; + tassert!(selector.0.get().is_none()); + + ds.seat.select_toplevel(selector.clone()); + + client.compare_screenshot("3", false).await?; + + ds.mouse.click(BTN_LEFT); + + client.compare_screenshot("4", false).await?; + + let tl = selector.0.get().expect("no toplevel selected"); + tassert_eq!(tl.node_id(), win2.tl.server.node_id); + + Ok(()) +} diff --git a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi new file mode 100644 index 0000000000000000000000000000000000000000..05d1a952067a4669ebeeaa330f19f67fb1c0ced8 GIT binary patch literal 10802 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN$au-i?3u z?p}xfDJdyI`7m+mLs0cMMn(SKK!k=7G@PUl!C3$NM3Uix&{THAVpQaY^r6wv_&b^s z{(`d4Xi6AO34cdZ0<=~btsO?o1aMLqtsO?o1aLwatsO>dhtW3DXf7Dd1*5rOG#8BK zg3(+snhQpA!DucR%>|>mU^Ewu=7P~&Fq#WSbHQjX7|jKvxnMLGjOK#TTripoMsvYv PE*Q-Pq~rny2w(&NMn=7F literal 0 HcmV?d00001 diff --git a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi new file mode 100644 index 0000000000000000000000000000000000000000..61f8ac6211141e6067f1e3ac6096a13f037baaef GIT binary patch literal 10800 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%XB5blkC z_U>MX#GxFhnj6xiB7gthzzhu|XgEn9g0Y~<>W1{8e|{p#P-%pq#i+=Q(a`vNL;BEY zO87gP68?^+1aN^kni57+0yrUzmI;4HQ^IIU7%dM+bHQjX7|jKvxnMLGjOK#TTripo zMsvYvE*Q-Pqq$%-7mVhD(OfW^3r2InXf7Dd1*5rOG#8BKg3(+snhQpA!DucR%>|>m LfP`GY00E2uz0|!T literal 0 HcmV?d00001 diff --git a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi new file mode 100644 index 0000000000000000000000000000000000000000..61f8ac6211141e6067f1e3ac6096a13f037baaef GIT binary patch literal 10800 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%XB5blkC z_U>MX#GxFhnj6xiB7gthzzhu|XgEn9g0Y~<>W1{8e|{p#P-%pq#i+=Q(a`vNL;BEY zO87gP68?^+1aN^kni57+0yrUzmI;4HQ^IIU7%dM+bHQjX7|jKvxnMLGjOK#TTripo zMsvYvE*Q-Pqq$%-7mVhD(OfW^3r2InXf7Dd1*5rOG#8BKg3(+snhQpA!DucR%>|>m LfP`GY00E2uz0|!T literal 0 HcmV?d00001 diff --git a/src/it/tests/t0042_toplevel_select/screenshot_4.qoi b/src/it/tests/t0042_toplevel_select/screenshot_4.qoi new file mode 100644 index 0000000000000000000000000000000000000000..714222f1611628b04d16f12865fff4e6bb6938c6 GIT binary patch literal 9671 zcmXTS&rD-rU{+vYV2WU7_@@zCe*PZ=1H)e=@KpS~DH8YZh~xh=Ha12MfN%XB5blkC z_U>MX#GxFhnj6xiB7gthzzhu|XgEn9g0Y~<>W1_oC<~(E(HZJ%AGelo(A9qv-*f6u{JIdKgU);Di7rM$^M+dVnScFg2PU zM$-d0AwY@I^e~zpph*Etji!gu^Z-r>P+~MajHU-@QUFt<>0vZIfD;0g7)=kO=>eJ) zz|?4Z7)=l0ga9Q*)5B { } } - pub fn render_placeholder(&mut self, placeholder: &PlaceholderNode, x: i32, y: i32) { + pub fn render_placeholder( + &mut self, + placeholder: &PlaceholderNode, + x: i32, + y: i32, + bounds: Option<&Rect>, + ) { let pos = placeholder.tl_data().pos.get(); self.base.fill_boxes( std::slice::from_ref(&pos.at_point(x, y)), @@ -236,6 +244,7 @@ impl Renderer<'_> { ReleaseSync::None, ); } + self.render_tl_aux(placeholder.tl_data(), bounds); } pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) { @@ -302,6 +311,16 @@ impl Renderer<'_> { } } + pub fn render_xwindow(&mut self, tl: &Xwindow, x: i32, y: i32, bounds: Option<&Rect>) { + self.render_surface(&tl.x.surface, x, y, bounds); + self.render_tl_aux(tl.tl_data(), bounds); + } + + pub fn render_xdg_toplevel(&mut self, tl: &XdgToplevel, x: i32, y: i32, bounds: Option<&Rect>) { + self.render_xdg_surface(&tl.xdg, x, y, bounds); + self.render_tl_aux(tl.tl_data(), bounds); + } + pub fn render_xdg_surface( &mut self, xdg: &XdgSurface, @@ -316,6 +335,21 @@ impl Renderer<'_> { self.render_surface(surface, x, y, bounds); } + fn render_tl_aux(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) { + self.render_highlight(tl_data, bounds); + } + + fn render_highlight(&mut self, tl_data: &ToplevelData, bounds: Option<&Rect>) { + if tl_data.render_highlight.get() == 0 { + return; + } + let Some(bounds) = bounds else { + return; + }; + let color = self.state.theme.colors.highlight.get(); + self.base.fill_boxes(slice::from_ref(bounds), &color); + } + pub fn render_surface(&mut self, surface: &WlSurface, x: i32, y: i32, bounds: Option<&Rect>) { let (x, y) = self.base.scale_point(x, y); self.render_surface_scaled(surface, x, y, None, bounds, false); diff --git a/src/theme.rs b/src/theme.rs index f94dc0bd..c03af771 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -137,7 +137,7 @@ impl From for Color { } macro_rules! colors { - ($($name:ident = ($r:expr, $g:expr, $b:expr),)*) => { + ($($name:ident = $colors:tt,)*) => { pub struct ThemeColors { $( pub $name: Cell, @@ -157,12 +157,18 @@ macro_rules! colors { fn default() -> Self { Self { $( - $name: Cell::new(Color::from_rgb($r, $g, $b)), + $name: Cell::new(colors!(@colors $colors)), )* } } } - } + }; + (@colors ($r:expr, $g:expr, $b:expr)) => { + Color::from_rgb($r, $g, $b) + }; + (@colors ($r:expr, $g:expr, $b:expr, $a:expr)) => { + Color::from_rgba_straight($r, $g, $b, $a) + }; } colors! { @@ -180,6 +186,7 @@ colors! { bar_background = (0x00, 0x00, 0x00), bar_text = (0xff, 0xff, 0xff), attention_requested_background = (0x23, 0x09, 0x2c), + highlight = (0x9d, 0x28, 0xc6, 0x7f), } macro_rules! sizes { diff --git a/src/tree.rs b/src/tree.rs index 4946497d..20bde902 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -100,6 +100,12 @@ impl FindTreeResult { } } +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum FindTreeUsecase { + None, + SelectToplevel, +} + pub trait Node: 'static { fn node_id(&self) -> NodeId; fn node_seat_state(&self) -> &NodeSeatState; @@ -122,10 +128,17 @@ pub trait Node: 'static { let _ = active; } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { let _ = x; let _ = y; let _ = tree; + let _ = usecase; FindTreeResult::Other } @@ -296,6 +309,10 @@ pub trait Node: 'static { None } + fn node_into_toplevel(self: Rc) -> Option> { + None + } + // TYPE CHECKERS fn node_is_container(&self) -> bool { diff --git a/src/tree/container.rs b/src/tree/container.rs index cde675cb..7750eb34 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -13,8 +13,8 @@ use { state::State, text::{self, TextTexture}, tree::{ - walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node, - NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, + walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase, + FoundNode, Node, NodeId, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, }, utils::{ clonecell::CloneCell, @@ -1131,7 +1131,13 @@ impl Node for ContainerNode { self.toplevel_data.update_self_active(self, active); } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { let mut recurse = |content: Rect, child: NodeRef| { if content.contains(x, y) { let (x, y) = content.translate(x, y); @@ -1140,7 +1146,7 @@ impl Node for ContainerNode { x, y, }); - child.node.node_find_tree_at(x, y, tree); + child.node.node_find_tree_at(x, y, tree, usecase); } }; if let Some(child) = self.mono_child.get() { @@ -1329,6 +1335,10 @@ impl Node for ContainerNode { fn node_is_container(&self) -> bool { true } + + fn node_into_toplevel(self: Rc) -> Option> { + Some(self) + } } impl ContainingNode for ContainerNode { @@ -1549,6 +1559,10 @@ impl ToplevelNodeBase for ContainerNode { } } } + + fn tl_admits_children(&self) -> bool { + true + } } fn direction_to_split(dir: Direction) -> (ContainerSplit, bool) { diff --git a/src/tree/display.rs b/src/tree/display.rs index e10333fd..0288bc4a 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -7,7 +7,8 @@ use { renderer::Renderer, state::State, tree::{ - walker::NodeVisitor, FindTreeResult, FoundNode, Node, NodeId, OutputNode, StackedNode, + walker::NodeVisitor, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, + OutputNode, StackedNode, }, utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList}, }, @@ -109,7 +110,13 @@ impl Node for DisplayNode { self.extents.get() } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { let outputs = self.outputs.lock(); for output in outputs.values() { let pos = output.global.pos.get(); @@ -120,7 +127,7 @@ impl Node for DisplayNode { x, y, }); - output.node_find_tree_at(x, y, tree); + output.node_find_tree_at(x, y, tree, usecase); break; } } diff --git a/src/tree/float.rs b/src/tree/float.rs index 4b8f1a5c..b9da33dc 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -10,8 +10,8 @@ use { state::State, text::{self, TextTexture}, tree::{ - walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FoundNode, Node, - NodeId, StackedNode, ToplevelNode, WorkspaceNode, + walker::NodeVisitor, ContainingNode, Direction, FindTreeResult, FindTreeUsecase, + FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode, }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, double_click_state::DoubleClickState, @@ -437,7 +437,13 @@ impl Node for FloatNode { self.update_child_title(title); } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let bw = theme.sizes.border_width.get(); @@ -459,7 +465,7 @@ impl Node for FloatNode { x, y, }); - child.node_find_tree_at(x, y, tree) + child.node_find_tree_at(x, y, tree, usecase) } fn node_child_active_changed(self: Rc, _child: &dyn Node, active: bool, _depth: u32) { diff --git a/src/tree/output.rs b/src/tree/output.rs index 7f4bf2c0..a76b619a 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -26,7 +26,8 @@ use { state::State, text::{self, TextTexture}, tree::{ - walker::NodeVisitor, Direction, FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode, + walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, + NodeId, WorkspaceNode, }, utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, @@ -470,14 +471,20 @@ impl OutputNode { y: i32, layers: &[u32], tree: &mut Vec, + usecase: FindTreeUsecase, ) -> FindTreeResult { + if usecase == FindTreeUsecase::SelectToplevel { + return FindTreeResult::Other; + } let len = tree.len(); for layer in layers.iter().copied() { for surface in self.layers[layer as usize].rev_iter() { let pos = surface.output_position(); if pos.contains(x, y) { let (x, y) = pos.translate(x, y); - if surface.node_find_tree_at(x, y, tree) == FindTreeResult::AcceptsInput { + if surface.node_find_tree_at(x, y, tree, usecase) + == FindTreeResult::AcceptsInput + { return FindTreeResult::AcceptsInput; } tree.truncate(len); @@ -627,20 +634,28 @@ impl Node for OutputNode { } } - fn node_find_tree_at(&self, x: i32, mut y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + mut y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { if self.state.lock.locked.get() { - if let Some(ls) = self.lock_surface.get() { - tree.push(FoundNode { - node: ls.clone(), - x, - y, - }); - return ls.node_find_tree_at(x, y, tree); + if usecase != FindTreeUsecase::SelectToplevel { + if let Some(ls) = self.lock_surface.get() { + tree.push(FoundNode { + node: ls.clone(), + x, + y, + }); + return ls.node_find_tree_at(x, y, tree, usecase); + } } return FindTreeResult::AcceptsInput; } { - let res = self.find_layer_surface_at(x, y, &[OVERLAY, TOP], tree); + let res = self.find_layer_surface_at(x, y, &[OVERLAY, TOP], tree, usecase); if res.accepts_input() { return res; } @@ -665,7 +680,7 @@ impl Node for OutputNode { x, y, }); - match stacked.node_find_tree_at(x, y, tree) { + match stacked.node_find_tree_at(x, y, tree, usecase) { FindTreeResult::AcceptsInput => { return FindTreeResult::AcceptsInput; } @@ -685,7 +700,7 @@ impl Node for OutputNode { x, y, }); - fs.tl_as_node().node_find_tree_at(x, y, tree) + fs.tl_as_node().node_find_tree_at(x, y, tree, usecase) } else { let bar_height = self.state.theme.sizes.title_height.get() + 1; if y >= bar_height { @@ -697,10 +712,10 @@ impl Node for OutputNode { x, y, }); - ws.node_find_tree_at(x, y, tree); + ws.node_find_tree_at(x, y, tree, usecase); } if tree.len() == len { - self.find_layer_surface_at(x, y, &[BOTTOM, BACKGROUND], tree); + self.find_layer_surface_at(x, y, &[BOTTOM, BACKGROUND], tree, usecase); } } FindTreeResult::AcceptsInput diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index b43a1888..fc8b0d8c 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -10,8 +10,8 @@ use { state::State, text::{self, TextTexture}, tree::{ - Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData, - ToplevelNode, ToplevelNodeBase, + Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitor, + ToplevelData, ToplevelNode, ToplevelNodeBase, }, utils::{errorfmt::ErrorFmt, smallmap::SmallMap}, }, @@ -116,12 +116,18 @@ impl Node for PlaceholderNode { self.toplevel.update_self_active(self, active); } - fn node_find_tree_at(&self, _x: i32, _y: i32, _tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + _x: i32, + _y: i32, + _tree: &mut Vec, + _usecase: FindTreeUsecase, + ) -> FindTreeResult { FindTreeResult::AcceptsInput } - fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, _bounds: Option<&Rect>) { - renderer.render_placeholder(self, x, y); + fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, bounds: Option<&Rect>) { + renderer.render_placeholder(self, x, y, bounds); } fn node_client(&self) -> Option> { @@ -140,6 +146,10 @@ impl Node for PlaceholderNode { fn node_is_placeholder(&self) -> bool { true } + + fn node_into_toplevel(self: Rc) -> Option> { + Some(self) + } } impl ToplevelNodeBase for PlaceholderNode { @@ -173,4 +183,8 @@ impl ToplevelNodeBase for PlaceholderNode { fn tl_last_active_child(self: Rc) -> Rc { self } + + fn tl_admits_children(&self) -> bool { + false + } } diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index c2708227..1ae52f59 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -13,6 +13,7 @@ use { utils::{ clonecell::CloneCell, copyhashmap::CopyHashMap, + numcell::NumCell, smallmap::SmallMap, threshold_counter::ThresholdCounter, toplevel_identifier::{toplevel_identifier, ToplevelIdentifier}, @@ -173,6 +174,8 @@ pub trait ToplevelNodeBase: Node { fn tl_restack_popups(&self) { // nothing } + + fn tl_admits_children(&self) -> bool; } pub struct FullscreenedData { @@ -203,6 +206,7 @@ pub struct ToplevelData { pub identifier: Cell, pub handles: CopyHashMap<(ClientId, ExtForeignToplevelHandleV1Id), Rc>, + pub render_highlight: NumCell, } impl ToplevelData { @@ -229,6 +233,7 @@ impl ToplevelData { app_id: Default::default(), identifier: Cell::new(toplevel_identifier()), handles: Default::default(), + render_highlight: Default::default(), } } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 472db74c..f210c095 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -13,8 +13,8 @@ use { text::TextTexture, tree::{ container::ContainerNode, walker::NodeVisitor, ContainingNode, Direction, - FindTreeResult, FoundNode, Node, NodeId, NodeVisitorBase, OutputNode, StackedNode, - ToplevelNode, + FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeVisitorBase, OutputNode, + StackedNode, ToplevelNode, }, utils::{ clonecell::CloneCell, @@ -224,14 +224,20 @@ impl Node for WorkspaceNode { } } - fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { + fn node_find_tree_at( + &self, + x: i32, + y: i32, + tree: &mut Vec, + usecase: FindTreeUsecase, + ) -> FindTreeResult { if let Some(n) = self.container.get() { tree.push(FoundNode { node: n.clone(), x, y, }); - n.node_find_tree_at(x, y, tree); + n.node_find_tree_at(x, y, tree, usecase); } FindTreeResult::AcceptsInput } diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index b4b047f7..7f7e1a62 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -139,6 +139,7 @@ pub struct Theme { pub separator_color: Option, pub unfocused_title_bg_color: Option, pub unfocused_title_text_color: Option, + pub highlight_color: Option, pub border_width: Option, pub title_height: Option, pub font: Option, diff --git a/toml-config/src/config/parsers/theme.rs b/toml-config/src/config/parsers/theme.rs index 4154cd0f..ee42c6d0 100644 --- a/toml-config/src/config/parsers/theme.rs +++ b/toml-config/src/config/parsers/theme.rs @@ -55,6 +55,7 @@ impl Parser for ThemeParser<'_> { separator_color, unfocused_title_bg_color, unfocused_title_text_color, + highlight_color, border_width, title_height, font, @@ -77,6 +78,7 @@ impl Parser for ThemeParser<'_> { opt(val("separator-color")), opt(val("unfocused-title-bg-color")), opt(val("unfocused-title-text-color")), + opt(val("highlight-color")), recover(opt(s32("border-width"))), recover(opt(s32("title-height"))), recover(opt(str("font"))), @@ -111,6 +113,7 @@ impl Parser for ThemeParser<'_> { separator_color: color!(separator_color), unfocused_title_bg_color: color!(unfocused_title_bg_color), unfocused_title_text_color: color!(unfocused_title_text_color), + highlight_color: color!(highlight_color), border_width: border_width.despan(), title_height: title_height.despan(), font: font.map(|f| f.value.to_string()), diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index b56fe396..33ba0b6d 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -665,6 +665,7 @@ impl State { color!(SEPARATOR_COLOR, separator_color); color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color); color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color); + color!(HIGHLIGHT_COLOR, highlight_color); macro_rules! size { ($sized:ident, $field:ident) => { if let Some(size) = theme.$field { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index fd9672f8..ebf1ba64 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1166,6 +1166,10 @@ "description": "The text color of unfocused titles.", "$ref": "#/$defs/Color" }, + "highlight-color": { + "description": "Color used to highlight parts of the UI.", + "$ref": "#/$defs/Color" + }, "border-width": { "type": "integer", "description": "The width of borders between windows.", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index f0400838..c2e7e11e 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -2472,6 +2472,12 @@ The table has the following fields: The value of this field should be a [Color](#types-Color). +- `highlight-color` (optional): + + Color used to highlight parts of the UI. + + The value of this field should be a [Color](#types-Color). + - `border-width` (optional): The width of borders between windows. diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 2b1ce4f2..4454b080 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -1602,6 +1602,10 @@ Theme: ref: Color required: false description: The text color of unfocused titles. + highlight-color: + ref: Color + required: false + description: Color used to highlight parts of the UI. border-width: kind: number integer_only: true