use { crate::{ backend::KeyState, cursor::KnownCursor, cursor_user::CursorUser, fixed::Fixed, ifs::wl_seat::{ BTN_LEFT, BTN_RIGHT, NodeSeatState, SeatId, WlSeatGlobal, tablet::{TabletTool, TabletToolChanges, TabletToolId}, }, rect::Rect, renderer::Renderer, scale::Scale, state::State, text::TextTexture, tree::{ ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation, OutputNode, PinnedNode, StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode, toplevel_set_floating, walker::NodeVisitor, }, utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState, errorfmt::ErrorFmt, linkedlist::LinkedNode, on_drop_event::OnDropEvent, smallmap::SmallMapMut, }, }, ahash::AHashMap, arrayvec::ArrayVec, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, ops::Deref, rc::Rc, }, }; tree_id!(FloatNodeId); pub struct FloatNode { pub id: FloatNodeId, pub state: Rc, pub visible: Cell, pub position: Cell, pub display_link: RefCell>>>, pub workspace_link: Cell>>>, pub pinned_link: RefCell>>>, pub workspace: CloneCell>, pub location: Cell, pub child: CloneCell>>, pub active: Cell, pub seat_state: NodeSeatState, pub layout_scheduled: Cell, pub render_titles_scheduled: Cell, pub title_rect: Cell, pub title: RefCell, pub title_textures: RefCell>, cursors: RefCell>, pub attention_requested: Cell, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] enum CursorType { Seat(SeatId), TabletTool(TabletToolId), } struct CursorState { cursor: KnownCursor, target: bool, x: i32, y: i32, op_type: OpType, op_active: bool, dist_hor: i32, dist_ver: i32, double_click_state: DoubleClickState, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum OpType { Move, ResizeLeft, ResizeTop, ResizeRight, ResizeBottom, ResizeTopLeft, ResizeTopRight, ResizeBottomLeft, ResizeBottomRight, } pub async fn float_layout(state: Rc) { loop { let node = state.pending_float_layout.pop().await; if node.layout_scheduled.get() { node.perform_layout(); } } } pub async fn float_titles(state: Rc) { loop { let node = state.pending_float_titles.pop().await; if node.render_titles_scheduled.get() { node.render_titles_scheduled.set(false); node.render_title_phase1().triggered().await; node.render_title_phase2(); } } } impl FloatNode { pub fn new( state: &Rc, ws: &Rc, position: Rect, child: Rc, ) -> Rc { let floater = Rc::new(FloatNode { id: state.node_ids.next(), state: state.clone(), visible: Cell::new(ws.float_visible()), position: Cell::new(position), display_link: RefCell::new(None), workspace_link: Cell::new(None), pinned_link: RefCell::new(None), workspace: CloneCell::new(ws.clone()), location: Cell::new(ws.location()), child: CloneCell::new(Some(child.clone())), active: Cell::new(false), seat_state: Default::default(), layout_scheduled: Cell::new(false), render_titles_scheduled: Cell::new(false), title_rect: Default::default(), title: Default::default(), title_textures: Default::default(), cursors: Default::default(), attention_requested: Cell::new(false), }); floater.pull_child_properties(); *floater.display_link.borrow_mut() = Some(state.root.stacked.add_last(floater.clone())); floater .workspace_link .set(Some(ws.stacked.add_last(floater.clone()))); child.tl_set_parent(floater.clone()); child.tl_set_visible(floater.visible.get()); child.tl_restack_popups(); floater.schedule_layout(); if floater.visible.get() { state.damage(position); } if child.tl_data().pinned.get() { floater.toggle_pinned(); } floater } pub fn on_spaces_changed(self: &Rc) { self.schedule_layout(); } pub fn on_colors_changed(self: &Rc) { self.schedule_render_titles(); } pub fn schedule_layout(self: &Rc) { if !self.layout_scheduled.replace(true) { self.state.pending_float_layout.push(self.clone()); } } fn perform_layout(self: &Rc) { let child = match self.child.get() { Some(c) => c, _ => return, }; let pos = self.position.get(); let theme = &self.state.theme; let bw = theme.sizes.border_width.get(); let th = theme.sizes.title_height.get(); let cpos = Rect::new_sized( pos.x1() + bw, pos.y1() + bw + th + 1, (pos.width() - 2 * bw).max(0), (pos.height() - 2 * bw - th - 1).max(0), ) .unwrap(); let tr = Rect::new_sized(bw, bw, (pos.width() - 2 * bw).max(0), th).unwrap(); child.clone().tl_change_extents(&cpos); self.title_rect.set(tr); self.layout_scheduled.set(false); self.schedule_render_titles(); } pub fn schedule_render_titles(self: &Rc) { if !self.render_titles_scheduled.replace(true) { self.state.pending_float_titles.push(self.clone()); } } fn render_title_phase1(&self) -> Rc { let on_completed = Rc::new(OnDropEvent::default()); let theme = &self.state.theme; let tc = match self.active.get() { true => theme.colors.focused_title_text.get(), false => theme.colors.unfocused_title_text.get(), }; let font = theme.title_font(); let title = self.title.borrow_mut(); let ctx = match self.state.render_ctx.get() { Some(c) => c, _ => return on_completed.event(), }; let scales = self.state.scales.lock(); let tr = self.title_rect.get(); let tt = &mut *self.title_textures.borrow_mut(); for (scale, _) in scales.iter() { let tex = tt.get_or_insert_with(*scale, || TextTexture::new(&self.state.cpu_worker, &ctx)); let mut th = tr.height(); let mut scalef = None; let mut width = tr.width(); if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() { width = (width - th).max(0); } if *scale != 1 { let scale = scale.to_f64(); th = (th as f64 * scale).round() as _; width = (width as f64 * scale).round() as _; scalef = Some(scale); } tex.schedule_render( on_completed.clone(), 1, None, width, th, 1, &font, &title, tc, true, false, scalef, ); } on_completed.event() } fn render_title_phase2(&self) { let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let bw = theme.sizes.border_width.get(); let title = self.title.borrow(); let tt = &*self.title_textures.borrow(); for (_, tt) in tt { if let Err(e) = tt.flip() { log::error!("Could not render title {}: {}", title, ErrorFmt(e)); } } let pos = self.position.get(); if self.visible.get() && pos.width() >= 2 * bw { let tr = Rect::new_sized(pos.x1() + bw, pos.y1() + bw, pos.width() - 2 * bw, th).unwrap(); self.state.damage(tr); } } fn pointer_move( self: &Rc, id: CursorType, cursor: &CursorUser, x: Fixed, y: Fixed, target: bool, ) { let x = x.round_down(); let y = y.round_down(); let theme = &self.state.theme; let bw = theme.sizes.border_width.get(); let th = theme.sizes.title_height.get(); let mut seats = self.cursors.borrow_mut(); let seat_state = seats.entry(id).or_insert_with(|| CursorState { cursor: KnownCursor::Default, target, x, y, op_type: OpType::Move, op_active: false, dist_hor: 0, dist_ver: 0, double_click_state: Default::default(), }); seat_state.x = x; seat_state.y = y; let pos = self.position.get(); if seat_state.op_active { let mut x1 = pos.x1(); let mut y1 = pos.y1(); let mut x2 = pos.x2(); let mut y2 = pos.y2(); match seat_state.op_type { OpType::Move => { let dx = x - seat_state.dist_hor; let dy = y - seat_state.dist_ver; x1 += dx; y1 += dy; x2 += dx; y2 += dy; } OpType::ResizeLeft => { x1 += x - seat_state.dist_hor; x1 = x1.min(x2 - 2 * bw); } OpType::ResizeTop => { y1 += y - seat_state.dist_ver; y1 = y1.min(y2 - 2 * bw - th - 1); } OpType::ResizeRight => { x2 += x - pos.width() + seat_state.dist_hor; x2 = x2.max(x1 + 2 * bw); } OpType::ResizeBottom => { y2 += y - pos.height() + seat_state.dist_ver; y2 = y2.max(y1 + 2 * bw + th + 1); } OpType::ResizeTopLeft => { x1 += x - seat_state.dist_hor; y1 += y - seat_state.dist_ver; x1 = x1.min(x2 - 2 * bw); y1 = y1.min(y2 - 2 * bw - th - 1); } OpType::ResizeTopRight => { x2 += x - pos.width() + seat_state.dist_hor; y1 += y - seat_state.dist_ver; x2 = x2.max(x1 + 2 * bw); y1 = y1.min(y2 - 2 * bw - th - 1); } OpType::ResizeBottomLeft => { x1 += x - seat_state.dist_hor; y2 += y - pos.height() + seat_state.dist_ver; x1 = x1.min(x2 - 2 * bw); y2 = y2.max(y1 + 2 * bw + th + 1); } OpType::ResizeBottomRight => { x2 += x - pos.width() + seat_state.dist_hor; y2 += y - pos.height() + seat_state.dist_ver; x2 = x2.max(x1 + 2 * bw); y2 = y2.max(y1 + 2 * bw + th + 1); } } let new_pos = Rect::new(x1, y1, x2, y2).unwrap(); self.position.set(new_pos); if self.visible.get() { self.state.damage(pos); self.state.damage(new_pos); } self.schedule_layout(); return; } let resize_left = x < bw; let resize_right = x >= pos.width() - bw; let resize_top = y < bw; let resize_bottom = y >= pos.height() - bw; let id = 0 | ((resize_left as usize) << 0) | ((resize_right as usize) << 1) | ((resize_top as usize) << 2) | ((resize_bottom as usize) << 3); const OP_TYPES: [OpType; 16] = [ OpType::Move, // 0000 OpType::ResizeLeft, // 0001 OpType::ResizeRight, // 0010 OpType::Move, // 0011 OpType::ResizeTop, // 0100 OpType::ResizeTopLeft, // 0101 OpType::ResizeTopRight, // 0110 OpType::Move, // 0111 OpType::ResizeBottom, // 1000 OpType::ResizeBottomLeft, // 1001 OpType::ResizeBottomRight, // 1010 OpType::Move, // 1011 OpType::Move, // 1100 OpType::Move, // 1101 OpType::Move, // 1110 OpType::Move, // 1111 ]; let op_type = OP_TYPES[id]; let new_cursor = match op_type { OpType::Move => KnownCursor::Default, OpType::ResizeLeft => KnownCursor::EwResize, OpType::ResizeTop => KnownCursor::NsResize, OpType::ResizeRight => KnownCursor::EwResize, OpType::ResizeBottom => KnownCursor::NsResize, OpType::ResizeTopLeft => KnownCursor::NwResize, OpType::ResizeTopRight => KnownCursor::NeResize, OpType::ResizeBottomLeft => KnownCursor::SwResize, OpType::ResizeBottomRight => KnownCursor::SeResize, }; seat_state.op_type = op_type; if new_cursor != mem::replace(&mut seat_state.cursor, new_cursor) { if seat_state.target { cursor.set_known(new_cursor); } } } fn set_workspace_( self: &Rc, ws: &Rc, update_pinned: bool, update_visible: bool, ) { if let Some(c) = self.child.get() { c.tl_set_workspace(ws); } self.workspace_link .set(Some(ws.stacked.add_last(self.clone()))); self.workspace.set(ws.clone()); self.location.set(ws.location()); if update_visible { self.stacked_set_visible(ws.float_visible()); } if update_pinned && let Some(pl) = &*self.pinned_link.borrow_mut() { ws.output.get().pinned.add_last_existing(pl); } } pub fn after_ws_move(self: &Rc, output: &Rc) { if let Some(pinned) = &*self.pinned_link.borrow() { output.pinned.add_last_existing(pinned); } if output.is_dummy { return; } let pos = self.position.get(); let opos = output.global.pos.get(); if pos.intersects(&opos) { return; } let bw = self.state.theme.sizes.border_width.get(); let th = self.state.theme.sizes.title_height.get(); let mut x1 = pos.x1(); let mut x2 = pos.x2(); let mut y1 = pos.y1(); let mut y2 = pos.y2(); const DELTA: i32 = 100; let delta = bw + DELTA; macro_rules! adjust { ($z1:ident, $z2:ident) => { if $z1 > opos.$z2() - delta { $z1 = (opos.$z2() - delta).max(opos.$z1()); $z2 += $z1 - pos.$z1(); } else if $z2 < opos.$z1() + delta { $z2 = (opos.$z1() + delta).min(opos.$z2()); $z1 += $z2 - pos.$z2(); } }; } adjust!(x1, x2); adjust!(y1, y2); if y1 + bw + th <= opos.y1() { y1 = opos.y1(); y2 += y1 - pos.y1(); } let new_pos = Rect::new(x1, y1, x2, y2).unwrap(); self.position.set(new_pos); if self.visible.get() { self.state.damage(pos); self.state.damage(new_pos); } self.schedule_layout(); } fn update_child_title(self: &Rc, title: &str) { let mut t = self.title.borrow_mut(); if t.deref() != title { t.clear(); t.push_str(title); self.schedule_render_titles(); } } fn update_child_active(self: &Rc, active: bool) { if self.active.replace(active) != active { self.schedule_render_titles(); } } fn pull_child_properties(self: &Rc) { let child = match self.child.get() { None => return, Some(c) => c, }; let data = child.tl_data(); let activation_requested = data.wants_attention.get(); self.attention_requested.set(activation_requested); if activation_requested { self.workspace .get() .cnode_child_attention_request_changed(&**self, true); } self.update_child_title(&data.title.borrow()); self.update_child_active(data.active()); } fn discard_child_properties(&self) { if self.attention_requested.get() { self.workspace .get() .cnode_child_attention_request_changed(self, false); } } fn restack(&self) { if let Some(dl) = &*self.display_link.borrow() { if dl.next().is_none() { return; } self.state.damage(self.position.get()); self.state.root.stacked.add_last_existing(&dl); if let Some(tl) = self.child.get() { tl.tl_restack_popups(); } self.state.tree_changed(); } } fn toggle_pinned(self: &Rc) { let pl = &mut *self.pinned_link.borrow_mut(); *pl = if pl.is_some() { None } else { let output = self.workspace.get().output.get(); Some(output.pinned.add_last(self.clone())) }; if let Some(tl) = self.child.get() { tl.tl_data().pinned.set(pl.is_some()); } self.schedule_render_titles(); } fn button( self: Rc, id: CursorType, cursor: &CursorUser, seat: &Rc, time_usec: u64, pressed: bool, ) { let mut cursors = self.cursors.borrow_mut(); let cursor_data = match cursors.get_mut(&id) { Some(s) => s, _ => return, }; let bw = self.state.theme.sizes.border_width.get(); let th = self.state.theme.sizes.title_height.get(); let mut is_icon_press = false; if pressed && cursor_data.x >= bw && cursor_data.y >= bw && cursor_data.y < bw + th { enum FloatIcon { Pin, } let mut icons = ArrayVec::::new(); if self.state.show_pin_icon.get() || self.pinned_link.borrow().is_some() { icons.push(FloatIcon::Pin); } let mut x2 = bw + th; let icon = 'icon: { for icon in icons { if cursor_data.x < x2 { break 'icon Some(icon); } x2 += th; } None }; if let Some(icon) = icon { is_icon_press = true; match icon { FloatIcon::Pin => self.toggle_pinned(), } } } if !cursor_data.op_active { if !pressed { return; } if cursor_data.op_type == OpType::Move && let Some(tl) = self.child.get() { tl.node_do_focus(seat, Direction::Unspecified); } if cursor_data.double_click_state.click( &self.state, time_usec, cursor_data.x, cursor_data.y, ) && cursor_data.op_type == OpType::Move && !is_icon_press && let Some(tl) = self.child.get() { drop(cursors); toplevel_set_floating(&self.state, tl, false); return; } cursor_data.op_active = true; let pos = self.position.get(); match cursor_data.op_type { OpType::Move => { self.restack(); cursor_data.dist_hor = cursor_data.x; cursor_data.dist_ver = cursor_data.y; } OpType::ResizeLeft => cursor_data.dist_hor = cursor_data.x, OpType::ResizeTop => cursor_data.dist_ver = cursor_data.y, OpType::ResizeRight => cursor_data.dist_hor = pos.width() - cursor_data.x, OpType::ResizeBottom => cursor_data.dist_ver = pos.height() - cursor_data.y, OpType::ResizeTopLeft => { cursor_data.dist_hor = cursor_data.x; cursor_data.dist_ver = cursor_data.y; } OpType::ResizeTopRight => { cursor_data.dist_hor = pos.width() - cursor_data.x; cursor_data.dist_ver = cursor_data.y; } OpType::ResizeBottomLeft => { cursor_data.dist_hor = cursor_data.x; cursor_data.dist_ver = pos.height() - cursor_data.y; } OpType::ResizeBottomRight => { cursor_data.dist_hor = pos.width() - cursor_data.x; cursor_data.dist_ver = pos.height() - cursor_data.y; } } } else if !pressed { cursor_data.op_active = false; let ws = cursor.output().ensure_workspace(); self.set_workspace_(&ws, true, true); } } pub fn tile_drag_destination( self: &Rc, source: NodeId, abs_x: i32, abs_y: i32, ) -> Option { let child = self.child.get()?; let theme = &self.state.theme.sizes; let bw = theme.border_width.get(); let th = theme.title_height.get(); let pos = self.position.get(); let body = Rect::new( pos.x1() + bw, pos.y1() + bw + th + 1, pos.x2() - bw, pos.y2() - bw, )?; child.tl_tile_drag_destination(source, None, body, abs_x, abs_y) } } impl Debug for FloatNode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("FloatNode").finish_non_exhaustive() } } impl Node for FloatNode { fn node_id(&self) -> NodeId { self.id.into() } fn node_seat_state(&self) -> &NodeSeatState { &self.seat_state } fn node_visit(self: Rc, visitor: &mut dyn NodeVisitor) { visitor.visit_float(&self); } fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) { if let Some(c) = self.child.get() { c.node_visit(visitor); } } fn node_visible(&self) -> bool { self.visible.get() } fn node_absolute_position(&self) -> Rect { self.position.get() } fn node_output(&self) -> Option> { Some(self.workspace.get().output.get()) } fn node_location(&self) -> Option { Some(self.location.get()) } fn node_layer(&self) -> NodeLayerLink { let Some(l) = self.display_link.borrow().as_ref().map(|l| l.to_ref()) else { return NodeLayerLink::Display; }; NodeLayerLink::Stacked(l) } fn node_child_title_changed(self: Rc, _child: &dyn Node, title: &str) { self.update_child_title(title); } fn node_accepts_focus(&self) -> bool { if let Some(c) = self.child.get() { return c.tl_accepts_keyboard_focus(); } false } fn node_do_focus(self: Rc, seat: &Rc, direction: Direction) { if let Some(c) = self.child.get() { c.node_do_focus(seat, direction); } } 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(); let pos = self.position.get(); if x < bw || x >= pos.width() - bw { return FindTreeResult::AcceptsInput; } if y < bw + th + 1 || y >= pos.height() - bw { return FindTreeResult::AcceptsInput; } let child = match self.child.get() { Some(c) => c, _ => return FindTreeResult::Other, }; let x = x - bw; let y = y - bw - th - 1; tree.push(FoundNode { node: child.clone(), x, y, }); child.node_find_tree_at(x, y, tree, usecase) } fn node_child_active_changed(self: Rc, _child: &dyn Node, active: bool, _depth: u32) { self.update_child_active(active); } fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32, _bounds: Option<&Rect>) { renderer.render_floating(self, x, y) } fn node_make_visible(self: Rc) { if self.visible.get() { return; } self.workspace.get().cnode_make_visible(&*self); } fn node_on_button( self: Rc, seat: &Rc, time_usec: u64, button: u32, state: KeyState, _serial: u64, ) { if button == BTN_RIGHT && state == KeyState::Pressed { self.toggle_pinned(); } if button != BTN_LEFT { return; } self.button( CursorType::Seat(seat.id()), seat.pointer_cursor(), seat, time_usec, state == KeyState::Pressed, ); } fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move( CursorType::Seat(seat.id()), seat.pointer_cursor(), x, y, false, ); } fn node_on_pointer_unfocus(&self, seat: &Rc) { let mut cursors = self.cursors.borrow_mut(); let id = CursorType::Seat(seat.id()); if let Some(seat_state) = cursors.get_mut(&id) { seat_state.target = false; } } fn node_on_pointer_focus(&self, seat: &Rc) { // log::info!("float focus"); let mut cursors = self.cursors.borrow_mut(); let id = CursorType::Seat(seat.id()); if let Some(seat_state) = cursors.get_mut(&id) { seat_state.target = true; seat.pointer_cursor().set_known(seat_state.cursor); } } fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move( CursorType::Seat(seat.id()), seat.pointer_cursor(), x, y, false, ); } fn node_on_tablet_tool_leave(&self, tool: &Rc, _time_usec: u64) { let id = CursorType::TabletTool(tool.id); self.cursors.borrow_mut().remove(&id); } fn node_on_tablet_tool_enter( self: Rc, tool: &Rc, _time_usec: u64, x: Fixed, y: Fixed, ) { tool.cursor().set_known(KnownCursor::Default); self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, true); } fn node_on_tablet_tool_apply_changes( self: Rc, tool: &Rc, time_usec: u64, changes: Option<&TabletToolChanges>, x: Fixed, y: Fixed, ) { self.pointer_move(CursorType::TabletTool(tool.id), tool.cursor(), x, y, false); if let Some(changes) = changes && let Some(pressed) = changes.down { self.button( CursorType::TabletTool(tool.id), tool.cursor(), tool.seat(), time_usec, pressed, ); } } fn node_into_float(self: Rc) -> Option> { Some(self.clone()) } fn node_into_containing_node(self: Rc) -> Option> { Some(self) } fn node_is_float(&self) -> bool { true } } impl ContainingNode for FloatNode { fn cnode_replace_child(self: Rc, _old: &dyn Node, new: Rc) { self.discard_child_properties(); self.child.set(Some(new.clone())); new.tl_set_parent(self.clone()); self.pull_child_properties(); new.tl_set_visible(self.visible.get()); self.schedule_layout(); if self.visible.get() { self.state.damage(self.position.get()); } } fn cnode_remove_child2(self: Rc, _child: &dyn Node, _preserve_focus: bool) { self.discard_child_properties(); self.child.set(None); self.display_link.borrow_mut().take(); self.workspace_link.set(None); self.pinned_link.take(); if self.visible.get() { self.state.damage(self.position.get()); } } fn cnode_accepts_child(&self, _node: &dyn Node) -> bool { true } fn cnode_child_attention_request_changed(self: Rc, _node: &dyn Node, set: bool) { if self.attention_requested.replace(set) != set { self.workspace .get() .cnode_child_attention_request_changed(&*self, set); } } fn cnode_workspace(self: Rc) -> Rc { self.workspace.get() } fn cnode_make_visible(self: Rc, _child: &dyn Node) { self.node_make_visible(); } fn cnode_set_child_position(self: Rc, _child: &dyn Node, x: i32, y: i32) { let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let bw = theme.sizes.border_width.get(); let (x, y) = (x - bw, y - th - bw - 1); let pos = self.position.get(); if pos.position() != (x, y) { let new_pos = pos.at_point(x, y); self.position.set(new_pos); self.state.damage(pos); self.state.damage(new_pos); self.schedule_layout(); } } fn cnode_resize_child( self: Rc, _child: &dyn Node, new_x1: Option, new_y1: Option, new_x2: Option, new_y2: Option, ) { let theme = &self.state.theme; let th = theme.sizes.title_height.get(); let bw = theme.sizes.border_width.get(); let pos = self.position.get(); let mut x1 = pos.x1(); let mut x2 = pos.x2(); let mut y1 = pos.y1(); let mut y2 = pos.y2(); if let Some(v) = new_x1 { x1 = (v - bw).min(x2 - bw - bw); } if let Some(v) = new_x2 { x2 = (v + bw).max(x1 + bw + bw); } if let Some(v) = new_y1 { y1 = (v - th - bw - 1).min(y2 - bw - th - bw - 1); } if let Some(v) = new_y2 { y2 = (v + bw).max(y1 + bw + th + bw + 1); } let new_pos = Rect::new(x1, y1, x2, y2).unwrap(); if new_pos != pos { self.position.set(new_pos); if self.visible.get() { self.state.damage(pos); self.state.damage(new_pos); } self.schedule_layout(); } } fn cnode_pinned(&self) -> bool { self.pinned_link.borrow().is_some() } fn cnode_set_pinned(self: Rc, pinned: bool) { if self.pinned_link.borrow().is_some() == pinned { return; } self.toggle_pinned(); } fn cnode_get_float(self: Rc) -> Option> { Some(self) } } impl StackedNode for FloatNode { fn stacked_set_visible(&self, visible: bool) { if self.visible.replace(visible) != visible { self.state.damage(self.position.get()); } if let Some(child) = self.child.get() { child.tl_set_visible(visible); } self.seat_state.set_visible(self, visible); } fn stacked_has_workspace_link(&self) -> bool { true } } impl PinnedNode for FloatNode { fn set_workspace(self: Rc, workspace: &Rc, update_visible: bool) { self.set_workspace_(workspace, false, update_visible); } } impl dyn Node { pub fn node_restack(self: &Rc) { if let Some(tl) = self.clone().node_toplevel() && let Some(float) = tl.tl_data().float.get() { float.restack(); } } }