diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-12-13 23:59:01 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-12-13 23:59:01 +0100 |
| commit | 665b4d2aca81af48b8e0eaca4e709ef2e7825844 (patch) | |
| tree | 4ada33f607455f14b6a170fe4b7fbe173056567b /src/library/maps | |
| parent | 971ff3a2dcff1e68bf7e19017113469aad5a30c2 (diff) | |
More consistent library code and functions 🎄
Diffstat (limited to 'src/library/maps')
| -rw-r--r-- | src/library/maps/alignment.rs | 76 | ||||
| -rw-r--r-- | src/library/maps/axis.rs | 122 | ||||
| -rw-r--r-- | src/library/maps/mod.rs | 103 | ||||
| -rw-r--r-- | src/library/maps/padding.rs | 92 |
4 files changed, 393 insertions, 0 deletions
diff --git a/src/library/maps/alignment.rs b/src/library/maps/alignment.rs new file mode 100644 index 00000000..486c8b2e --- /dev/null +++ b/src/library/maps/alignment.rs @@ -0,0 +1,76 @@ +use super::*; +use AlignmentKey::*; + +/// An argument key which describes a target alignment. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AlignmentKey { + Align(Alignment), + Left, + Top, + Right, + Bottom, +} + +impl AlignmentKey { + /// The generic axis this alignment key corresponds to in the given system + /// of layouting axes. `None` if the alignment is generic. + pub fn axis(self, axes: LayoutAxes) -> Option<GenericAxis> { + match self { + Left | Right => Some(Horizontal.to_generic(axes)), + Top | Bottom => Some(Vertical.to_generic(axes)), + Align(_) => None, + } + } + + /// The generic version of this alignment in the given system of layouting + /// axes. + /// + /// Returns an error if the alignment is invalid for the given axis. + pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> LayoutResult<Alignment> { + let specific = axis.to_specific(axes); + let start = match axes.get(axis).is_positive() { + true => Origin, + false => End, + }; + + Ok(match (self, specific) { + (Align(alignment), _) => alignment, + (Left, Horizontal) | (Top, Vertical) => start, + (Right, Horizontal) | (Bottom, Vertical) => start.inv(), + + _ => error!( + "invalid alignment `{}` for {} axis", + format!("{:?}", self).to_lowercase(), + format!("{:?}", axis).to_lowercase() + ) + }) + } + + /// The specific version of this alignment in the given system of layouting + /// axes. + pub fn to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentKey { + let direction = axes.get_specific(axis); + if let Align(alignment) = self { + match (direction, alignment) { + (LeftToRight, Origin) | (RightToLeft, End) => Left, + (LeftToRight, End) | (RightToLeft, Origin) => Right, + (TopToBottom, Origin) | (BottomToTop, End) => Top, + (TopToBottom, End) | (BottomToTop, Origin) => Bottom, + (_, Center) => self, + } + } else { + self + } + } +} + +key!(AlignmentKey, "alignment", + "origin" => Align(Origin), + "center" => Align(Center), + "end" => Align(End), + + "left" => Left, + "top" => Top, + "right" => Right, + "bottom" => Bottom, +); diff --git a/src/library/maps/axis.rs b/src/library/maps/axis.rs new file mode 100644 index 00000000..a177a324 --- /dev/null +++ b/src/library/maps/axis.rs @@ -0,0 +1,122 @@ +use super::*; +use AxisKey::*; + +/// An argument key which identifies a layouting axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum AxisKey { + Generic(GenericAxis), + Specific(SpecificAxis), +} + +impl AxisKey { + /// The generic version of this axis key in the given system of axes. + pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { + match self { + Generic(axis) => axis, + Specific(axis) => axis.to_generic(axes), + } + } + + /// The specific version of this axis key in the given system of axes. + pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { + match self { + Generic(axis) => axis.to_specific(axes), + Specific(axis) => axis, + } + } +} + +key!(AxisKey, "axis", + "horizontal" | "h" => Specific(Horizontal), + "vertical" | "v" => Specific(Vertical), + "primary" | "p" => Generic(Primary), + "secondary" | "s" => Generic(Secondary), +); + +/// A map for storing extents along axes. +#[derive(Debug, Clone, PartialEq)] +pub struct ExtentMap<E: ExpressionKind + Copy>(ConsistentMap<AxisKey, E>); + +impl<E: ExpressionKind + Copy> ExtentMap<E> { + /// Parse an extent map from the function args. + /// + /// If `enforce` is true other arguments will create an error, otherwise + /// they are left intact. + pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult<ExtentMap<E>> { + let mut map = ConsistentMap::new(); + + for arg in args.keys() { + let key = match arg.v.key.v.0.as_str() { + "width" | "w" => AxisKey::Specific(Horizontal), + "height" | "h" => AxisKey::Specific(Vertical), + "primary-size" | "ps" => AxisKey::Generic(Primary), + "secondary-size" | "ss" => AxisKey::Generic(Secondary), + + _ => if enforce { + error!("expected dimension") + } else { + args.add_key(arg); + continue; + } + }; + + let e = E::from_expr(arg.v.value)?; + map.add(key, e)?; + } + + Ok(ExtentMap(map)) + } + + /// Deduplicate from generic to specific axes. + pub fn dedup(&self, axes: LayoutAxes) -> LayoutResult<ConsistentMap<SpecificAxis, E>> { + self.0.dedup(|key, &val| Ok((key.to_specific(axes), val))) + } +} + +/// An argument key which identifies an axis, but allows for positional +/// arguments with unspecified axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PosAxisKey { + /// The first positional argument. + First, + /// The second positional argument. + Second, + /// An axis keyword argument. + Keyword(AxisKey), +} + +/// A map for storing some data for via keyword or positionally given axes. +#[derive(Debug, Clone, PartialEq)] +pub struct PosAxisMap<E: ExpressionKind + Copy>(ConsistentMap<PosAxisKey, E>); + +impl<E: ExpressionKind + Copy> PosAxisMap<E> { + /// Parse a positional axis map from the function args. + pub fn new(args: &mut FuncArgs) -> ParseResult<PosAxisMap<E>> { + let mut map = ConsistentMap::new(); + + map.add_opt(PosAxisKey::First, args.get_pos_opt::<E>()?)?; + map.add_opt(PosAxisKey::Second, args.get_pos_opt::<E>()?)?; + + for arg in args.keys() { + let axis = AxisKey::from_ident(&arg.v.key)?; + let value = E::from_expr(arg.v.value)?; + + map.add(PosAxisKey::Keyword(axis), value)?; + } + + Ok(PosAxisMap(map)) + } + + /// Deduplicate from positional or specific to generic axes. + pub fn dedup<F>(&self, axes: LayoutAxes, f: F) -> LayoutResult<ConsistentMap<GenericAxis, E>> + where F: Fn(E) -> Option<GenericAxis> { + self.0.dedup(|key, &e| { + Ok((match key { + PosAxisKey::First => f(e).unwrap_or(Primary), + PosAxisKey::Second => f(e).unwrap_or(Secondary), + PosAxisKey::Keyword(AxisKey::Specific(axis)) => axis.to_generic(axes), + PosAxisKey::Keyword(AxisKey::Generic(axis)) => *axis, + }, e)) + }) + } +} diff --git a/src/library/maps/mod.rs b/src/library/maps/mod.rs new file mode 100644 index 00000000..284c7181 --- /dev/null +++ b/src/library/maps/mod.rs @@ -0,0 +1,103 @@ +//! Deduplicating maps and keys for argument parsing. + +use std::collections::HashMap; +use std::hash::Hash; + +use crate::func::prelude::*; + +macro_rules! key { + ($type:ty, $name:expr, $($patterns:tt)*) => { + impl $type { + /// Parse this key from an identifier. + pub fn from_ident(ident: &Spanned<Ident>) -> ParseResult<Self> { + Ok(match ident.v.0.as_str() { + $($patterns)* + _ => error!("expected {}", <Self as ExpressionKind>::NAME), + }) + } + } + + impl ExpressionKind for $type { + const NAME: &'static str = $name; + + fn from_expr(expr: Spanned<Expression>) -> ParseResult<Self> { + if let Expression::Ident(ident) = expr.v { + Self::from_ident(&Spanned::new(ident, expr.span)) + } else { + error!("expected {}", Self::NAME); + } + } + } + }; +} + +pub_use_mod!(axis); +pub_use_mod!(alignment); +pub_use_mod!(padding); + +/// A deduplicating map type useful for storing possibly redundant arguments. +#[derive(Debug, Clone, PartialEq)] +pub struct ConsistentMap<K, V> where K: Hash + Eq { + map: HashMap<K, V>, +} + +impl<K, V> ConsistentMap<K, V> where K: Hash + Eq { + pub fn new() -> ConsistentMap<K, V> { + ConsistentMap { map: HashMap::new() } + } + + /// Add a key-value pair. + pub fn add(&mut self, key: K, value: V) -> ParseResult<()> { + match self.map.insert(key, value) { + Some(_) => error!("duplicate argument"), + None => Ok(()) + } + } + + /// Add a key-value pair if the value is not `None`. + pub fn add_opt(&mut self, key: K, value: Option<V>) -> ParseResult<()> { + Ok(if let Some(value) = value { + self.add(key, value)?; + }) + } + + /// Get the value at a key if it is present. + pub fn get(&self, key: K) -> Option<&V> { + self.map.get(&key) + } + + /// Call a function with the value if the key is present. + pub fn with<F>(&self, key: K, callback: F) where F: FnOnce(&V) { + if let Some(value) = self.map.get(&key) { + callback(value); + } + } + + /// Create a new consistent map where keys and values are mapped to new keys + /// and values. + /// + /// Returns an error if a new key is duplicate. + pub fn dedup<F, K2, V2>(&self, f: F) -> LayoutResult<ConsistentMap<K2, V2>> + where F: Fn(&K, &V) -> ParseResult<(K2, V2)>, K2: Hash + Eq { + let mut map = ConsistentMap::new(); + + for (key, value) in self.map.iter() { + let (key, value) = f(key, value)?; + map.add(key, value)?; + } + + Ok(map) + } + + /// Iterate over the (key, value) pairs. + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> { + self.map.iter() + } +} + +key!(Direction, "direction", + "left-to-right" | "ltr" => LeftToRight, + "right-to-left" | "rtl" => RightToLeft, + "top-to-bottom" | "ttb" => TopToBottom, + "bottom-to-top" | "btt" => BottomToTop, +); diff --git a/src/library/maps/padding.rs b/src/library/maps/padding.rs new file mode 100644 index 00000000..37f2ba4a --- /dev/null +++ b/src/library/maps/padding.rs @@ -0,0 +1,92 @@ +use super::*; +use AxisKey::*; +use AlignmentKey::*; +use PaddingKey::*; + +/// An argument key which identifies a margin or padding target. +/// +/// A is the used axis type. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum PaddingKey<A> { + /// All four sides should have the specified padding. + All, + /// Both sides of the given axis should have the specified padding. + Both(A), + /// Only the given side of the given axis should have the specified padding. + Side(A, AlignmentKey), +} + +key!(PaddingKey<AxisKey>, "axis or side", + "horizontal" | "h" => Both(Specific(Horizontal)), + "vertical" | "v" => Both(Specific(Vertical)), + "primary" | "p" => Both(Generic(Primary)), + "secondary" | "s" => Both(Generic(Secondary)), + + "left" => Side(Specific(Horizontal), Left), + "right" => Side(Specific(Horizontal), Right), + "top" => Side(Specific(Vertical), Top), + "bottom" => Side(Specific(Vertical), Bottom), + + "primary-origin" => Side(Generic(Primary), Align(Origin)), + "primary-end" => Side(Generic(Primary), Align(End)), + "secondary-origin" => Side(Generic(Secondary), Align(Origin)), + "secondary-end" => Side(Generic(Secondary), Align(End)), + "horizontal-origin" => Side(Specific(Horizontal), Align(Origin)), + "horizontal-end" => Side(Specific(Horizontal), Align(End)), + "vertical-origin" => Side(Specific(Vertical), Align(Origin)), + "vertical-end" => Side(Specific(Vertical), Align(End)), +); + +/// A map for storing padding at sides. +#[derive(Debug, Clone, PartialEq)] +pub struct PaddingMap(ConsistentMap<PaddingKey<AxisKey>, Size>); + +impl PaddingMap { + /// Parse a padding map from the function args. + pub fn new(args: &mut FuncArgs) -> ParseResult<PaddingMap> { + let mut map = ConsistentMap::new(); + map.add_opt(PaddingKey::All, args.get_pos_opt::<Size>()?)?; + + for arg in args.keys() { + let key = PaddingKey::from_ident(&arg.v.key)?; + let size = Size::from_expr(arg.v.value)?; + map.add(key, size)?; + } + + Ok(PaddingMap(map)) + } + + /// Apply the specified padding on the size box. + pub fn apply(&self, axes: LayoutAxes, padding: &mut SizeBox) -> LayoutResult<()> { + use PaddingKey::*; + + let map = self.0.dedup(|key, &val| { + Ok((match key { + All => All, + Both(axis) => Both(axis.to_specific(axes)), + Side(axis, alignment) => { + let axis = axis.to_specific(axes); + Side(axis, alignment.to_specific(axes, axis)) + } + }, val)) + })?; + + map.with(All, |&val| padding.set_all(val)); + map.with(Both(Horizontal), |&val| padding.set_horizontal(val)); + map.with(Both(Vertical), |&val| padding.set_vertical(val)); + + for (key, &val) in map.iter() { + if let Side(_, alignment) = key { + match alignment { + AlignmentKey::Left => padding.left = val, + AlignmentKey::Right => padding.right = val, + AlignmentKey::Top => padding.top = val, + AlignmentKey::Bottom => padding.bottom = val, + _ => {}, + } + } + } + + Ok(()) + } +} |
