From 11e4f6a0ba71b7adad44af5866a2b0789175aafa Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Sun, 14 Jan 2024 16:58:58 -0500 Subject: [PATCH] =?UTF-8?q?docs:=20Adds=20better=20documentation=20for=20c?= =?UTF-8?q?onstraints=20and=20flex=20=F0=9F=93=9A=20(#818)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/flex.rs | 234 +++++++++++++++++++++++++++---------------- src/layout/layout.rs | 16 +-- 2 files changed, 157 insertions(+), 93 deletions(-) diff --git a/src/layout/flex.rs b/src/layout/flex.rs index 78f036c2..8ffcc83c 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -1,5 +1,8 @@ use strum::{Display, EnumString}; +#[allow(unused_imports)] +use super::constraint::Constraint; + /// Defines the options for layout flex justify content in a container. /// /// This enumeration controls the distribution of space when layout constraints are met. @@ -14,28 +17,98 @@ use strum::{Display, EnumString}; /// - `SpaceAround`: Adds excess space around each element. #[derive(Copy, Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash)] pub enum Flex { - /// Fills the available space within the container, putting excess space into the last element. - /// This matches the default behavior of ratatui and tui applications without [`Flex`] + /// Fills the available space within the container, putting excess space into the last + /// constraint of the lowest priority. This matches the default behavior of ratatui and tui + /// applications without [`Flex`] + /// + /// The following examples illustrate the allocation of excess in various combinations of + /// constraints. As a refresher, the priorities of constraints are as follows: + /// + /// 1. [`Constraint::Fixed`] + /// 2. [`Constraint::Min`] / [`Constraint::Max`] + /// 3. [`Constraint::Length`] / [`Constraint::Percentage`] / [`Constraint::Ratio`] + /// 4. [`Constraint::Proportional`] + /// + /// When every constraint is `Length`, the last element gets the excess. + /// + /// ```plain + /// <----------------------------------- 80 px ------------------------------------> + /// ┌──────20 px───────┐┌──────20 px───────┐┌────────────────40 px─────────────────┐ + /// │ Length(20) ││ Length(20) ││ Length(20) │ + /// └──────────────────┘└──────────────────┘└──────────────────────────────────────┘ + /// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^ + /// ``` + /// + /// If we replace the constraint at the end with a `Fixed`, because it has a + /// higher priority, the last constraint with the lowest priority, i.e. the last + /// `Length` gets the excess. + /// + /// ```plain + /// <----------------------------------- 80 px ------------------------------------> + /// ┌──────20 px───────┐┌────────────────40 px─────────────────┐┌──────20 px───────┐ + /// │ Length(20) ││ Length(20) ││ Fixed(20) │ + /// └──────────────────┘└──────────────────────────────────────┘└──────────────────┘ + /// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^ + /// ``` + /// + /// Violating a `Max` is lower priority than `Fixed` but higher + /// than `Length`. + /// + /// ```plain + /// <----------------------------------- 80 px ------------------------------------> + /// ┌────────────────40 px─────────────────┐┌──────20 px───────┐┌──────20 px───────┐ + /// │ Length(20) ││ Max(20) ││ Fixed(20) │ + /// └──────────────────────────────────────┘└──────────────────┘└──────────────────┘ + /// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^ + /// ``` + /// + /// It's important to note that while not violating a `Min` or `Max` constraint is + /// prioritized higher than a `Length`, `Min` and `Max` constraints allow for a range + /// of values and excess can (and will) be dumped into these ranges first, if possible, + /// even if it not the last constraint. + /// + /// ```plain + /// <----------------------------------- 80 px ------------------------------------> + /// ┌──────20 px───────┐┌────────────────40 px─────────────────┐┌──────20 px───────┐ + /// │ Length(20) ││ Min(20) ││ Fixed(20) │ + /// └──────────────────┘└──────────────────────────────────────┘└──────────────────┘ + /// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^ + /// + /// <----------------------------------- 80 px ------------------------------------> + /// ┌────────────────40 px─────────────────┐┌──────20 px───────┐┌──────20 px───────┐ + /// │ Min(20) ││ Length(20) ││ Fixed(20) │ + /// └──────────────────────────────────────┘└──────────────────┘└──────────────────┘ + /// ^^^^^^^^^^^^^^^^ EXCESS ^^^^^^^^^^^^^^^^ + /// ``` + /// + /// Proportional constraints have the lowest priority amongst all the constraints and hence + /// will always take up any excess space available. + /// + /// ```plain + /// <----------------------------------- 80 px ------------------------------------> + /// ┌──────20 px───────┐┌──────20 px───────┐┌──────20 px───────┐┌──────20 px───────┐ + /// │ Proportional(0) ││ Min(20) ││ Length(20) ││ Fixed(20) │ + /// └──────────────────┘└──────────────────┘└──────────────────┘└──────────────────┘ + /// ^^^^^^ EXCESS ^^^^^^ + /// ``` /// /// # Examples /// /// ```plain - /// - /// Length(20), Length(10) + /// <------------------------------------80 px-------------------------------------> + /// ┌───────────30 px────────────┐┌───────────30 px────────────┐┌──────20 px───────┐ + /// │ Percentage(20) ││ Length(20) ││ Fixed(20) │ + /// └────────────────────────────┘└────────────────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐┌──────────────────────────────────────────────────────────┐ - /// │ 20 px ││ 60 px │ - /// └──────────────────┘└──────────────────────────────────────────────────────────┘ - /// - /// Length(20), Fixed(10) + /// ┌──────────────────────────60 px───────────────────────────┐┌──────20 px───────┐ + /// │ Min(20) ││ Max(20) │ + /// └──────────────────────────────────────────────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌────────────────────────────────────────────────────────────────────┐┌────────┐ - /// │ 70 px ││ 10 px │ - /// └────────────────────────────────────────────────────────────────────┘└────────┘ + /// ┌────────────────────────────────────80 px─────────────────────────────────────┐ + /// │ Max(20) │ + /// └──────────────────────────────────────────────────────────────────────────────┘ /// ``` #[default] StretchLast, @@ -44,25 +117,21 @@ pub enum Flex { /// /// # Examples /// - /// Length(40), Length(20) - /// /// ```plain - /// - /// Length(20), Length(10) + /// <------------------------------------80 px-------------------------------------> + /// ┌────16 px─────┐┌──────────────────44 px───────────────────┐┌──────20 px───────┐ + /// │Percentage(20)││ Length(20) ││ Fixed(20) │ + /// └──────────────┘└──────────────────────────────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────────────────────────┐┌──────────────────────────────────────┐ - /// │ 40 px ││ 40 px │ - /// └──────────────────────────────────────┘└──────────────────────────────────────┘ - /// - /// Length(20), Fixed(10) + /// ┌──────────────────────────60 px───────────────────────────┐┌──────20 px───────┐ + /// │ Min(20) ││ Max(20) │ + /// └──────────────────────────────────────────────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌────────────────────────────────────────────────────────────────────┐┌────────┐ - /// │ 70 px ││ 10 px │ - /// └────────────────────────────────────────────────────────────────────┘└────────┘ + /// ┌────────────────────────────────────80 px─────────────────────────────────────┐ + /// │ Max(20) │ + /// └──────────────────────────────────────────────────────────────────────────────┘ /// ``` Stretch, @@ -71,22 +140,20 @@ pub enum Flex { /// # Examples /// /// ```plain - /// - /// Length(20), Length(10) + /// <------------------------------------80 px-------------------------------------> + /// ┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐ + /// │Percentage(20)││ Length(20) ││ Fixed(20) │ + /// └──────────────┘└──────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐┌────────┐ - /// │ 20 px ││ 10 px │ - /// └──────────────────┘└────────┘ - /// - /// Length(20), Fixed(10) + /// ┌──────20 px───────┐┌──────20 px───────┐ + /// │ Min(20) ││ Max(20) │ + /// └──────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐┌────────┐ - /// │ 20 px ││ 10 px │ - /// └──────────────────┘└────────┘ + /// ┌──────20 px───────┐ + /// │ Max(20) │ + /// └──────────────────┘ /// ``` Start, @@ -95,22 +162,20 @@ pub enum Flex { /// # Examples /// /// ```plain - /// - /// Length(20), Length(10) + /// <------------------------------------80 px-------------------------------------> + /// ┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐ + /// │Percentage(20)││ Length(20) ││ Fixed(20) │ + /// └──────────────┘└──────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐┌────────┐ - /// │ 20 px ││ 10 px │ - /// └──────────────────┘└────────┘ - /// - /// Length(20), Fixed(10) + /// ┌──────20 px───────┐┌──────20 px───────┐ + /// │ Min(20) ││ Max(20) │ + /// └──────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐┌────────┐ - /// │ 20 px ││ 10 px │ - /// └──────────────────┘└────────┘ + /// ┌──────20 px───────┐ + /// │ Max(20) │ + /// └──────────────────┘ /// ``` End, @@ -119,22 +184,20 @@ pub enum Flex { /// # Examples /// /// ```plain - /// - /// Length(20), Length(10) + /// <------------------------------------80 px-------------------------------------> + /// ┌────16 px─────┐┌──────20 px───────┐┌──────20 px───────┐ + /// │Percentage(20)││ Length(20) ││ Fixed(20) │ + /// └──────────────┘└──────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐┌────────┐ - /// │ 20 px ││ 10 px │ - /// └──────────────────┘└────────┘ - /// - /// Length(20), Fixed(10) + /// ┌──────20 px───────┐┌──────20 px───────┐ + /// │ Min(20) ││ Max(20) │ + /// └──────────────────┘└──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐┌────────┐ - /// │ 20 px ││ 10 px │ - /// └──────────────────┘└────────┘ + /// ┌──────20 px───────┐ + /// │ Max(20) │ + /// └──────────────────┘ /// ``` Center, @@ -144,21 +207,20 @@ pub enum Flex { /// /// ```plain /// - /// Length(20), Length(10) + /// <------------------------------------80 px-------------------------------------> + /// ┌────16 px─────┐ ┌──────20 px───────┐ ┌──────20 px───────┐ + /// │Percentage(20)│ │ Length(20) │ │ Fixed(20) │ + /// └──────────────┘ └──────────────────┘ └──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐ ┌────────┐ - /// │ 20 px │ │ 10 px │ - /// └──────────────────┘ └────────┘ - /// - /// Length(20), Fixed(10) + /// ┌──────20 px───────┐ ┌──────20 px───────┐ + /// │ Min(20) │ │ Max(20) │ + /// └──────────────────┘ └──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐ ┌────────┐ - /// │ 20 px │ │ 10 px │ - /// └──────────────────┘ └────────┘ + /// ┌────────────────────────────────────80 px─────────────────────────────────────┐ + /// │ Max(20) │ + /// └──────────────────────────────────────────────────────────────────────────────┘ /// ``` SpaceBetween, @@ -167,22 +229,20 @@ pub enum Flex { /// # Examples /// /// ```plain - /// - /// Length(20), Length(10) + /// <------------------------------------80 px-------------------------------------> + /// ┌────16 px─────┐ ┌──────20 px───────┐ ┌──────20 px───────┐ + /// │Percentage(20)│ │ Length(20) │ │ Fixed(20) │ + /// └──────────────┘ └──────────────────┘ └──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐ ┌────────┐ - /// │ 20 px │ │ 10 px │ - /// └──────────────────┘ └────────┘ - /// - /// Length(20), Fixed(10) + /// ┌──────20 px───────┐ ┌──────20 px───────┐ + /// │ Min(20) │ │ Max(20) │ + /// └──────────────────┘ └──────────────────┘ /// /// <------------------------------------80 px-------------------------------------> - /// - /// ┌──────────────────┐ ┌────────┐ - /// │ 20 px │ │ 10 px │ - /// └──────────────────┘ └────────┘ + /// ┌──────20 px───────┐ + /// │ Max(20) │ + /// └──────────────────┘ /// ``` SpaceAround, } diff --git a/src/layout/layout.rs b/src/layout/layout.rs index 75923c95..022d5c97 100644 --- a/src/layout/layout.rs +++ b/src/layout/layout.rs @@ -527,7 +527,7 @@ impl Layout { // interleave spacers and elements // for `SpaceAround` we want the following // `[spacer, element, spacer, element, ..., element, spacer]` - // this is why we use one spacer than elements + // this is why we use one more spacer than elements for pair in Itertools::interleave(spacers.iter(), elements.iter()) .collect::>() .windows(2) @@ -537,7 +537,8 @@ impl Layout { } Flex::StretchLast => { // this is the default behavior - // within reason, cassowary tends to put excess into the last constraint + // by default cassowary tends to put excess into the last constraint of the lowest + // priority. if let Some(first) = elements.first() { solver.add_constraint(first.start | EQ(REQUIRED) | area_start)?; } @@ -550,16 +551,19 @@ impl Layout { } } Flex::Stretch => { + // this is the same as `StretchLast` + // however, we add one additional constraint to take priority over cassowary's + // default behavior. + // We prefer equal elements if other constraints are all satisfied. + for (left, right) in elements.iter().tuple_combinations() { + solver.add_constraint(left.size() | EQ(WEAK) | right.size())?; + } if let Some(first) = elements.first() { solver.add_constraint(first.start | EQ(REQUIRED) | area_start)?; } if let Some(last) = elements.last() { solver.add_constraint(last.end | EQ(REQUIRED) | area_end)?; } - // prefer equal elements if other constraints are all satisfied - for (left, right) in elements.iter().tuple_combinations() { - solver.add_constraint(left.size() | EQ(WEAK) | right.size())?; - } // ensure there are no gaps between the elements for pair in elements.windows(2) { solver.add_constraint(pair[0].end | EQ(REQUIRED) | pair[1].start)?;