diff --git a/Cargo.lock b/Cargo.lock index 6711f40..76b5063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,7 @@ version = 3 [[package]] name = "cherrytree" version = "0.1.0" +source = "git+https://github.com/raunakab/cherrytree#75aabcb9e4a54f8176ee15bd331275c07229e6b5" dependencies = [ "indexmap", "slotmap", diff --git a/Cargo.toml b/Cargo.toml index 2de3cae..7eae8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = [ description = "A 2-dimensional layout solver." [dependencies] -cherrytree = { path = "../cherrytree" } +cherrytree = { git = "https://github.com/raunakab/cherrytree" } indexmap = "2.0.0" petgraph = "0.6.4" slotmap = "1.0.6" diff --git a/src/lib.rs b/src/lib.rs index 7994f5a..4eec720 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,12 @@ impl Solver { constraint: Constraint, capacity: usize, ) -> Option { - matches!(constraint.fill_x, Fill::Scale(..)).then(|| { + let both_fills_are_absolute_scales = matches! { constraint.fill, Fill::Absolute { x: FillType::Scale(..), y: FillType::Scale(..) }}; + let both_fills_are_relative_scales = matches! { constraint.fill, Fill::Relative { main: FillType::Scale(..), cross: FillType::Scale(..) }}; + + let both_fills_are_scales = both_fills_are_absolute_scales | both_fills_are_relative_scales; + + both_fills_are_scales.then(|| { let root_key = self .constraint_tree .insert_root_with_capacity(constraint, capacity); @@ -189,7 +194,7 @@ impl Solver { // Solve method: - pub fn solve(&mut self, length_x: f64) { + pub fn solve(&mut self, length_x: f64, length_y: f64) { let is_dirty = self.is_dirty; let is_empty = self.constraint_tree.is_empty(); @@ -198,12 +203,14 @@ impl Solver { (true, false) => { let length_x = length_x.max(0.); + let length_y = length_y.max(0.); solve( &self.constraint_tree, &mut self.frame_tree, &mut self.key_map, length_x, + length_y, ); self.is_dirty = false; @@ -216,31 +223,117 @@ impl Solver { #[derive(Default, Debug, Clone, Copy, PartialEq)] pub struct Constraint { - pub fill_x: Fill, - pub padding: Padding, - pub align_x: Align, + pub fill: Fill, + pub content: Content, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum Fill { + Absolute { x: FillType, y: FillType }, + Relative { main: FillType, cross: FillType }, +} + +impl Fill { + fn to_relative_fill(self, direction: Direction) -> RelativeFill { + match self { + Self::Absolute { x, y } => match direction { + Direction::Horizontal => RelativeFill { main: x, cross: y }, + Direction::Vertical => RelativeFill { main: y, cross: x }, + }, + Self::Relative { main, cross } => RelativeFill { main, cross }, + } + } +} + +impl Default for Fill { + fn default() -> Self { + Self::Relative { + main: FillType::default(), + cross: FillType::default(), + } + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq)] +struct RelativeFill { + main: FillType, + cross: FillType, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FillType { Exact(f64), Scale(usize), Minimize, } -impl Default for Fill { +impl Default for FillType { fn default() -> Self { Self::Scale(1) } } +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub struct Content { + pub direction: Direction, + pub padding: Padding, + pub align_main: Align, + pub align_cross: Align, +} + #[derive(Default, Debug, Clone, Copy, PartialEq)] pub struct Padding { pub left: f64, pub right: f64, + + pub top: f64, + pub bottom: f64, +} + +impl Padding { + fn to_relative_padding(self, direction: Direction) -> RelativePadding { + let Self { + left, + right, + top, + bottom, + } = self; + + match direction { + Direction::Horizontal => RelativePadding { + main_start: left, + main_end: right, + cross_start: top, + cross_end: bottom, + }, + Direction::Vertical => RelativePadding { + main_start: top, + main_end: bottom, + cross_start: left, + cross_end: right, + }, + } + } } #[derive(Default, Debug, Clone, Copy, PartialEq)] +struct RelativePadding { + pub main_start: f64, + pub main_end: f64, + + pub cross_start: f64, + pub cross_end: f64, +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Horizontal, + + #[default] + Vertical, +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum Align { #[default] Start, @@ -252,4 +345,42 @@ pub enum Align { pub struct Frame { pub offset_x: f64, pub length_x: f64, + + pub offset_y: f64, + pub length_y: f64, +} + +#[derive(Default, Debug, Clone, Copy, PartialEq)] +struct RelativeFrame { + pub offset_main: f64, + pub length_main: f64, + + pub offset_cross: f64, + pub length_cross: f64, +} + +impl RelativeFrame { + fn to_frame(self, direction: Direction) -> Frame { + let Self { + offset_main, + length_main, + offset_cross, + length_cross, + } = self; + + match direction { + Direction::Horizontal => Frame { + offset_x: offset_main, + length_x: length_main, + offset_y: offset_cross, + length_y: length_cross, + }, + Direction::Vertical => Frame { + offset_x: offset_cross, + length_x: length_cross, + offset_y: offset_main, + length_y: length_main, + }, + } + } } diff --git a/src/solver/mod.rs b/src/solver/mod.rs index aa1d2e1..42ce8ef 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -1,188 +1,300 @@ -#[cfg(test)] -mod tests; - use std::collections::BTreeMap; use cherrytree::{Node, Tree}; use indexmap::IndexSet; -use crate::{Align, Constraint, ConstraintKey, Fill, Frame, FrameKey, Padding}; +use crate::{ + Align, Constraint, ConstraintKey, Content, Direction, FillType, Frame, FrameKey, RelativeFrame, + RelativePadding, +}; pub(super) fn solve( constraint_tree: &Tree, frame_tree: &mut Tree, key_map: &mut BTreeMap, length_x: f64, + length_y: f64, ) { let (root_constraint_key, root_constraint_node) = constraint_tree.root_key_value().unwrap(); - match root_constraint_node.value.fill_x { - Fill::Scale(scale) => { - let length_x = match scale { - 0 => 0., - _ => length_x, - }; + let relative_fill = root_constraint_node + .value + .fill + .to_relative_fill(Direction::Vertical); - let root_frame = Frame { - offset_x: 0., - length_x, - }; + let length_x = match relative_fill.cross { + FillType::Scale(0) => 0., + FillType::Scale(_) => length_x, + _ => unreachable!(), + }; - let root_frame_key = frame_tree.insert_root(root_frame); - key_map.insert(root_constraint_key, root_frame_key); - - let root_content_frame = - generate_content_frame(length_x, root_constraint_node.value.padding); - - solve_child_keys( - constraint_tree, - frame_tree, - key_map, - root_constraint_node.child_keys, - root_frame_key, - root_content_frame, - root_constraint_node.value.align_x, - ); - } + let length_y = match relative_fill.main { + FillType::Scale(0) => 0., + FillType::Scale(_) => length_y, + _ => unreachable!(), + }; - Fill::Exact(..) | Fill::Minimize => unreachable!(), - } + let root_frame = Frame { + offset_x: 0., + length_x, + offset_y: 0., + length_y, + }; + + let number_of_child_keys = root_constraint_node.child_keys.len(); + let root_frame_key = frame_tree.insert_root_with_capacity(root_frame, number_of_child_keys); + key_map.insert(root_constraint_key, root_frame_key); + + let relative_padding = root_constraint_node + .value + .content + .padding + .to_relative_padding(Direction::Vertical); + let root_relative_content_frame = + generate_content_frame_relative(relative_padding, length_y, length_x); + + solve_child_keys_relative( + constraint_tree, + frame_tree, + key_map, + root_constraint_node.child_keys, + root_frame_key, + root_relative_content_frame, + root_constraint_node.value.content, + ); } -fn solve_child_keys( +fn solve_child_keys_relative( constraint_tree: &Tree, frame_tree: &mut Tree, key_map: &mut BTreeMap, constraint_keys: &IndexSet, parent_frame_key: FrameKey, - content_frame: Frame, - align_x: Align, + relative_content_frame: RelativeFrame, + parent_content: Content, ) { - let mut data = iter(constraint_tree, constraint_keys) - .map(|(constraint_key, constraint_node)| (constraint_key, constraint_node, None)) - .collect::>(); + let mut remaining_length_main = relative_content_frame.length_main; + let mut total_scale_main: usize = 0; - let mut remaining_length_x = content_frame.length_x; - let mut total_scale: usize = 0; + let mut relative_lengths = iter(constraint_tree, constraint_keys) + .map(|(_, constraint_node)| { + let relatve_fill = constraint_node + .value + .fill + .to_relative_fill(parent_content.direction); - for (_, constraint_node, current_length_x) in data.iter_mut() { - match constraint_node.value.fill_x { - Fill::Exact(exact) => { - let length_x = exact.min(remaining_length_x); - *current_length_x = Some(length_x); - remaining_length_x -= length_x; - } - Fill::Scale(scale) => { - total_scale = total_scale.saturating_add(scale); - } - Fill::Minimize => { - let length_x = find_minimizing_length_x( - constraint_tree, - constraint_node.child_keys, - remaining_length_x, - ); - *current_length_x = Some(length_x); - remaining_length_x -= length_x; - } - } - } + let mut cache = None; + + let length_main = match relatve_fill.main { + FillType::Exact(exact_main) => { + let exact_main = exact_main.min(remaining_length_main); + remaining_length_main -= exact_main; + Some(exact_main) + } + FillType::Scale(scale_main) => { + total_scale_main = total_scale_main.checked_add(scale_main).unwrap(); + None + } + FillType::Minimize => { + let (minimizing_length_main, minimizing_length_cross) = + find_minimizing_length_relative( + constraint_tree, + constraint_node.child_keys, + constraint_node.value.content.direction, + remaining_length_main, + relative_content_frame.length_cross, + ); + cache = Some(minimizing_length_cross); + Some(minimizing_length_main) + } + }; - let remaining_length_x = remaining_length_x.max(0.); + let length_cross = match relatve_fill.cross { + FillType::Exact(exact_cross) => { + exact_cross.min(relative_content_frame.length_cross) + } + FillType::Scale(0) => 0., + FillType::Scale(_) => relative_content_frame.length_cross, + FillType::Minimize => cache.unwrap_or_else(|| { + let (_, minimizing_length_cross) = find_minimizing_length_relative( + constraint_tree, + constraint_node.child_keys, + constraint_node.value.content.direction, + remaining_length_main, + relative_content_frame.length_cross, + ); + minimizing_length_cross + }), + }; + + let remaining_length_cross = relative_content_frame.length_cross - length_cross; + let offset_cross = relative_content_frame.offset_cross + + match parent_content.align_cross { + Align::Start => 0., + Align::Middle => remaining_length_cross / 2., + Align::End => remaining_length_cross, + }; + + (relatve_fill, length_main, length_cross, offset_cross) + }) + .collect::>(); - let mut offset_x = match total_scale { + let mut offset_main = match total_scale_main { 0 => { - if remaining_length_x == 0. { - content_frame.offset_x - } else { - match align_x { - Align::Start => content_frame.offset_x, - Align::Middle => { - let half_remaining_length_x = remaining_length_x / 2.; - content_frame.offset_x + half_remaining_length_x - } - Align::End => content_frame.offset_x + remaining_length_x, + relative_content_frame.offset_main + + match parent_content.align_main { + Align::Start => 0., + Align::Middle => remaining_length_main / 2., + Align::End => remaining_length_main, } - } } _ => { - for (_, constraint_node, current_length_x) in data.iter_mut() { - if let Fill::Scale(scale) = constraint_node.value.fill_x { - let length_x = ((scale as f64) / (total_scale as f64)) * remaining_length_x; - *current_length_x = Some(length_x); - } + for (relative_fill, length_main, _, _) in &mut relative_lengths { + if let FillType::Scale(scale_main) = relative_fill.main { + let proportion = (scale_main as f64) / (total_scale_main as f64); + *length_main = Some(proportion * remaining_length_main); + }; } - content_frame.offset_x + 0. } }; - for (constraint_key, constraint_node, length_x) in data { - let length_x = length_x.unwrap_or_default(); + for ((constraint_key, constraint_node), (_, length_main, length_cross, offset_cross)) in + iter(constraint_tree, constraint_keys).zip(relative_lengths) + { + let length_main = length_main.unwrap_or_default(); - let frame = Frame { offset_x, length_x }; + let relative_frame = RelativeFrame { + offset_main, + length_main, + offset_cross, + length_cross, + }; - let frame_key = frame_tree.insert(frame, parent_frame_key).unwrap(); - key_map.insert(constraint_key, frame_key); + offset_main -= length_main; - offset_x += length_x; + let number_of_child_keys = constraint_node.child_keys.len(); + let frame = relative_frame.to_frame(parent_content.direction); + let frame_key = frame_tree + .insert_with_capacity(frame, parent_frame_key, number_of_child_keys) + .unwrap(); + key_map.insert(constraint_key, frame_key); - let content_frame = generate_content_frame(length_x, constraint_node.value.padding); + let relative_padding = constraint_node + .value + .content + .padding + .to_relative_padding(parent_content.direction); + let relative_content_frame = + generate_content_frame_relative(relative_padding, length_main, length_cross); - solve_child_keys( + solve_child_keys_relative( constraint_tree, frame_tree, key_map, constraint_node.child_keys, frame_key, - content_frame, - constraint_node.value.align_x, + relative_content_frame, + constraint_node.value.content, ); } } -fn iter<'a>( - constraint_tree: &'a Tree, - constraint_keys: &'a IndexSet, -) -> impl ExactSizeIterator)> { - constraint_keys.iter().map(|&constraint_key| { - let constraint_node = constraint_tree.get(constraint_key).unwrap(); - (constraint_key, constraint_node) - }) +fn generate_content_frame_relative( + relative_padding: RelativePadding, + length_main: f64, + length_cross: f64, +) -> RelativeFrame { + let content_start_main = relative_padding.main_start.min(length_main); + let content_end_main = (length_main - relative_padding.main_end).max(0.); + let content_length_main = (content_end_main - content_start_main).max(0.); + + let content_start_cross = relative_padding.cross_start.min(length_cross); + let content_end_cross = (length_cross - relative_padding.cross_end).max(0.); + let content_length_cross = (content_end_cross - content_start_cross).max(0.); + + RelativeFrame { + offset_main: content_start_main, + length_main: content_length_main, + offset_cross: content_start_cross, + length_cross: content_length_cross, + } } -fn find_minimizing_length_x( +fn find_minimizing_length_relative( constraint_tree: &Tree, constraint_keys: &IndexSet, - length_x: f64, -) -> f64 { - let mut total_length_x: f64 = 0.; + direction: Direction, + max_length_main: f64, + max_length_cross: f64, +) -> (f64, f64) { + let mut remaining_length_main: f64 = max_length_main; + let mut max_seen_length_cross: f64 = 0.; + + let mut cache = None; for (_, constraint_node) in iter(constraint_tree, constraint_keys) { - match constraint_node.value.fill_x { - Fill::Exact(exact) => total_length_x = (total_length_x + exact).min(length_x), - Fill::Scale(..) => (), - Fill::Minimize => { - let remaining_length_x = length_x - total_length_x; - let minimize = find_minimizing_length_x( + let relative_fill = constraint_node.value.fill.to_relative_fill(direction); + let relative_padding = constraint_node + .value + .content + .padding + .to_relative_padding(direction); + + let length_main = match relative_fill.main { + FillType::Exact(exact_main) => { + exact_main + relative_padding.main_start + relative_padding.main_end + } + FillType::Scale(..) => relative_padding.main_start + relative_padding.main_end, + FillType::Minimize => { + let (sub_minimizing_length_main, sub_minimizing_length_cross) = + find_minimizing_length_relative( + constraint_tree, + constraint_node.child_keys, + constraint_node.value.content.direction, + remaining_length_main, + max_length_cross, + ); + cache = Some(sub_minimizing_length_cross); + sub_minimizing_length_main + } + } + .min(remaining_length_main); + + let length_cross = match relative_fill.cross { + FillType::Exact(exact_cross) => { + exact_cross + relative_padding.cross_start + relative_padding.cross_end + } + FillType::Scale(..) => relative_padding.cross_start + relative_padding.cross_end, + FillType::Minimize => cache.unwrap_or_else(|| { + let (_, sub_minimizing_length_cross) = find_minimizing_length_relative( constraint_tree, constraint_node.child_keys, - remaining_length_x, + constraint_node.value.content.direction, + remaining_length_main, + max_length_cross, ); - total_length_x += minimize; - } - } + sub_minimizing_length_cross + }), + }; + + remaining_length_main -= length_main; + max_seen_length_cross = max_seen_length_cross.max(length_cross); } - total_length_x -} + let minimizing_length_main = max_length_main - remaining_length_main; + let minimizing_length_cross = max_seen_length_cross.min(max_length_cross); -fn generate_content_frame(length_x: f64, padding: Padding) -> Frame { - let content_start_x = padding.left.min(length_x); - let content_end_x = (length_x - padding.right).max(0.); - let content_length_x = (content_end_x - content_start_x).max(0.); + (minimizing_length_main, minimizing_length_cross) +} - Frame { - offset_x: content_start_x, - length_x: content_length_x, - } +fn iter<'a>( + constraint_tree: &'a Tree, + constraint_keys: &'a IndexSet, +) -> impl Iterator)> { + constraint_keys.iter().map(|&constraint_key| { + let constraint_node = constraint_tree.get(constraint_key).unwrap(); + (constraint_key, constraint_node) + }) } diff --git a/tests/test_solver.rs b/tests/test_solver.rs index ed2a979..b58dc94 100644 --- a/tests/test_solver.rs +++ b/tests/test_solver.rs @@ -2,140 +2,36 @@ mod common; use common::{make_frame_tree, make_solver}; -use stretchbox::{Constraint, Fill, Frame}; +use stretchbox::{Constraint, Fill, FillType, Frame}; #[test] fn test_solver_with_empty_tree() { let mut solver = make_solver(None).unwrap(); - solver.solve(10.); + solver.solve(10., 10.); let actual_frame_tree = make_frame_tree(&solver); let expected_frame_tree = None; - assert_eq!(actual_frame_tree, expected_frame_tree); } #[test] -fn test_solver_with_zero_length() { - let mut solver = make_solver(Some(&node! { Constraint::default() })).unwrap(); - - solver.solve(0.); - - let actual_frame_tree = make_frame_tree(&solver); - let expected_frame_tree = Some(node! { Frame::default() }); +fn test_solver_with_invalid_root_constraint() { + let solver = make_solver(Some( + &node! { Constraint { fill: Fill::Absolute { x: FillType::Exact(10.), y: FillType::Scale(1) }, ..Default::default() } }, + )); - assert_eq!(actual_frame_tree, expected_frame_tree); + assert!(solver.is_none()); } #[test] -fn test_solver_with_nonzero_length() { +fn test_solver_with_single_element_tree() { let mut solver = make_solver(Some(&node! { Constraint::default() })).unwrap(); - solver.solve(100.); + solver.solve(10., 10.); let actual_frame_tree = make_frame_tree(&solver); - let expected_frame_tree = Some(node! { Frame { length_x: 100., ..Default::default() } }); - + let expected_frame_tree = + Some(node! { Frame { offset_x: 0., length_x: 10., offset_y: 0., length_y: 10. }}); assert_eq!(actual_frame_tree, expected_frame_tree); } - -#[test] -fn test_solver_with_invalid_root_constraint() { - let solver = make_solver(Some( - &node! { Constraint { fill_x: Fill::Minimize, ..Default::default() } }, - )); - - assert!(solver.is_none()); -} - -// #[test] -// fn test_solver() { -// let mut solver = Solver::default(); - -// assert!(!solver.is_dirty()); - -// let root_constraint_key = solver.insert_root(Constraint { -// fill_x: Fill::Scale(1), -// padding: Padding { -// left: 1., -// right: 1., -// }, -// }); -// let child_constraint_key_1 = solver -// .insert( -// Constraint { -// fill_x: Fill::Scale(1), -// padding: Padding { -// left: 100., -// right: 100., -// }, -// }, -// root_constraint_key, -// ) -// .unwrap(); -// let child_constraint_key_2 = solver -// .insert( -// Constraint { -// fill_x: Fill::Exact(10.), -// padding: Padding { -// left: 100., -// right: 100., -// }, -// }, -// root_constraint_key, -// ) -// .unwrap(); -// let child_constraint_key_3 = solver -// .insert( -// Constraint { -// fill_x: Fill::Minimize, -// padding: Padding { -// left: 100., -// right: 100., -// }, -// }, -// root_constraint_key, -// ) -// .unwrap(); - -// assert!(solver.is_dirty()); - -// let did_solve = solver.solve(12.); -// assert!(did_solve); -// assert!(!solver.is_dirty()); - -// let root_frame = solver.get_frame(root_constraint_key).unwrap(); -// let child_frame_1 = solver.get_frame(child_constraint_key_1).unwrap(); -// let child_frame_2 = solver.get_frame(child_constraint_key_2).unwrap(); -// let child_frame_3 = solver.get_frame(child_constraint_key_3).unwrap(); - -// assert_eq!( -// root_frame, -// Frame { -// offset_x: 0., -// length_x: 12. -// } -// ); -// assert_eq!( -// child_frame_1, -// Frame { -// offset_x: 1., -// length_x: 0. -// } -// ); -// assert_eq!( -// child_frame_2, -// Frame { -// offset_x: 1., -// length_x: 10. -// } -// ); -// assert_eq!( -// child_frame_3, -// Frame { -// offset_x: 11., -// length_x: 0. -// } -// ); -// }