Merge pull request #673 from mahkoh/jorth/move-between-outputs
tree: allow moving focus between containers on different outputs
This commit is contained in:
commit
db7520bffa
5 changed files with 101 additions and 19 deletions
|
|
@ -1607,7 +1607,7 @@ impl ConfigProxyHandler {
|
||||||
let source_output = self.get_output_node(connector)?;
|
let source_output = self.get_output_node(connector)?;
|
||||||
let connector = self
|
let connector = self
|
||||||
.state
|
.state
|
||||||
.find_connector_in_direction(&source_output, direction.into())
|
.find_output_in_direction(&source_output, direction.into())
|
||||||
.map(|o| Connector(o.global.connector.id.raw() as u64))
|
.map(|o| Connector(o.global.connector.id.raw() as u64))
|
||||||
.unwrap_or(Connector(0));
|
.unwrap_or(Connector(0));
|
||||||
self.respond(Response::GetConnectorInDirection { connector });
|
self.respond(Response::GetConnectorInDirection { connector });
|
||||||
|
|
|
||||||
|
|
@ -730,8 +730,16 @@ impl WlSeatGlobal {
|
||||||
};
|
};
|
||||||
if direction == Direction::Down && tl.node_is_container() {
|
if direction == Direction::Down && tl.node_is_container() {
|
||||||
tl.node_do_focus(self, direction);
|
tl.node_do_focus(self, direction);
|
||||||
} else if let Some(p) = tl.tl_data().parent.get() {
|
} else {
|
||||||
if let Some(c) = p.node_into_container() {
|
let data = tl.tl_data();
|
||||||
|
if data.is_fullscreen.get()
|
||||||
|
&& let Some(output) = data.output_opt()
|
||||||
|
&& let Some(target) = self.state.find_output_in_direction(&output, direction)
|
||||||
|
{
|
||||||
|
target.take_keyboard_navigation_focus(self, direction);
|
||||||
|
} else if let Some(p) = data.parent.get()
|
||||||
|
&& let Some(c) = p.node_into_container()
|
||||||
|
{
|
||||||
c.move_focus_from_child(self, tl.deref(), direction);
|
c.move_focus_from_child(self, tl.deref(), direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -739,8 +747,17 @@ impl WlSeatGlobal {
|
||||||
|
|
||||||
pub fn move_focused(self: &Rc<Self>, direction: Direction) {
|
pub fn move_focused(self: &Rc<Self>, direction: Direction) {
|
||||||
let kb_node = self.keyboard_node.get();
|
let kb_node = self.keyboard_node.get();
|
||||||
if let Some(tl) = kb_node.node_toplevel()
|
let Some(tl) = kb_node.node_toplevel() else {
|
||||||
&& let Some(parent) = tl.tl_data().parent.get()
|
return;
|
||||||
|
};
|
||||||
|
let data = tl.tl_data();
|
||||||
|
if data.is_fullscreen.get()
|
||||||
|
&& let Some(output) = data.output_opt()
|
||||||
|
&& let Some(target) = self.state.find_output_in_direction(&output, direction)
|
||||||
|
{
|
||||||
|
let ws = target.ensure_workspace();
|
||||||
|
toplevel_set_workspace(&self.state, tl, &ws);
|
||||||
|
} else if let Some(parent) = data.parent.get()
|
||||||
&& let Some(c) = parent.node_into_container()
|
&& let Some(c) = parent.node_into_container()
|
||||||
{
|
{
|
||||||
c.move_child(tl, direction);
|
c.move_child(tl, direction);
|
||||||
|
|
|
||||||
|
|
@ -1560,11 +1560,15 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_connector_in_direction(
|
pub fn find_output_in_direction(
|
||||||
&self,
|
&self,
|
||||||
source_output: &OutputNode,
|
source_output: &OutputNode,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
) -> Option<Rc<OutputNode>> {
|
) -> Option<Rc<OutputNode>> {
|
||||||
|
if source_output.is_dummy {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let outputs = self.root.outputs.lock();
|
let outputs = self.root.outputs.lock();
|
||||||
|
|
||||||
let ref_box = source_output.global.pos.get();
|
let ref_box = source_output.global.pos.get();
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ use {
|
||||||
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node,
|
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FloatNode, FoundNode, Node,
|
||||||
NodeId, NodeLayerLink, NodeLocation, OutputNode, TddType, TileDragDestination,
|
NodeId, NodeLayerLink, NodeLocation, OutputNode, TddType, TileDragDestination,
|
||||||
ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelType, WorkspaceNode,
|
ToplevelData, ToplevelNode, ToplevelNodeBase, ToplevelType, WorkspaceNode,
|
||||||
default_tile_drag_bounds, toplevel_set_floating, walker::NodeVisitor,
|
default_tile_drag_bounds, toplevel_set_floating, toplevel_set_workspace,
|
||||||
|
walker::NodeVisitor,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
asyncevent::AsyncEvent,
|
asyncevent::AsyncEvent,
|
||||||
|
|
@ -976,6 +977,17 @@ impl ContainerNode {
|
||||||
.and_then(|p| p.node_into_container())
|
.and_then(|p| p.node_into_container())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_neighboring_output(&self, direction: Direction) -> Option<Rc<OutputNode>> {
|
||||||
|
if self.toplevel_data.parent.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if self.toplevel_data.float.is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.state
|
||||||
|
.find_output_in_direction(&self.workspace.get().output.get(), direction)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_focus_from_child(
|
pub fn move_focus_from_child(
|
||||||
self: Rc<Self>,
|
self: Rc<Self>,
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
|
@ -997,10 +1009,17 @@ impl ContainerNode {
|
||||||
ContainerSplit::Vertical => matches!(direction, Direction::Up | Direction::Down),
|
ContainerSplit::Vertical => matches!(direction, Direction::Up | Direction::Down),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !in_line {
|
let focus_in_parent = || {
|
||||||
if let Some(c) = self.parent_container() {
|
if let Some(parent) = self.toplevel_data.parent.get() {
|
||||||
c.move_focus_from_child(seat, self.deref(), direction);
|
if let Some(c) = parent.node_into_container() {
|
||||||
|
c.move_focus_from_child(seat, self.deref(), direction);
|
||||||
|
} else if let Some(output) = self.find_neighboring_output(direction) {
|
||||||
|
output.take_keyboard_navigation_focus(seat, direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if !in_line {
|
||||||
|
focus_in_parent();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let prev = match direction {
|
let prev = match direction {
|
||||||
|
|
@ -1017,9 +1036,7 @@ impl ContainerNode {
|
||||||
let sibling = match sibling {
|
let sibling = match sibling {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => {
|
None => {
|
||||||
if let Some(c) = self.parent_container() {
|
focus_in_parent();
|
||||||
c.move_focus_from_child(seat, self.deref(), direction);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1032,16 +1049,42 @@ impl ContainerNode {
|
||||||
|
|
||||||
//
|
//
|
||||||
pub fn move_child(self: Rc<Self>, child: Rc<dyn ToplevelNode>, direction: Direction) {
|
pub fn move_child(self: Rc<Self>, child: Rc<dyn ToplevelNode>, direction: Direction) {
|
||||||
|
let move_to_neighboring_output = |child: Rc<dyn ToplevelNode>| {
|
||||||
|
let Some(output) = self.find_neighboring_output(direction) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ws = output.ensure_workspace();
|
||||||
|
let mut foci = SmallVec::new();
|
||||||
|
let move_foci = !ws.container_visible();
|
||||||
|
if move_foci {
|
||||||
|
collect_kb_foci2(child.clone(), &mut foci);
|
||||||
|
}
|
||||||
|
if let Some(c) = ws.container.get() {
|
||||||
|
self.clone().cnode_remove_child2(&*child, true);
|
||||||
|
c.insert_child(child, direction);
|
||||||
|
} else {
|
||||||
|
toplevel_set_workspace(&self.state, child, &ws);
|
||||||
|
}
|
||||||
|
if move_foci {
|
||||||
|
for seat in foci {
|
||||||
|
ws.clone().node_do_focus(&seat, Direction::Unspecified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// CASE 1: This is the only child of the container. Replace the container by the child.
|
// CASE 1: This is the only child of the container. Replace the container by the child.
|
||||||
if self.num_children.get() == 1 {
|
if self.num_children.get() == 1 {
|
||||||
if let Some(parent) = self.toplevel_data.parent.get()
|
if let Some(parent) = self.toplevel_data.parent.get()
|
||||||
&& !self.toplevel_data.is_fullscreen.get()
|
&& !self.toplevel_data.is_fullscreen.get()
|
||||||
&& parent.cnode_accepts_child(&*child)
|
|
||||||
{
|
{
|
||||||
parent.cnode_replace_child(self.deref(), child.clone());
|
if parent.cnode_accepts_child(&*child) {
|
||||||
self.toplevel_data.parent.take();
|
parent.cnode_replace_child(self.deref(), child.clone());
|
||||||
self.child_nodes.borrow_mut().clear();
|
self.toplevel_data.parent.take();
|
||||||
self.tl_destroy();
|
self.child_nodes.borrow_mut().clear();
|
||||||
|
self.tl_destroy();
|
||||||
|
} else {
|
||||||
|
move_to_neighboring_output(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1092,7 +1135,10 @@ impl ContainerNode {
|
||||||
}
|
}
|
||||||
let parent = match parent_opt {
|
let parent = match parent_opt {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
_ => return,
|
_ => {
|
||||||
|
move_to_neighboring_output(child);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.cnode_remove_child2(&*child, true);
|
self.cnode_remove_child2(&*child, true);
|
||||||
match prev {
|
match prev {
|
||||||
|
|
|
||||||
|
|
@ -1488,6 +1488,21 @@ impl OutputNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn take_keyboard_navigation_focus(&self, seat: &Rc<WlSeatGlobal>, direction: Direction) {
|
||||||
|
let Some(ws) = self.workspace.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(fs) = ws.fullscreen.get() {
|
||||||
|
if fs.node_visible() {
|
||||||
|
fs.node_do_focus(seat, direction);
|
||||||
|
}
|
||||||
|
} else if let Some(c) = ws.container.get() {
|
||||||
|
if c.node_visible() {
|
||||||
|
c.node_do_focus(seat, direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OutputTitle {
|
pub struct OutputTitle {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue