container: autotile uses major axis as first split
This commit is contained in:
parent
dc62d2240f
commit
c1b2c7f17c
8 changed files with 83 additions and 11 deletions
|
|
@ -80,9 +80,9 @@ container.
|
||||||
## Autotiling
|
## Autotiling
|
||||||
|
|
||||||
Autotiling makes newly tiled windows alternate split direction from the focused
|
Autotiling makes newly tiled windows alternate split direction from the focused
|
||||||
tiled window. The first split uses the containing group direction, then later
|
tiled window by splitting the focused tile along its largest axis. On an empty
|
||||||
windows wrap the focused tile in the opposite direction, producing a horizontal,
|
workspace, the first split uses the largest axis of the output. This starts with
|
||||||
vertical, horizontal pattern as the layout grows.
|
horizontal splits on landscape outputs and vertical splits on portrait outputs.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[shortcuts]
|
[shortcuts]
|
||||||
|
|
|
||||||
|
|
@ -438,9 +438,9 @@ pub fn get_corner_radius() -> f32 {
|
||||||
/// Enables or disables autotiling.
|
/// Enables or disables autotiling.
|
||||||
///
|
///
|
||||||
/// When enabled, newly tiled windows alternate split orientation from the
|
/// When enabled, newly tiled windows alternate split orientation from the
|
||||||
/// focused tiled window: the first split uses the containing group's direction,
|
/// focused tiled window by splitting the focused tile along its largest axis.
|
||||||
/// then subsequent splits wrap the focused window in the perpendicular
|
/// On an empty workspace, the first split uses the workspace output's largest
|
||||||
/// direction.
|
/// axis.
|
||||||
///
|
///
|
||||||
/// The default is `false`.
|
/// The default is `false`.
|
||||||
pub fn set_autotile(enabled: bool) {
|
pub fn set_autotile(enabled: bool) {
|
||||||
|
|
|
||||||
|
|
@ -1215,7 +1215,7 @@
|
||||||
},
|
},
|
||||||
"autotile": {
|
"autotile": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Configures whether autotiling is enabled by default.\n\nWhen enabled, newly mapped tiled windows alternate their split\norientation automatically. This can also be toggled at runtime via the\n`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.\n\nThe default is `false`.\n"
|
"description": "Configures whether autotiling is enabled by default.\n\nWhen enabled, newly mapped tiled windows alternate their split\norientation automatically by splitting the focused tile along its\nlargest axis. On an empty workspace, the first split uses the largest\naxis of the output, so landscape outputs start horizontally and portrait\noutputs start vertically. This can also be toggled at runtime via the\n`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.\n\nThe default is `false`.\n"
|
||||||
},
|
},
|
||||||
"modes": {
|
"modes": {
|
||||||
"description": "Configures the input modes.\n\nModes can be used to define shortcuts that are only active when the mode is\nactive.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n\nModes can be activated with the `push-mode` and `latch-mode` actions.\n",
|
"description": "Configures the input modes.\n\nModes can be used to define shortcuts that are only active when the mode is\nactive.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n\nModes can be activated with the `push-mode` and `latch-mode` actions.\n",
|
||||||
|
|
|
||||||
|
|
@ -3188,7 +3188,10 @@ Config:
|
||||||
Configures whether autotiling is enabled by default.
|
Configures whether autotiling is enabled by default.
|
||||||
|
|
||||||
When enabled, newly mapped tiled windows alternate their split
|
When enabled, newly mapped tiled windows alternate their split
|
||||||
orientation automatically. This can also be toggled at runtime via the
|
orientation automatically by splitting the focused tile along its
|
||||||
|
largest axis. On an empty workspace, the first split uses the largest
|
||||||
|
axis of the output, so landscape outputs start horizontally and portrait
|
||||||
|
outputs start vertically. This can also be toggled at runtime via the
|
||||||
`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.
|
`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.
|
||||||
|
|
||||||
The default is `false`.
|
The default is `false`.
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,12 @@ impl TestConfig {
|
||||||
pub fn set_autotile(&self, enabled: bool) -> TestResult {
|
pub fn set_autotile(&self, enabled: bool) -> TestResult {
|
||||||
self.send(ClientMessage::SetAutotile { enabled })
|
self.send(ClientMessage::SetAutotile { enabled })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_autotile(&self) -> Result<bool, TestError> {
|
||||||
|
let reply = self.send_with_reply(ClientMessage::GetAutotile)?;
|
||||||
|
get_response!(reply, GetAutotile { enabled });
|
||||||
|
Ok(enabled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TestConfig {
|
impl Drop for TestConfig {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ testcase!();
|
||||||
async fn test(run: Rc<TestRun>) -> TestResult {
|
async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
run.backend.install_default()?;
|
run.backend.install_default()?;
|
||||||
run.cfg.set_autotile(true)?;
|
run.cfg.set_autotile(true)?;
|
||||||
|
tassert_eq!(run.cfg.get_autotile()?, true);
|
||||||
|
|
||||||
let client = run.create_client().await?;
|
let client = run.create_client().await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,27 @@ impl State {
|
||||||
} else {
|
} else {
|
||||||
lap.add_child_after(&*la, node);
|
lap.add_child_after(&*la, node);
|
||||||
}
|
}
|
||||||
|
} else if autotile {
|
||||||
|
if let Some(last) = c.children.last() {
|
||||||
|
let la = last_child_descendant(last.node.clone());
|
||||||
|
let lap = la
|
||||||
|
.tl_data()
|
||||||
|
.parent
|
||||||
|
.get()
|
||||||
|
.and_then(|n| n.node_into_container());
|
||||||
|
if let Some(lap) = lap {
|
||||||
|
lap.add_tiled_child_after(&*la, node);
|
||||||
|
} else {
|
||||||
|
c.append_child(node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.append_child(node);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.append_child(node);
|
c.append_child(node);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let container = ContainerNode::new(self, ws, node, ContainerSplit::Horizontal);
|
let container = ContainerNode::new(self, ws, node, initial_split_for_workspace(ws));
|
||||||
ws.set_container(&container);
|
ws.set_container(&container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -537,3 +553,40 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initial_split_for_workspace(ws: &WorkspaceNode) -> ContainerSplit {
|
||||||
|
ContainerSplit::for_largest_axis(ws.output.get().global.pos.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_child_descendant(mut node: Rc<dyn ToplevelNode>) -> Rc<dyn ToplevelNode> {
|
||||||
|
loop {
|
||||||
|
let Some(container) = node.clone().node_into_container() else {
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
let Some(last) = container.children.last() else {
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
node = last.node.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initial_split_uses_largest_output_axis() {
|
||||||
|
assert_eq!(
|
||||||
|
ContainerSplit::for_largest_axis(Rect::new_sized_saturating(0, 0, 800, 600)),
|
||||||
|
ContainerSplit::Horizontal
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ContainerSplit::for_largest_axis(Rect::new_sized_saturating(0, 0, 600, 800)),
|
||||||
|
ContainerSplit::Vertical
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ContainerSplit::for_largest_axis(Rect::new_sized_saturating(0, 0, 600, 600)),
|
||||||
|
ContainerSplit::Horizontal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,14 @@ pub enum ContainerSplit {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerSplit {
|
impl ContainerSplit {
|
||||||
|
pub fn for_largest_axis(rect: Rect) -> Self {
|
||||||
|
if rect.height() > rect.width() {
|
||||||
|
ContainerSplit::Vertical
|
||||||
|
} else {
|
||||||
|
ContainerSplit::Horizontal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn other(self) -> Self {
|
pub fn other(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
ContainerSplit::Horizontal => ContainerSplit::Vertical,
|
ContainerSplit::Horizontal => ContainerSplit::Vertical,
|
||||||
|
|
@ -302,11 +310,12 @@ impl ContainerNode {
|
||||||
};
|
};
|
||||||
let focused_node = focused.node.clone();
|
let focused_node = focused.node.clone();
|
||||||
let focused_active = focused_node.tl_data().active();
|
let focused_active = focused_node.tl_data().active();
|
||||||
|
let split = ContainerSplit::for_largest_axis(focused.body.get());
|
||||||
let sub = ContainerNode::new(
|
let sub = ContainerNode::new(
|
||||||
&self.state,
|
&self.state,
|
||||||
&self.workspace.get(),
|
&self.workspace.get(),
|
||||||
focused_node.clone(),
|
focused_node.clone(),
|
||||||
self.split.get().other(),
|
split,
|
||||||
);
|
);
|
||||||
// Autotile-created groups are structural and collapse once only one
|
// Autotile-created groups are structural and collapse once only one
|
||||||
// child remains. Explicit make-group commands control their own
|
// child remains. Explicit make-group commands control their own
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue