feat: add alternating autotiling
This commit is contained in:
parent
ce14169d6b
commit
5c2f631fdb
17 changed files with 244 additions and 59 deletions
|
|
@ -3587,6 +3587,11 @@ impl ConfigProxyHandler {
|
|||
ClientMessage::SetAutotile { enabled } => {
|
||||
self.state.theme.autotile_enabled.set(enabled);
|
||||
}
|
||||
ClientMessage::GetAutotile => {
|
||||
self.respond(Response::GetAutotile {
|
||||
enabled: self.state.theme.autotile_enabled.get(),
|
||||
});
|
||||
}
|
||||
ClientMessage::SeatToggleExpand { .. } => {
|
||||
// Removed feature; kept for binary protocol compatibility.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -331,6 +331,10 @@ impl TestConfig {
|
|||
pub fn set_show_titles(&self, show: bool) -> TestResult {
|
||||
self.send(ClientMessage::SetShowTitles { show })
|
||||
}
|
||||
|
||||
pub fn set_autotile(&self, enabled: bool) -> TestResult {
|
||||
self.send(ClientMessage::SetAutotile { enabled })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestConfig {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ mod t0051_pointer_warp;
|
|||
mod t0052_bar;
|
||||
mod t0053_theme;
|
||||
mod t0054_subsurface_already_attached;
|
||||
mod t0055_autotiling;
|
||||
|
||||
pub trait TestCase: Sync {
|
||||
fn name(&self) -> &'static str;
|
||||
|
|
@ -158,5 +159,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> {
|
|||
t0052_bar,
|
||||
t0053_theme,
|
||||
t0054_subsurface_already_attached,
|
||||
t0055_autotiling,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
src/it/tests/t0055_autotiling.rs
Normal file
58
src/it/tests/t0055_autotiling.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{test_error::TestResult, testrun::TestRun},
|
||||
tree::{ContainerSplit, Node, ToplevelNodeBase},
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
testcase!();
|
||||
|
||||
async fn test(run: Rc<TestRun>) -> TestResult {
|
||||
run.backend.install_default()?;
|
||||
run.cfg.set_autotile(true)?;
|
||||
|
||||
let client = run.create_client().await?;
|
||||
|
||||
let win1 = client.create_window().await?;
|
||||
win1.map().await?;
|
||||
let root = win1.tl.container_parent()?;
|
||||
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
|
||||
|
||||
let win2 = client.create_window().await?;
|
||||
win2.map().await?;
|
||||
client.sync().await;
|
||||
|
||||
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
|
||||
tassert_eq!(win1.tl.container_parent()?.node_id(), root.node_id());
|
||||
tassert_eq!(win2.tl.container_parent()?.node_id(), root.node_id());
|
||||
|
||||
let win3 = client.create_window().await?;
|
||||
win3.map().await?;
|
||||
client.sync().await;
|
||||
|
||||
let v_group = win3.tl.container_parent()?;
|
||||
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
|
||||
tassert_eq!(v_group.split.get(), ContainerSplit::Vertical);
|
||||
tassert_eq!(win2.tl.container_parent()?.node_id(), v_group.node_id());
|
||||
|
||||
let win4 = client.create_window().await?;
|
||||
win4.map().await?;
|
||||
client.sync().await;
|
||||
|
||||
let h_group = win4.tl.container_parent()?;
|
||||
tassert_eq!(h_group.split.get(), ContainerSplit::Horizontal);
|
||||
tassert_eq!(win3.tl.container_parent()?.node_id(), h_group.node_id());
|
||||
let h_parent = match h_group
|
||||
.tl_data()
|
||||
.parent
|
||||
.get()
|
||||
.and_then(|p| p.node_into_container())
|
||||
{
|
||||
Some(parent) => parent,
|
||||
None => bail!("autotile group does not have a container parent"),
|
||||
};
|
||||
tassert_eq!(h_parent.node_id(), v_group.node_id());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
34
src/state.rs
34
src/state.rs
|
|
@ -925,19 +925,39 @@ impl State {
|
|||
&& 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()));
|
||||
self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone(), true));
|
||||
} else {
|
||||
self.do_map_tiled(seat.as_deref(), node.clone());
|
||||
self.do_map_tiled(seat.as_deref(), node.clone(), true);
|
||||
}
|
||||
self.focus_after_map(node, seat.as_deref());
|
||||
}
|
||||
|
||||
fn do_map_tiled(self: &Rc<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
|
||||
pub fn map_tiled_without_autotile(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {
|
||||
let seat = self.seat_queue.last();
|
||||
self.do_map_tiled(seat.as_deref(), node.clone(), false);
|
||||
self.focus_after_map(node, seat.as_deref());
|
||||
}
|
||||
|
||||
fn do_map_tiled(
|
||||
self: &Rc<Self>,
|
||||
seat: Option<&Rc<WlSeatGlobal>>,
|
||||
node: Rc<dyn ToplevelNode>,
|
||||
autotile: bool,
|
||||
) {
|
||||
let ws = self.ensure_map_workspace(seat);
|
||||
self.map_tiled_on(node, &ws);
|
||||
self.map_tiled_on_(node, &ws, autotile);
|
||||
}
|
||||
|
||||
pub fn map_tiled_on(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, ws: &Rc<WorkspaceNode>) {
|
||||
self.map_tiled_on_(node, ws, false);
|
||||
}
|
||||
|
||||
fn map_tiled_on_(
|
||||
self: &Rc<Self>,
|
||||
node: Rc<dyn ToplevelNode>,
|
||||
ws: &Rc<WorkspaceNode>,
|
||||
autotile: bool,
|
||||
) {
|
||||
if let Some(c) = ws.container.get() {
|
||||
let la = c.clone().tl_last_active_child();
|
||||
let lap = la
|
||||
|
|
@ -946,7 +966,11 @@ impl State {
|
|||
.get()
|
||||
.and_then(|n| n.node_into_container());
|
||||
if let Some(lap) = lap {
|
||||
lap.add_child_after(&*la, node);
|
||||
if autotile {
|
||||
lap.add_tiled_child_after(&*la, node);
|
||||
} else {
|
||||
lap.add_child_after(&*la, node);
|
||||
}
|
||||
} else {
|
||||
c.append_child(node);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,6 +290,47 @@ impl ContainerNode {
|
|||
self.add_child_x(prev, new, |prev, new| self.add_child_after_(prev, new));
|
||||
}
|
||||
|
||||
pub fn add_tiled_child_after(self: &Rc<Self>, prev: &dyn Node, new: Rc<dyn ToplevelNode>) {
|
||||
if !self.state.theme.autotile_enabled.get()
|
||||
|| self.mono_child.is_some()
|
||||
|| self.num_children.get() <= 1
|
||||
{
|
||||
self.add_child_after(prev, new);
|
||||
return;
|
||||
}
|
||||
let focused = self
|
||||
.child_nodes
|
||||
.borrow()
|
||||
.get(&prev.node_id())
|
||||
.map(|n| n.to_ref());
|
||||
let Some(focused) = focused else {
|
||||
log::error!(
|
||||
"Tried to autotile a child into a container but the preceding node is not in the container"
|
||||
);
|
||||
return;
|
||||
};
|
||||
let focused_node = focused.node.clone();
|
||||
let focused_active = focused_node.tl_data().active();
|
||||
let sub = ContainerNode::new(
|
||||
&self.state,
|
||||
&self.workspace.get(),
|
||||
focused_node.clone(),
|
||||
self.split.get().other(),
|
||||
);
|
||||
// Autotile-created groups are structural and collapse once only one
|
||||
// child remains. Explicit make-group commands control their own
|
||||
// grouping through the regular manual paths.
|
||||
sub.ephemeral.set(Ephemeral::On);
|
||||
sub.append_child(new);
|
||||
let sub_id = sub.node_id();
|
||||
self.clone().cnode_replace_child(&*focused_node, sub);
|
||||
if focused_active
|
||||
&& let Some(group) = self.child_nodes.borrow().get(&sub_id).map(|n| n.to_ref())
|
||||
{
|
||||
self.update_child_active(&group, true, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_child_before(self: &Rc<Self>, prev: &dyn Node, new: Rc<dyn ToplevelNode>) {
|
||||
self.add_child_x(prev, new, |prev, new| self.add_child_before_(prev, new));
|
||||
}
|
||||
|
|
@ -1369,42 +1410,6 @@ impl ContainerNode {
|
|||
}
|
||||
|
||||
pub fn insert_child(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, direction: Direction) {
|
||||
// Autotile: if the container would become too narrow/tall, wrap the
|
||||
// focused child and new node in a perpendicular sub-container.
|
||||
if self.state.theme.autotile_enabled.get() && self.mono_child.is_none() {
|
||||
let (pw, ph) = self.predict_child_body_size();
|
||||
let opposite = match self.split.get() {
|
||||
ContainerSplit::Horizontal if pw > 0 && ph > 0 && pw < ph => {
|
||||
Some(ContainerSplit::Vertical)
|
||||
}
|
||||
ContainerSplit::Vertical if pw > 0 && ph > 0 && ph < pw => {
|
||||
Some(ContainerSplit::Horizontal)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(opp_split) = opposite {
|
||||
if let Some(focused) = self.focus_history.last() {
|
||||
if self.num_children.get() <= 1 {
|
||||
// Single child, autotile not applicable.
|
||||
} else {
|
||||
let focused_node = focused.node.clone();
|
||||
let was_ephemeral = self.ephemeral.replace(Ephemeral::Off);
|
||||
self.clone().cnode_remove_child2(&*focused_node, true);
|
||||
self.ephemeral.set(was_ephemeral);
|
||||
let sub = ContainerNode::new(
|
||||
&self.state,
|
||||
&self.workspace.get(),
|
||||
focused_node,
|
||||
opp_split,
|
||||
);
|
||||
sub.ephemeral.set(Ephemeral::On);
|
||||
sub.append_child(node);
|
||||
self.append_child(sub);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let (split, right) = direction_to_split(direction);
|
||||
if split != self.split.get() || right {
|
||||
self.append_child(node);
|
||||
|
|
|
|||
|
|
@ -979,7 +979,7 @@ impl ToplevelData {
|
|||
}
|
||||
fd.workspace.remove_fullscreen_node();
|
||||
if fd.placeholder.is_destroyed() {
|
||||
state.map_tiled(node);
|
||||
state.map_tiled_without_autotile(node);
|
||||
return;
|
||||
}
|
||||
let parent = fd.placeholder.tl_data().parent.take().unwrap();
|
||||
|
|
@ -1262,7 +1262,7 @@ pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floati
|
|||
};
|
||||
if !floating {
|
||||
parent.cnode_remove_child2(&*tl, true);
|
||||
state.map_tiled(tl);
|
||||
state.map_tiled_without_autotile(tl);
|
||||
} else if let Some(ws) = data.workspace.get() {
|
||||
let node_id = data.node_id;
|
||||
let old_body =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue