state: split tree operations
This commit is contained in:
parent
36eb811f25
commit
c4655d4641
2 changed files with 378 additions and 362 deletions
373
src/state/tree_ops.rs
Normal file
373
src/state/tree_ops.rs
Normal file
|
|
@ -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<WlSeatGlobal>>) -> Rc<WorkspaceNode> {
|
||||
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<Self>, node: Rc<dyn ToplevelNode>) {
|
||||
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<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
|
||||
let ws = self.ensure_map_workspace(seat);
|
||||
self.map_tiled_on(node, &ws);
|
||||
}
|
||||
|
||||
pub fn map_tiled_on(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, ws: &Rc<WorkspaceNode>) {
|
||||
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<Self>,
|
||||
node: Rc<dyn ToplevelNode>,
|
||||
mut width: i32,
|
||||
mut height: i32,
|
||||
workspace: &Rc<WorkspaceNode>,
|
||||
abs_pos: Option<(i32, i32)>,
|
||||
) -> Rc<FloatNode> {
|
||||
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<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
|
||||
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<WlSeatGlobal>>,
|
||||
output: &Rc<OutputNode>,
|
||||
ws: &Rc<WorkspaceNode>,
|
||||
) {
|
||||
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<WlSeatGlobal>,
|
||||
name: &str,
|
||||
output: Option<Rc<OutputNode>>,
|
||||
) {
|
||||
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<WorkspaceNode> {
|
||||
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<OutputNode>, 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<TileState> {
|
||||
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<Rc<OutputNode>> {
|
||||
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<OutputNode>) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue