docs: Adds better documentation for constraints and flex 📚 (#818)

This commit is contained in:
Dheepak Krishnamurthy 2024-01-14 16:58:58 -05:00 committed by GitHub
parent cc6737b8bc
commit 11e4f6a0ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 157 additions and 93 deletions

View file

@ -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,
}

View file

@ -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::<Vec<&Element>>()
.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)?;