Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove BoxConstraints::min #651

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 24 additions & 87 deletions masonry/src/box_constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use vello::kurbo::Size;
/// The layout strategy for Masonry is strongly inspired by Flutter,
/// and this struct is similar to the [Flutter BoxConstraints] class.
///
/// At the moment, it represents simply a minimum and maximum size.
/// At the moment, it represents only a maximum size.
/// A widget's [`layout`] method should choose an appropriate size that
/// meets these constraints.
/// meets that constraint.
/// The algorithm is supposed to use a minimum constraint, but we're phasing that out.
///
/// Further, a container widget should compute appropriate constraints
/// for each of its child widgets, and pass those down when recursing.
Expand All @@ -23,7 +24,6 @@ use vello::kurbo::Size;
/// [rounded away from zero]: Size::expand
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxConstraints {
min: Size,
max: Size,
}

Expand All @@ -32,7 +32,6 @@ impl BoxConstraints {
///
/// Can be satisfied by any nonnegative size.
pub const UNBOUNDED: BoxConstraints = BoxConstraints {
min: Size::ZERO,
max: Size::new(f64::INFINITY, f64::INFINITY),
};

Expand All @@ -44,37 +43,8 @@ impl BoxConstraints {
/// so that the layout is aligned to integers.
///
/// [rounded away from zero]: Size::expand
pub fn new(min: Size, max: Size) -> BoxConstraints {
BoxConstraints {
min: min.expand(),
max: max.expand(),
}
}

/// Create a "tight" box constraints object.
///
/// A "tight" constraint can only be satisfied by a single size.
///
/// The given size is also [rounded away from zero],
/// so that the layout is aligned to integers.
///
/// [rounded away from zero]: Size::expand
pub fn tight(size: Size) -> BoxConstraints {
let size = size.expand();
BoxConstraints {
min: size,
max: size,
}
}

/// Create a "loose" version of the constraints.
///
/// Make a version with zero minimum size, but the same maximum size.
pub fn loosen(&self) -> BoxConstraints {
BoxConstraints {
min: Size::ZERO,
max: self.max,
}
pub fn new(max: Size) -> BoxConstraints {
BoxConstraints { max: max.expand() }
}

/// Clamp a given size so that it fits within the constraints.
Expand All @@ -84,19 +54,14 @@ impl BoxConstraints {
///
/// [rounded away from zero]: Size::expand
pub fn constrain(&self, size: impl Into<Size>) -> Size {
size.into().expand().clamp(self.min, self.max)
size.into().expand().clamp(Size::ZERO, self.max)
}

/// Returns the max size of these constraints.
pub fn max(&self) -> Size {
self.max
}

/// Returns the min size of these constraints.
pub fn min(&self) -> Size {
self.min
}

/// Whether there is an upper bound on the width.
pub fn is_width_bounded(&self) -> bool {
self.max.width.is_finite()
Expand All @@ -115,62 +80,38 @@ impl BoxConstraints {
return;
}

if self.min.width.is_nan() {
debug_panic!("Minimum width constraint passed to {name} is NaN");
}
if self.min.height.is_nan() {
debug_panic!("Minimum height constraint passed to {name} is NaN");
}
if self.max.width.is_nan() {
debug_panic!("Maximum width constraint passed to {name} is NaN");
}
if self.max.height.is_nan() {
debug_panic!("Maximum height constraint passed to {name} is NaN");
}

if self.min.width.is_infinite() {
debug_panic!("Infinite minimum width constraint passed to {name}");
}
if self.min.height.is_infinite() {
debug_panic!("Infinite minimum height constraint passed to {name}");
}

if !(0.0 <= self.min.width
&& self.min.width <= self.max.width
&& 0.0 <= self.min.height
&& self.min.height <= self.max.height
&& self.min.expand() == self.min
&& self.max.expand() == self.max)
{
if !(0.0 <= self.max.width && 0.0 <= self.max.height && self.max.expand() == self.max) {
debug_panic!("Bad BoxConstraints passed to {name}: {self:?}",);
}
}

/// Shrink min and max constraints by size
/// Shrink max constraint by size
///
/// The given size is also [rounded away from zero],
/// so that the layout is aligned to integers.
///
/// [rounded away from zero]: Size::expand
pub fn shrink(&self, diff: impl Into<Size>) -> BoxConstraints {
let diff = diff.into().expand();
let min = Size::new(
(self.min().width - diff.width).max(0.),
(self.min().height - diff.height).max(0.),
);
let max = Size::new(
(self.max().width - diff.width).max(0.),
(self.max().height - diff.height).max(0.),
);

BoxConstraints::new(min, max)
BoxConstraints::new(max)
}

/// Test whether these constraints contain the given `Size`.
pub fn contains(&self, size: impl Into<Size>) -> bool {
let size = size.into();
(self.min.width <= size.width && size.width <= self.max.width)
&& (self.min.height <= size.height && size.height <= self.max.height)
(size.width <= self.max.width) && (size.height <= self.max.height)
}

/// Find the `Size` within these `BoxConstraint`s that minimises the difference between the
Expand Down Expand Up @@ -203,9 +144,9 @@ impl BoxConstraints {

// Then we check if any `Size`s with our desired aspect ratio are inside the constraints.
// TODO this currently outputs garbage when things are < 0 - See https://github.com/linebender/xilem/issues/377
let min_w_min_h = self.min.height / self.min.width;
let max_w_min_h = self.min.height / self.max.width;
let min_w_max_h = self.max.height / self.min.width;
let min_w_min_h = 0.0 / 0.0;
let max_w_min_h = 0.0 / self.max.width;
let min_w_max_h = self.max.height / 0.0;
let max_w_max_h = self.max.height / self.max.width;

// When the aspect ratio line crosses the constraints, the closest point must be one of the
Expand All @@ -219,22 +160,22 @@ impl BoxConstraints {
if aspect_ratio > min_w_max_h {
// outside max height min width
Size {
width: self.min.width,
width: 0.0,
height: self.max.height,
}
} else if aspect_ratio < max_w_min_h {
// outside min height max width
Size {
width: self.max.width,
height: self.min.height,
height: 0.0,
}
} else if aspect_ratio > min_w_min_h {
// hits the constraints on the min width line
if width < self.min.width {
if width < 0.0 {
// we take the point on the min width
Size {
width: self.min.width,
height: self.min.width * aspect_ratio,
width: 0.0,
height: 0.0 * aspect_ratio,
}
} else if aspect_ratio < max_w_max_h {
// exits through max.width
Expand All @@ -251,11 +192,11 @@ impl BoxConstraints {
}
} else {
// final case is where we hit constraints on the min height line
if width < self.min.width {
if width < 0.0 {
// take the point on the min height
Size {
width: self.min.height * aspect_ratio.recip(),
height: self.min.height,
width: 0.0 * aspect_ratio.recip(),
height: 0.0,
}
} else if aspect_ratio > max_w_max_h {
// exit thru max height
Expand All @@ -278,13 +219,11 @@ impl BoxConstraints {
mod tests {
use super::*;

fn bc(min_width: f64, min_height: f64, max_width: f64, max_height: f64) -> BoxConstraints {
BoxConstraints::new(
Size::new(min_width, min_height),
Size::new(max_width, max_height),
)
fn bc(_min_width: f64, _min_height: f64, max_width: f64, max_height: f64) -> BoxConstraints {
BoxConstraints::new(Size::new(max_width, max_height))
}

// TODO - Fix this test
#[test]
fn constrain_aspect_ratio() {
for (bc, aspect_ratio, width, output) in [
Expand Down Expand Up @@ -373,7 +312,5 @@ mod tests {
fn unbounded() {
assert!(!BoxConstraints::UNBOUNDED.is_width_bounded());
assert!(!BoxConstraints::UNBOUNDED.is_height_bounded());

assert_eq!(BoxConstraints::UNBOUNDED.min(), Size::ZERO);
}
}
2 changes: 1 addition & 1 deletion masonry/src/passes/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ pub(crate) fn run_layout_pass(root: &mut RenderRoot) {

let window_size = root.get_kurbo_size();
let bc = match root.size_policy {
WindowSizePolicy::User => BoxConstraints::tight(window_size),
WindowSizePolicy::User => BoxConstraints::new(window_size),
WindowSizePolicy::Content => BoxConstraints::UNBOUNDED,
};

Expand Down
2 changes: 1 addition & 1 deletion masonry/src/testing/helper_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ impl<W: Widget> Widget for Recorder<W> {

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let size = self.child.layout(ctx, bc);
self.recording.push(Record::Layout(size));
self.recording.push(Record::Layout(bc.max()));
size
}

Expand Down
2 changes: 1 addition & 1 deletion masonry/src/widget/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl Widget for Align {
}

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let size = ctx.run_layout(&mut self.child, &bc.loosen());
let size = ctx.run_layout(&mut self.child, &bc);

log_size_warnings(size);

Expand Down
16 changes: 11 additions & 5 deletions masonry/src/widget/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const LABEL_INSETS: Insets = Insets::uniform_xy(8., 2.);
/// Emits [`Action::ButtonPressed`] when pressed.
pub struct Button {
label: WidgetPod<Label>,
expand: bool,
}

// --- MARK: BUILDERS ---
Expand Down Expand Up @@ -59,6 +60,7 @@ impl Button {
pub fn from_label(label: Label) -> Button {
Button {
label: WidgetPod::new(label),
expand: true,
}
}
}
Expand Down Expand Up @@ -127,7 +129,7 @@ impl Widget for Button {

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let padding = Size::new(LABEL_INSETS.x_value(), LABEL_INSETS.y_value());
let label_bc = bc.shrink(padding).loosen();
let label_bc = bc.shrink(padding);

let label_size = ctx.run_layout(&mut self.label, &label_bc);

Expand All @@ -138,10 +140,14 @@ impl Widget for Button {
// we make sure we will have at least the same height as the default textbox.
let min_height = theme::BORDERED_WIDGET_HEIGHT;

let button_size = bc.constrain(Size::new(
label_size.width + padding.width,
(label_size.height + padding.height).max(min_height),
));
let button_size = if self.expand {
bc.max()
} else {
bc.constrain(Size::new(
label_size.width + padding.width,
(label_size.height + padding.height).max(min_height),
))
};

let label_offset = (button_size.to_vec2() - label_size.to_vec2()) / 2.0;
ctx.place_child(&mut self.label, label_offset.to_point());
Expand Down
Loading
Loading