|
@@ -1,10 +1,102 @@
|
|
|
-//! Layout calculations for the widget tree.
|
|
|
+//! Layout calculations for the widget tree. All layout calculations are performed on a tree of
|
|
|
+//! [`LayoutNode`]s, which are stored in various widget structs.
|
|
|
//!
|
|
|
//! Layout calculations are done in two passes:
|
|
|
//! - **Arrangement**: computes minimum sizes, net size policies, and records parent/child relationships between
|
|
|
-//! nodes.
|
|
|
+//! nodes for optimization purposes.
|
|
|
//! - **Layout**: given minimum sizes and net size policies from the arrangement step, computes
|
|
|
-//! exact pixel values for positions and sizes.
|
|
|
+//! exact pixel values for positions and sizes by distributing the available space appropriately.
|
|
|
+//!
|
|
|
+//! Collectively, layout nodes form a tree structure with _externally_-stored edges. This is a
|
|
|
+//! slightly odd data structure, so it deserves some elaboration. In order to play nicely with
|
|
|
+//! Rust's borrow checker without using interior mutability (which comes with a large set of
|
|
|
+//! gotchas and a performance hit), when using internally-stored edges, layout nodes must be
|
|
|
+//! traversed from the root node to acquire a mutable reference to an interior node.
|
|
|
+//!
|
|
|
+//! To make this less awkward, the tree structure of the layout nodes -- and indeed the ownership
|
|
|
+//! of nodes -- is not stored directly in layout nodes themselves, but instead in a separate widget
|
|
|
+//! tree structure. At first this seems an unnecessary complication and major source of
|
|
|
+//! awkwardness, but it allows for a few very useful tricks:
|
|
|
+//! - Mutable references can be created for any number of nodes,
|
|
|
+//! - The entire layout tree need not be traversed to find a specific child,
|
|
|
+//! - There is no 1:1 requirement between widgets and layout nodes, so layout nodes can be used for
|
|
|
+//! internal alignment,
|
|
|
+//! - Layout is significantly easier to debug as it is by design entirely agnostic to details of
|
|
|
+//! the widgets involved,
|
|
|
+//! - Rather than numerical indices or similar awkward indexing schemes, nodes are simply stored as
|
|
|
+//! members of a struct and benefit from all the usual borrow checker behaviour.
|
|
|
+//!
|
|
|
+//! The tree structure is created by implementations of the [`LayoutTreeNode`] trait. Consider the
|
|
|
+//! following tree:
|
|
|
+//! ```text
|
|
|
+//! root --+-- child1
|
|
|
+//! |
|
|
|
+//! \-- child2 --+-- child2a
|
|
|
+//! |
|
|
|
+//! \-- child2b
|
|
|
+//! ```
|
|
|
+//!
|
|
|
+//! The following is one simplified way the structure can be imposed externally:
|
|
|
+//! ```
|
|
|
+//! # use patina::layout::*;
|
|
|
+//! trait Widget {
|
|
|
+//! fn lnode(&self) -> LayoutNodeAccess<'_>;
|
|
|
+//! }
|
|
|
+//! struct RootWidget {
|
|
|
+//! node: LayoutNode,
|
|
|
+//! label: LabelWidget,
|
|
|
+//! select: ChoiceWidget,
|
|
|
+//! }
|
|
|
+//! impl LayoutTreeNode<()> for RootWidget {
|
|
|
+//! fn current_node(&self) -> &LayoutNode { &self.node }
|
|
|
+//! fn child_count(&self) -> usize { 2 }
|
|
|
+//! fn child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>> {
|
|
|
+//! match ndx {
|
|
|
+//! 0 => Some(self.label.lnode()), // root -> child1 edge
|
|
|
+//! 1 => Some(self.select.lnode()), // root -> child2 edge
|
|
|
+//! _ => None
|
|
|
+//! }
|
|
|
+//! }
|
|
|
+//! }
|
|
|
+//! impl Widget for RootWidget {
|
|
|
+//! fn lnode(&self) -> LayoutNodeAccess<'_> {
|
|
|
+//! LayoutNodeAccess::new::<()>(self)
|
|
|
+//! }
|
|
|
+//! }
|
|
|
+//! struct LabelWidget {
|
|
|
+//! // see documentation on LeafLayoutNode for more details
|
|
|
+//! // this is basically a shortcut to avoid reimplementing LayoutTreeNode
|
|
|
+//! // to store a node with no children
|
|
|
+//! node: LeafLayoutNode
|
|
|
+//! }
|
|
|
+//! impl Widget for LabelWidget {
|
|
|
+//! fn lnode(&self) -> LayoutNodeAccess<'_> {
|
|
|
+//! self.node.access()
|
|
|
+//! }
|
|
|
+//! }
|
|
|
+//! struct ChoiceWidget {
|
|
|
+//! node: LayoutNode,
|
|
|
+//! choice1: LabelWidget,
|
|
|
+//! choice2: LabelWidget,
|
|
|
+//! }
|
|
|
+//! impl LayoutTreeNode<()> for ChoiceWidget {
|
|
|
+//! // elided for brevity
|
|
|
+//! # fn current_node(&self) -> &LayoutNode { &self.node }
|
|
|
+//! # fn child_count(&self) -> usize { 2 }
|
|
|
+//! # fn child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>> {
|
|
|
+//! # match ndx {
|
|
|
+//! # 0 => Some(self.choice1.lnode()),
|
|
|
+//! # 1 => Some(self.choice2.lnode()),
|
|
|
+//! # _ => None
|
|
|
+//! # }
|
|
|
+//! # }
|
|
|
+//! }
|
|
|
+//! impl Widget for ChoiceWidget {
|
|
|
+//! fn lnode(&self) -> LayoutNodeAccess<'_> {
|
|
|
+//! LayoutNodeAccess::new::<()>(self)
|
|
|
+//! }
|
|
|
+//! }
|
|
|
+//! ```
|
|
|
|
|
|
use std::{ops::Deref, rc::Rc};
|
|
|
|
|
@@ -14,12 +106,18 @@ use kahlo::math::{PixelBox, PixelSideOffsets};
|
|
|
mod arr;
|
|
|
mod cache;
|
|
|
mod calc;
|
|
|
+mod dump;
|
|
|
|
|
|
pub use cache::{LayoutCache, LayoutNodeID};
|
|
|
pub use calc::recalculate;
|
|
|
|
|
|
+pub use dump::*;
|
|
|
+
|
|
|
+#[doc(hidden)]
|
|
|
pub struct CellMarker;
|
|
|
+/// Type representing a single cell's coordinates in a table.
|
|
|
pub type TableCell = euclid::Point2D<usize, CellMarker>;
|
|
|
+/// Type representing the total size of a table.
|
|
|
pub type TableSize = euclid::Size2D<usize, CellMarker>;
|
|
|
|
|
|
/// Sizing policy for a layout dimension. Defaults to no minimum or desired size and a low-weighted
|
|
@@ -102,7 +200,9 @@ impl SizePolicy {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#[doc(hidden)]
|
|
|
pub struct SizePolicyTag;
|
|
|
+/// A 2D size policy type using [`euclid`].
|
|
|
pub type SizePolicy2D = euclid::Size2D<SizePolicy, SizePolicyTag>;
|
|
|
|
|
|
impl std::ops::Add<Self> for SizePolicy {
|
|
@@ -130,6 +230,7 @@ impl Default for HorizontalAlignment {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// How to vertically align a smaller node inside a larger node.
|
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
|
pub enum VerticalAlignment {
|
|
|
Top,
|
|
@@ -155,6 +256,7 @@ pub enum NodeBehaviour {
|
|
|
Anchored,
|
|
|
}
|
|
|
|
|
|
+/// Represents a layout style for a layout node to arrange its children with.
|
|
|
#[derive(Clone)]
|
|
|
pub enum ChildArrangement {
|
|
|
Custom(std::rc::Rc<dyn arr::ArrangementCalculator>),
|
|
@@ -189,6 +291,10 @@ impl Deref for ChildArrangement {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Type representing a single node in the layout tree.
|
|
|
+///
|
|
|
+/// Notably, this struct is unaware of any edges it may participate in; see the module
|
|
|
+/// documentation for more information.
|
|
|
pub struct LayoutNode {
|
|
|
/// Human-readable label for making layout tree dumps easier to read.
|
|
|
label: Option<String>,
|
|
@@ -210,7 +316,7 @@ pub struct LayoutNode {
|
|
|
valign: VerticalAlignment,
|
|
|
/// User-exposed margins, or spacing between the parent and the render area of this node.
|
|
|
margin: PixelSideOffsets,
|
|
|
- /// Table row/column assignment for this node insite its parent.
|
|
|
+ /// Table row/column assignment for this node inside its parent.
|
|
|
table_cell: Option<TableCell>,
|
|
|
}
|
|
|
|
|
@@ -402,114 +508,6 @@ impl Drop for LayoutNode {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-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.with_state(|v| v.needs_update),
|
|
|
- lna.with_state(|v| v.needs_render),
|
|
|
- )
|
|
|
- .as_str(),
|
|
|
- );
|
|
|
- out.push_str(ind.as_str());
|
|
|
- out.push_str(" ");
|
|
|
- out.push_str(format!("net policy: {:?}\n", lna.with_state(|v| v.net_policy)).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);
|
|
|
-}
|
|
|
-
|
|
|
-pub fn dump_tree_json<W: ?Sized + std::io::Write>(
|
|
|
- lna: LayoutNodeAccess,
|
|
|
- out: &mut std::io::BufWriter<W>,
|
|
|
-) -> std::io::Result<()> {
|
|
|
- use std::io::Write;
|
|
|
-
|
|
|
- write!(out, "{{")?;
|
|
|
- write!(out, "\"label\": \"{}\" ", lna.label().unwrap_or("null"))?;
|
|
|
- write!(
|
|
|
- out,
|
|
|
- ",\"id\": {} ",
|
|
|
- <LayoutNodeID as Into<usize>>::into(lna.id())
|
|
|
- )?;
|
|
|
- write!(out, ",\"behaviour\": \"{:?}\"", lna.behaviour())?;
|
|
|
- write!(out, ",\"child_arrangement\": \"{:?}\"", lna.arrangement())?;
|
|
|
- write!(
|
|
|
- out,
|
|
|
- ",\"width_policy\": [{},{},{}]",
|
|
|
- lna.width_policy().minimum,
|
|
|
- lna.width_policy().desired,
|
|
|
- lna.width_policy().slack_weight
|
|
|
- )?;
|
|
|
- write!(
|
|
|
- out,
|
|
|
- ",\"height_policy\": [{},{},{}]",
|
|
|
- lna.height_policy().minimum,
|
|
|
- lna.height_policy().desired,
|
|
|
- lna.height_policy().slack_weight
|
|
|
- )?;
|
|
|
- write!(out, ",\"halign\": \"{:?}\"", lna.halign())?;
|
|
|
- write!(out, ",\"valign\": \"{:?}\"", lna.valign())?;
|
|
|
- write!(
|
|
|
- out,
|
|
|
- ",\"margin\": {{\"top\": {}, \"bottom\": {}, \"left\": {}, \"right\": {}}}",
|
|
|
- lna.margin().top,
|
|
|
- lna.margin().bottom,
|
|
|
- lna.margin().left,
|
|
|
- lna.margin().right,
|
|
|
- )?;
|
|
|
- match lna.table_cell() {
|
|
|
- None => write!(out, ",\"table_cell\": null")?,
|
|
|
- Some(c) => write!(out, ",\"table_cell\": [{},{}]", c.x, c.y)?,
|
|
|
- }
|
|
|
- match lna.render_area() {
|
|
|
- None => write!(out, ",\"render_area\": null")?,
|
|
|
- Some(a) => write!(
|
|
|
- out,
|
|
|
- ",\"render_area\": [[{},{}],[{},{}]]",
|
|
|
- a.min.x, a.min.y, a.max.x, a.max.y
|
|
|
- )?,
|
|
|
- }
|
|
|
-
|
|
|
- write!(out, ",\"children\": [")?;
|
|
|
-
|
|
|
- let mut needs_sep = false;
|
|
|
- for child in lna.child_iter() {
|
|
|
- if needs_sep {
|
|
|
- out.write(",".as_bytes())?;
|
|
|
- } else {
|
|
|
- needs_sep = true;
|
|
|
- }
|
|
|
- dump_tree_json(child, out)?;
|
|
|
- }
|
|
|
-
|
|
|
- write!(out, r#"]}}"#)?;
|
|
|
-
|
|
|
- Ok(())
|
|
|
-}
|
|
|
-
|
|
|
/// Iterator implementation to iterate across children of a LayoutNodeContainer
|
|
|
#[derive(Clone)]
|
|
|
struct LayoutChildIter<'l> {
|
|
@@ -537,6 +535,7 @@ impl<'l> Iterator for LayoutChildIter<'l> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Accessor trait for a struct that stores edge information for the layout tree.
|
|
|
pub trait LayoutTreeNode<Tag: 'static> {
|
|
|
fn current_node(&self) -> &LayoutNode;
|
|
|
fn child_count(&self) -> usize;
|
|
@@ -559,6 +558,10 @@ fn child_dispatch<'l, Tag: 'static>(dynptr: DynPtr, ndx: usize) -> Option<Layout
|
|
|
unsafe { std::mem::transmute::<_, &'l dyn LayoutTreeNode<Tag>>(dynptr).child(ndx) }
|
|
|
}
|
|
|
|
|
|
+/// Edge-aware accessor for a [`LayoutNode`].
|
|
|
+///
|
|
|
+/// Use this struct if you wish to accept arbitrary [`LayoutNode`]s and require information about
|
|
|
+/// the layout tree as well.
|
|
|
#[derive(Clone, Copy)]
|
|
|
pub struct LayoutNodeAccess<'l> {
|
|
|
current_node_func: fn(DynPtr) -> &'l LayoutNode,
|
|
@@ -609,7 +612,14 @@ impl<'l> Deref for LayoutNodeAccess<'l> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// Helper struct to store a leaf LayoutNode and automatically provide a LayoutTreeNode impl
|
|
|
+/// Helper struct to store a leaf LayoutNode and automatically provide a [`LayoutTreeNode`]
|
|
|
+/// implementation.
|
|
|
+///
|
|
|
+/// This helps avoid repetitive reimplementations of [`LayoutTreeNode`] when a container stores a
|
|
|
+/// node with no children.
|
|
|
+///
|
|
|
+/// If you're reading this documentation, you probably want the [`LeafLayoutNode::access`]
|
|
|
+/// function, which gives a [`LayoutNodeAccess`].
|
|
|
pub struct LeafLayoutNode(LayoutNode);
|
|
|
|
|
|
impl LeafLayoutNode {
|
|
@@ -652,3 +662,9 @@ impl LayoutTreeNode<()> for LeafLayoutNode {
|
|
|
0
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/*macro_rules! layout_tree {
|
|
|
+ ($ctype:ident, $($tag:ident => $nname:ident | [$($ctag:ident),*]),*) => {
|
|
|
+
|
|
|
+ };
|
|
|
+}*/
|