summaryrefslogtreecommitdiff
path: root/src/layout/fixed.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout/fixed.rs')
-rw-r--r--src/layout/fixed.rs77
1 files changed, 62 insertions, 15 deletions
diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs
index 9fa2af8a..7f292f14 100644
--- a/src/layout/fixed.rs
+++ b/src/layout/fixed.rs
@@ -1,3 +1,5 @@
+use decorum::N64;
+
use super::*;
/// A node that can fix its child's width and height.
@@ -8,6 +10,10 @@ pub struct FixedNode {
pub width: Option<Linear>,
/// The fixed height, if any.
pub height: Option<Linear>,
+ /// The fixed aspect ratio between width and height.
+ ///
+ /// The resulting frame will satisfy `width = aspect * height`.
+ pub aspect: Option<N64>,
/// The child node whose size to fix.
pub child: LayoutNode,
}
@@ -16,33 +22,74 @@ impl Layout for FixedNode {
fn layout(
&self,
ctx: &mut LayoutContext,
- regions: &Regions,
+ &Regions { current, base, expand, .. }: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
- let Regions { current, base, .. } = regions;
- let mut constraints = Constraints::new(regions.expand);
- constraints.set_base_using_linears(Spec::new(self.width, self.height), &regions);
+ // Fill in width or height if aspect ratio and the other is given.
+ let aspect = self.aspect.map(N64::into_inner);
+ let width = self.width.or(self.height.zip(aspect).map(|(h, a)| a * h));
+ let height = self.height.or(self.width.zip(aspect).map(|(w, a)| w / a));
- let size = Size::new(
- self.width.map_or(current.width, |w| w.resolve(base.width)),
- self.height.map_or(current.height, |h| h.resolve(base.height)),
- );
+ // Prepare constraints.
+ let mut constraints = Constraints::new(expand);
+ constraints.set_base_if_linear(base, Spec::new(width, height));
- // If one dimension was not specified, the `current` size needs to remain static.
- if self.width.is_none() {
+ // If the size for one axis isn't specified, the `current` size along
+ // that axis needs to remain the same for the result to be reusable.
+ if width.is_none() {
constraints.exact.horizontal = Some(current.width);
}
- if self.height.is_none() {
+
+ if height.is_none() {
constraints.exact.vertical = Some(current.height);
}
- let expand = Spec::new(self.width.is_some(), self.height.is_some());
- let regions = Regions::one(size, expand);
+ // Resolve the linears based on the current width and height.
+ let mut size = Size::new(
+ width.map_or(current.width, |w| w.resolve(base.width)),
+ height.map_or(current.height, |h| h.resolve(base.height)),
+ );
+
+ // If width or height aren't set for an axis, the base should be
+ // inherited from the parent for that axis.
+ let base = Size::new(
+ width.map_or(base.width, |_| size.width),
+ height.map_or(base.height, |_| size.height),
+ );
+
+ // Handle the aspect ratio.
+ if let Some(aspect) = aspect {
+ constraints.exact = current.to_spec().map(Some);
+ constraints.min = Spec::splat(None);
+ constraints.max = Spec::splat(None);
+
+ let width = size.width.min(aspect * size.height);
+ size = Size::new(width, width / aspect);
+ }
+
+ // If width or height are fixed, the child should fill the available
+ // space along that axis.
+ let expand = Spec::new(width.is_some(), height.is_some());
+
+ // Layout the child.
+ let mut regions = Regions::one(size, base, expand);
let mut frames = self.child.layout(ctx, &regions);
- if let Some(frame) = frames.first_mut() {
- frame.constraints = constraints;
+ // If we have an aspect ratio and the child is content-sized, we need to
+ // relayout with expansion.
+ if let Some(aspect) = aspect {
+ if width.is_none() && height.is_none() {
+ let needed = frames[0].size.cap(size);
+ let width = needed.width.max(aspect * needed.height);
+ regions.current = Size::new(width, width / aspect);
+ regions.expand = Spec::splat(true);
+ frames = self.child.layout(ctx, &regions);
+ }
}
+ // Overwrite the child's constraints with ours.
+ frames[0].constraints = constraints;
+ assert_eq!(frames.len(), 1);
+
frames
}
}