123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- //! Layout calculations for the widget tree.
- //!
- //! Layout calculations are done in two passes:
- //! - **Arrangement**: computes minimum sizes, net size policies, and records parent/child relationships between
- //! nodes.
- //! - **Layout**: given minimum sizes and net size policies from the arrangement step, computes
- //! exact pixel values for positions and sizes.
- use std::{collections::HashMap, 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 expanding(weight: usize) -> Self {
- Self {
- minimum: 0,
- desired: 0,
- slack_weight: weight,
- }
- }
- pub fn fixed(size: usize) -> Self {
- Self {
- minimum: size,
- desired: size,
- slack_weight: 0,
- }
- }
- 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<Self> 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<dyn arr::ArrangementCalculator>),
- 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 => &arr::TableArrangement,
- }
- }
- }
- pub struct LayoutNode {
- /// Human-readable label for making layout tree dumps easier to read.
- label: Option<String>,
- /// Unique identifier for this LayoutNode
- cache_key: LayoutCacheKey,
- /// Reference to the global layout item cache.
- cache: Rc<LayoutCache>,
- /// Layout behaviour, or how this node behaves with respect to its parent.
- behaviour: NodeBehaviour,
- /// Child arrangement, or how this node arranges its children.
- child_arrangement: ChildArrangement,
- /// Width policy: how does this widget take up horizontal space?
- width_policy: SizePolicy,
- /// Height policy: how does this widget take up vertical space?
- height_policy: SizePolicy,
- /// Horizontal alignment of children inside this node.
- halign: HorizontalAlignment,
- /// Vertical alignment of children inside this node.
- valign: VerticalAlignment,
- /// User-exposed margins, or spacing between the parent and the render area of this node.
- margin: PixelSideOffsets,
- /// Table row/column assignment for child nodes, coordinates stored as (row, column).
- table_cells: Option<HashMap<(usize, usize), LayoutCacheKey>>,
- }
- 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<LayoutCache>) -> 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),
- table_cells: None,
- }
- }
- pub fn relayout(&self) {
- self.cache.update_queue.borrow_mut().push(self.cache_key);
- }
- pub fn relayout_tree(&self) {
- let mut to_mark: Vec<LayoutCacheKey> = vec![self.cache_key];
- while let Some(next) = to_mark.pop() {
- self.cache.with_state(next, |ns| {
- ns.needs_update = true;
- to_mark.extend(ns.children.iter());
- });
- }
- }
- pub fn render_area(&self) -> Option<PixelBox> {
- self.cache
- .with_state(self.cache_key, |ns| ns.area)
- .flatten()
- .map(|pb| pb.inner_box(self.margin))
- }
- /// Checks if node needs to be rerendered, clearing the flag if so.
- 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_deref()
- }
- pub fn set_label(&mut self, to: impl AsRef<str>) {
- 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 {
- if self.behaviour != mode {
- 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,
- table_cells: self.table_cells.clone(),
- }
- }
- }
- 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 => "<anon>",
- });
- out.push_str(
- format!(
- ") [wpol: {}/{}/{} hpol: {}/{}/{} behaviour: {:?} upd: {:?} rend: {:?}]\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,
- lna.cache.with_state(lna.cache_key, |v| v.needs_update),
- lna.cache.with_state(lna.cache_key, |v| v.needs_render),
- )
- .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) {
- 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<Self::Item> {
- 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<LayoutNodeAccess<'l>> {
- 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<LayoutNodeAccess<'_>>;
- 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<LayoutNode> 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<LayoutNodeAccess<'_>> {
- 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<LayoutNodeAccess> {
- if ndx == 0 {
- Some(self.1)
- } else {
- None
- }
- }
- fn layout_child_count(&self) -> usize {
- 1
- }
- }
|