Move aspect ratio into stack

This commit is contained in:
Laurenz 2021-05-17 15:23:04 +02:00
parent 1003d320d4
commit 24c4a746bc
5 changed files with 41 additions and 43 deletions

View File

@ -197,7 +197,7 @@ impl StackBuilder {
children.extend(last.any()); children.extend(last.any());
children.push(par); children.push(par);
} }
StackNode { dirs, children } StackNode { dirs, aspect: None, children }
} }
} }

View File

@ -7,10 +7,6 @@ pub struct FixedNode {
pub width: Option<Linear>, pub width: Option<Linear>,
/// The fixed height, if any. /// The fixed height, if any.
pub height: Option<Linear>, pub height: Option<Linear>,
/// The fixed aspect ratio between width and height, if any.
///
/// The resulting frame will satisfy `width = aspect * height`.
pub aspect: Option<f64>,
/// The child node whose size to fix. /// The child node whose size to fix.
pub child: AnyNode, pub child: AnyNode,
} }
@ -18,21 +14,13 @@ pub struct FixedNode {
impl Layout for FixedNode { impl Layout for FixedNode {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
let Areas { current, full, .. } = areas; let Areas { current, full, .. } = areas;
let size = Size::new(
let full = Size::new( self.width.map_or(current.width, |w| w.resolve(full.width)),
self.width.map(|w| w.resolve(full.width)).unwrap_or(current.width), self.height.map_or(current.height, |h| h.resolve(full.height)),
self.height.map(|h| h.resolve(full.height)).unwrap_or(current.height),
); );
let mut size = full;
if let Some(aspect) = self.aspect {
// Shrink the size to ensure that the aspect ratio can be satisfied.
let width = size.width.min(aspect * size.height);
size = Size::new(width, width / aspect);
}
let fixed = Spec::new(self.width.is_some(), self.height.is_some()); let fixed = Spec::new(self.width.is_some(), self.height.is_some());
let areas = Areas::once(size, full, fixed).with_aspect(self.aspect); let areas = Areas::once(size, size, fixed);
self.child.layout(ctx, &areas) self.child.layout(ctx, &areas)
} }
} }

View File

@ -155,10 +155,6 @@ pub struct Areas {
/// ///
/// If this is false, the frame will shrink to fit its content. /// If this is false, the frame will shrink to fit its content.
pub fixed: Spec<bool>, pub fixed: Spec<bool>,
/// The aspect ratio the resulting frame should respect.
///
/// This property is only handled by the stack layouter.
pub aspect: Option<f64>,
} }
impl Areas { impl Areas {
@ -170,7 +166,6 @@ impl Areas {
backlog: vec![], backlog: vec![],
last: None, last: None,
fixed, fixed,
aspect: None,
} }
} }
@ -182,16 +177,9 @@ impl Areas {
backlog: vec![], backlog: vec![],
last: Some(size), last: Some(size),
fixed, fixed,
aspect: None,
} }
} }
/// Builder-style method for setting the aspect ratio.
pub fn with_aspect(mut self, aspect: Option<f64>) -> Self {
self.aspect = aspect;
self
}
/// Map all areas. /// Map all areas.
pub fn map<F>(&self, mut f: F) -> Self pub fn map<F>(&self, mut f: F) -> Self
where where
@ -203,7 +191,6 @@ impl Areas {
backlog: self.backlog.iter().copied().map(|s| f(s)).collect(), backlog: self.backlog.iter().copied().map(|s| f(s)).collect(),
last: self.last.map(f), last: self.last.map(f),
fixed: self.fixed, fixed: self.fixed,
aspect: self.aspect,
} }
} }
@ -224,4 +211,10 @@ impl Areas {
self.current.is_nan() || size.is_nan() || self.current == size self.current.is_nan() || size.is_nan() || self.current == size
}) })
} }
/// Shrink `current` to ensure that the aspect ratio can be satisfied.
pub fn apply_aspect_ratio(&mut self, ratio: f64) {
let Size { width, height } = self.current;
self.current = Size::new(width.min(ratio * height), height.min(width / ratio));
}
} }

View File

@ -8,6 +8,10 @@ pub struct StackNode {
/// The children are stacked along the `main` direction. The `cross` /// The children are stacked along the `main` direction. The `cross`
/// direction is required for aligning the children. /// direction is required for aligning the children.
pub dirs: Gen<Dir>, pub dirs: Gen<Dir>,
/// The fixed aspect ratio between width and height, if any.
///
/// The resulting frames will satisfy `width = aspect * height`.
pub aspect: Option<f64>,
/// The nodes to be stacked. /// The nodes to be stacked.
pub children: Vec<StackChild>, pub children: Vec<StackChild>,
} }
@ -23,7 +27,7 @@ pub enum StackChild {
impl Layout for StackNode { impl Layout for StackNode {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Frame> {
let mut layouter = StackLayouter::new(self.dirs, areas.clone()); let mut layouter = StackLayouter::new(self.dirs, self.aspect, areas.clone());
for child in &self.children { for child in &self.children {
match *child { match *child {
StackChild::Spacing(amount) => layouter.push_spacing(amount), StackChild::Spacing(amount) => layouter.push_spacing(amount),
@ -52,6 +56,7 @@ impl From<StackNode> for AnyNode {
struct StackLayouter { struct StackLayouter {
dirs: Gen<Dir>, dirs: Gen<Dir>,
aspect: Option<f64>,
main: SpecAxis, main: SpecAxis,
areas: Areas, areas: Areas,
finished: Vec<Frame>, finished: Vec<Frame>,
@ -61,9 +66,14 @@ struct StackLayouter {
} }
impl StackLayouter { impl StackLayouter {
fn new(dirs: Gen<Dir>, areas: Areas) -> Self { fn new(dirs: Gen<Dir>, aspect: Option<f64>, mut areas: Areas) -> Self {
if let Some(aspect) = aspect {
areas.apply_aspect_ratio(aspect);
}
Self { Self {
dirs, dirs,
aspect,
main: dirs.main.axis(), main: dirs.main.axis(),
areas, areas,
finished: vec![], finished: vec![],
@ -112,7 +122,7 @@ impl StackLayouter {
if fixed.vertical { full.height } else { used.height }, if fixed.vertical { full.height } else { used.height },
); );
if let Some(aspect) = self.areas.aspect { if let Some(aspect) = self.aspect {
let width = size let width = size
.width .width
.max(aspect * size.height) .max(aspect * size.height)
@ -162,6 +172,10 @@ impl StackLayouter {
self.areas.next(); self.areas.next();
self.ruler = Align::Start; self.ruler = Align::Start;
self.size = Gen::ZERO; self.size = Gen::ZERO;
if let Some(aspect) = self.aspect {
self.areas.apply_aspect_ratio(aspect);
}
} }
fn finish(mut self) -> Vec<Frame> { fn finish(mut self) -> Vec<Frame> {

View File

@ -59,17 +59,19 @@ fn rect_impl(
body: TemplateValue, body: TemplateValue,
) -> Value { ) -> Value {
Value::template(name, move |ctx| { Value::template(name, move |ctx| {
let child = ctx.exec_group(&body).into(); let mut stack = ctx.exec_group(&body);
let node = FixedNode { width, height, aspect, child }; stack.aspect = aspect;
let fixed = FixedNode { width, height, child: stack.into() };
if let Some(color) = fill { if let Some(color) = fill {
ctx.push(BackgroundNode { ctx.push(BackgroundNode {
shape: BackgroundShape::Rect, shape: BackgroundShape::Rect,
fill: Fill::Color(color), fill: Fill::Color(color),
child: node.into(), child: fixed.into(),
}); });
} else { } else {
ctx.push(node); ctx.push(fixed);
} }
}) })
} }
@ -133,14 +135,15 @@ fn ellipse_impl(
// perfectly into the ellipse. // perfectly into the ellipse.
const PAD: f64 = 0.5 - SQRT_2 / 4.0; const PAD: f64 = 0.5 - SQRT_2 / 4.0;
let child = ctx.exec_group(&body).into(); let mut stack = ctx.exec_group(&body);
let node = FixedNode { stack.aspect = aspect;
let fixed = FixedNode {
width, width,
height, height,
aspect,
child: PadNode { child: PadNode {
padding: Sides::uniform(Relative::new(PAD).into()), padding: Sides::uniform(Relative::new(PAD).into()),
child, child: stack.into(),
} }
.into(), .into(),
}; };
@ -149,10 +152,10 @@ fn ellipse_impl(
ctx.push(BackgroundNode { ctx.push(BackgroundNode {
shape: BackgroundShape::Ellipse, shape: BackgroundShape::Ellipse,
fill: Fill::Color(color), fill: Fill::Color(color),
child: node.into(), child: fixed.into(),
}); });
} else { } else {
ctx.push(node); ctx.push(fixed);
} }
}) })
} }