diff --git a/masonry/README.md b/masonry/README.md index 108c2d688..c953c4cf6 100644 --- a/masonry/README.md +++ b/masonry/README.md @@ -133,7 +133,7 @@ pub fn make_widget_tree() -> NewWidget { let list = Flex::column() .with_fixed( NewWidget::new(Flex::row().with(text_input, 1.0).with_fixed(button)) - .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING.get()))), + .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING))), ) .with_fixed_spacer(WIDGET_SPACING); diff --git a/masonry/examples/calc_masonry.rs b/masonry/examples/calc_masonry.rs index be14fb0d5..12f310872 100644 --- a/masonry/examples/calc_masonry.rs +++ b/masonry/examples/calc_masonry.rs @@ -255,7 +255,7 @@ pub fn build_calc() -> NewWidget { NewWidget::new(root_widget).with_props( PropertySet::new() .with(Background::Color(AlphaColor::from_str("#794869").unwrap())) - .with(Padding::all(2.0)) + .with(Padding::all(2.px())) .with(Gap::new(1.px())), ) } @@ -274,7 +274,7 @@ fn custom_property_set() -> DefaultProperties { PropertySet::new() .with(Background::Color(BLUE)) .with(BorderColor::new(Color::TRANSPARENT)) - .with(BorderWidth::all(2.0)), + .with(BorderWidth::all(2.px())), ); stack.push( Selector::classes(&["op_button"]).with_active(true), @@ -285,7 +285,7 @@ fn custom_property_set() -> DefaultProperties { PropertySet::new() .with(Background::Color(GRAY)) .with(BorderColor::new(Color::TRANSPARENT)) - .with(BorderWidth::all(2.0)), + .with(BorderWidth::all(2.px())), ); stack.push( Selector::classes(&["digit_button"]).with_active(true), diff --git a/masonry/examples/custom_widget.rs b/masonry/examples/custom_widget.rs index 0d2671cd7..b9cab1efe 100644 --- a/masonry/examples/custom_widget.rs +++ b/masonry/examples/custom_widget.rs @@ -16,7 +16,7 @@ use masonry::core::{ }; use masonry::imaging::Painter; use masonry::kurbo::{Affine, Axis, BezPath, Point, Rect, Size, Stroke}; -use masonry::layout::LenReq; +use masonry::layout::{AsUnit, LenReq, Length}; use masonry::parley::FontFamilyName; use masonry::parley::style::{FontFamily, GenericFamily, StyleProperty}; use masonry::peniko::{Color, ImageBrush, ImageFormat}; @@ -80,16 +80,16 @@ impl Widget for CustomWidget { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { + _cross_length: Option, + ) -> Length { // We currently just define our preferred min/max, // but often it takes actual work to derive these. let min_size = Size::new(100., 50.); let max_size = Size::new(200., 200.); // Measurement is per axis, so we only care about a single dimension right now - let min_length = min_size.get_coord(axis); - let max_length = max_size.get_coord(axis); + let min_length = min_size.get_coord(axis).px(); + let max_length = max_size.get_coord(axis).px(); // Return a result based on the parent's request match len_req { @@ -113,10 +113,9 @@ impl Widget for CustomWidget { painter: &mut Painter<'_>, ) { // Clear the whole widget with the color of your choice - // (ctx.content_box_size() returns the size of the content rect we're painting in) - let size = ctx.content_box_size(); - let rect = ctx.content_box(); - painter.fill(rect, palette::css::WHITE).draw(); + let content_box = ctx.content_box(); + let size = content_box.size(); + painter.fill(content_box, palette::css::WHITE).draw(); // Create an arbitrary bezier path let mut path = BezPath::new(); @@ -166,7 +165,7 @@ impl Widget for CustomWidget { width: 256, height: 256, }); - let transform = ObjectFit::Stretch.affine(size, Size::new(256., 256.)); + let transform = ObjectFit::Stretch.affine(content_box, Rect::new(0., 0., 256., 256.)); painter.draw_image(&image_data, transform); } diff --git a/masonry/examples/gallery/badge.rs b/masonry/examples/gallery/badge.rs index b4c1a7a3d..b1f767048 100644 --- a/masonry/examples/gallery/badge.rs +++ b/masonry/examples/gallery/badge.rs @@ -6,7 +6,7 @@ use masonry::core::{ ErasedAction, Handled, NewWidget, PropertySet, StyleProperty, Widget, WidgetId, WidgetTag, }; use masonry::kurbo::Vec2; -use masonry::layout::Length; +use masonry::layout::{AsUnit, Length}; use masonry::parley::style::FontWeight; use masonry::peniko::Color; use masonry::properties::types::CrossAxisAlignment; @@ -93,7 +93,7 @@ impl DemoPage for BadgeDemo { let outline_badge = NewWidget::new(Badge::with_text("99+")).with_props( PropertySet::new() .with(Background::Color(Color::TRANSPARENT)) - .with(BorderWidth { width: 1.0 }) + .with(BorderWidth { width: 1.px() }) .with(BorderColor { color: Color::from_rgb8(0x71, 0x71, 0x7a), }), @@ -155,8 +155,8 @@ impl DemoPage for BadgeDemo { .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x3f, 0x3f, 0x46))) - .with(CornerRadius { radius: 999.0 }) - .with(Padding::all(0.0)), + .with(CornerRadius { radius: 999.px() }) + .with(Padding::ZERO), ); let online_dot = NewWidget::new(Badge::new( @@ -166,9 +166,9 @@ impl DemoPage for BadgeDemo { )) .with_props( PropertySet::new() - .with(Padding::all(0.0)) - .with(CornerRadius { radius: 999.0 }) - .with(BorderWidth { width: 0.0 }) + .with(Padding::ZERO) + .with(CornerRadius { radius: 999.px() }) + .with(BorderWidth { width: 0.px() }) .with(Background::Color(Color::from_rgb8(0x22, 0xc5, 0x5e))), ); diff --git a/masonry/examples/gallery/image.rs b/masonry/examples/gallery/image.rs index 1a3025d47..aa16d8cb3 100644 --- a/masonry/examples/gallery/image.rs +++ b/masonry/examples/gallery/image.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, PropertySet, StyleProperty, Widget}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::peniko::{ImageAlphaType, ImageData, ImageFormat}; use masonry::properties::ObjectFit; use masonry::properties::types::CrossAxisAlignment; @@ -54,7 +54,7 @@ impl DemoPage for ImageDemo { .prepare(), ) .with_fixed_spacer(CONTENT_GAP) - .with_fixed(SizedBox::new(image).size(420.0.px(), 280.0.px()).prepare()); + .with_fixed(SizedBox::new(image).size(420.px(), 280.px()).prepare()); wrap_in_shell(self.shell, NewWidget::new(body).erased()) } diff --git a/masonry/examples/gallery/kitchen_sink.rs b/masonry/examples/gallery/kitchen_sink.rs index 6bf2ff18d..9e7a29c37 100644 --- a/masonry/examples/gallery/kitchen_sink.rs +++ b/masonry/examples/gallery/kitchen_sink.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, PropertySet, StyleProperty, Widget}; -use masonry::layout::{AsUnit as _, UnitPoint}; +use masonry::layout::{AsUnit, UnitPoint}; use masonry::peniko::Color; use masonry::properties::types::CrossAxisAlignment; use masonry::properties::{Background, Padding}; @@ -43,10 +43,10 @@ impl DemoPage for KitchenSinkDemo { let grid = NewWidget::new(SizedBox::new(grid.prepare())).with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x24, 0x24, 0x24))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); - let bg = NewWidget::new(SizedBox::empty().size(220.0.px(), 120.0.px())).with_props( + let bg = NewWidget::new(SizedBox::empty().size(220.px(), 120.px())).with_props( PropertySet::one(Background::Color(Color::from_rgb8(0x44, 0x22, 0x66))), ); diff --git a/masonry/examples/gallery/main.rs b/masonry/examples/gallery/main.rs index beaf20028..23c373ce4 100644 --- a/masonry/examples/gallery/main.rs +++ b/masonry/examples/gallery/main.rs @@ -32,6 +32,7 @@ mod transforms; use masonry::core::{ErasedAction, NewWidget, StyleProperty, Widget as _, WidgetId, WidgetTag}; use masonry::dpi::LogicalSize; +use masonry::layout::Length; use masonry::parley::style::FontWeight; use masonry::properties::Padding; use masonry::properties::types::CrossAxisAlignment; @@ -45,11 +46,11 @@ use masonry_winit::winit::window::Window; use crate::demo::{DemoPage, new_demo_shell_tags}; use crate::switch::SwitchDemo; -const SIDEBAR_WIDTH: masonry::layout::Length = masonry::layout::Length::const_px(240.0); -const SIDEBAR_SCROLLBAR_INSET: f64 = 12.0; -const LEFT_PANE_TOP_PADDING: f64 = 12.0; -const LEFT_PANE_LEFT_PADDING: f64 = 12.0; -const RIGHT_PANE_PADDING: f64 = 12.0; +const SIDEBAR_WIDTH: Length = Length::const_px(240.0); +const SIDEBAR_SCROLLBAR_INSET: Length = Length::const_px(12.0); +const LEFT_PANE_TOP_PADDING: Length = Length::const_px(12.0); +const LEFT_PANE_LEFT_PADDING: Length = Length::const_px(12.0); +const RIGHT_PANE_PADDING: Length = Length::const_px(12.0); const DEMO_TITLE_FONT_SIZE: f32 = 20.0; @@ -222,7 +223,7 @@ fn main() { // scrollbar doesn't sit on top of the buttons. let list = NewWidget::new(SizedBox::new(list.prepare())).with_props(Padding { top: LEFT_PANE_TOP_PADDING, - bottom: 0.0, + bottom: Length::ZERO, left: LEFT_PANE_LEFT_PADDING, right: SIDEBAR_SCROLLBAR_INSET, }); diff --git a/masonry/examples/gallery/spinner.rs b/masonry/examples/gallery/spinner.rs index fafe54a0f..1048362e8 100644 --- a/masonry/examples/gallery/spinner.rs +++ b/masonry/examples/gallery/spinner.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, Widget}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::properties::types::CrossAxisAlignment; use masonry::widgets::{Flex, SizedBox, Spinner}; @@ -32,7 +32,7 @@ impl DemoPage for SpinnerDemo { .cross_axis_alignment(CrossAxisAlignment::Center) .with_fixed( SizedBox::new(Spinner::new().prepare()) - .size(80.0.px(), 80.0.px()) + .size(80.px(), 80.px()) .prepare(), ); diff --git a/masonry/examples/gallery/split.rs b/masonry/examples/gallery/split.rs index 5a5e9ed93..142524cab 100644 --- a/masonry/examples/gallery/split.rs +++ b/masonry/examples/gallery/split.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use masonry::core::{NewWidget, PropertySet, StyleProperty, Widget}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::peniko::Color; use masonry::properties::{Background, Padding}; use masonry::widgets::{Label, SizedBox, Split}; @@ -37,7 +37,7 @@ impl DemoPage for SplitDemo { .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x1f, 0x2a, 0x44))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); let right = NewWidget::new(SizedBox::new( @@ -48,11 +48,11 @@ impl DemoPage for SplitDemo { .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x2b, 0x3c, 0x2f))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); - let body = SizedBox::new(Split::new(left, right).split_fraction(0.33).prepare()) - .height(260.0.px()); + let body = + SizedBox::new(Split::new(left, right).split_fraction(0.33).prepare()).height(260.px()); wrap_in_shell(self.shell, NewWidget::new(body).erased()) } diff --git a/masonry/examples/gallery/step_input.rs b/masonry/examples/gallery/step_input.rs index 3dad7c9f6..785af4b87 100644 --- a/masonry/examples/gallery/step_input.rs +++ b/masonry/examples/gallery/step_input.rs @@ -72,7 +72,7 @@ impl DemoPage for StepInputDemo { stack.push( Selector::new(), - (BorderWidth::all(2.), CornerRadius::all(20.)), + (BorderWidth::all(2.px()), CornerRadius::all(20.px())), ); stack.push( diff --git a/masonry/examples/gallery/transforms.rs b/masonry/examples/gallery/transforms.rs index 4a424f059..0dc94bed4 100644 --- a/masonry/examples/gallery/transforms.rs +++ b/masonry/examples/gallery/transforms.rs @@ -6,7 +6,7 @@ use masonry::core::{ ErasedAction, Handled, NewWidget, PropertySet, StyleProperty, Widget, WidgetId, WidgetTag, }; use masonry::kurbo::{Affine, Vec2}; -use masonry::layout::AsUnit as _; +use masonry::layout::AsUnit; use masonry::peniko::Color; use masonry::properties::types::CrossAxisAlignment; use masonry::properties::{Background, Padding}; @@ -108,13 +108,13 @@ impl DemoPage for TransformsDemo { .with_style(StyleProperty::FontSize(14.0)) .prepare(), ) - .size(160.0.px(), 160.0.px()), + .size(160.px(), 160.px()), ) .with_tag(self.target) .with_props( PropertySet::new() .with(Background::Color(Color::from_rgb8(0x35, 0x35, 0x35))) - .with(Padding::all(12.0)), + .with(Padding::all(12.px())), ); let body = Flex::column() diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 92dd95b08..d287be9b1 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -11,7 +11,7 @@ use masonry::core::{ ErasedAction, NewWidget, PointerButton, PropertySet, StyleProperty, Widget as _, WidgetId, }; use masonry::dpi::LogicalSize; -use masonry::layout::Length; +use masonry::layout::{AsUnit, Length}; use masonry::peniko::Color; use masonry::properties::{BorderColor, BorderWidth, Gap}; use masonry::theme::default_property_set; @@ -69,7 +69,7 @@ pub fn make_grid(grid_gap: f64) -> NewWidget { let props = PropertySet::new() .with(BorderColor::new(Color::from_rgb8(40, 40, 80))) - .with(BorderWidth::all(1.0)); + .with(BorderWidth::all(1.px())); let label = SizedBox::new(NewWidget::new(label).with_props(props)); let button_inputs = vec![ diff --git a/masonry/examples/layers.rs b/masonry/examples/layers.rs index e5115b3b1..3e577ba9f 100644 --- a/masonry/examples/layers.rs +++ b/masonry/examples/layers.rs @@ -15,7 +15,7 @@ use masonry::core::{ use masonry::imaging::Painter; use masonry::kurbo::{Axis, Point, Size, Vec2}; use masonry::layers::Tooltip; -use masonry::layout::{AsUnit, LayoutSize, LenReq, SizeDef}; +use masonry::layout::{AsUnit, LayoutSize, LenReq, Length, SizeDef}; use masonry::parley::FontWeight; use masonry::peniko::Color; use masonry::properties::{Background, BorderColor, BorderWidth, ContentColor}; @@ -117,8 +117,8 @@ impl Widget for OverlayBox { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -178,7 +178,7 @@ fn main() { .with_props(PropertySet::one(ContentColor::new(Color::BLACK))), )) .with_props(PropertySet::from(( - BorderWidth::all(1.), + BorderWidth::all(1.px()), BorderColor::new(Color::BLACK), Background::Color(Color::WHITE), ))) diff --git a/masonry/examples/to_do_list.rs b/masonry/examples/to_do_list.rs index 5a44311d7..21947a2a5 100644 --- a/masonry/examples/to_do_list.rs +++ b/masonry/examples/to_do_list.rs @@ -77,7 +77,7 @@ pub fn make_widget_tree() -> NewWidget { let root = Flex::column() .with_fixed( NewWidget::new(Flex::row().with(text_input, 1.0).with_fixed(button)) - .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING.get()))), + .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING))), ) .with_fixed_spacer(WIDGET_SPACING) .with(portal, 1.0); diff --git a/masonry/screenshots/badged_button.png b/masonry/screenshots/badged_button.png index 217ad96e5..98a1ab018 100644 Binary files a/masonry/screenshots/badged_button.png and b/masonry/screenshots/badged_button.png differ diff --git a/masonry/screenshots/badged_button_no_badge.png b/masonry/screenshots/badged_button_no_badge.png index 36a03d008..87a4ae694 100644 Binary files a/masonry/screenshots/badged_button_no_badge.png and b/masonry/screenshots/badged_button_no_badge.png differ diff --git a/masonry/screenshots/example_calc_masonry_initial.png b/masonry/screenshots/example_calc_masonry_initial.png index 9d4bf69e2..768d67acc 100644 Binary files a/masonry/screenshots/example_calc_masonry_initial.png and b/masonry/screenshots/example_calc_masonry_initial.png differ diff --git a/masonry/screenshots/example_grid_masonry_initial.png b/masonry/screenshots/example_grid_masonry_initial.png index efd83a0b2..4e75d4f3e 100644 Binary files a/masonry/screenshots/example_grid_masonry_initial.png and b/masonry/screenshots/example_grid_masonry_initial.png differ diff --git a/masonry/screenshots/flex_row_baselines_four_center_and_first.png b/masonry/screenshots/flex_row_baselines_four_center_and_first.png index d2b42e2c1..dc36c4ed5 100644 Binary files a/masonry/screenshots/flex_row_baselines_four_center_and_first.png and b/masonry/screenshots/flex_row_baselines_four_center_and_first.png differ diff --git a/masonry/screenshots/flex_row_baselines_four_center_and_last.png b/masonry/screenshots/flex_row_baselines_four_center_and_last.png index b3e3a3971..803b2d621 100644 Binary files a/masonry/screenshots/flex_row_baselines_four_center_and_last.png and b/masonry/screenshots/flex_row_baselines_four_center_and_last.png differ diff --git a/masonry/screenshots/flex_row_baselines_four_first_and_first.png b/masonry/screenshots/flex_row_baselines_four_first_and_first.png index 431ad2246..279326283 100644 Binary files a/masonry/screenshots/flex_row_baselines_four_first_and_first.png and b/masonry/screenshots/flex_row_baselines_four_first_and_first.png differ diff --git a/masonry/screenshots/flex_row_baselines_four_first_and_last.png b/masonry/screenshots/flex_row_baselines_four_first_and_last.png index 431ad2246..279326283 100644 Binary files a/masonry/screenshots/flex_row_baselines_four_first_and_last.png and b/masonry/screenshots/flex_row_baselines_four_first_and_last.png differ diff --git a/masonry/screenshots/flex_row_baselines_four_last_and_first.png b/masonry/screenshots/flex_row_baselines_four_last_and_first.png index 2ed3890a3..c19cf1c30 100644 Binary files a/masonry/screenshots/flex_row_baselines_four_last_and_first.png and b/masonry/screenshots/flex_row_baselines_four_last_and_first.png differ diff --git a/masonry/screenshots/flex_row_baselines_four_last_and_last.png b/masonry/screenshots/flex_row_baselines_four_last_and_last.png index 2ed3890a3..c19cf1c30 100644 Binary files a/masonry/screenshots/flex_row_baselines_four_last_and_last.png and b/masonry/screenshots/flex_row_baselines_four_last_and_last.png differ diff --git a/masonry/screenshots/flex_row_baselines_one_first_three_last_and_first.png b/masonry/screenshots/flex_row_baselines_one_first_three_last_and_first.png index 17141532c..dafb84307 100644 Binary files a/masonry/screenshots/flex_row_baselines_one_first_three_last_and_first.png and b/masonry/screenshots/flex_row_baselines_one_first_three_last_and_first.png differ diff --git a/masonry/screenshots/flex_row_baselines_one_first_three_last_and_last.png b/masonry/screenshots/flex_row_baselines_one_first_three_last_and_last.png index 578275990..c6b96253a 100644 Binary files a/masonry/screenshots/flex_row_baselines_one_first_three_last_and_last.png and b/masonry/screenshots/flex_row_baselines_one_first_three_last_and_last.png differ diff --git a/masonry/screenshots/flex_row_baselines_three_first_one_last_and_first.png b/masonry/screenshots/flex_row_baselines_three_first_one_last_and_first.png index d2a25ff36..bbd7b7977 100644 Binary files a/masonry/screenshots/flex_row_baselines_three_first_one_last_and_first.png and b/masonry/screenshots/flex_row_baselines_three_first_one_last_and_first.png differ diff --git a/masonry/screenshots/flex_row_baselines_three_first_one_last_and_last.png b/masonry/screenshots/flex_row_baselines_three_first_one_last_and_last.png index 5aa586657..e59c5ed84 100644 Binary files a/masonry/screenshots/flex_row_baselines_three_first_one_last_and_last.png and b/masonry/screenshots/flex_row_baselines_three_first_one_last_and_last.png differ diff --git a/masonry/screenshots/flex_row_baselines_two_first_two_last_and_first.png b/masonry/screenshots/flex_row_baselines_two_first_two_last_and_first.png index 25e94f918..8c9ad38b6 100644 Binary files a/masonry/screenshots/flex_row_baselines_two_first_two_last_and_first.png and b/masonry/screenshots/flex_row_baselines_two_first_two_last_and_first.png differ diff --git a/masonry/screenshots/flex_row_baselines_two_first_two_last_and_last.png b/masonry/screenshots/flex_row_baselines_two_first_two_last_and_last.png index 0eb63d4fb..bbec528ed 100644 Binary files a/masonry/screenshots/flex_row_baselines_two_first_two_last_and_last.png and b/masonry/screenshots/flex_row_baselines_two_first_two_last_and_last.png differ diff --git a/masonry/screenshots/grid_baselines_first.png b/masonry/screenshots/grid_baselines_first.png index 5917fb908..bf57978a7 100644 Binary files a/masonry/screenshots/grid_baselines_first.png and b/masonry/screenshots/grid_baselines_first.png differ diff --git a/masonry/screenshots/grid_baselines_last.png b/masonry/screenshots/grid_baselines_last.png index 128f950eb..77d65a580 100644 Binary files a/masonry/screenshots/grid_baselines_last.png and b/masonry/screenshots/grid_baselines_last.png differ diff --git a/masonry/src/doc/color_rectangle.rs b/masonry/src/doc/color_rectangle.rs index 930ab9bff..be3498409 100644 --- a/masonry/src/doc/color_rectangle.rs +++ b/masonry/src/doc/color_rectangle.rs @@ -25,13 +25,14 @@ use masonry::core::{Update, UpdateCtx}; // --- use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Size}; -use masonry::layout::LenReq; +use masonry::layout::{LenReq, Length}; // --- use masonry::accesskit::{Node, Role}; use masonry::core::{AccessCtx, PaintCtx}; use masonry::imaging::Painter; // --- use masonry::core::WidgetId; +use masonry_core::layout::AsUnit; use tracing::{Span, trace_span}; // --- use masonry::core::{ChildrenIds, RegisterCtx}; @@ -130,16 +131,12 @@ impl Widget for ColorRectangle { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + _cross_length: Option, + ) -> Length { match len_req { LenReq::MinContent | LenReq::MaxContent => match axis { - Axis::Horizontal => 200. * scale, - Axis::Vertical => 100. * scale, + Axis::Horizontal => 200.px(), + Axis::Vertical => 100.px(), }, LenReq::FitContent(space) => space, } diff --git a/masonry/src/doc/implementing_container_widget.md b/masonry/src/doc/implementing_container_widget.md index c41430a20..ae4fdad8d 100644 --- a/masonry/src/doc/implementing_container_widget.md +++ b/masonry/src/doc/implementing_container_widget.md @@ -54,7 +54,7 @@ A container widget needs to pay special attention to these methods: trait Widget { // ... - fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> f64; + fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> Length; fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size); fn compose(&mut self, ctx: &mut ComposeCtx<'_>); @@ -87,7 +87,7 @@ For our `VerticalStack`, we'll lay out our children in a vertical line, with a g ```rust,ignore use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Point, Size}; -use masonry::layout::{LayoutSize, LenDef, LenReq, SizeDef}; +use masonry::layout::{AsUnit, LayoutSize, Length, LenDef, LenReq, SizeDef}; impl Widget for VerticalStack { // ... @@ -98,28 +98,29 @@ impl Widget for VerticalStack { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), LenReq::FitContent(space) => (LenReq::MinContent, space), }; let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in &mut self.children { let child_length = ctx.compute_length(child, auto_length, context_size, axis, cross_length); match axis { Axis::Horizontal => length = length.max(child_length), - Axis::Vertical => length += child_length, + Axis::Vertical => length = length.saturating_add(child_length), } } if axis == Axis::Vertical { let gap_count = (self.children.len() - 1) as f64; - length += gap_count * self.gap; + let gaps_length = gap_count * self.gap; + length = length.saturating_add(gaps_length.px()); } min_result.max(length) @@ -135,8 +136,8 @@ impl Widget for VerticalStack { let total_child_vertical_space = size.height - self.gap * gap_count; let child_vertical_space = total_child_vertical_space / self.children.len() as f64; - let width_def = LenDef::FitContent(size.width); - let height_def = LenDef::FitContent(child_vertical_space.max(0.)); + let width_def = LenDef::FitContent(size.width.px()); + let height_def = LenDef::FitContent(child_vertical_space.max(0.).px()); let auto_size = SizeDef::new(width_def, height_def); let context_size = size.into(); diff --git a/masonry/src/doc/implementing_widget.md b/masonry/src/doc/implementing_widget.md index 44e135cf3..c72e1be55 100644 --- a/masonry/src/doc/implementing_widget.md +++ b/masonry/src/doc/implementing_widget.md @@ -32,7 +32,7 @@ trait Widget { fn on_anim_frame(&mut self, ctx: &mut UpdateCtx<'_>, props: &mut PropertiesMut<'_>, interval: u64); fn update(&mut self, ctx: &mut UpdateCtx<'_>, props: &mut PropertiesMut<'_>, event: &Update); - fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> f64; + fn measure(&mut self, ctx: &mut MeasureCtx<'_>, props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, cross_length: Option) -> Length; fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size); fn paint(&mut self, ctx: &mut PaintCtx<'_>, props: &PropertiesRef<'_>, painter: &mut Painter<'_>); @@ -169,7 +169,7 @@ Next we implement layout: ```rust,ignore use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Size}; -use masonry::layout::LenReq; +use masonry::layout::{Length, LenReq}; impl Widget for ColorRectangle { // ... @@ -180,16 +180,13 @@ impl Widget for ColorRectangle { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - _cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; + _cross_length: Option, + ) -> Length { match len_req { LenReq::MinContent | LenReq::MaxContent => match axis { - Axis::Horizontal => 200. * scale, - Axis::Vertical => 100. * scale, + Axis::Horizontal => 200.px(), + Axis::Vertical => 100.px(), } LenReq::FitContent(space) => space, } diff --git a/masonry/src/doc/vertical_stack.rs b/masonry/src/doc/vertical_stack.rs index 880d57218..cb19b8641 100644 --- a/masonry/src/doc/vertical_stack.rs +++ b/masonry/src/doc/vertical_stack.rs @@ -21,7 +21,7 @@ use masonry::core::{Widget, WidgetPod}; // --- use masonry::core::{LayoutCtx, MeasureCtx, PropertiesRef}; use masonry::kurbo::{Axis, Point, Size}; -use masonry::layout::{LayoutSize, LenDef, LenReq, SizeDef}; +use masonry::layout::{AsUnit, LayoutSize, LenDef, LenReq, Length, SizeDef}; // --- use masonry::core::ComposeCtx; // --- @@ -94,28 +94,29 @@ impl Widget for VerticalStack { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), LenReq::FitContent(space) => (LenReq::MinContent, space), }; let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in &mut self.children { let child_length = ctx.compute_length(child, auto_length, context_size, axis, cross_length); match axis { Axis::Horizontal => length = length.max(child_length), - Axis::Vertical => length += child_length, + Axis::Vertical => length = length.saturating_add(child_length), } } if axis == Axis::Vertical { let gap_count = (self.children.len() - 1) as f64; - length += gap_count * self.gap; + let gaps_length = gap_count * self.gap; + length = length.saturating_add(gaps_length.px()); } min_result.max(length) @@ -131,8 +132,8 @@ impl Widget for VerticalStack { let total_child_vertical_space = size.height - self.gap * gap_count; let child_vertical_space = total_child_vertical_space / self.children.len() as f64; - let width_def = LenDef::FitContent(size.width); - let height_def = LenDef::FitContent(child_vertical_space.max(0.)); + let width_def = LenDef::FitContent(size.width.px()); + let height_def = LenDef::FitContent(child_vertical_space.max(0.).px()); let auto_size = SizeDef::new(width_def, height_def); let context_size = size.into(); diff --git a/masonry/src/layers/selector_menu.rs b/masonry/src/layers/selector_menu.rs index 460322211..7a1a836a1 100644 --- a/masonry/src/layers/selector_menu.rs +++ b/masonry/src/layers/selector_menu.rs @@ -12,7 +12,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size}; -use crate::layout::{LayoutSize, LenDef, LenReq, SizeDef}; +use crate::layout::{AsUnit, LayoutSize, LenDef, LenReq, Length, SizeDef}; use crate::properties::Gap; use crate::widgets::{SelectionChanged, Selector}; @@ -236,58 +236,51 @@ impl Widget for SelectorMenu { props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let cache = ctx.property_cache(); let gap = props.get::(cache); - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let (len_req, min_result) = match len_req { - LenReq::MinContent | LenReq::MaxContent => (len_req, 0.), + LenReq::MinContent | LenReq::MaxContent => (len_req, Length::ZERO), LenReq::FitContent(space) => (LenReq::MinContent, space), }; let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); - let mut length: f64 = 0.; + let mut length = Length::ZERO; for child in &mut self.children { let child_length = ctx.compute_length(child, auto_length, context_size, axis, cross_length); match axis { Axis::Horizontal => length = length.max(child_length), - Axis::Vertical => length += child_length, + Axis::Vertical => length = length.saturating_add(child_length), } } if axis == Axis::Vertical { let gap_count = (self.children.len() - 1) as f64; - length += gap_count * gap_length; + let gaps_length = gap_count * gap_length; + length = length.saturating_add(gaps_length.px()); } min_result.max(length) } fn layout(&mut self, ctx: &mut LayoutCtx<'_>, props: &PropertiesRef<'_>, size: Size) { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - let cache = ctx.property_cache(); let gap = props.get::(cache); let gap_count = (self.children.len() - 1) as f64; - let gap_length = gap.gap.dp(scale); + let gap_length = gap.gap.get(); let total_child_vertical_space = size.height - gap_length * gap_count; let child_vertical_space = total_child_vertical_space / self.children.len() as f64; - let width_def = LenDef::FitContent(size.width); - let height_def = LenDef::FitContent(child_vertical_space.max(0.)); + let width_def = LenDef::FitContent(size.width.px()); + let height_def = LenDef::FitContent(child_vertical_space.max(0.).px()); let auto_size = SizeDef::new(width_def, height_def); let context_size = size.into(); @@ -302,12 +295,12 @@ impl Widget for SelectorMenu { if !self.children.is_empty() { let first_child = self.children.first().unwrap(); - let (first_baseline, _) = ctx.child_aligned_baselines(first_child); + let (first_baseline, _) = ctx.child_baselines(first_child); let first_child_origin = ctx.child_origin(first_child); let first_baseline = first_child_origin.y + first_baseline; let last_child = self.children.last().unwrap(); - let (_, last_baseline) = ctx.child_aligned_baselines(last_child); + let (_, last_baseline) = ctx.child_baselines(last_child); let last_child_origin = ctx.child_origin(last_child); let last_baseline = last_child_origin.y + last_baseline; @@ -370,7 +363,7 @@ impl Layer for SelectorMenu { PointerEvent::Down(PointerButtonEvent { state, .. }) => { let local_pos = ctx.local_position(state.position); - !ctx.border_box_size().to_rect().contains(local_pos) + !ctx.border_box().contains(local_pos) } PointerEvent::Cancel(..) => true, _ => false, diff --git a/masonry/src/layers/tooltip.rs b/masonry/src/layers/tooltip.rs index 2e489919d..78953fff6 100644 --- a/masonry/src/layers/tooltip.rs +++ b/masonry/src/layers/tooltip.rs @@ -13,7 +13,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; /// A [`Layer`] representing a simple tooltip showing some content until the mouse moves. pub struct Tooltip { @@ -94,8 +94,8 @@ impl Widget for Tooltip { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); diff --git a/masonry/src/lib.rs b/masonry/src/lib.rs index 57e24e869..9baf3fe54 100644 --- a/masonry/src/lib.rs +++ b/masonry/src/lib.rs @@ -103,7 +103,7 @@ //! let list = Flex::column() //! .with_fixed( //! NewWidget::new(Flex::row().with(text_input, 1.0).with_fixed(button)) -//! .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING.get()))), +//! .with_props(PropertySet::new().with(Padding::all(WIDGET_SPACING))), //! ) //! .with_fixed_spacer(WIDGET_SPACING); //! diff --git a/masonry/src/properties/object_fit.rs b/masonry/src/properties/object_fit.rs index caf9f3bbf..4fe8cb2da 100644 --- a/masonry/src/properties/object_fit.rs +++ b/masonry/src/properties/object_fit.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::core::Property; -use crate::kurbo::{Affine, Axis, Size}; -use crate::layout::LenReq; +use crate::kurbo::{Affine, Axis, Rect, Size}; +use crate::layout::{LenReq, Length}; use crate::util::Sanitize; // These are based on https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit @@ -67,8 +67,6 @@ impl Default for ObjectFit { } } -// TODO - Need to write tests for this, in a way that's relatively easy to visualize. - impl ObjectFit { /// Calculates an [`Affine`] transform to fit `content` inside `container`. /// @@ -76,25 +74,22 @@ impl ObjectFit { /// /// # Panics /// - /// Panics if either `content` or `container` is non-finite or negative + /// Panics if either `content` or `container` has non-finite or negative size /// and debug assertions are enabled. - pub fn affine(self, container: Size, content: Size) -> Affine { + pub fn affine(self, container: Rect, content: Rect) -> Affine { // Guard against invalid input - let container = Size::new( - container.width.sanitize("container width"), - container.height.sanitize("container height"), - ); - let content = Size::new( - content.width.sanitize("content width"), - content.height.sanitize("content height"), - ); + let container_width = container.width().sanitize("container width"); + let container_height = container.height().sanitize("container height"); + let content_width = content.width().sanitize("content width"); + let content_height = content.height().sanitize("content height"); + // Guard against division by zero - if content.width == 0. || content.height == 0. { + if content_width == 0. || content_height == 0. { return Affine::IDENTITY; } - let raw_scalex = container.width / content.width; - let raw_scaley = container.height / content.height; + let raw_scalex = container_width / content_width; + let raw_scaley = container_height / content_height; let (scalex, scaley) = match self { Self::Contain => { @@ -115,13 +110,20 @@ impl ObjectFit { Self::Stretch => (raw_scalex, raw_scaley), }; - let origin_x = (container.width - (content.width * scalex)) * 0.5; - let origin_y = (container.height - (content.height * scaley)) * 0.5; + let origin_x = container.x0 + (container_width - (content_width * scalex)) * 0.5; + let origin_y = container.y0 + (container_height - (content_height * scaley)) * 0.5; - Affine::new([scalex, 0., 0., scaley, origin_x, origin_y]) + Affine::new([ + scalex, + 0., + 0., + scaley, + origin_x - content.x0 * scalex, + origin_y - content.y0 * scaley, + ]) } - /// Calculates the length of `axis`. + /// Calculates the [`Length`] of `axis`. /// /// `preferred_size` is the natural size that is used for /// both aspect ratio and minimum preferred size. @@ -129,15 +131,15 @@ impl ObjectFit { self, axis: Axis, len_req: LenReq, - cross_length: Option, + cross_length: Option, preferred_size: Size, - ) -> f64 { + ) -> Length { let preferred_length = preferred_size.get_coord(axis); let (space, space_or_preferred) = match len_req { - LenReq::MinContent => return preferred_length, + LenReq::MinContent => return Length::px(preferred_length), LenReq::MaxContent => (f64::INFINITY, preferred_length), - LenReq::FitContent(space) => (space, space), + LenReq::FitContent(space) => (space.get(), space.get()), }; let mut ar = preferred_size.get_coord(axis) / preferred_size.get_coord(axis.cross()); @@ -145,11 +147,11 @@ impl ObjectFit { ar = 1.; } - match self { + let length = match self { // Use all available space or if cross is known attempt to maintain AR, // but don't exceed available space (will letterbox cross). Self::Contain => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred), // Always use all available space. Self::Cover | Self::Stretch => space_or_preferred, @@ -157,7 +159,7 @@ impl ObjectFit { // Greedily take all horizontal space unless cross is known. Self::FitHeight => match axis { Axis::Horizontal => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred), Axis::Vertical => space_or_preferred, }, @@ -166,16 +168,18 @@ impl ObjectFit { Self::FitWidth => match axis { Axis::Horizontal => space_or_preferred, Axis::Vertical => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred), }, // None == preferred size Self::None => preferred_length, // ScaleDown == min(Contain, None) Self::ScaleDown => cross_length - .map(|cl| (cl * ar).min(space)) + .map(|cl| (cl.get() * ar).min(space)) .unwrap_or(space_or_preferred) .min(preferred_length), - } + }; + + Length::px(length) } } diff --git a/masonry/src/properties/slider.rs b/masonry/src/properties/slider.rs index 28f01de32..66a6eb4f6 100644 --- a/masonry/src/properties/slider.rs +++ b/masonry/src/properties/slider.rs @@ -4,16 +4,17 @@ use std::any::TypeId; use crate::core::{Property, UpdateCtx}; +use crate::layout::Length; use crate::peniko::Color; use crate::theme; -/// The thickness of a slider's track, in logical pixels. +/// The thickness of a slider's track. #[derive(Default, Clone, Copy, Debug, PartialEq)] -pub struct TrackThickness(pub f64); +pub struct TrackThickness(pub Length); impl Property for TrackThickness { fn static_default() -> &'static Self { - static DEFAULT: TrackThickness = TrackThickness(4.); + static DEFAULT: TrackThickness = TrackThickness(Length::const_px(4.)); &DEFAULT } } @@ -27,13 +28,13 @@ impl TrackThickness { } } -/// The radius of a slider's thumb, in logical pixels. +/// The radius of a slider's thumb. #[derive(Default, Clone, Copy, Debug, PartialEq)] -pub struct ThumbRadius(pub f64); +pub struct ThumbRadius(pub Length); impl Property for ThumbRadius { fn static_default() -> &'static Self { - static DEFAULT: ThumbRadius = ThumbRadius(6.); + static DEFAULT: ThumbRadius = ThumbRadius(Length::const_px(6.)); &DEFAULT } } diff --git a/masonry/src/tests/action.rs b/masonry/src/tests/action.rs index 0c3cd1544..212307e9b 100644 --- a/masonry/src/tests/action.rs +++ b/masonry/src/tests/action.rs @@ -8,7 +8,7 @@ use assert_matches::assert_matches; use crate::core::{ChildrenIds, Widget}; use crate::kurbo::Point; -use crate::layout::{AsUnit, LayoutSize}; +use crate::layout::{AsUnit, LayoutSize, Length}; use crate::properties::Dimensions; use crate::testing::{ModularWidget, TestHarness}; use crate::theme::test_property_set; @@ -61,7 +61,7 @@ fn action_source_removed() { ctx.compute_length(child, auto_length, context_size, axis, cross_length) } else { - 0. + Length::ZERO } }) .layout_fn(move |child, ctx, _props, size| { diff --git a/masonry/src/tests/compose.rs b/masonry/src/tests/compose.rs index fb28558f5..7a1140141 100644 --- a/masonry/src/tests/compose.rs +++ b/masonry/src/tests/compose.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use assert_matches::assert_matches; -use masonry_testing::{ModularWidget, Record, TestHarness, TestWidgetExt}; use crate::core::{ChildrenIds, NewWidget, Widget, WidgetPod, WidgetTag}; -use crate::kurbo::{Affine, Point, Vec2}; -use crate::layout::SizeDef; +use crate::kurbo::{Affine, Point, Rect, Size, Vec2}; +use crate::layout::{AsUnit, Length, SizeDef}; +use crate::testing::{ModularWidget, Record, TestHarness, TestWidgetExt}; +use crate::tests::assert_rect_approx_eq; use crate::theme::test_property_set; use crate::widgets::SizedBox; @@ -29,7 +30,7 @@ fn request_compose() { }; let parent = ModularWidget::new(child) - .measure_fn(|_state, _ctx, _props, _axis, _len_req, _cross_length| 0.) + .measure_fn(|_state, _ctx, _props, _axis, _len_req, _cross_length| Length::ZERO) .layout_fn(|state, ctx, _props, size| { let child_size = ctx.compute_size(&mut state.child, SizeDef::fit(size), size.into()); ctx.run_layout(&mut state.child, child_size); @@ -71,13 +72,180 @@ fn request_compose() { }); // Origin should be "parent_origin + pos + scroll_offset" - let origin = harness.get_widget(child_tag).ctx().window_origin(); + let border_box = harness.get_widget(child_tag).ctx().border_box(); + let origin = harness + .get_widget(child_tag) + .ctx() + .to_window(border_box.origin()); assert_eq!( origin.to_vec2(), Vec2::new(7., 7.) + Point::new(30., 30.).to_vec2() + Vec2::new(8., 8.) ); } +#[test] +fn pixel_snapping() { + let child_tag = WidgetTag::named("child"); + let child = NewWidget::new(SizedBox::empty().size(10.3.px(), 10.3.px())).with_tag(child_tag); + let pos = Point::new(5.1, 5.3); + let parent = ModularWidget::new_parent(child).layout_fn(move |child, ctx, _, size| { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, pos); + ctx.set_baselines(2.4, 2.6); + }); + let parent_tag = WidgetTag::named("parent"); + let parent = NewWidget::new(parent).with_tag(parent_tag); + + let harness = TestHarness::create(test_property_set(), parent); + + let child = harness.get_widget(child_tag); + let ctx = child.ctx(); + let border_box = ctx.border_box(); + let content_box = ctx.content_box(); + let layout_content_box = ctx.layout_content_box(); + let child_pos = ctx.to_window(border_box.origin()); + let first_baseline = harness.get_widget(parent_tag).ctx().first_baseline(); + let last_baseline = harness.get_widget(parent_tag).ctx().last_baseline(); + + assert_eq!(child_pos, Point::new(5.0, 5.0)); + assert_eq!(content_box.origin(), Point::ORIGIN); + assert_rect_approx_eq( + "layout_content_box", + layout_content_box, + Rect::from_origin_size(Point::new(0.1, 0.3), Size::new(10.3, 10.3)), + ); + assert_eq!(border_box.size(), Size::new(10., 11.)); + assert_eq!(first_baseline, 2.4); + assert_eq!(last_baseline, 2.6); +} + +#[test] +fn pixel_snapping_after_window_transforms() { + #[track_caller] + fn assert_has_fractional_edge(name: &str, rect: Rect) { + let edges = [rect.x0, rect.y0, rect.x1, rect.y1]; + assert!( + edges.iter().any(|edge| (edge - edge.round()).abs() > 1e-9), + "{name}: expected at least one fractional layout edge, got {rect:?}" + ); + } + + let translated_tag = WidgetTag::unique(); + let scaled_tag = WidgetTag::unique(); + let flipped_tag = WidgetTag::unique(); + let nested_tag = WidgetTag::unique(); + + let translated = NewWidget::new(SizedBox::empty().size(12.2.px(), 8.4.px())) + .with_tag(translated_tag) + .with_transform(Affine::translate(Vec2::new(0.37, 0.61))) + .erased(); + let scaled = NewWidget::new(SizedBox::empty().size(9.3.px(), 11.7.px())) + .with_tag(scaled_tag) + .with_transform(Affine::scale_non_uniform(1.25, 0.8).then_translate(Vec2::new(0.41, 0.29))) + .erased(); + let flipped = NewWidget::new(SizedBox::empty().size(10.6.px(), 7.5.px())) + .with_tag(flipped_tag) + .with_transform( + Affine::scale_non_uniform(-0.75, 1.4).then_translate(Vec2::new(0.48, -0.33)), + ) + .erased(); + let nested = NewWidget::new(SizedBox::empty().size(8.2.px(), 6.6.px())) + .with_tag(nested_tag) + .with_transform(Affine::scale_non_uniform(0.6, 1.35).then_translate(Vec2::new(0.27, 0.43))) + .erased(); + + let inner = ModularWidget::new_parent(nested) + .layout_fn(|child, ctx, _, size| { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, Point::new(1.7, 2.2)); + }) + .compose_fn(|child, ctx| { + ctx.set_child_scroll_translation(child, Vec2::new(0.33, -0.47)); + }); + let inner = NewWidget::new(inner) + .with_transform(Affine::scale_non_uniform(1.5, -0.9).then_translate(Vec2::new(0.19, 0.71))) + .erased(); + + let outer = ModularWidget::new_parent(inner) + .layout_fn(|child, ctx, _, size| { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, Point::new(4.6, 3.9)); + }) + .compose_fn(|child, ctx| { + ctx.set_child_scroll_translation(child, Vec2::new(-0.22, 0.35)); + }); + let outer = NewWidget::new(outer) + .with_transform(Affine::scale_non_uniform(-1.2, 0.7).then_translate(Vec2::new(0.52, -0.24))) + .erased(); + + let positions = [ + Point::new(2.3, 4.7), + Point::new(19.4, 3.6), + Point::new(37.8, 8.2), + Point::new(57.1, 5.4), + ]; + let scroll_offsets = [ + Vec2::new(0.21, 0.36), + Vec2::new(-0.44, 0.52), + Vec2::new(0.68, -0.17), + Vec2::new(-0.31, 0.49), + ]; + + let root = ModularWidget::new(vec![ + translated.to_pod(), + scaled.to_pod(), + flipped.to_pod(), + outer.to_pod(), + ]) + .layout_fn(move |children, ctx, _, size| { + for (idx, child) in children.iter_mut().enumerate() { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, positions[idx]); + } + }) + .compose_fn(move |children, ctx| { + for (idx, child) in children.iter_mut().enumerate() { + ctx.set_child_scroll_translation(child, scroll_offsets[idx]); + } + }) + .register_children_fn(|children, ctx| { + for child in children { + ctx.register_child(child); + } + }) + .children_fn(|children| children.iter().map(|child| child.id()).collect()) + .prepare(); + + let harness = TestHarness::create_with_size(test_property_set(), root, (200, 120)); + + let assert_snapped = |name: &str, tag: WidgetTag| { + let widget = harness.get_widget(tag); + let ctx = widget.ctx(); + let layout_window = ctx + .window_transform() + .transform_rect_bbox(ctx.layout_border_box()); + let visual_window = ctx.window_transform().transform_rect_bbox(ctx.border_box()); + let expected_visual_window = Rect::new( + layout_window.x0.round(), + layout_window.y0.round(), + layout_window.x1.round(), + layout_window.y1.round(), + ); + + assert_has_fractional_edge(name, layout_window); + assert_rect_approx_eq(name, visual_window, expected_visual_window); + }; + + assert_snapped("translated", translated_tag); + assert_snapped("scaled", scaled_tag); + assert_snapped("flipped", flipped_tag); + assert_snapped("nested", nested_tag); +} + #[test] fn scroll_pixel_snap() { let child_tag = WidgetTag::named("child"); @@ -93,7 +261,12 @@ fn scroll_pixel_snap() { let harness = TestHarness::create(test_property_set(), parent); + let border_box = harness.get_widget(child_tag).ctx().border_box(); + let origin = harness + .get_widget(child_tag) + .ctx() + .to_window(border_box.origin()); + // Origin should be rounded to (0., 1.) by pixel-snapping. - let origin = harness.get_widget(child_tag).ctx().window_origin(); assert_eq!(origin, Point::new(0., 1.)); } diff --git a/masonry/src/tests/event.rs b/masonry/src/tests/event.rs index 1afdae6e0..28d2db98d 100644 --- a/masonry/src/tests/event.rs +++ b/masonry/src/tests/event.rs @@ -28,7 +28,7 @@ fn create_capture_target() -> ModularWidget<()> { ctx.capture_pointer(); } }) - .measure_fn(|_, _, _, _, _, _| 10.) + .measure_fn(|_, _, _, _, _, _| 10.px()) } #[test] diff --git a/masonry/src/tests/layout.rs b/masonry/src/tests/layout.rs index 37e23274d..aa6bc5d5c 100644 --- a/masonry/src/tests/layout.rs +++ b/masonry/src/tests/layout.rs @@ -4,10 +4,11 @@ use assert_matches::assert_matches; use crate::core::{NewWidget, Widget, WidgetTag}; -use crate::kurbo::{Insets, Point, Rect, Size}; +use crate::kurbo::{Affine, Insets, Point, Rect, Size, Vec2}; use crate::layout::{AsUnit, Length, SizeDef}; use crate::properties::{BorderWidth, Dimensions, Padding}; use crate::testing::{ModularWidget, TestHarness, TestWidgetExt, assert_debug_panics}; +use crate::tests::{assert_point_approx_eq, assert_rect_approx_eq, assert_vec2_approx_eq}; use crate::theme::test_property_set; use crate::widgets::{Button, ChildAlignment, Flex, Portal, SizedBox, ZStack}; @@ -38,8 +39,8 @@ fn layout_simple() { let harness = TestHarness::create(test_property_set(), widget); - let first_box_size = harness.get_widget(tag_1).ctx().border_box_size(); - let first_box_paint_rect = harness.get_widget(tag_1).ctx().paint_box(); + let first_box_size = harness.get_widget(tag_1).ctx().layout_border_box().size(); + let first_box_paint_rect = harness.get_widget(tag_1).ctx().layout_paint_box(); assert_eq!(first_box_size.width, BOX_WIDTH); assert_eq!(first_box_size.height, BOX_WIDTH); @@ -53,7 +54,7 @@ fn layout_simple() { #[test] fn forget_to_recurse_layout() { let widget = ModularWidget::new_parent(Flex::row().prepare()) - .measure_fn(|_, _, _, _, _, _| 0.) + .measure_fn(|_, _, _, _, _, _| Length::ZERO) .layout_fn(|_child, _ctx, _, _| { // We forget to call ctx.run_layout(); }) @@ -83,7 +84,7 @@ fn forget_to_call_place_child() { #[test] fn call_place_child_before_layout() { let widget = ModularWidget::new_parent(Flex::row().prepare()) - .measure_fn(|_, _, _, _, _, _| 0.) + .measure_fn(|_, _, _, _, _, _| Length::ZERO) .layout_fn(|child, ctx, _, _| { // We call ctx.place_child(), but forget run_layout ctx.place_child(child, Point::ORIGIN); @@ -194,33 +195,6 @@ fn skip_layout_when_cached() { assert_matches!(button_records[..], []); } -#[test] -fn pixel_snapping() { - let child_tag = WidgetTag::named("child"); - let child = NewWidget::new(SizedBox::empty().size(10.3.px(), 10.3.px())).with_tag(child_tag); - let pos = Point::new(5.1, 5.3); - let parent = ModularWidget::new_parent(child).layout_fn(move |child, ctx, _, size| { - let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); - ctx.run_layout(child, child_size); - ctx.place_child(child, pos); - ctx.set_baselines(2.4, 2.6); - }); - let parent_tag = WidgetTag::named("parent"); - let parent = NewWidget::new(parent).with_tag(parent_tag); - - let harness = TestHarness::create(test_property_set(), parent); - - let child_pos = harness.get_widget(child_tag).ctx().window_origin(); - let child_size = harness.get_widget(child_tag).ctx().border_box_size(); - let first_baseline = harness.get_widget(parent_tag).ctx().first_baseline(); - let last_baseline = harness.get_widget(parent_tag).ctx().last_baseline(); - - assert_eq!(child_pos, Point::new(5.0, 5.0)); - assert_eq!(child_size, Size::new(10., 11.)); - assert_eq!(first_baseline, 2.4); - assert_eq!(last_baseline, 2.6); -} - #[test] fn layout_insets() { const BOX_WIDTH: f64 = 50.; @@ -229,7 +203,7 @@ fn layout_insets() { let parent_tag = WidgetTag::named("parent"); let child_widget = ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| BOX_WIDTH) + .measure_fn(|_, _, _, _, _, _| BOX_WIDTH.px()) .layout_fn(|_, ctx, _, _| { // this widget paints twenty points above and below its layout bounds ctx.set_paint_insets(Insets::uniform_xy(0., 20.)); @@ -244,8 +218,8 @@ fn layout_insets() { let harness = TestHarness::create(test_property_set(), root_widget); - let child_paint_rect = harness.get_widget(child_tag).ctx().paint_box(); - let parent_paint_rect = harness.get_widget(parent_tag).ctx().paint_box(); + let child_paint_rect = harness.get_widget(child_tag).ctx().layout_paint_box(); + let parent_paint_rect = harness.get_widget(parent_tag).ctx().layout_paint_box(); let parent_bounding_rect = harness.get_widget(parent_tag).ctx().bounding_box(); // The child's paint box is affected by its paint insets @@ -274,12 +248,12 @@ fn content_box() { let props = ( Dimensions::fixed(100.px(), 100.px()), Padding { - left: 1., - right: 2., - top: 3., - bottom: 4., + left: 1.px(), + right: 2.px(), + top: 3.px(), + bottom: 4.px(), }, - BorderWidth::all(1.), + BorderWidth::all(1.px()), ); let hero = NewWidget::new(Button::with_text("Hero")) @@ -288,10 +262,10 @@ fn content_box() { let harness = TestHarness::create(test_property_set(), hero); - let border_box = harness.get_widget(tag).ctx().border_box(); - let border_box_size = harness.get_widget(tag).ctx().border_box_size(); - let content_box = harness.get_widget(tag).ctx().content_box(); - let content_box_size = harness.get_widget(tag).ctx().content_box_size(); + let border_box = harness.get_widget(tag).ctx().layout_border_box(); + let border_box_size = border_box.size(); + let content_box = harness.get_widget(tag).ctx().layout_content_box(); + let content_box_size = content_box.size(); let border_box_translation = harness.get_widget(tag).ctx().border_box_translation(); let expected_border_box_size = Size::new(100., 100.); @@ -319,3 +293,276 @@ fn content_box() { assert_eq!(border_box, expected_border_box); assert_eq!(content_box, expected_content_box); } + +#[test] +fn boxes_match_without_insets_or_snapping() { + let tag = WidgetTag::unique(); + let child = NewWidget::new(SizedBox::empty().size(10.px(), 8.px())).with_tag(tag); + let root = ModularWidget::new_parent(child) + .layout_fn(|child, ctx, _, size| { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, Point::new(2., 3.)); + }) + .prepare(); + + let harness = TestHarness::create(test_property_set(), root); + let child = harness.get_widget(tag); + let ctx = child.ctx(); + + // Everything besides the bounding box will be exactly the same + let local_box = Rect::new(0., 0., 10., 8.); + assert_rect_approx_eq("content_box", ctx.content_box(), local_box); + assert_rect_approx_eq("layout_content_box", ctx.layout_content_box(), local_box); + assert_rect_approx_eq("border_box", ctx.border_box(), local_box); + assert_rect_approx_eq("layout_border_box", ctx.layout_border_box(), local_box); + assert_rect_approx_eq("paint_box", ctx.paint_box(), local_box); + assert_rect_approx_eq("layout_paint_box", ctx.layout_paint_box(), local_box); + assert_rect_approx_eq( + "bounding_box", + ctx.bounding_box(), + Rect::new(2., 3., 12., 11.), + ); +} + +#[test] +fn boxes_use_visual_content_box_coordinates() { + let tag = WidgetTag::unique(); + + let child = ModularWidget::new(()) + .layout_fn(|_, ctx, _, _| { + ctx.set_paint_insets(Insets::new(5.9, 6.1, 7.4, 8.2)); + }) + .prepare() + .with_tag(tag) + .with_props(( + Dimensions::fixed(23.4.px(), 17.6.px()), + BorderWidth::all(0.7.px()), + Padding { + left: 1.2.px(), + right: 2.3.px(), + top: 3.4.px(), + bottom: 4.5.px(), + }, + )); + + let root = ModularWidget::new_parent(child) + .layout_fn(|child, ctx, _, size| { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, Point::new(5.3, 7.6)); + }) + .prepare() + .with_props(Dimensions::fixed(80.px(), 80.px())); + + let harness = TestHarness::create(test_property_set(), root); + let child = harness.get_widget(tag); + let ctx = child.ctx(); + + // (5.3,7.6)..(28.7,25.2) snaps to (5,8)..(29,25), so visual origin is (-0.3,0.4). + assert_vec2_approx_eq( + "visual_translation", + ctx.visual_translation(), + Vec2::new(-0.3, 0.4), + ); + // Border 0.7 + padding (1.2,3.4) gives top-left content inset (1.9,4.1). + assert_vec2_approx_eq( + "border_box_translation", + ctx.border_box_translation(), + Vec2::new(1.9, 4.1), + ); + + // Visual size (24,17) minus insets (1.9+3.0,4.1+5.2) gives content size (19.1,7.7). + assert_rect_approx_eq( + "content_box", + ctx.content_box(), + Rect::new(0., 0., 19.1, 7.7), + ); + // Layout content (0,0)..(18.5,8.3) minus visual origin (-0.3,0.4) gives (0.3,-0.4)..(18.8,7.9). + assert_rect_approx_eq( + "layout_content_box", + ctx.layout_content_box(), + Rect::new(0.3, -0.4, 18.8, 7.9), + ); + // Visual border box (-0.3,0.4)..(23.7,17.4) minus visual+border-box translation (1.6,4.5). + assert_rect_approx_eq( + "border_box", + ctx.border_box(), + Rect::new(-1.9, -4.1, 22.1, 12.9), + ); + // Layout border box (0,0)..(23.4,17.6) minus visual+border-box translation (1.6,4.5). + assert_rect_approx_eq( + "layout_border_box", + ctx.layout_border_box(), + Rect::new(-1.6, -4.5, 21.8, 13.1), + ); + // Paint insets become (4,2,4.4,3); visual border box + those, + // minus visual+border-box translation (1.6,4.5). + assert_rect_approx_eq( + "paint_box", + ctx.paint_box(), + Rect::new(-5.9, -6.1, 26.5, 15.9), + ); + // Layout border box + paint insets (4,2,4.4,3), minus visual+border-box translation (1.6,4.5). + assert_rect_approx_eq( + "layout_paint_box", + ctx.layout_paint_box(), + Rect::new(-5.6, -6.5, 26.2, 16.1), + ); + // Visual paint box (-4.3,-1.6)..(28.1,20.4) plus layout origin (5.3,7.6). + assert_rect_approx_eq( + "bounding_box", + ctx.bounding_box(), + Rect::new(1., 6., 33.4, 28.), + ); + + // Local origin (== visual content box) maps to + // visual+border-box translation (1.6,4.5) plus layout origin (5.3,7.6). + assert_point_approx_eq( + "to_window content box origin", + ctx.to_window(Point::ORIGIN), + Point::new(6.9, 12.1), + ); + // Border box origin (-1.9,-4.1) plus visual+border-box translation (1.6,4.5) + // gives visual origin (-0.3,0.4), then plus layout origin (5.3,7.6). + assert_point_approx_eq( + "to_window border box origin", + ctx.to_window(ctx.border_box().origin()), + Point::new(5., 8.), + ); + // Inverse of content box window origin: (6.9,12.1) - (5.3,7.6) - (1.6,4.5) = (0,0). + assert_point_approx_eq( + "to_local content box origin", + ctx.to_local(Point::new(6.9, 12.1)), + Point::ORIGIN, + ); + // window_transform adds visual+border-box translation (1.6,4.5) and layout origin (5.3,7.6). + assert_point_approx_eq( + "window_transform", + ctx.window_transform() * Point::new(2., 1.), + Point::new(8.9, 13.1), + ); +} + +#[test] +fn visual_content_box_clamps_when_snapping_shrinks_below_insets() { + let tag = WidgetTag::unique(); + + let child = ModularWidget::new(()).prepare().with_tag(tag).with_props(( + Dimensions::fixed(2.5.px(), 2.5.px()), + BorderWidth::all(0.5.px()), + Padding { + left: 0.7.px(), + right: 0.8.px(), + top: 0.6.px(), + bottom: 0.9.px(), + }, + )); + + let root = ModularWidget::new_parent(child) + .layout_fn(|child, ctx, _, size| { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, Point::new(0.6, 0.6)); + }) + .prepare() + .with_props(Dimensions::fixed(20.px(), 20.px())); + + let harness = TestHarness::create(test_property_set(), root); + let child = harness.get_widget(tag); + let ctx = child.ctx(); + + // (0.6,0.6)..(3.1,3.1) snaps to (1,1)..(3,3), so visual origin is (0.4,0.4). + assert_vec2_approx_eq( + "visual_translation", + ctx.visual_translation(), + Vec2::new(0.4, 0.4), + ); + // Border 0.5 + padding (0.7,0.6) gives top-left content inset (1.2,1.1). + assert_vec2_approx_eq( + "border_box_translation", + ctx.border_box_translation(), + Vec2::new(1.2, 1.1), + ); + // Visual border box (0.4,0.4)..(2.4,2.4) minus visual+border-box translation (1.6,1.5). + assert_rect_approx_eq( + "border_box", + ctx.border_box(), + Rect::new(-1.2, -1.1, 0.8, 0.9), + ); + // Visual size (2,2) is smaller than inset sums (1.2+1.3, 1.1+1.4), so content clamps to zero. + assert_rect_approx_eq("content_box", ctx.content_box(), Rect::ZERO); + // Layout content size is 2.5 - (1.2+1.3) = 0, then subtract visual origin (0.4,0.4). + assert_rect_approx_eq( + "layout_content_box", + ctx.layout_content_box(), + Rect::new(-0.4, -0.4, -0.4, -0.4), + ); +} + +#[test] +fn transforms_handle_visual_content_box_space_translation() { + let tag = WidgetTag::unique(); + let child = NewWidget::new(SizedBox::empty().size(10.px(), 8.px())) + .with_tag(tag) + .with_transform(Affine::scale_non_uniform(2., 3.)) + .with_props(( + BorderWidth::all(0.5.px()), + Padding { + left: 1.px(), + right: 0.5.px(), + top: 2.px(), + bottom: 1.5.px(), + }, + )); + + let root = ModularWidget::new_parent(child) + .layout_fn(|child, ctx, _, size| { + let child_size = ctx.compute_size(child, SizeDef::fit(size), size.into()); + ctx.run_layout(child, child_size); + ctx.place_child(child, Point::new(5., 7.)); + }) + .prepare() + .with_props(Dimensions::fixed(40.px(), 40.px())); + + let harness = TestHarness::create(test_property_set(), root); + let child = harness.get_widget(tag); + let ctx = child.ctx(); + + // Border 0.5 + padding (1.0,2.0) gives top-left content inset (1.5,2.5). + assert_vec2_approx_eq( + "border_box_translation", + ctx.border_box_translation(), + Vec2::new(1.5, 2.5), + ); + // (0,0) + content inset (1.5,2.5), then scale (2,3) and add layout origin (5,7). + assert_point_approx_eq( + "to_window content origin", + ctx.to_window(Point::ORIGIN), + Point::new(8., 14.5), + ); + // (2,1) + content inset (1.5,2.5) = (3.5,3.5), then scale (2,3) and add (5,7). + assert_point_approx_eq( + "to_window local point", + ctx.to_window(Point::new(2., 1.)), + Point::new(12., 17.5), + ); + // Border box origin (-1.5,-2.5) cancels content inset, leaving the layout origin (5,7). + assert_point_approx_eq( + "to_window border origin", + ctx.to_window(ctx.border_box().origin()), + Point::new(5., 7.), + ); + // Inverse: ((12,17.5) - (5,7)) / (2,3) = (3.5,3.5), then subtract inset (1.5,2.5). + assert_point_approx_eq( + "to_local", + ctx.to_local(Point::new(12., 17.5)), + Point::new(2., 1.), + ); + // window_transform bakes in the required calculations and achieves the same result. + assert_point_approx_eq( + "window_transform", + ctx.window_transform() * Point::new(2., 1.), + Point::new(12., 17.5), + ); +} diff --git a/masonry/src/tests/mod.rs b/masonry/src/tests/mod.rs index 64da0e86a..560f4e615 100644 --- a/masonry/src/tests/mod.rs +++ b/masonry/src/tests/mod.rs @@ -5,6 +5,8 @@ //! both to centralize tests in a single crate and to have access to the `masonry` //! widget/property set in our tests if needed. +use crate::kurbo::{Point, Rect, Vec2}; + mod accessibility; mod action; mod anim; @@ -16,3 +18,31 @@ mod paint; mod properties; mod update; mod widget_tag; + +#[track_caller] +pub(crate) fn assert_approx_eq(name: &str, actual: f64, expected: f64) { + assert!( + (actual - expected).abs() <= 1e-9, + "{name}: expected {expected}, got {actual}" + ); +} + +#[track_caller] +pub(crate) fn assert_point_approx_eq(name: &str, actual: Point, expected: Point) { + assert_approx_eq(&format!("{name}.x"), actual.x, expected.x); + assert_approx_eq(&format!("{name}.y"), actual.y, expected.y); +} + +#[track_caller] +pub(crate) fn assert_vec2_approx_eq(name: &str, actual: Vec2, expected: Vec2) { + assert_approx_eq(&format!("{name}.x"), actual.x, expected.x); + assert_approx_eq(&format!("{name}.y"), actual.y, expected.y); +} + +#[track_caller] +pub(crate) fn assert_rect_approx_eq(name: &str, actual: Rect, expected: Rect) { + assert_approx_eq(&format!("{name}.x0"), actual.x0, expected.x0); + assert_approx_eq(&format!("{name}.y0"), actual.y0, expected.y0); + assert_approx_eq(&format!("{name}.x1"), actual.x1, expected.x1); + assert_approx_eq(&format!("{name}.y1"), actual.y1, expected.y1); +} diff --git a/masonry/src/tests/paint.rs b/masonry/src/tests/paint.rs index 708406250..5852d5895 100644 --- a/masonry/src/tests/paint.rs +++ b/masonry/src/tests/paint.rs @@ -76,7 +76,7 @@ fn paint_order() { let children = vec![child1, child2, child3]; let parent = NewWidget::new( ModularWidget::new_multi_parent(children) - .measure_fn(|_, _, _, _, _, _| SQUARE_SIZE * 2.) + .measure_fn(|_, _, _, _, _, _| (SQUARE_SIZE * 2.).px()) .layout_fn(move |children, ctx, _props, size| { let mut pos = Point::ZERO; for child in children { @@ -122,7 +122,7 @@ fn paint_clipping() { let parent = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| SQUARE_SIZE) + .measure_fn(|_, _, _, _, _, _| SQUARE_SIZE.px()) .layout_fn(|_, ctx, _, size| { ctx.set_clip_path(size.to_rect()); }) @@ -159,14 +159,14 @@ fn paint_clipping() { fn make_layer_split_tree(isolate_trailing_box: bool) -> NewWidget { let leading = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|_, ctx, _, scene| { scene.fill(ctx.content_box(), RED).draw(); }), ); let trailing = NewWidget::new( ModularWidget::new(isolate_trailing_box) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|isolate, ctx, _, scene| { if *isolate { ctx.set_paint_layer_mode(PaintLayerMode::IsolatedScene); @@ -184,14 +184,14 @@ fn make_layer_split_tree(isolate_trailing_box: bool) -> NewWidget { fn make_external_placeholder_tree() -> NewWidget { let leading = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|_, ctx, _, scene| { scene.fill(ctx.content_box(), RED).draw(); }), ); let placeholder = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .layout_fn(|_, ctx, _, size| { ctx.set_clip_path(size.to_rect()); }) @@ -201,7 +201,7 @@ fn make_external_placeholder_tree() -> NewWidget { ); let trailing = NewWidget::new( ModularWidget::new(()) - .measure_fn(|_, _, _, _, _, _| 20.) + .measure_fn(|_, _, _, _, _, _| 20.px()) .paint_fn(|_, ctx, _, scene| { scene.fill(ctx.content_box(), BLUE).draw(); }), @@ -330,7 +330,7 @@ fn paint_transparency() { GridParams::new(13, 0, 3, 1), ); - let props = (Padding::all(20.), Gap::new(10.px())); + let props = (Padding::all(20.px()), Gap::new(10.px())); let grid_a = grid_a.prepare().with_props(props); let grid_b = grid_b.prepare().with_props(props); diff --git a/masonry/src/tests/properties.rs b/masonry/src/tests/properties.rs index a82b6a081..c99f0365a 100644 --- a/masonry/src/tests/properties.rs +++ b/masonry/src/tests/properties.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::core::Widget as _; -use crate::layout::AsUnit as _; +use crate::kurbo::Rect; +use crate::layout::AsUnit; use crate::palette::css::BLUE; -use crate::properties::{ContentColor, Dimensions, Gap}; +use crate::properties::{ContentColor, Dimensions, Gap, ObjectFit}; +use crate::tests::assert_rect_approx_eq; use crate::widgets::Button; #[test] @@ -30,3 +32,31 @@ fn widget_new_properties() { assert_eq!(props.get::(), Some(&Gap::ZERO)); assert_eq!(props.get::(), Some(&ContentColor::new(BLUE))); } + +#[test] +fn object_fit_affine_stretch_maps_rect_to_rect() { + let container = Rect::new(10., -20., 110., 30.); + let content = Rect::new(-5., 10., 15., 20.); + + let transform = ObjectFit::Stretch.affine(container, content); + + assert_rect_approx_eq( + "transformed", + transform.transform_rect_bbox(content), + container, + ); +} + +#[test] +fn object_fit_affine_contain_handles_negative_origins() { + let container = Rect::new(-30., -20., 70., 30.); + let content = Rect::new(-10., -5., 10., 15.); + + let transform = ObjectFit::Contain.affine(container, content); + + assert_rect_approx_eq( + "transformed", + transform.transform_rect_bbox(content), + Rect::new(-5., -20., 45., 30.), + ); +} diff --git a/masonry/src/tests/transforms.rs b/masonry/src/tests/transforms.rs index b0c8dba71..bf81b52bd 100644 --- a/masonry/src/tests/transforms.rs +++ b/masonry/src/tests/transforms.rs @@ -21,7 +21,7 @@ fn blue_box(inner: impl Widget) -> impl Widget { let mut box_props = PropertySet::new(); box_props.insert(Background::Color(palette::css::BLUE)); box_props.insert(BorderColor::new(palette::css::TEAL)); - box_props.insert(BorderWidth::all(2.0)); + box_props.insert(BorderWidth::all(2.px())); WrapperWidget::new( SizedBox::new(inner.prepare()) diff --git a/masonry/src/tests/update.rs b/masonry/src/tests/update.rs index 4b5d8c460..f93756449 100644 --- a/masonry/src/tests/update.rs +++ b/masonry/src/tests/update.rs @@ -4,17 +4,17 @@ use std::sync::mpsc; use assert_matches::assert_matches; -use masonry_testing::{ - DebugName, ModularWidget, PRIMARY_MOUSE, Record, TestHarness, TestWidgetExt, assert_any, - assert_debug_panics, -}; use crate::core::pointer::PointerEvent; use crate::core::{ CursorIcon, Ime, NewWidget, PropertySet, TextEvent, Update, Widget, WidgetId, WidgetPod, WidgetTag, }; -use crate::layout::Length; +use crate::layout::{AsUnit, Length}; +use crate::testing::{ + DebugName, ModularWidget, PRIMARY_MOUSE, Record, TestHarness, TestWidgetExt, assert_any, + assert_debug_panics, +}; use crate::theme::test_property_set; use crate::widgets::{Button, Flex, Label, SizedBox, TextArea}; @@ -557,7 +557,7 @@ fn create_icon_widget() -> ModularWidget<()> { } }) .cursor_icon(CursorIcon::Crosshair) - .measure_fn(|_, _, _, _, _, _| 10.) + .measure_fn(|_, _, _, _, _, _| 10.px()) } #[test] @@ -648,12 +648,11 @@ fn change_hovered_when_widget_changes() { let parent_tag = WidgetTag::named("parent"); let child = - NewWidget::new(ModularWidget::new(BOX_SIZE).measure_fn(|size, _, _, _, _, _| size.get())) + NewWidget::new(ModularWidget::new(BOX_SIZE).measure_fn(|size, _, _, _, _, _| *size)) .with_tag(child_tag); - let parent = NewWidget::new( - ModularWidget::new_parent(child).measure_fn(|_, _, _, _, _, _| BOX_SIZE.get()), - ) - .with_tag(parent_tag); + let parent = + NewWidget::new(ModularWidget::new_parent(child).measure_fn(|_, _, _, _, _, _| BOX_SIZE)) + .with_tag(parent_tag); let mut harness = TestHarness::create(test_property_set(), parent); let child_id = harness.get_widget(child_tag).id(); @@ -697,7 +696,7 @@ fn make_reporter_parent( ctx.set_handled(); } }) - .measure_fn(|_, _, _, _, _, _| 100.) + .measure_fn(|_, _, _, _, _, _| 100.px()) .update_fn(move |_, _, _, event| { sender.send((event.short_name().to_string(), n)).unwrap(); }) diff --git a/masonry/src/theme.rs b/masonry/src/theme.rs index b58e751db..ce8b6b47d 100644 --- a/masonry/src/theme.rs +++ b/masonry/src/theme.rs @@ -8,7 +8,7 @@ use crate::core::{ DefaultProperties, PropertySet, PropertyStack, Selector, StyleProperty, StyleSet, }; -use crate::layout::Length; +use crate::layout::{AsUnit, Length}; use crate::palette::css::DIM_GRAY; use crate::parley::{GenericFamily, LineHeight}; use crate::peniko::Color; @@ -23,7 +23,7 @@ use crate::widgets::*; /// it should clear with this color by default. pub const BACKGROUND_COLOR: Color = Color::from_rgb8(0x1D, 0x1D, 0x1D); -pub const BORDER_WIDTH: f64 = 1.; +pub const BORDER_WIDTH: Length = Length::const_px(1.); // Zync color variations from https://tailwindcss.com/docs/colors pub const ZYNC_900: Color = Color::from_rgb8(0x18, 0x18, 0x1b); @@ -59,9 +59,9 @@ pub fn default_property_set() -> DefaultProperties { let mut properties = DefaultProperties::new(); // Badge - properties.insert::(Padding::from_vh(3., 5.)); - properties.insert::(CornerRadius { radius: 999. }); - properties.insert::(BorderWidth { width: 0. }); + properties.insert::(Padding::from_vh(3.px(), 5.px())); + properties.insert::(CornerRadius { radius: 999.px() }); + properties.insert::(BorderWidth { width: 0.px() }); properties.insert::(Background::Color(ACCENT_COLOR)); properties.insert::(BorderColor { color: ZYNC_700 }); { @@ -74,8 +74,8 @@ pub fn default_property_set() -> DefaultProperties { } // Button - properties.insert::(Padding::from_vh(6., 16.)); - properties.insert::(CornerRadius { radius: 6. }); + properties.insert::(Padding::from_vh(6.px(), 16.px())); + properties.insert::(CornerRadius { radius: 6.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -103,7 +103,7 @@ pub fn default_property_set() -> DefaultProperties { } // Checkbox - properties.insert::(CornerRadius { radius: 4. }); + properties.insert::(CornerRadius { radius: 4.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -142,21 +142,21 @@ pub fn default_property_set() -> DefaultProperties { Length::const_px(16.), Length::const_px(16.), )); - properties.insert::(Padding::all(4.)); + properties.insert::(Padding::all(4.px())); // Divider properties.insert::(ContentColor::new(ZYNC_500)); // Switch - properties.insert::(CornerRadius { radius: 10. }); // Full pill shape + properties.insert::(CornerRadius { radius: 10.px() }); // Full pill shape properties.insert::(BorderWidth { width: BORDER_WIDTH, }); properties.insert::(Background::Color(ZYNC_700)); properties.insert::(BorderColor { color: ZYNC_700 }); properties.insert::(ThumbColor(Color::WHITE)); - properties.insert::(ThumbRadius(8.0)); - properties.insert::(TrackThickness(20.0)); + properties.insert::(ThumbRadius(8.px())); + properties.insert::(TrackThickness(20.px())); { let mut stack = PropertyStack::new(); stack.push( @@ -186,8 +186,8 @@ pub fn default_property_set() -> DefaultProperties { use crate::widgets::Selector as SelectorButton; // Selector - properties.insert::(Padding::from_vh(6., 16.)); - properties.insert::(CornerRadius { radius: 2. }); + properties.insert::(Padding::from_vh(6.px(), 16.px())); + properties.insert::(CornerRadius { radius: 2.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -216,7 +216,7 @@ pub fn default_property_set() -> DefaultProperties { } // SelectorItem - properties.insert::(Padding::from_vh(6., 16.)); + properties.insert::(Padding::from_vh(6.px(), 16.px())); properties.insert::(Background::Color(ZYNC_900)); { let mut stack = PropertyStack::new(); @@ -238,8 +238,8 @@ pub fn default_property_set() -> DefaultProperties { properties.insert::(Gap::ZERO); // TextInput - properties.insert::(Padding::from_vh(6., 12.)); - properties.insert::(CornerRadius { radius: 4. }); + properties.insert::(Padding::from_vh(6.px(), 12.px())); + properties.insert::(CornerRadius { radius: 4.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -309,7 +309,7 @@ pub fn default_property_set() -> DefaultProperties { } // ProgressBar - properties.insert::(CornerRadius { radius: 2. }); + properties.insert::(CornerRadius { radius: 2.px() }); properties.insert::(BorderWidth { width: BORDER_WIDTH, }); @@ -381,8 +381,8 @@ pub fn default_text_styles(styles: &mut StyleSet) { } fn default_step_input_style(properties: &mut DefaultProperties) { - properties.insert::, _>(Padding::from_vh(6., 0.)); - properties.insert::, _>(CornerRadius { radius: 6. }); + properties.insert::, _>(Padding::from_vh(6.px(), 0.px())); + properties.insert::, _>(CornerRadius { radius: 6.px() }); properties.insert::, _>(BorderWidth { width: BORDER_WIDTH, }); diff --git a/masonry/src/widgets/align.rs b/masonry/src/widgets/align.rs index 68a12183b..bf6445f85 100644 --- a/masonry/src/widgets/align.rs +++ b/masonry/src/widgets/align.rs @@ -17,7 +17,7 @@ use crate::core::{ use crate::core::{MeasureCtx, WidgetMut}; use crate::imaging::Painter; use crate::kurbo::{Axis, Rect, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef, UnitPoint}; +use crate::layout::{AsUnit, LayoutSize, LenReq, Length, SizeDef, UnitPoint}; // TODO - Have child widget type as generic argument @@ -125,8 +125,8 @@ impl Widget for Align { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -152,7 +152,7 @@ impl Widget for Align { Axis::Horizontal => self.width_factor, Axis::Vertical => self.height_factor, } { - length = child_length * factor; + length = (child_length.get() * factor).px(); } // Never return a length larger than the bounds @@ -258,7 +258,7 @@ mod tests { .with_tag(align_tag) .with_props(( Dimensions::fixed(50.px(), 50.px()), - BorderWidth::all(2.), + BorderWidth::all(2.px()), BorderColor::new(palette::css::BLACK), )); let root = Align::centered(align).prepare(); diff --git a/masonry/src/widgets/badge.rs b/masonry/src/widgets/badge.rs index 1d15b8b59..39bd8e30b 100644 --- a/masonry/src/widgets/badge.rs +++ b/masonry/src/widgets/badge.rs @@ -14,7 +14,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::parley::style::FontWeight; use crate::widgets::Label; @@ -165,8 +165,8 @@ impl Widget for Badge { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); diff --git a/masonry/src/widgets/badged.rs b/masonry/src/widgets/badged.rs index 3e2f681b8..c41912f53 100644 --- a/masonry/src/widgets/badged.rs +++ b/masonry/src/widgets/badged.rs @@ -11,7 +11,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Point, Size, Vec2}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; /// Where a badge is placed relative to the content. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -185,8 +185,8 @@ impl Widget for Badged { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); ctx.compute_length( diff --git a/masonry/src/widgets/button.rs b/masonry/src/widgets/button.rs index ac81b562a..84c6d43fa 100644 --- a/masonry/src/widgets/button.rs +++ b/masonry/src/widgets/button.rs @@ -18,7 +18,7 @@ use crate::core::{ }; use crate::imaging::Painter; use crate::kurbo::{Axis, Size}; -use crate::layout::{LayoutSize, LenReq, SizeDef}; +use crate::layout::{LayoutSize, LenReq, Length, SizeDef}; use crate::theme; use crate::widgets::Label; @@ -178,12 +178,8 @@ impl Widget for Button { _props: &PropertiesRef<'_>, axis: Axis, len_req: LenReq, - cross_length: Option, - ) -> f64 { - // TODO: Remove HACK: Until scale factor rework happens, just pretend it's always 1.0. - // https://github.com/linebender/xilem/issues/1264 - let scale = 1.0; - + cross_length: Option, + ) -> Length { let auto_length = len_req.into(); let context_size = LayoutSize::maybe(axis.cross(), cross_length); @@ -202,7 +198,7 @@ impl Widget for Button { // we make sure we will have at least the same height as the default text input. match axis { Axis::Horizontal => length, - Axis::Vertical => length.max(theme::BASIC_WIDGET_HEIGHT.dp(scale)), + Axis::Vertical => length.max(theme::BASIC_WIDGET_HEIGHT), } } @@ -373,9 +369,9 @@ mod tests { harness.edit_root_widget(|mut button| { button.insert_prop(BorderColor { color: red }); - button.insert_prop(BorderWidth { width: 5.0 }); - button.insert_prop(CornerRadius { radius: 20.0 }); - button.insert_prop(Padding::from_vh(3., 8.)); + button.insert_prop(BorderWidth { width: 5.px() }); + button.insert_prop(CornerRadius { radius: 20.px() }); + button.insert_prop(Padding::from_vh(3.px(), 8.px())); let mut label = Button::child_mut(&mut button); label.insert_prop(ContentColor::new(red)); @@ -407,7 +403,7 @@ mod tests { ); let root_widget = NewWidget::new(grid).with_props( PropertySet::new() - .with(Padding::all(20.0)) + .with(Padding::all(20.px())) .with(Gap::new(40.px())), ); @@ -427,19 +423,19 @@ mod tests { { let mut button = Grid::get_mut(&mut grid, 1); let mut button = button.downcast::