Outset; fix folding

This commit is contained in:
Martin Haug 2022-05-01 13:21:07 +02:00
parent 5f1499d380
commit 84a4961a5d
12 changed files with 93 additions and 54 deletions

View File

@ -478,7 +478,7 @@ pub fn rect_paths(
]; ];
for (side, radius) in sides.into_iter().zip(radius.windows(2)) { for (side, radius) in sides.into_iter().zip(radius.windows(2)) {
let stroke_continuity = strokes.get(side) == strokes.get(side.clockwise()); let stroke_continuity = strokes.get(side) == strokes.get(side.next_cw());
connection = connection.advance(stroke_continuity && side != Side::Left); connection = connection.advance(stroke_continuity && side != Side::Left);
always_continuous &= stroke_continuity; always_continuous &= stroke_continuity;

View File

@ -128,7 +128,7 @@ impl Side {
} }
/// The next side, clockwise. /// The next side, clockwise.
pub fn clockwise(self) -> Self { pub fn next_cw(self) -> Self {
match self { match self {
Self::Left => Self::Top, Self::Left => Self::Top,
Self::Top => Self::Right, Self::Top => Self::Right,
@ -138,7 +138,7 @@ impl Side {
} }
/// The next side, counter-clockwise. /// The next side, counter-clockwise.
pub fn counter_clockwise(self) -> Self { pub fn next_ccw(self) -> Self {
match self { match self {
Self::Left => Self::Bottom, Self::Left => Self::Bottom,
Self::Top => Self::Left, Self::Top => Self::Left,

View File

@ -102,6 +102,7 @@ castable! {
} }
}, },
Value::Length(l) => Sides::splat(Some(l.into())), Value::Length(l) => Sides::splat(Some(l.into())),
Value::Ratio(r) => Sides::splat(Some(r.into())),
Value::Relative(r) => Sides::splat(Some(r)), Value::Relative(r) => Sides::splat(Some(r)),
} }
@ -117,15 +118,14 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
let inset = styles.get(Self::INSET); let inset = styles.get(Self::INSET);
// Pad the child. // Pad the child.
let child = child let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
.clone()
.padded(inset.map(|side| side.map(|abs| RawLength::from(abs))));
let mut pod = Regions::one(regions.first, regions.base, regions.expand); let mut pod = Regions::one(regions.first, regions.base, regions.expand);
frames = child.layout(ctx, &pod, styles)?; frames = child.layout(ctx, &pod, styles)?;
// Relayout with full expansion into square region to make sure // Relayout with full expansion into square region to make sure
// the result is really a square or circle. // the result is really a square or circle.
if is_quadratic(S) {
let length = if regions.expand.x || regions.expand.y { let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.first, Size::zero()); let target = regions.expand.select(regions.first, Size::zero());
target.x.max(target.y) target.x.max(target.y)
@ -138,12 +138,14 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
pod.first = Size::splat(length); pod.first = Size::splat(length);
pod.expand = Spec::splat(true); pod.expand = Spec::splat(true);
frames = child.layout(ctx, &pod, styles)?; frames = child.layout(ctx, &pod, styles)?;
}
} else { } else {
// The default size that a shape takes on if it has no child and // The default size that a shape takes on if it has no child and
// enough space. // enough space.
let mut size = let mut size =
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first); Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first);
if is_quadratic(S) {
let length = if regions.expand.x || regions.expand.y { let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.first, Size::zero()); let target = regions.expand.select(regions.first, Size::zero());
target.x.max(target.y) target.x.max(target.y)
@ -151,6 +153,9 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
size.x.min(size.y) size.x.min(size.y)
}; };
size = Size::splat(length); size = Size::splat(length);
} else {
size = regions.expand.select(regions.first, size);
}
frames = vec![Arc::new(Frame::new(size))]; frames = vec![Arc::new(Frame::new(size))];
} }
@ -162,27 +167,38 @@ impl<const S: ShapeKind> Layout for AngularNode<S> {
let stroke = match styles.get(Self::STROKE) { let stroke = match styles.get(Self::STROKE) {
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())), Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
Smart::Auto => Sides::splat(None), Smart::Auto => Sides::splat(None),
Smart::Custom(strokes) => strokes.map(|s| Some(s.unwrap_or_default())), Smart::Custom(strokes) => strokes.map(|s| s.map(|s| s.unwrap_or_default())),
}; };
let radius = { let outset = styles.get(Self::OUTSET);
let outset = Sides {
left: outset.left.relative_to(frame.size.x),
top: outset.top.relative_to(frame.size.y),
right: outset.right.relative_to(frame.size.x),
bottom: outset.bottom.relative_to(frame.size.y),
};
let size = Spec::new(
frame.size.x + outset.left + outset.right,
frame.size.y + outset.top + outset.bottom,
);
let radius = styles.get(Self::RADIUS); let radius = styles.get(Self::RADIUS);
let radius = Sides {
Sides { left: radius.left.relative_to(size.x / 2.0),
left: radius.left.relative_to(frame.size.x / 2.0), top: radius.top.relative_to(size.y / 2.0),
top: radius.top.relative_to(frame.size.y / 2.0), right: radius.right.relative_to(size.x / 2.0),
right: radius.right.relative_to(frame.size.x / 2.0), bottom: radius.bottom.relative_to(size.y / 2.0),
bottom: radius.bottom.relative_to(frame.size.y / 2.0),
}
}; };
if fill.is_some() || stroke.iter().any(Option::is_some) { if fill.is_some() || stroke.iter().any(Option::is_some) {
let shape = Shape { let shape = Shape {
geometry: Geometry::Rect(frame.size, radius), geometry: Geometry::Rect(size, radius),
fill, fill,
stroke, stroke,
}; };
frame.prepend(Point::zero(), Element::Shape(shape)); frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape));
} }
// Apply link if it exists. // Apply link if it exists.
@ -208,3 +224,13 @@ const CIRCLE: ShapeKind = 2;
/// A curve around two focal points. /// A curve around two focal points.
const ELLIPSE: ShapeKind = 3; const ELLIPSE: ShapeKind = 3;
/// Whether a shape kind is curvy.
fn is_round(kind: ShapeKind) -> bool {
matches!(kind, CIRCLE | ELLIPSE)
}
/// Whether a shape kind has equal side length.
fn is_quadratic(kind: ShapeKind) -> bool {
matches!(kind, SQUARE | CIRCLE)
}

View File

@ -8,7 +8,7 @@ use std::sync::Arc;
use super::{Content, Show, ShowNode}; use super::{Content, Show, ShowNode};
use crate::diag::{At, TypResult}; use crate::diag::{At, TypResult};
use crate::eval::{Args, Func, Node, Smart, Value}; use crate::eval::{Args, Func, Node, Smart, Value};
use crate::geom::{Numeric, Relative, Sides, Spec}; use crate::geom::{Length, Numeric, Relative, Sides, Spec};
use crate::library::layout::PageNode; use crate::library::layout::PageNode;
use crate::library::structure::{EnumNode, ListNode}; use crate::library::structure::{EnumNode, ListNode};
use crate::library::text::{FontFamily, ParNode, TextNode}; use crate::library::text::{FontFamily, ParNode, TextNode};
@ -459,17 +459,30 @@ where
} }
} }
impl<T> Fold for Sides<Option<T>> impl<T> Fold for Sides<T>
where where
T: Default, T: Fold,
{ {
type Output = Sides<T>; type Output = Sides<T::Output>;
fn fold(self, outer: Self::Output) -> Self::Output {
Sides {
left: self.left.fold(outer.left),
top: self.top.fold(outer.top),
right: self.right.fold(outer.right),
bottom: self.bottom.fold(outer.bottom),
}
}
}
impl Fold for Sides<Option<Relative<Length>>> {
type Output = Sides<Relative<Length>>;
fn fold(self, outer: Self::Output) -> Self::Output { fn fold(self, outer: Self::Output) -> Self::Output {
Sides { Sides {
left: self.left.unwrap_or(outer.left), left: self.left.unwrap_or(outer.left),
right: self.right.unwrap_or(outer.right),
top: self.top.unwrap_or(outer.top), top: self.top.unwrap_or(outer.top),
right: self.right.unwrap_or(outer.right),
bottom: self.bottom.unwrap_or(outer.bottom), bottom: self.bottom.unwrap_or(outer.bottom),
} }
} }

View File

@ -11,7 +11,7 @@
// Syntax sugar for function definitions. // Syntax sugar for function definitions.
#let fill = conifer #let fill = conifer
#let rect(body) = rect(width: 2cm, fill: fill, padding: 5pt, body) #let rect(body) = rect(width: 2cm, fill: fill, inset: 5pt, body)
#rect[Hi!] #rect[Hi!]
--- ---

View File

@ -7,6 +7,6 @@
#let d = 3 #let d = 3
#let value = [hi] #let value = [hi]
#let item(a, b) = a + b #let item(a, b) = a + b
#let fn = rect.with(fill: conifer, padding: 5pt) #let fn = rect.with(fill: conifer, inset: 5pt)
Some _includable_ text. Some _includable_ text.

View File

@ -11,7 +11,7 @@
--- ---
// Test alignment in automatically sized square and circle. // Test alignment in automatically sized square and circle.
#set text(8pt) #set text(8pt)
#square(padding: 4pt)[ #square(inset: 4pt)[
Hey there, #align(center + bottom, rotate(180deg, [you!])) Hey there, #align(center + bottom, rotate(180deg, [you!]))
] ]
#circle(align(center + horizon, [Hey.])) #circle(align(center + horizon, [Hey.]))

View File

@ -16,13 +16,13 @@ Auto-sized circle. \
Center-aligned rect in auto-sized circle. Center-aligned rect in auto-sized circle.
#circle(fill: forest, stroke: conifer, #circle(fill: forest, stroke: conifer,
align(center + horizon, align(center + horizon,
rect(fill: conifer, padding: 5pt)[But, soft!] rect(fill: conifer, inset: 5pt)[But, soft!]
) )
) )
Rect in auto-sized circle. \ Rect in auto-sized circle. \
#circle(fill: forest, #circle(fill: forest,
rect(fill: conifer, stroke: white, padding: 4pt)[ rect(fill: conifer, stroke: white, inset: 4pt)[
#set text(8pt) #set text(8pt)
But, soft! what light through yonder window breaks? But, soft! what light through yonder window breaks?
] ]

View File

@ -8,7 +8,7 @@
#set page(width: 150pt) #set page(width: 150pt)
// Fit to text. // Fit to text.
#rect(fill: conifer, padding: 3pt)[Textbox] #rect(fill: conifer, inset: 3pt)[Textbox]
// Empty with fixed width and height. // Empty with fixed width and height.
#block(rect( #block(rect(
@ -18,7 +18,7 @@
)) ))
// Fixed width, text height. // Fixed width, text height.
#rect(width: 2cm, fill: rgb("9650d6"), padding: 5pt)[Fixed and padded] #rect(width: 2cm, fill: rgb("9650d6"), inset: 5pt)[Fixed and padded]
// Page width, fixed height. // Page width, fixed height.
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft] #rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]

View File

@ -7,7 +7,7 @@
--- ---
// Test auto-sized square. // Test auto-sized square.
#square(fill: eastern, padding: 5pt)[ #square(fill: eastern, inset: 5pt)[
#set text(fill: white, weight: "bold") #set text(fill: white, weight: "bold")
Typst Typst
] ]

View File

@ -16,7 +16,7 @@
// Test the `columns` function. // Test the `columns` function.
#set page(width: auto) #set page(width: auto)
#rect(width: 180pt, height: 100pt, padding: 8pt, columns(2, [ #rect(width: 180pt, height: 100pt, inset: 8pt, columns(2, [
A special plight has befallen our document. A special plight has befallen our document.
Columns in text boxes reigned down unto the soil Columns in text boxes reigned down unto the soil
to waste a year's crop of rich layouts. to waste a year's crop of rich layouts.
@ -40,7 +40,7 @@ a page for a test but it does get the job done.
// Test the expansion behavior. // Test the expansion behavior.
#set page(height: 2.5cm, width: 7.05cm) #set page(height: 2.5cm, width: 7.05cm)
#rect(padding: 6pt, columns(2, [ #rect(inset: 6pt, columns(2, [
ABC \ ABC \
BCD BCD
#colbreak() #colbreak()
@ -73,7 +73,7 @@ D
// Test an empty second column. // Test an empty second column.
#set page(width: 7.05cm, columns: 2) #set page(width: 7.05cm, columns: 2)
#rect(width: 100%, padding: 3pt)[So there isn't anything in the second column?] #rect(width: 100%, inset: 3pt)[So there isn't anything in the second column?]
--- ---
// Test columns when one of them is empty. // Test columns when one of them is empty.

View File

@ -16,17 +16,17 @@
// but the B should be center-aligned. // but the B should be center-aligned.
#set par(align: center) #set par(align: center)
#par(align: right)[ #par(align: right)[
A #rect(width: 2cm, fill: conifer, padding: 4pt)[B] A #rect(width: 2cm, fill: conifer, inset: 4pt)[B]
] ]
--- ---
// The inner rectangle should also be yellow here. // The inner rectangle should also be yellow here.
// (and therefore invisible) // (and therefore invisible)
[#set rect(fill: yellow);#text(1em, rect(padding: 5pt, rect()))] [#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
--- ---
// The inner rectangle should not be yellow here. // The inner rectangle should not be yellow here.
A #rect(fill: yellow, padding: 5pt, rect()) B A #rect(fill: yellow, inset: 5pt, rect()) B
--- ---
// The inner list should not be indented extra. // The inner list should not be indented extra.