Make radius configuration unconfusing
This commit is contained in:
parent
6832ca2a26
commit
7a6c2cce77
@ -76,6 +76,11 @@ impl Dict {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the value if the dictionary contains the given key.
|
||||
pub fn take(&mut self, key: &str) -> Option<Value> {
|
||||
Arc::make_mut(&mut self.0).remove(key)
|
||||
}
|
||||
|
||||
/// Clear the dictionary.
|
||||
pub fn clear(&mut self) {
|
||||
if Arc::strong_count(&self.0) == 1 {
|
||||
|
@ -8,7 +8,8 @@ use std::sync::Arc;
|
||||
use super::{ops, Args, Array, Dict, Func, RawLength, Regex};
|
||||
use crate::diag::{with_alternative, StrResult};
|
||||
use crate::geom::{
|
||||
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
|
||||
Angle, Color, Corners, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor,
|
||||
Sides,
|
||||
};
|
||||
use crate::library::text::RawNode;
|
||||
use crate::model::{Content, Group, Layout, LayoutNode, Pattern};
|
||||
@ -516,6 +517,71 @@ impl<T: Cast> Cast<Spanned<Value>> for Spanned<T> {
|
||||
}
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Dir: "direction",
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Regex: "regular expression",
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Group: "group",
|
||||
}
|
||||
|
||||
castable! {
|
||||
usize,
|
||||
Expected: "non-negative integer",
|
||||
Value::Int(int) => int.try_into().map_err(|_| {
|
||||
if int < 0 {
|
||||
"must be at least zero"
|
||||
} else {
|
||||
"number too large"
|
||||
}
|
||||
})?,
|
||||
}
|
||||
|
||||
castable! {
|
||||
NonZeroUsize,
|
||||
Expected: "positive integer",
|
||||
Value::Int(int) => int
|
||||
.try_into()
|
||||
.and_then(|int: usize| int.try_into())
|
||||
.map_err(|_| if int <= 0 {
|
||||
"must be positive"
|
||||
} else {
|
||||
"number too large"
|
||||
})?,
|
||||
}
|
||||
|
||||
castable! {
|
||||
Paint,
|
||||
Expected: "color",
|
||||
Value::Color(color) => Paint::Solid(color),
|
||||
}
|
||||
|
||||
castable! {
|
||||
String,
|
||||
Expected: "string",
|
||||
Value::Str(string) => string.into(),
|
||||
}
|
||||
|
||||
castable! {
|
||||
LayoutNode,
|
||||
Expected: "content",
|
||||
Value::None => Self::default(),
|
||||
Value::Str(text) => Content::Text(text).pack(),
|
||||
Value::Content(content) => content.pack(),
|
||||
}
|
||||
|
||||
castable! {
|
||||
Pattern,
|
||||
Expected: "function, string or regular expression",
|
||||
Value::Func(func) => Self::Node(func.node()?),
|
||||
Value::Str(text) => Self::text(&text),
|
||||
@regex: Regex => Self::Regex(regex.clone()),
|
||||
}
|
||||
|
||||
impl<T: Cast> Cast for Option<T> {
|
||||
fn is(value: &Value) -> bool {
|
||||
matches!(value, Value::None) || T::is(value)
|
||||
@ -609,112 +675,84 @@ impl<T: Cast> Cast for Smart<T> {
|
||||
|
||||
impl<T> Cast for Sides<T>
|
||||
where
|
||||
T: Cast + Default + Clone,
|
||||
T: Cast + Default + Copy,
|
||||
{
|
||||
fn is(value: &Value) -> bool {
|
||||
matches!(value, Value::Dict(_)) || T::is(value)
|
||||
}
|
||||
|
||||
fn cast(value: Value) -> StrResult<Self> {
|
||||
match value {
|
||||
Value::Dict(dict) => {
|
||||
for (key, _) in &dict {
|
||||
if !matches!(
|
||||
key.as_str(),
|
||||
"left" | "top" | "right" | "bottom" | "x" | "y" | "rest"
|
||||
) {
|
||||
return Err(format!("unexpected key {key:?}"));
|
||||
}
|
||||
}
|
||||
fn cast(mut value: Value) -> StrResult<Self> {
|
||||
if let Value::Dict(dict) = &mut value {
|
||||
let mut take = |key| dict.take(key).map(T::cast).transpose();
|
||||
|
||||
let sides = Sides {
|
||||
left: dict.get("left").or(dict.get("x")),
|
||||
top: dict.get("top").or(dict.get("y")),
|
||||
right: dict.get("right").or(dict.get("x")),
|
||||
bottom: dict.get("bottom").or(dict.get("y")),
|
||||
};
|
||||
let rest = take("rest")?;
|
||||
let x = take("x")?.or(rest);
|
||||
let y = take("y")?.or(rest);
|
||||
let sides = Sides {
|
||||
left: take("left")?.or(x),
|
||||
top: take("top")?.or(y),
|
||||
right: take("right")?.or(x),
|
||||
bottom: take("bottom")?.or(y),
|
||||
};
|
||||
|
||||
Ok(sides.map(|side| {
|
||||
side.or(dict.get("rest"))
|
||||
.cloned()
|
||||
.and_then(T::cast)
|
||||
.unwrap_or_default()
|
||||
}))
|
||||
if let Some((key, _)) = dict.iter().next() {
|
||||
return Err(format!("unexpected key {key:?}"));
|
||||
}
|
||||
v => T::cast(v).map(Sides::splat).map_err(|msg| {
|
||||
|
||||
Ok(sides.map(Option::unwrap_or_default))
|
||||
} else {
|
||||
T::cast(value).map(Self::splat).map_err(|msg| {
|
||||
with_alternative(
|
||||
msg,
|
||||
"dictionary with any of `left`, `top`, `right`, `bottom`, \
|
||||
"dictionary with any of \
|
||||
`left`, `top`, `right`, `bottom`, \
|
||||
`x`, `y`, or `rest` as keys",
|
||||
)
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Dir: "direction",
|
||||
}
|
||||
impl<T> Cast for Corners<T>
|
||||
where
|
||||
T: Cast + Default + Copy,
|
||||
{
|
||||
fn is(value: &Value) -> bool {
|
||||
matches!(value, Value::Dict(_)) || T::is(value)
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Regex: "regular expression",
|
||||
}
|
||||
fn cast(mut value: Value) -> StrResult<Self> {
|
||||
if let Value::Dict(dict) = &mut value {
|
||||
let mut take = |key| dict.take(key).map(T::cast).transpose();
|
||||
|
||||
dynamic! {
|
||||
Group: "group",
|
||||
}
|
||||
let rest = take("rest")?;
|
||||
let left = take("left")?.or(rest);
|
||||
let top = take("top")?.or(rest);
|
||||
let right = take("right")?.or(rest);
|
||||
let bottom = take("bottom")?.or(rest);
|
||||
let corners = Corners {
|
||||
top_left: take("top-left")?.or(top).or(left),
|
||||
top_right: take("top-right")?.or(top).or(right),
|
||||
bottom_right: take("bottom-right")?.or(bottom).or(right),
|
||||
bottom_left: take("bottom-left")?.or(bottom).or(left),
|
||||
};
|
||||
|
||||
castable! {
|
||||
usize,
|
||||
Expected: "non-negative integer",
|
||||
Value::Int(int) => int.try_into().map_err(|_| {
|
||||
if int < 0 {
|
||||
"must be at least zero"
|
||||
if let Some((key, _)) = dict.iter().next() {
|
||||
return Err(format!("unexpected key {key:?}"));
|
||||
}
|
||||
|
||||
Ok(corners.map(Option::unwrap_or_default))
|
||||
} else {
|
||||
"number too large"
|
||||
T::cast(value).map(Self::splat).map_err(|msg| {
|
||||
with_alternative(
|
||||
msg,
|
||||
"dictionary with any of \
|
||||
`top-left`, `top-right`, `bottom-right`, `bottom-left`, \
|
||||
`left`, `top`, `right`, `bottom`, or `rest` as keys",
|
||||
)
|
||||
})
|
||||
}
|
||||
})?,
|
||||
}
|
||||
|
||||
castable! {
|
||||
NonZeroUsize,
|
||||
Expected: "positive integer",
|
||||
Value::Int(int) => int
|
||||
.try_into()
|
||||
.and_then(|int: usize| int.try_into())
|
||||
.map_err(|_| if int <= 0 {
|
||||
"must be positive"
|
||||
} else {
|
||||
"number too large"
|
||||
})?,
|
||||
}
|
||||
|
||||
castable! {
|
||||
Paint,
|
||||
Expected: "color",
|
||||
Value::Color(color) => Paint::Solid(color),
|
||||
}
|
||||
|
||||
castable! {
|
||||
String,
|
||||
Expected: "string",
|
||||
Value::Str(string) => string.into(),
|
||||
}
|
||||
|
||||
castable! {
|
||||
LayoutNode,
|
||||
Expected: "content",
|
||||
Value::None => Self::default(),
|
||||
Value::Str(text) => Content::Text(text).pack(),
|
||||
Value::Content(content) => content.pack(),
|
||||
}
|
||||
|
||||
castable! {
|
||||
Pattern,
|
||||
Expected: "function, string or regular expression",
|
||||
Value::Func(func) => Self::Node(func.node()?),
|
||||
Value::Str(text) => Self::text(&text),
|
||||
@regex: Regex => Self::Regex(regex.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
122
src/geom/corners.rs
Normal file
122
src/geom/corners.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use super::*;
|
||||
|
||||
/// A container with components for the four corners of a rectangle.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Corners<T> {
|
||||
/// The value for the top left corner.
|
||||
pub top_left: T,
|
||||
/// The value for the top right corner.
|
||||
pub top_right: T,
|
||||
/// The value for the bottom right corner.
|
||||
pub bottom_right: T,
|
||||
/// The value for the bottom left corner.
|
||||
pub bottom_left: T,
|
||||
}
|
||||
|
||||
impl<T> Corners<T> {
|
||||
/// Create a new instance from the four components.
|
||||
pub const fn new(top_left: T, top_right: T, bottom_right: T, bottom_left: T) -> Self {
|
||||
Self {
|
||||
top_left,
|
||||
top_right,
|
||||
bottom_right,
|
||||
bottom_left,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an instance with four equal components.
|
||||
pub fn splat(value: T) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Self {
|
||||
top_left: value.clone(),
|
||||
top_right: value.clone(),
|
||||
bottom_right: value.clone(),
|
||||
bottom_left: value,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map the individual fields with `f`.
|
||||
pub fn map<F, U>(self, mut f: F) -> Corners<U>
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
Corners {
|
||||
top_left: f(self.top_left),
|
||||
top_right: f(self.top_right),
|
||||
bottom_right: f(self.bottom_right),
|
||||
bottom_left: f(self.bottom_left),
|
||||
}
|
||||
}
|
||||
|
||||
/// Zip two instances into an instance.
|
||||
pub fn zip<F, V, W>(self, other: Corners<V>, mut f: F) -> Corners<W>
|
||||
where
|
||||
F: FnMut(T, V) -> W,
|
||||
{
|
||||
Corners {
|
||||
top_left: f(self.top_left, other.top_left),
|
||||
top_right: f(self.top_right, other.top_right),
|
||||
bottom_right: f(self.bottom_right, other.bottom_right),
|
||||
bottom_left: f(self.bottom_left, other.bottom_left),
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the corners, starting with the top left corner,
|
||||
/// clockwise.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[
|
||||
&self.top_left,
|
||||
&self.top_right,
|
||||
&self.bottom_right,
|
||||
&self.bottom_left,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
/// Whether all sides are equal.
|
||||
pub fn is_uniform(&self) -> bool
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
self.top_left == self.top_right
|
||||
&& self.top_right == self.bottom_right
|
||||
&& self.bottom_right == self.bottom_left
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<Corner> for Corners<T> {
|
||||
type Component = T;
|
||||
|
||||
fn get(self, corner: Corner) -> T {
|
||||
match corner {
|
||||
Corner::TopLeft => self.top_left,
|
||||
Corner::TopRight => self.top_right,
|
||||
Corner::BottomRight => self.bottom_right,
|
||||
Corner::BottomLeft => self.bottom_left,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, corner: Corner) -> &mut T {
|
||||
match corner {
|
||||
Corner::TopLeft => &mut self.top_left,
|
||||
Corner::TopRight => &mut self.top_right,
|
||||
Corner::BottomRight => &mut self.bottom_right,
|
||||
Corner::BottomLeft => &mut self.bottom_left,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The four corners of a rectangle.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Corner {
|
||||
/// The top left corner.
|
||||
TopLeft,
|
||||
/// The top right corner.
|
||||
TopRight,
|
||||
/// The bottom right corner.
|
||||
BottomRight,
|
||||
/// The bottom left corner.
|
||||
BottomLeft,
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
mod macros;
|
||||
mod align;
|
||||
mod angle;
|
||||
mod corners;
|
||||
mod dir;
|
||||
mod em;
|
||||
mod fraction;
|
||||
@ -22,6 +23,7 @@ mod transform;
|
||||
|
||||
pub use align::*;
|
||||
pub use angle::*;
|
||||
pub use corners::*;
|
||||
pub use dir::*;
|
||||
pub use em::*;
|
||||
pub use fraction::*;
|
||||
|
@ -7,13 +7,13 @@ use std::mem;
|
||||
pub struct RoundedRect {
|
||||
/// The size of the rectangle.
|
||||
pub size: Size,
|
||||
/// The radius at each side.
|
||||
pub radius: Sides<Length>,
|
||||
/// The radius at each corner.
|
||||
pub radius: Corners<Length>,
|
||||
}
|
||||
|
||||
impl RoundedRect {
|
||||
/// Create a new rounded rectangle.
|
||||
pub fn new(size: Size, radius: Sides<Length>) -> Self {
|
||||
pub fn new(size: Size, radius: Corners<Length>) -> Self {
|
||||
Self { size, radius }
|
||||
}
|
||||
|
||||
@ -73,20 +73,20 @@ impl RoundedRect {
|
||||
let mut always_continuous = true;
|
||||
|
||||
for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] {
|
||||
let is_continuous = strokes.get(side) == strokes.get(side.next_cw());
|
||||
connection = connection.advance(is_continuous && side != Side::Left);
|
||||
always_continuous &= is_continuous;
|
||||
let continuous = strokes.get(side) == strokes.get(side.next_cw());
|
||||
connection = connection.advance(continuous && side != Side::Left);
|
||||
always_continuous &= continuous;
|
||||
|
||||
draw_side(
|
||||
&mut path,
|
||||
side,
|
||||
self.size,
|
||||
self.radius.get(side.next_ccw()),
|
||||
self.radius.get(side),
|
||||
self.radius.get(side.start_corner()),
|
||||
self.radius.get(side.end_corner()),
|
||||
connection,
|
||||
);
|
||||
|
||||
if !is_continuous {
|
||||
if !continuous {
|
||||
res.push((mem::take(&mut path), strokes.get(side)));
|
||||
}
|
||||
}
|
||||
@ -109,8 +109,8 @@ fn draw_side(
|
||||
path: &mut Path,
|
||||
side: Side,
|
||||
size: Size,
|
||||
radius_left: Length,
|
||||
radius_right: Length,
|
||||
start_radius: Length,
|
||||
end_radius: Length,
|
||||
connection: Connection,
|
||||
) {
|
||||
let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 });
|
||||
@ -118,23 +118,23 @@ fn draw_side(
|
||||
let length = size.get(side.axis());
|
||||
|
||||
// The arcs for a border of the rectangle along the x-axis, starting at (0,0).
|
||||
let p1 = Point::with_x(radius_left);
|
||||
let p1 = Point::with_x(start_radius);
|
||||
let mut arc1 = bezier_arc(
|
||||
p1 + Point::new(
|
||||
-angle_left.sin() * radius_left,
|
||||
(1.0 - angle_left.cos()) * radius_left,
|
||||
-angle_left.sin() * start_radius,
|
||||
(1.0 - angle_left.cos()) * start_radius,
|
||||
),
|
||||
Point::new(radius_left, radius_left),
|
||||
Point::new(start_radius, start_radius),
|
||||
p1,
|
||||
);
|
||||
|
||||
let p2 = Point::with_x(length - radius_right);
|
||||
let p2 = Point::with_x(length - end_radius);
|
||||
let mut arc2 = bezier_arc(
|
||||
p2,
|
||||
Point::new(length - radius_right, radius_right),
|
||||
Point::new(length - end_radius, end_radius),
|
||||
p2 + Point::new(
|
||||
angle_right.sin() * radius_right,
|
||||
(1.0 - angle_right.cos()) * radius_right,
|
||||
angle_right.sin() * end_radius,
|
||||
(1.0 - angle_right.cos()) * end_radius,
|
||||
),
|
||||
);
|
||||
|
||||
@ -152,16 +152,16 @@ fn draw_side(
|
||||
arc2 = arc2.map(|x| x.transform(transform));
|
||||
|
||||
if !connection.prev {
|
||||
path.move_to(if radius_left.is_zero() { arc1[3] } else { arc1[0] });
|
||||
path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] });
|
||||
}
|
||||
|
||||
if !radius_left.is_zero() {
|
||||
if !start_radius.is_zero() {
|
||||
path.cubic_to(arc1[1], arc1[2], arc1[3]);
|
||||
}
|
||||
|
||||
path.line_to(arc2[0]);
|
||||
|
||||
if !connection.next && !radius_right.is_zero() {
|
||||
if !connection.next && !end_radius.is_zero() {
|
||||
path.cubic_to(arc2[1], arc2[2], arc2[3]);
|
||||
}
|
||||
}
|
||||
|
@ -48,17 +48,17 @@ impl<T> Sides<T> {
|
||||
/// Zip two instances into an instance.
|
||||
pub fn zip<F, V, W>(self, other: Sides<V>, mut f: F) -> Sides<W>
|
||||
where
|
||||
F: FnMut(T, V, Side) -> W,
|
||||
F: FnMut(T, V) -> W,
|
||||
{
|
||||
Sides {
|
||||
left: f(self.left, other.left, Side::Left),
|
||||
top: f(self.top, other.top, Side::Top),
|
||||
right: f(self.right, other.right, Side::Right),
|
||||
bottom: f(self.bottom, other.bottom, Side::Bottom),
|
||||
left: f(self.left, other.left),
|
||||
top: f(self.top, other.top),
|
||||
right: f(self.right, other.right),
|
||||
bottom: f(self.bottom, other.bottom),
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the sides.
|
||||
/// An iterator over the sides, starting with the left side, clockwise.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[&self.left, &self.top, &self.right, &self.bottom].into_iter()
|
||||
}
|
||||
@ -157,6 +157,21 @@ impl Side {
|
||||
}
|
||||
}
|
||||
|
||||
/// The first corner of the side in clockwise order.
|
||||
pub fn start_corner(self) -> Corner {
|
||||
match self {
|
||||
Self::Left => Corner::BottomLeft,
|
||||
Self::Top => Corner::TopLeft,
|
||||
Self::Right => Corner::TopRight,
|
||||
Self::Bottom => Corner::BottomRight,
|
||||
}
|
||||
}
|
||||
|
||||
/// The second corner of the side in clockwise order.
|
||||
pub fn end_corner(self) -> Corner {
|
||||
self.next_cw().start_corner()
|
||||
}
|
||||
|
||||
/// Return the corresponding axis.
|
||||
pub fn axis(self) -> SpecAxis {
|
||||
match self {
|
||||
|
@ -33,9 +33,11 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
||||
/// How much to extend the shape's dimensions beyond the allocated space.
|
||||
#[property(resolve, fold)]
|
||||
pub const OUTSET: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
|
||||
|
||||
/// How much to round the shape's corners.
|
||||
#[property(skip, resolve, fold)]
|
||||
pub const RADIUS: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
|
||||
pub const RADIUS: Corners<Option<Relative<RawLength>>> =
|
||||
Corners::splat(Relative::zero());
|
||||
|
||||
fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> {
|
||||
let size = match S {
|
||||
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
|
||||
use super::{Interruption, NodeId, StyleChain};
|
||||
use crate::eval::{RawLength, Smart};
|
||||
use crate::geom::{Length, Numeric, Relative, Sides, Spec};
|
||||
use crate::geom::{Corners, Length, Numeric, Relative, Sides, Spec};
|
||||
use crate::library::layout::PageNode;
|
||||
use crate::library::structure::{EnumNode, ListNode};
|
||||
use crate::library::text::ParNode;
|
||||
@ -191,12 +191,15 @@ impl<T: Resolve> Resolve for Sides<T> {
|
||||
type Output = Sides<T::Output>;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
Sides {
|
||||
left: self.left.resolve(styles),
|
||||
right: self.right.resolve(styles),
|
||||
top: self.top.resolve(styles),
|
||||
bottom: self.bottom.resolve(styles),
|
||||
}
|
||||
self.map(|v| v.resolve(styles))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Resolve> Resolve for Corners<T> {
|
||||
type Output = Corners<T::Output>;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
self.map(|v| v.resolve(styles))
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,7 +255,7 @@ where
|
||||
type Output = Sides<T::Output>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer, _| inner.fold(outer))
|
||||
self.zip(outer, |inner, outer| inner.fold(outer))
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,7 +263,7 @@ impl Fold for Sides<Option<Relative<Length>>> {
|
||||
type Output = Sides<Relative<Length>>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer, _| inner.unwrap_or(outer))
|
||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +271,26 @@ impl Fold for Sides<Option<Smart<Relative<RawLength>>>> {
|
||||
type Output = Sides<Smart<Relative<RawLength>>>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer, _| inner.unwrap_or(outer))
|
||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fold for Corners<T>
|
||||
where
|
||||
T: Fold,
|
||||
{
|
||||
type Output = Corners<T::Output>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer| inner.fold(outer))
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for Corners<Option<Relative<Length>>> {
|
||||
type Output = Corners<Relative<Length>>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||
}
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -30,8 +30,13 @@
|
||||
|
||||
// Rounded corners.
|
||||
#rect(width: 2cm, radius: 60%)
|
||||
#rect(width: 1cm, radius: (x: 5pt, y: 10pt))
|
||||
#rect(width: 1.25cm, radius: (left: 2pt, top: 5pt, right: 8pt, bottom: 11pt))
|
||||
#rect(width: 1cm, radius: (left: 10pt, right: 5pt))
|
||||
#rect(width: 1.25cm, radius: (
|
||||
top-left: 2pt,
|
||||
top-right: 5pt,
|
||||
bottom-right: 8pt,
|
||||
bottom-left: 11pt
|
||||
))
|
||||
|
||||
// Different strokes.
|
||||
[
|
||||
@ -54,3 +59,11 @@ Use the `*const T` pointer or the `&mut T` reference.
|
||||
---
|
||||
// Error: 15-38 unexpected key "cake"
|
||||
#rect(radius: (left: 10pt, cake: 5pt))
|
||||
|
||||
---
|
||||
// Error: 15-21 expected stroke or none or dictionary with any of `left`, `top`, `right`, `bottom`, `x`, `y`, or `rest` as keys or auto, found array
|
||||
#rect(stroke: (1, 2))
|
||||
|
||||
---
|
||||
// Error: 15-19 expected relative length or none or dictionary with any of `top-left`, `top-right`, `bottom-right`, `bottom-left`, `left`, `top`, `right`, `bottom`, or `rest` as keys, found color
|
||||
#rect(radius: blue)
|
||||
|
Loading…
x
Reference in New Issue
Block a user