diff --git a/src/state.rs b/src/state.rs index afde018e..794a2721 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,6 +3,7 @@ mod connectors; mod idle; mod rendering; mod settings; +mod tree_ops; mod xwayland; pub(crate) use animations::LayoutAnimationCandidate; @@ -89,11 +90,9 @@ use { theme::Theme, time::Time, tree::{ - ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode, - FoundNode, LatchListener, Node, NodeIds, NodeVisitorBase, OutputNode, - PlaceholderNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier, - ToplevelNode, ToplevelNodeBase, VrrMode, WorkspaceDisplayOrder, - WorkspaceNode, WorkspaceNodeId, WsMoveConfig, generic_node_visitor, move_ws_to_output, + ContainerNode, DisplayNode, FloatNode, FoundNode, LatchListener, Node, NodeIds, + NodeVisitorBase, OutputNode, PlaceholderNode, TearingMode, ToplevelIdentifier, + ToplevelNode, VrrMode, WorkspaceDisplayOrder, WorkspaceNode, WorkspaceNodeId, }, udmabuf::UdmabufHolder, utils::{ @@ -129,7 +128,7 @@ use { cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, - ops::{Deref, DerefMut}, + ops::DerefMut, rc::{Rc, Weak}, sync::Arc, time::SystemTime, @@ -564,190 +563,6 @@ impl State { self.globals.remove(self, global) } - pub fn tree_changed(&self) { - // log::info!("state.tree_changed\n{:?}", Backtrace::new()); - if self.tree_changed_sent.replace(true) { - return; - } - let seats = self.globals.seats.lock(); - for seat in seats.values() { - seat.trigger_tree_changed(false); - } - } - - pub fn ensure_map_workspace(&self, seat: Option<&Rc>) -> Rc { - seat.cloned() - .or_else(|| self.seat_queue.last().map(|s| s.deref().clone())) - .map(|s| s.get_fallback_output()) - .or_else(|| self.root.outputs.lock().values().next().cloned()) - .or_else(|| self.dummy_output.get()) - .unwrap() - .ensure_workspace() - } - - pub fn map_tiled(self: &Rc, node: Rc) { - let seat = self.seat_queue.last(); - let animate_new_app_map = node.tl_data().parent.is_none() - && node.tl_data().kind.is_app_window() - && !node.tl_data().visible.get(); - if animate_new_app_map { - self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone())); - } else { - self.do_map_tiled(seat.as_deref(), node.clone()); - } - self.focus_after_map(node, seat.as_deref()); - } - - fn do_map_tiled(self: &Rc, seat: Option<&Rc>, node: Rc) { - let ws = self.ensure_map_workspace(seat); - self.map_tiled_on(node, &ws); - } - - pub fn map_tiled_on(self: &Rc, node: Rc, ws: &Rc) { - if let Some(c) = ws.container.get() { - let la = c.clone().tl_last_active_child(); - let lap = la - .tl_data() - .parent - .get() - .and_then(|n| n.node_into_container()); - if let Some(lap) = lap { - lap.add_child_after(&*la, node); - } else { - c.append_child(node); - } - } else { - let container = ContainerNode::new(self, ws, node, ContainerSplit::Horizontal); - ws.set_container(&container); - } - } - - pub fn map_floating( - self: &Rc, - node: Rc, - mut width: i32, - mut height: i32, - workspace: &Rc, - abs_pos: Option<(i32, i32)>, - ) -> Rc { - width += 2 * self.theme.sizes.border_width.get(); - height += - 2 * self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); - let output = workspace.output.get(); - let output_rect = output.global.pos.get(); - let position = if let Some((mut x1, mut y1)) = abs_pos { - if y1 <= output_rect.y1() { - y1 = output_rect.y1() + 1; - } - if y1 > output_rect.y2() { - y1 = output_rect.y2(); - } - y1 -= self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); - x1 -= self.theme.sizes.border_width.get(); - Rect::new_sized_saturating(x1, y1, width, height) - } else { - let mut x1 = output_rect.x1(); - let mut y1 = output_rect.y1(); - if width < output_rect.width() { - x1 += (output_rect.width() - width) / 2; - } else { - width = output_rect.width(); - } - if height < output_rect.height() { - y1 += (output_rect.height() - height) / 2; - } else { - height = output_rect.height(); - } - Rect::new_sized_saturating(x1, y1, width, height) - }; - let float = FloatNode::new(self, workspace, position, node.clone()); - self.focus_after_map(node, self.seat_queue.last().as_deref()); - float - } - - fn focus_after_map(&self, node: Rc, seat: Option<&Rc>) { - if !node.node_visible() { - return; - } - let Some(seat) = seat else { - return; - }; - if let Some(config) = self.config.get() - && !config.auto_focus(node.tl_data()) - { - return; - } - node.node_do_focus(&seat, Direction::Unspecified); - } - - pub fn show_workspace2( - &self, - seat: Option<&Rc>, - output: &Rc, - ws: &Rc, - ) { - let mut pinned_is_focused = false; - if let Some(seat) = seat { - for pinned in output.pinned.iter() { - pinned - .deref() - .clone() - .node_visit(&mut generic_node_visitor(|node| { - node.node_seat_state().for_each_kb_focus(|s| { - pinned_is_focused |= s.id() == seat.id(); - }); - })); - } - } - let did_change = output.show_workspace(&ws); - if !pinned_is_focused && let Some(seat) = seat { - ws.clone().node_do_focus(seat, Direction::Unspecified); - } - if !did_change { - return; - } - ws.flush_jay_workspaces(); - if !output.is_dummy { - output.schedule_update_render_data(); - self.tree_changed(); - output.workspace_switched.trigger(); - } - } - - pub fn show_workspace( - &self, - seat: &Rc, - name: &str, - output: Option>, - ) { - let output = output.unwrap_or_else(|| seat.get_fallback_output()); - let ws = match output.find_workspace(name) { - Some(ws) => ws, - _ => { - if output.is_dummy { - log::warn!("Not showing workspace because seat is on dummy output"); - return; - } - output.create_workspace(name) - } - }; - self.show_workspace2(Some(seat), &ws.output.get(), &ws); - seat.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn float_map_ws(&self) -> Rc { - if let Some(seat) = self.seat_queue.last() { - let output = seat.get_fallback_output(); - if !output.is_dummy { - return output.ensure_workspace(); - } - } - if let Some(output) = self.root.outputs.lock().values().next().cloned() { - return output.ensure_workspace(); - } - self.dummy_output.get().unwrap().ensure_workspace() - } - pub fn set_status(&self, status: &str) { let status = Rc::new(status.to_owned()); self.status.set(status.clone()); @@ -968,43 +783,6 @@ impl State { seat } - pub fn find_closest_output(&self, mut x: i32, mut y: i32) -> (Rc, i32, i32) { - let mut optimal_dist = i128::MAX; - let mut optimal_output = None; - let outputs = self.root.outputs.lock(); - for output in outputs.values() { - let pos = output.global.pos.get(); - let dist = pos.dist_squared(x, y); - if dist == 0 { - if pos.contains(x, y) { - return (output.clone(), x, y); - } - } - if dist < optimal_dist { - optimal_dist = dist; - optimal_output = Some(output.clone()); - } - } - if let Some(output) = optimal_output { - let pos = output.global.pos.get(); - if pos.is_empty() { - return (output, pos.x1(), pos.y1()); - } - if x < pos.x1() { - x = pos.x1(); - } else if x >= pos.x2() { - x = pos.x2() - 1; - } - if y < pos.y1() { - y = pos.y1(); - } else if y >= pos.y2() { - y = pos.y2() - 1; - } - return (output, x, y); - } - (self.dummy_output.get().unwrap(), 0, 0) - } - pub fn now(&self) -> Time { self.eng.now() } @@ -1021,13 +799,6 @@ impl State { self.eng.now().msec() } - pub fn output_extents_changed(&self) { - self.root.update_extents(); - for seat in self.globals.seats.lock().values() { - seat.output_extents_changed(); - } - } - pub fn update_ei_acceptor(self: &Rc) { self.update_ei_acceptor2(); if let Some(forker) = self.forker.get() { @@ -1119,132 +890,4 @@ impl State { ctx.supports_color_management() } - pub fn initial_tile_state(&self, data: &ToplevelData) -> Option { - self.config.get()?.initial_tile_state(data) - } - - pub fn predict_tiled_body_size(&self) -> Option<(i32, i32)> { - let seat = self.seat_queue.last(); - let ws = self.ensure_map_workspace(seat.as_deref()); - let pos = ws.position.get(); - if pos.is_empty() { - return None; - } - if let Some(c) = ws.container.get() { - let la = c.clone().tl_last_active_child(); - let target = la - .tl_data() - .parent - .get() - .and_then(|n| n.node_into_container()) - .unwrap_or(c); - Some(target.predict_child_body_size()) - } else { - Some((pos.width(), pos.height())) - } - } - - pub fn find_output_in_direction( - &self, - source_output: &OutputNode, - direction: Direction, - ) -> Option> { - if source_output.is_dummy { - return None; - } - - let outputs = self.root.outputs.lock(); - - let ref_box = source_output.global.pos.get(); - let ref_x1 = ref_box.x1(); - let ref_y1 = ref_box.y1(); - let ref_x2 = ref_box.x2(); - let ref_y2 = ref_box.y2(); - - // Use the center of the source output as the reference point (like wlroots) - let (ref_lx, ref_ly) = ref_box.center(); - - // Find the closest output in the given direction using wlroots-style algorithm - let mut min_distance = i64::MAX; - let mut closest_output = None; - - for output in outputs.values() { - if output.id == source_output.id { - continue; - } - - let box_pos = output.global.pos.get(); - let box_x1 = box_pos.x1(); - let box_y1 = box_pos.y1(); - let box_x2 = box_pos.x2(); - let box_y2 = box_pos.y2(); - - // Edge-based direction check (like wlroots) - // Test to make sure this output is in the given direction - let is_in_direction = match direction { - Direction::Left => box_x2 <= ref_x1, - Direction::Right => box_x1 >= ref_x2, - Direction::Up => box_y2 <= ref_y1, - Direction::Down => box_y1 >= ref_y2, - Direction::Unspecified => false, - }; - - if !is_in_direction { - continue; - } - - // Calculate distance from reference point to closest point on this output - // This mimics wlr_box_closest_point + squared Euclidean distance - let closest_x = ref_lx.clamp(box_x1, box_x2); - let closest_y = ref_ly.clamp(box_y1, box_y2); - - let dx = (closest_x - ref_lx) as i64; - let dy = (closest_y - ref_ly) as i64; - let distance = dx * dx + dy * dy; - - if distance < min_distance { - min_distance = distance; - closest_output = Some(output); - } - } - - closest_output.cloned() - } - - pub fn node_at(&self, x: i32, y: i32) -> FoundNode { - let mut found_tree = self.node_at_tree.borrow_mut(); - found_tree.push(FoundNode { - node: self.root.clone(), - x, - y, - }); - self.root - .node_find_tree_at(x, y, &mut found_tree, FindTreeUsecase::None); - let node = found_tree.pop().unwrap(); - found_tree.clear(); - node - } - - pub fn move_ws_to_output(&self, ws: &WorkspaceNode, output: &Rc) { - if ws.is_dummy || output.is_dummy { - return; - } - if ws.output.get().id == output.id { - return; - } - let link = match &*ws.output_link.borrow() { - None => return, - Some(l) => l.to_ref(), - }; - let config = WsMoveConfig { - make_visible_always: false, - make_visible_if_empty: true, - source_is_destroyed: false, - before: None, - }; - move_ws_to_output(&link, &output, config); - ws.desired_output.set(output.global.output_id.clone()); - self.tree_changed(); - } - } diff --git a/src/state/tree_ops.rs b/src/state/tree_ops.rs new file mode 100644 index 00000000..8a8e8f99 --- /dev/null +++ b/src/state/tree_ops.rs @@ -0,0 +1,373 @@ +use { + crate::{ + ifs::wl_seat::WlSeatGlobal, + rect::Rect, + tree::{ + ContainerNode, ContainerSplit, Direction, FindTreeUsecase, FloatNode, FoundNode, Node, + OutputNode, TileState, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode, + WsMoveConfig, generic_node_visitor, move_ws_to_output, + }, + }, + std::{ops::Deref, rc::Rc}, +}; + +use super::State; + +impl State { + pub fn tree_changed(&self) { + // log::info!("state.tree_changed\n{:?}", Backtrace::new()); + if self.tree_changed_sent.replace(true) { + return; + } + let seats = self.globals.seats.lock(); + for seat in seats.values() { + seat.trigger_tree_changed(false); + } + } + + pub fn ensure_map_workspace(&self, seat: Option<&Rc>) -> Rc { + seat.cloned() + .or_else(|| self.seat_queue.last().map(|s| s.deref().clone())) + .map(|s| s.get_fallback_output()) + .or_else(|| self.root.outputs.lock().values().next().cloned()) + .or_else(|| self.dummy_output.get()) + .unwrap() + .ensure_workspace() + } + + pub fn map_tiled(self: &Rc, node: Rc) { + let seat = self.seat_queue.last(); + let animate_new_app_map = node.tl_data().parent.is_none() + && node.tl_data().kind.is_app_window() + && !node.tl_data().visible.get(); + if animate_new_app_map { + self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone())); + } else { + self.do_map_tiled(seat.as_deref(), node.clone()); + } + self.focus_after_map(node, seat.as_deref()); + } + + fn do_map_tiled(self: &Rc, seat: Option<&Rc>, node: Rc) { + let ws = self.ensure_map_workspace(seat); + self.map_tiled_on(node, &ws); + } + + pub fn map_tiled_on(self: &Rc, node: Rc, ws: &Rc) { + if let Some(c) = ws.container.get() { + let la = c.clone().tl_last_active_child(); + let lap = la + .tl_data() + .parent + .get() + .and_then(|n| n.node_into_container()); + if let Some(lap) = lap { + lap.add_child_after(&*la, node); + } else { + c.append_child(node); + } + } else { + let container = ContainerNode::new(self, ws, node, ContainerSplit::Horizontal); + ws.set_container(&container); + } + } + + pub fn map_floating( + self: &Rc, + node: Rc, + mut width: i32, + mut height: i32, + workspace: &Rc, + abs_pos: Option<(i32, i32)>, + ) -> Rc { + width += 2 * self.theme.sizes.border_width.get(); + height += + 2 * self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); + let output = workspace.output.get(); + let output_rect = output.global.pos.get(); + let position = if let Some((mut x1, mut y1)) = abs_pos { + if y1 <= output_rect.y1() { + y1 = output_rect.y1() + 1; + } + if y1 > output_rect.y2() { + y1 = output_rect.y2(); + } + y1 -= self.theme.sizes.border_width.get() + self.theme.title_plus_underline_height(); + x1 -= self.theme.sizes.border_width.get(); + Rect::new_sized_saturating(x1, y1, width, height) + } else { + let mut x1 = output_rect.x1(); + let mut y1 = output_rect.y1(); + if width < output_rect.width() { + x1 += (output_rect.width() - width) / 2; + } else { + width = output_rect.width(); + } + if height < output_rect.height() { + y1 += (output_rect.height() - height) / 2; + } else { + height = output_rect.height(); + } + Rect::new_sized_saturating(x1, y1, width, height) + }; + let float = FloatNode::new(self, workspace, position, node.clone()); + self.focus_after_map(node, self.seat_queue.last().as_deref()); + float + } + + fn focus_after_map(&self, node: Rc, seat: Option<&Rc>) { + if !node.node_visible() { + return; + } + let Some(seat) = seat else { + return; + }; + if let Some(config) = self.config.get() + && !config.auto_focus(node.tl_data()) + { + return; + } + node.node_do_focus(&seat, Direction::Unspecified); + } + + pub fn show_workspace2( + &self, + seat: Option<&Rc>, + output: &Rc, + ws: &Rc, + ) { + let mut pinned_is_focused = false; + if let Some(seat) = seat { + for pinned in output.pinned.iter() { + pinned + .deref() + .clone() + .node_visit(&mut generic_node_visitor(|node| { + node.node_seat_state().for_each_kb_focus(|s| { + pinned_is_focused |= s.id() == seat.id(); + }); + })); + } + } + let did_change = output.show_workspace(&ws); + if !pinned_is_focused && let Some(seat) = seat { + ws.clone().node_do_focus(seat, Direction::Unspecified); + } + if !did_change { + return; + } + ws.flush_jay_workspaces(); + if !output.is_dummy { + output.schedule_update_render_data(); + self.tree_changed(); + output.workspace_switched.trigger(); + } + } + + pub fn show_workspace( + &self, + seat: &Rc, + name: &str, + output: Option>, + ) { + let output = output.unwrap_or_else(|| seat.get_fallback_output()); + let ws = match output.find_workspace(name) { + Some(ws) => ws, + _ => { + if output.is_dummy { + log::warn!("Not showing workspace because seat is on dummy output"); + return; + } + output.create_workspace(name) + } + }; + self.show_workspace2(Some(seat), &ws.output.get(), &ws); + seat.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn float_map_ws(&self) -> Rc { + if let Some(seat) = self.seat_queue.last() { + let output = seat.get_fallback_output(); + if !output.is_dummy { + return output.ensure_workspace(); + } + } + if let Some(output) = self.root.outputs.lock().values().next().cloned() { + return output.ensure_workspace(); + } + self.dummy_output.get().unwrap().ensure_workspace() + } + + pub fn output_extents_changed(&self) { + self.root.update_extents(); + for seat in self.globals.seats.lock().values() { + seat.output_extents_changed(); + } + } + + pub fn find_closest_output(&self, mut x: i32, mut y: i32) -> (Rc, i32, i32) { + let mut optimal_dist = i128::MAX; + let mut optimal_output = None; + let outputs = self.root.outputs.lock(); + for output in outputs.values() { + let pos = output.global.pos.get(); + let dist = pos.dist_squared(x, y); + if dist == 0 { + if pos.contains(x, y) { + return (output.clone(), x, y); + } + } + if dist < optimal_dist { + optimal_dist = dist; + optimal_output = Some(output.clone()); + } + } + if let Some(output) = optimal_output { + let pos = output.global.pos.get(); + if pos.is_empty() { + return (output, pos.x1(), pos.y1()); + } + if x < pos.x1() { + x = pos.x1(); + } else if x >= pos.x2() { + x = pos.x2() - 1; + } + if y < pos.y1() { + y = pos.y1(); + } else if y >= pos.y2() { + y = pos.y2() - 1; + } + return (output, x, y); + } + (self.dummy_output.get().unwrap(), 0, 0) + } + + pub fn initial_tile_state(&self, data: &ToplevelData) -> Option { + self.config.get()?.initial_tile_state(data) + } + + pub fn predict_tiled_body_size(&self) -> Option<(i32, i32)> { + let seat = self.seat_queue.last(); + let ws = self.ensure_map_workspace(seat.as_deref()); + let pos = ws.position.get(); + if pos.is_empty() { + return None; + } + if let Some(c) = ws.container.get() { + let la = c.clone().tl_last_active_child(); + let target = la + .tl_data() + .parent + .get() + .and_then(|n| n.node_into_container()) + .unwrap_or(c); + Some(target.predict_child_body_size()) + } else { + Some((pos.width(), pos.height())) + } + } + + pub fn find_output_in_direction( + &self, + source_output: &OutputNode, + direction: Direction, + ) -> Option> { + if source_output.is_dummy { + return None; + } + + let outputs = self.root.outputs.lock(); + + let ref_box = source_output.global.pos.get(); + let ref_x1 = ref_box.x1(); + let ref_y1 = ref_box.y1(); + let ref_x2 = ref_box.x2(); + let ref_y2 = ref_box.y2(); + + // Use the center of the source output as the reference point (like wlroots) + let (ref_lx, ref_ly) = ref_box.center(); + + // Find the closest output in the given direction using wlroots-style algorithm + let mut min_distance = i64::MAX; + let mut closest_output = None; + + for output in outputs.values() { + if output.id == source_output.id { + continue; + } + + let box_pos = output.global.pos.get(); + let box_x1 = box_pos.x1(); + let box_y1 = box_pos.y1(); + let box_x2 = box_pos.x2(); + let box_y2 = box_pos.y2(); + + // Edge-based direction check (like wlroots) + // Test to make sure this output is in the given direction + let is_in_direction = match direction { + Direction::Left => box_x2 <= ref_x1, + Direction::Right => box_x1 >= ref_x2, + Direction::Up => box_y2 <= ref_y1, + Direction::Down => box_y1 >= ref_y2, + Direction::Unspecified => false, + }; + + if !is_in_direction { + continue; + } + + // Calculate distance from reference point to closest point on this output + // This mimics wlr_box_closest_point + squared Euclidean distance + let closest_x = ref_lx.clamp(box_x1, box_x2); + let closest_y = ref_ly.clamp(box_y1, box_y2); + + let dx = (closest_x - ref_lx) as i64; + let dy = (closest_y - ref_ly) as i64; + let distance = dx * dx + dy * dy; + + if distance < min_distance { + min_distance = distance; + closest_output = Some(output); + } + } + + closest_output.cloned() + } + + pub fn node_at(&self, x: i32, y: i32) -> FoundNode { + let mut found_tree = self.node_at_tree.borrow_mut(); + found_tree.push(FoundNode { + node: self.root.clone(), + x, + y, + }); + self.root + .node_find_tree_at(x, y, &mut found_tree, FindTreeUsecase::None); + let node = found_tree.pop().unwrap(); + found_tree.clear(); + node + } + + pub fn move_ws_to_output(&self, ws: &WorkspaceNode, output: &Rc) { + if ws.is_dummy || output.is_dummy { + return; + } + if ws.output.get().id == output.id { + return; + } + let link = match &*ws.output_link.borrow() { + None => return, + Some(l) => l.to_ref(), + }; + let config = WsMoveConfig { + make_visible_always: false, + make_visible_if_empty: true, + source_is_destroyed: false, + before: None, + }; + move_ws_to_output(&link, &output, config); + ws.desired_output.set(output.global.output_id.clone()); + self.tree_changed(); + } + +}