From 9927046c7e45683a5d6f7e8f55c522560057236c Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Tue, 9 Jun 2026 21:15:47 +0700 Subject: [PATCH] feat(core): 10 layout preset generators Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/spacesh-core/src/lib.rs | 1 + crates/spacesh-core/src/presets.rs | 102 +++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 crates/spacesh-core/src/presets.rs diff --git a/crates/spacesh-core/src/lib.rs b/crates/spacesh-core/src/lib.rs index ff47eea..04b0164 100644 --- a/crates/spacesh-core/src/lib.rs +++ b/crates/spacesh-core/src/lib.rs @@ -1,5 +1,6 @@ pub mod grid; pub mod ops; +pub mod presets; pub mod snapshot; pub use grid::GridSurface; diff --git a/crates/spacesh-core/src/presets.rs b/crates/spacesh-core/src/presets.rs new file mode 100644 index 0000000..6928dcd --- /dev/null +++ b/crates/spacesh-core/src/presets.rs @@ -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 { + 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 { 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::Split { orient: Orient::V, ratios: even(children.len()), children } +} +fn rown(children: Vec) -> 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 { + 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 { (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"), + } + } +}