summaryrefslogtreecommitdiff
path: root/src/layout/primitive.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-02 22:05:49 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-02 22:05:49 +0200
commit266d457292e7461d448f9141030028ea68b573d1 (patch)
treeff3ff3cc289d34040db421b6a7faa1f2aa402b05 /src/layout/primitive.rs
parentcbbc46215fe0a0ad8a50e991ec442890b8eadc0a (diff)
Refactor model into tree 🛒
Diffstat (limited to 'src/layout/primitive.rs')
-rw-r--r--src/layout/primitive.rs350
1 files changed, 350 insertions, 0 deletions
diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs
new file mode 100644
index 00000000..2eb5669b
--- /dev/null
+++ b/src/layout/primitive.rs
@@ -0,0 +1,350 @@
+//! Layouting primitives.
+
+use std::fmt::{self, Display, Formatter};
+use super::prelude::*;
+
+/// Specifies along which axes content is laid out.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct LayoutAxes {
+ /// The primary layouting direction.
+ pub primary: Dir,
+ /// The secondary layouting direction.
+ pub secondary: Dir,
+}
+
+impl LayoutAxes {
+ /// Create a new instance from the two values.
+ ///
+ /// # Panics
+ /// This function panics if the axes are aligned, that is, they are
+ /// on the same axis.
+ pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes {
+ if primary.axis() == secondary.axis() {
+ panic!("invalid aligned axes {} and {}", primary, secondary);
+ }
+
+ LayoutAxes { primary, secondary }
+ }
+
+ /// Return the direction of the specified generic axis.
+ pub fn get(self, axis: GenAxis) -> Dir {
+ match axis {
+ Primary => self.primary,
+ Secondary => self.secondary,
+ }
+ }
+
+ /// Borrow the direction of the specified generic axis mutably.
+ pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir {
+ match axis {
+ Primary => &mut self.primary,
+ Secondary => &mut self.secondary,
+ }
+ }
+}
+
+/// Directions along which content is laid out.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum Dir {
+ LTT,
+ RTL,
+ TTB,
+ BTT,
+}
+
+impl Dir {
+ /// The specific axis this direction belongs to.
+ pub fn axis(self) -> SpecAxis {
+ match self {
+ LTT | RTL => Horizontal,
+ TTB | BTT => Vertical,
+ }
+ }
+
+ /// Whether this axis points into the positive coordinate direction.
+ ///
+ /// The positive axes are left-to-right and top-to-bottom.
+ pub fn is_positive(self) -> bool {
+ match self {
+ LTT | TTB => true,
+ RTL | BTT => false,
+ }
+ }
+
+ /// The factor for this direction.
+ ///
+ /// - `1` if the direction is positive.
+ /// - `-1` if the direction is negative.
+ pub fn factor(self) -> f64 {
+ if self.is_positive() { 1.0 } else { -1.0 }
+ }
+
+ /// The inverse axis.
+ pub fn inv(self) -> Dir {
+ match self {
+ LTT => RTL,
+ RTL => LTT,
+ TTB => BTT,
+ BTT => TTB,
+ }
+ }
+}
+
+impl Display for Dir {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ LTT => "ltr",
+ RTL => "rtl",
+ TTB => "ttb",
+ BTT => "btt",
+ })
+ }
+}
+
+/// The two generic layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum GenAxis {
+ /// The primary axis along which words are laid out.
+ Primary,
+ /// The secondary axis along which lines and paragraphs are laid out.
+ Secondary,
+}
+
+impl GenAxis {
+ /// The specific version of this axis in the given system of axes.
+ pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis {
+ axes.get(self).axis()
+ }
+}
+
+impl Display for GenAxis {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Primary => "primary",
+ Secondary => "secondary",
+ })
+ }
+}
+
+/// The two specific layouting axes.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum SpecAxis {
+ /// The horizontal layouting axis.
+ Horizontal,
+ /// The vertical layouting axis.
+ Vertical,
+}
+
+impl SpecAxis {
+ /// The generic version of this axis in the given system of axes.
+ pub fn to_generic(self, axes: LayoutAxes) -> GenAxis {
+ if self == axes.primary.axis() { Primary } else { Secondary }
+ }
+}
+
+impl Display for SpecAxis {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Horizontal => "horizontal",
+ Vertical => "vertical",
+ })
+ }
+}
+
+/// Specifies where to align a layout in a parent container.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct LayoutAlign {
+ /// The alignment along the primary axis.
+ pub primary: GenAlign,
+ /// The alignment along the secondary axis.
+ pub secondary: GenAlign,
+}
+
+impl LayoutAlign {
+ /// Create a new instance from the two values.
+ pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign {
+ LayoutAlign { primary, secondary }
+ }
+
+ /// Return the alignment of the specified generic axis.
+ pub fn get(self, axis: GenAxis) -> GenAlign {
+ match axis {
+ Primary => self.primary,
+ Secondary => self.secondary,
+ }
+ }
+
+ /// Borrow the alignment of the specified generic axis mutably.
+ pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
+ match axis {
+ Primary => &mut self.primary,
+ Secondary => &mut self.secondary,
+ }
+ }
+}
+
+/// Where to align content along a generic context.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum GenAlign {
+ Start,
+ Center,
+ End,
+}
+
+impl GenAlign {
+ /// The inverse alignment.
+ pub fn inv(self) -> GenAlign {
+ match self {
+ Start => End,
+ Center => Center,
+ End => Start,
+ }
+ }
+}
+
+impl Display for GenAlign {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Start => "start",
+ Center => "center",
+ End => "end",
+ })
+ }
+}
+
+/// Where to align content in a specific context.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum SpecAlign {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ Center,
+}
+
+impl SpecAlign {
+ /// The specific axis this alignment refers to.
+ ///
+ /// Returns `None` if this is center.
+ pub fn axis(self) -> Option<SpecAxis> {
+ match self {
+ Self::Left => Some(Horizontal),
+ Self::Right => Some(Horizontal),
+ Self::Top => Some(Vertical),
+ Self::Bottom => Some(Vertical),
+ Self::Center => None,
+ }
+ }
+
+ /// Convert this to a generic alignment.
+ pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
+ let get = |spec: SpecAxis, align: GenAlign| {
+ let axis = spec.to_generic(axes);
+ if axes.get(axis).is_positive() { align } else { align.inv() }
+ };
+
+ match self {
+ Self::Left => get(Horizontal, Start),
+ Self::Right => get(Horizontal, End),
+ Self::Top => get(Vertical, Start),
+ Self::Bottom => get(Vertical, End),
+ Self::Center => GenAlign::Center,
+ }
+ }
+}
+
+impl Display for SpecAlign {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(match self {
+ Self::Left => "left",
+ Self::Right => "right",
+ Self::Top => "top",
+ Self::Bottom => "bottom",
+ Self::Center => "center",
+ })
+ }
+}
+
+/// Specifies whether to expand a layout to the full size of the space it is
+/// laid out in or to shrink it to fit the content.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct LayoutExpansion {
+ /// Whether to expand on the horizontal axis.
+ pub horizontal: bool,
+ /// Whether to expand on the vertical axis.
+ pub vertical: bool,
+}
+
+impl LayoutExpansion {
+ /// Create a new instance from the two values.
+ pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion {
+ LayoutExpansion { horizontal, vertical }
+ }
+
+ /// Return the expansion value for the given specific axis.
+ pub fn get(self, axis: SpecAxis) -> bool {
+ match axis {
+ Horizontal => self.horizontal,
+ Vertical => self.vertical,
+ }
+ }
+
+ /// Borrow the expansion value for the given specific axis mutably.
+ pub fn get_mut(&mut self, axis: SpecAxis) -> &mut bool {
+ match axis {
+ Horizontal => &mut self.horizontal,
+ Vertical => &mut self.vertical,
+ }
+ }
+}
+
+/// Defines how a given spacing interacts with (possibly existing) surrounding
+/// spacing.
+///
+/// There are two options for interaction: Hard and soft spacing. Typically,
+/// hard spacing is used when a fixed amount of space needs to be inserted no
+/// matter what. In contrast, soft spacing can be used to insert a default
+/// spacing between e.g. two words or paragraphs that can still be overridden by
+/// a hard space.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum SpacingKind {
+ /// Hard spaces are always laid out and consume surrounding soft space.
+ Hard,
+ /// Soft spaces are not laid out if they are touching a hard space and
+ /// consume neighbouring soft spaces with higher levels.
+ Soft(u32),
+}
+
+impl SpacingKind {
+ /// The standard spacing kind used for paragraph spacing.
+ pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1);
+
+ /// The standard spacing kind used for line spacing.
+ pub const LINE: SpacingKind = SpacingKind::Soft(2);
+
+ /// The standard spacing kind used for word spacing.
+ pub const WORD: SpacingKind = SpacingKind::Soft(1);
+}
+
+/// The spacing kind of the most recently inserted item in a layouting process.
+/// This is not about the last _spacing item_, but the last _item_, which is why
+/// this can be `None`.
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub(crate) enum LastSpacing {
+ /// The last item was hard spacing.
+ Hard,
+ /// The last item was soft spacing with the given width and level.
+ Soft(f64, u32),
+ /// The last item was not spacing.
+ None,
+}
+
+impl LastSpacing {
+ /// The width of the soft space if this is a soft space or zero otherwise.
+ pub(crate) fn soft_or_zero(self) -> f64 {
+ match self {
+ LastSpacing::Soft(space, _) => space,
+ _ => 0.0,
+ }
+ }
+}