feat(core): 10 layout preset generators
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
pub mod grid;
|
||||
pub mod ops;
|
||||
pub mod presets;
|
||||
pub mod snapshot;
|
||||
|
||||
pub use grid::GridSurface;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
//! The 10 layout presets (DOCS/MAIN.md §8.2). A preset maps a list of surface
|
||||
//! ids (one per slot, in order) to a LayoutNode. `slot_count` says how many
|
||||
//! panels the preset needs.
|
||||
use spacesh_proto::ids::SurfaceId;
|
||||
use spacesh_proto::layout::{LayoutNode, Orient};
|
||||
|
||||
/// Known preset ids and their panel counts.
|
||||
pub fn slot_count(preset_id: &str) -> Option<usize> {
|
||||
Some(match preset_id {
|
||||
"1" => 1,
|
||||
"2lr" => 2, // 2↔
|
||||
"2tb" => 2, // 2↕
|
||||
"2+1" => 3,
|
||||
"1+2" => 3,
|
||||
"3" => 3,
|
||||
"2x2" => 4,
|
||||
"4" => 4, // single row of 4
|
||||
"2x3" => 6,
|
||||
"2x4" => 8,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn leaf(id: &SurfaceId) -> LayoutNode { LayoutNode::leaf(id.clone()) }
|
||||
fn even(n: usize) -> Vec<f32> { vec![1.0 / n as f32; n] }
|
||||
|
||||
fn row(ids: &[SurfaceId]) -> LayoutNode {
|
||||
LayoutNode::Split { orient: Orient::H, ratios: even(ids.len()), children: ids.iter().map(leaf).collect() }
|
||||
}
|
||||
fn col(children: Vec<LayoutNode>) -> LayoutNode {
|
||||
LayoutNode::Split { orient: Orient::V, ratios: even(children.len()), children }
|
||||
}
|
||||
fn rown(children: Vec<LayoutNode>) -> LayoutNode {
|
||||
LayoutNode::Split { orient: Orient::H, ratios: even(children.len()), children }
|
||||
}
|
||||
|
||||
/// Build the preset tree from exactly `slot_count(preset_id)` ids.
|
||||
/// Returns None for an unknown id or wrong id count.
|
||||
pub fn build(preset_id: &str, ids: &[SurfaceId]) -> Option<LayoutNode> {
|
||||
if slot_count(preset_id)? != ids.len() {
|
||||
return None;
|
||||
}
|
||||
Some(match preset_id {
|
||||
"1" => leaf(&ids[0]),
|
||||
"2lr" => row(&ids),
|
||||
"2tb" => col(vec![leaf(&ids[0]), leaf(&ids[1])]),
|
||||
// left big column over... 2 stacked on the right.
|
||||
"2+1" => rown(vec![leaf(&ids[0]), col(vec![leaf(&ids[1]), leaf(&ids[2])])]),
|
||||
// one big on the left, 2 stacked on the right (mirror naming kept simple).
|
||||
"1+2" => rown(vec![col(vec![leaf(&ids[0]), leaf(&ids[1])]), leaf(&ids[2])]),
|
||||
"3" => row(&ids),
|
||||
"2x2" => col(vec![row(&ids[0..2]), row(&ids[2..4])]),
|
||||
"4" => row(&ids),
|
||||
"2x3" => col(vec![row(&ids[0..3]), row(&ids[3..6])]),
|
||||
"2x4" => col(vec![row(&ids[0..4]), row(&ids[4..8])]),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ops::leaves;
|
||||
fn ids(n: usize) -> Vec<SurfaceId> { (0..n).map(|i| SurfaceId(format!("s_{i}"))).collect() }
|
||||
|
||||
#[test]
|
||||
fn all_presets_have_counts() {
|
||||
for p in ["1","2lr","2tb","2+1","1+2","3","2x2","4","2x3","2x4"] {
|
||||
assert!(slot_count(p).is_some(), "missing count for {p}");
|
||||
}
|
||||
assert!(slot_count("nope").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_uses_all_ids_in_order() {
|
||||
for p in ["1","2lr","2tb","2+1","1+2","3","2x2","4","2x3","2x4"] {
|
||||
let n = slot_count(p).unwrap();
|
||||
let tree = build(p, &ids(n)).unwrap();
|
||||
assert_eq!(leaves(&tree), ids(n), "preset {p} must place all ids in order");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_rejects_wrong_id_count() {
|
||||
assert!(build("2x2", &ids(3)).is_none());
|
||||
assert!(build("bogus", &ids(1)).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_2x2_is_two_rows() {
|
||||
let tree = build("2x2", &ids(4)).unwrap();
|
||||
match tree {
|
||||
LayoutNode::Split { orient: Orient::V, children, .. } => {
|
||||
assert_eq!(children.len(), 2);
|
||||
for r in &children {
|
||||
matches!(r, LayoutNode::Split { orient: Orient::H, .. });
|
||||
}
|
||||
}
|
||||
_ => panic!("2x2 should be a vertical split of two horizontal rows"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user