use std::{ops::Deref, rc::Rc}; use cache::{Layer, LayoutCacheKey, NodeState}; use kahlo::math::{PixelBox, PixelSideOffsets}; mod arr; mod cache; mod calc; pub use cache::LayoutCache; pub use calc::recalculate; /// Sizing policy for a layout dimension. Defaults to no minimum or desired size and a low-weighted /// desire to take up slack space. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct SizePolicy { /// The minimum number of pixels required along this dimension to avoid overlap and other /// issues. pub minimum: usize, /// The number of pixels requested to have a comfortable amount of space and avoid ugly-looking /// UI. pub desired: usize, /// How much to take up any remaining slack space. pub slack_weight: usize, } impl Default for SizePolicy { fn default() -> Self { Self { minimum: 0, desired: 0, slack_weight: 1, } } } impl SizePolicy { pub fn expands(weight: usize) -> Self { Self { minimum: 0, desired: 0, slack_weight: weight, } } pub fn max(self, rhs: SizePolicy) -> SizePolicy { Self { minimum: self.minimum.max(rhs.minimum), desired: self.desired.max(rhs.desired), slack_weight: self.slack_weight.max(rhs.slack_weight), } } pub fn max_preserve_slack(self, rhs: SizePolicy) -> SizePolicy { Self { minimum: self.minimum.max(rhs.minimum), desired: self.desired.max(rhs.desired), slack_weight: self.slack_weight, } } } impl std::ops::Add for SizePolicy { type Output = Self; fn add(self, rhs: Self) -> Self::Output { Self { minimum: self.minimum + rhs.minimum, desired: self.desired + rhs.desired, slack_weight: self.slack_weight + rhs.slack_weight, } } } /// How to horizontally align a smaller node inside a larger node. #[derive(Clone, Copy, PartialEq, Debug)] pub enum HorizontalAlignment { Left, Centre, Right, } impl Default for HorizontalAlignment { fn default() -> Self { Self::Centre } } #[derive(Clone, Copy, PartialEq, Debug)] pub enum VerticalAlignment { Top, Centre, Bottom, } impl Default for VerticalAlignment { fn default() -> Self { Self::Centre } } /// What sort of layout does a node represent? #[derive(Clone, Copy, PartialEq, Debug)] pub enum NodeBehaviour { /// A fixed rendering area, probably a root node. Fixed { area: PixelBox }, /// An ordinary box sitting inside another node, hinting its size and receiving a final /// size assignment from its parent. Element, /// A node that floats above other content, anchored with a zero-size flexbox. Anchored, } #[derive(Clone)] pub enum ChildArrangement { Custom(std::rc::Rc), Column, Row, Table, } impl std::fmt::Debug for ChildArrangement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Custom(_) => f.write_str("ChildArrangement::Custom"), Self::Column => f.write_str("ChildArrangement::Column"), Self::Row => f.write_str("ChildArrangement::Row"), Self::Table => f.write_str("ChildArrangement::Table"), } } } impl Deref for ChildArrangement { type Target = dyn arr::ArrangementCalculator; fn deref(&self) -> &Self::Target { match self { Self::Custom(calc) => calc.as_ref(), Self::Column => &arr::LineArrangement::Column, Self::Row => &arr::LineArrangement::Row, Self::Table => todo!(), } } } pub struct LayoutNode { label: Option, cache_key: LayoutCacheKey, cache: Rc, behaviour: NodeBehaviour, child_arrangement: ChildArrangement, width_policy: SizePolicy, height_policy: SizePolicy, halign: HorizontalAlignment, valign: VerticalAlignment, margin: PixelSideOffsets, } impl std::fmt::Debug for LayoutNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LayoutNode") .field("label", &self.label) .field("cache_key", &self.cache_key) .field("behaviour", &self.behaviour) .field("child_arrangement", &self.child_arrangement) .field("width_policy", &self.width_policy) .field("height_policy", &self.height_policy) .field("margin", &self.margin) .finish() } } impl LayoutNode { pub fn new(cache: Rc) -> Self { let cache_key = LayoutCacheKey::generate(); cache.update_queue.borrow_mut().push(cache_key); Self { label: None, cache_key, cache, behaviour: NodeBehaviour::Element, child_arrangement: ChildArrangement::Column, width_policy: SizePolicy::default(), height_policy: SizePolicy::default(), halign: HorizontalAlignment::default(), valign: VerticalAlignment::default(), margin: PixelSideOffsets::new_all_same(0), } } pub fn relayout(&self) { self.cache.update_queue.borrow_mut().push(self.cache_key); } pub fn render_area(&self) -> Option { self.cache .with_state(self.cache_key, |ns| ns.area) .flatten() .map(|pb| pb.inner_box(self.margin)) } pub fn render_check(&self) -> bool { self.cache.with_state(self.cache_key, |ns| { let ret = ns.needs_render; ns.needs_render = false; ret }).unwrap_or(false) } pub fn render_needed(&self) { self.cache.render_queue.borrow_mut().push(self.cache_key); } } /// Accessors impl LayoutNode { pub fn label(&self) -> Option<&str> { self.label.as_ref().map(String::as_str) } pub fn set_label(&mut self, to: impl AsRef) { self.label = Some(to.as_ref().to_string()); } pub fn behaviour(&self) -> NodeBehaviour { self.behaviour } pub fn set_behaviour(&mut self, mode: NodeBehaviour) -> &mut Self { self.behaviour = mode; self.relayout(); self } pub fn arrangement(&self) -> &ChildArrangement { &self.child_arrangement } pub fn set_arrangement(&mut self, arr: ChildArrangement) -> &mut Self { self.child_arrangement = arr; self.relayout(); self } pub fn width_policy(&self) -> SizePolicy { self.width_policy } pub fn set_width_policy(&mut self, policy: SizePolicy) -> &mut Self { self.width_policy = policy; self.relayout(); self } pub fn height_policy(&self) -> SizePolicy { self.height_policy } pub fn set_height_policy(&mut self, policy: SizePolicy) -> &mut Self { self.height_policy = policy; self.relayout(); self } pub fn halign(&self) -> HorizontalAlignment { self.halign } pub fn set_halign(&mut self, halign: HorizontalAlignment) -> &mut Self { self.halign = halign; self } pub fn valign(&self) -> VerticalAlignment { self.valign } pub fn set_valign(&mut self, valign: VerticalAlignment) -> &mut Self { self.valign = valign; self } pub fn margin(&self) -> PixelSideOffsets { self.margin } pub fn margin_mut(&mut self) -> &mut PixelSideOffsets { &mut self.margin } } impl Clone for LayoutNode { fn clone(&self) -> Self { Self { label: self.label.clone(), cache_key: LayoutCacheKey::generate(), cache: self.cache.clone(), behaviour: self.behaviour, child_arrangement: self.child_arrangement.clone(), width_policy: self.width_policy, height_policy: self.height_policy, halign: self.halign, valign: self.valign, margin: self.margin, } } } impl Drop for LayoutNode { fn drop(&mut self) { self.cache.update_queue.borrow_mut().push(self.cache_key); } } fn dump_node_tree_helper(lna: LayoutNodeAccess, indent: usize, out: &mut String) { let ind = " ".repeat(indent); out.push_str(ind.as_str()); out.push_str("Node ("); out.push_str(match lna.label() { Some(v) => v, None => "", }); out.push_str( format!( ") [wpol: {}/{}/{} hpol: {}/{}/{} behaviour: {:?}]\n", lna.width_policy.minimum, lna.width_policy.desired, lna.width_policy.slack_weight, lna.height_policy.minimum, lna.height_policy.desired, lna.height_policy.slack_weight, lna.behaviour ) .as_str(), ); out.push_str(ind.as_str()); out.push_str(" "); out.push_str(format!("render area: {:?}\n", lna.render_area()).as_str()); for child in lna.child_iter() { dump_node_tree_helper(child, indent + 1, out); } } pub fn dump_node_tree(lna: LayoutNodeAccess, out: &mut String) { out.clear(); dump_node_tree_helper(lna, 0, out); } /// Iterator implementation to iterate across children of a LayoutNodeContainer #[derive(Clone)] pub struct LayoutChildIter<'l> { lnc: &'l dyn LayoutNodeContainer, next_index: usize, } impl<'l> LayoutChildIter<'l> { fn new(lnc: &'l dyn LayoutNodeContainer) -> Self { Self { lnc, next_index: 0 } } } impl<'l> Iterator for LayoutChildIter<'l> { type Item = LayoutNodeAccess<'l>; fn next(&mut self) -> Option { let index = self.next_index; if index >= self.lnc.layout_child_count() { None } else { self.next_index += 1; self.lnc.layout_child(index) } } } /// Wrapper struct to access a [`LayoutNodeContainer`]. #[derive(Clone, Copy)] pub struct LayoutNodeAccess<'l> { lnc: &'l dyn LayoutNodeContainer, } impl<'l> LayoutNodeAccess<'l> { pub fn new(lnc: &'l dyn LayoutNodeContainer) -> Self { Self { lnc } } } impl<'l> Deref for LayoutNodeAccess<'l> { type Target = LayoutNode; fn deref(&self) -> &Self::Target { self.lnc.layout_node() } } impl<'l> LayoutNodeAccess<'l> { pub fn child(&self, ndx: usize) -> Option> { self.lnc.layout_child(ndx) } pub fn child_len(&self) -> usize { self.lnc.layout_child_count() } pub fn child_iter(&self) -> LayoutChildIter { LayoutChildIter::new(self.lnc) } } /// Data source trait for LayoutNodeAccess. pub trait LayoutNodeContainer { fn layout_node(&self) -> &LayoutNode; fn layout_child(&self, ndx: usize) -> Option>; fn layout_child_count(&self) -> usize; } /// Helper struct to store a leaf LayoutNode and automatically provide a LayoutNodeContainer impl pub struct LeafLayoutNode(LayoutNode); impl LeafLayoutNode { pub fn new(ln: LayoutNode) -> Self { Self(ln) } } impl From for LeafLayoutNode { fn from(value: LayoutNode) -> Self { Self::new(value) } } impl std::ops::Deref for LeafLayoutNode { type Target = LayoutNode; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for LeafLayoutNode { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl LayoutNodeContainer for LeafLayoutNode { fn layout_node(&self) -> &LayoutNode { &self.0 } fn layout_child(&self, _ndx: usize) -> Option> { None } fn layout_child_count(&self) -> usize { 0 } } /// Helper LayoutNodeContainer implementation for LayoutNodes with one child pub struct LinearAccess<'l>(&'l LayoutNode, LayoutNodeAccess<'l>); impl<'l> LinearAccess<'l> { pub fn new(parent: &'l LayoutNode, child: LayoutNodeAccess<'l>) -> Self { Self(parent, child) } } impl<'l> LayoutNodeContainer for LinearAccess<'l> { fn layout_node(&self) -> &LayoutNode { self.0 } fn layout_child(&self, ndx: usize) -> Option { if ndx == 0 { Some(self.1) } else { None } } fn layout_child_count(&self) -> usize { 1 } }