From a8d2cc5aca1de286d76759dc405b223685a41907 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Mon, 19 Aug 2024 22:55:49 -0400 Subject: [PATCH 01/24] Initial grid layout --- masonry/examples/grid_masonry.rs | 79 ++++++++++++++ masonry/src/widget/grid.rs | 177 +++++++++++++++++++++++++++++++ masonry/src/widget/mod.rs | 2 + 3 files changed, 258 insertions(+) create mode 100644 masonry/examples/grid_masonry.rs create mode 100644 masonry/src/widget/grid.rs diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs new file mode 100644 index 000000000..dafdf7a6a --- /dev/null +++ b/masonry/examples/grid_masonry.rs @@ -0,0 +1,79 @@ +// Copyright 2019 the Xilem Authors and the Druid Authors +// SPDX-License-Identifier: Apache-2.0 + +//! This is a very small example of how to setup a masonry application. +//! It does the almost bare minimum while still being useful. + +// On Windows platform, don't show a console when opening the app. +#![windows_subsystem = "windows"] + +use masonry::app_driver::{AppDriver, DriverCtx}; +use masonry::dpi::LogicalSize; +use masonry::widget::{Button, Flex, Grid, Label, RootWidget}; +use masonry::{Action, WidgetId}; +use winit::window::Window; + +const GRID_SPACING: f64 = 10.0; + +struct Driver; + +impl AppDriver for Driver { + fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) { + + } +} + +struct DrawnButton { + x: i32, + y: i32, + width: i32, + height: i32, + // TODO: Color? +} + +impl DrawnButton { + fn get_label(&self) -> String { + format!("X: {}, Y: {}, W: {}, H: {}", self.x, self.y, self.width, self.height) + } +} + +pub fn main() { + let label = Label::new("A Label").with_text_size(28.0); + let button_inputs = vec![ + DrawnButton{ x: 0, y: 0, width: 1, height: 1 }, + DrawnButton{ x: 2, y: 0, width: 2, height: 1 }, + DrawnButton{ x: 0, y: 1, width: 1, height: 2 }, + DrawnButton{ x: 1, y: 1, width: 2, height: 2 }, + DrawnButton{ x: 3, y: 1, width: 1, height: 1 }, + DrawnButton{ x: 3, y: 2, width: 1, height: 1 }, + DrawnButton{ x: 0, y: 3, width: 4, height: 1 }, + ]; + + // Arrange the two widgets vertically, with some padding + let mut main_widget = Grid::with_dimensions(4, 4) + .with_child(label, 1, 0, 1, 1); + for button_input in button_inputs { + let button = Button::new(button_input.get_label()); + main_widget = main_widget.with_child( + button, + button_input.x, + button_input.y, + button_input.width, + button_input.height, + ) + } + + let window_size = LogicalSize::new(600.0, 500.0); + let window_attributes = Window::default_attributes() + .with_title("Grid Layout") + .with_resizable(true) + .with_min_inner_size(window_size); + + masonry::event_loop_runner::run( + masonry::event_loop_runner::EventLoop::with_user_event(), + window_attributes, + RootWidget::new(main_widget), + Driver, + ) + .unwrap(); +} diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs new file mode 100644 index 000000000..0d82a6e78 --- /dev/null +++ b/masonry/src/widget/grid.rs @@ -0,0 +1,177 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use accesskit::Role; +use smallvec::SmallVec; +use tracing::{trace, trace_span, Span}; +use vello::kurbo::{common::FloatExt, Affine, Line, Stroke, Vec2}; +use vello::Scene; + +use crate::theme::get_debug_color; +use crate::widget::{WidgetMut}; +use crate::{ + AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, + Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, +}; + +pub struct Grid { + children: Vec, + grid_width: i32, + grid_height: i32, +} + +// --- MARK: IMPL GRID --- +impl Grid { + pub fn with_dimensions(width: i32, height: i32) -> Self { + Grid { + children: Vec::new(), + grid_width: width, + grid_height: height, + } + } + + /// Builder-style variant of [`WidgetMut::add_child`]. + /// + /// Convenient for assembling a group of widgets in a single expression. + pub fn with_child(self, child: impl Widget, x: i32, y: i32, width: i32, height: i32) -> Self { + self.with_child_pod(WidgetPod::new(Box::new(child)), x, y, width, height) + } + + pub fn with_child_id(self, child: impl Widget, id: WidgetId, x: i32, y: i32, width: i32, height: i32) -> Self { + self.with_child_pod(WidgetPod::new_with_id(Box::new(child), id), x, y, width, height) + } + + pub fn with_child_pod(mut self, widget: WidgetPod>, x: i32, y: i32, width: i32, height: i32) -> Self { + let child = Child { + widget, + x, + y, + width, + height, + }; + self.children.push(child); + self + } +} + +// --- MARK: WIDGETMUT--- +impl<'a> WidgetMut<'a, Grid> { + /*/// Add a child widget. + /// + /// See also [`with_child`]. + /// + /// [`with_child`]: Grid::with_child + pub fn add_child(&mut self, child: impl Widget, x: i32, y: i32, width: i32, height: i32) { + let child_pod = WidgetPod::new(Box::new(child)); + self.insert_child_pod(child_pod, x, y, width, height); + } + + pub fn add_child_id(&mut self, child: impl Widget, id: WidgetId, x: i32, y: i32, width: i32, height: i32) { + let child_pod = WidgetPod::new_with_id(Box::new(child), id); + self.insert_child_pod(child_pod, x, y, width, height); + } + + /// Add a child widget. + pub fn insert_child_pod(&mut self, widget: WidgetPod>, x: i32, y: i32, width: i32, height: i32) { + let child = Child { + widget, + x, + y, + width, + height, + }; + self.widget.children.push(child); + self.ctx.children_changed(); + }*/ +} + +// --- MARK: IMPL WIDGET--- +impl Widget for Grid { + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} + + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &TextEvent) {} + + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} + + fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {} + + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) { + for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { + child.lifecycle(ctx, event); + } + } + + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { + bc.debug_check("Grid"); + let total_size = bc.max(); + let width_unit = total_size.width / (self.grid_width as f64); + let height_unit = total_size.height / (self.grid_height as f64); + for child in &mut self.children { + let cell_size = Size::new( + child.width as f64 * width_unit, + child.height as f64 * height_unit, + ); + let child_bc = BoxConstraints::new(cell_size, cell_size); + let _ = child.widget.layout(ctx, &child_bc); + // TODO: Baseline offset? + // TODO: Insets? + ctx.place_child(&mut child.widget, Point::new(child.x as f64 *width_unit, child.y as f64 * height_unit)) + } + total_size + } + + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { + child.paint(ctx, scene); + } + + // paint the baseline if we're debugging layout + if ctx.debug_paint && ctx.widget_state.baseline_offset != 0.0 { + let color = get_debug_color(ctx.widget_id().to_raw()); + let my_baseline = ctx.size().height - ctx.widget_state.baseline_offset; + let line = Line::new((0.0, my_baseline), (ctx.size().width, my_baseline)); + + let stroke_style = Stroke::new(1.0).with_dashes(0., [4.0, 4.0]); + scene.stroke(&stroke_style, Affine::IDENTITY, color, None, &line); + } + } + + fn accessibility_role(&self) -> Role { + Role::GenericContainer + } + + fn accessibility(&mut self, ctx: &mut AccessCtx) { + for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { + child.accessibility(ctx); + } + } + + fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { + self.children + .iter() + .filter_map(|child| child.widget()) + .map(|widget_pod| widget_pod.id()) + .collect() + } + + fn make_trace_span(&self) -> Span { + trace_span!("Grid") + } +} + +struct Child { + widget: WidgetPod>, + x: i32, + y: i32, + width: i32, + height: i32, +} + +impl Child { + fn widget_mut(&mut self) -> Option<&mut WidgetPod>> { + Some(&mut self.widget) + } + fn widget(&self) -> Option<&WidgetPod>> { + Some(&self.widget) + } +} \ No newline at end of file diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index 34def0863..8d27e648e 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -30,12 +30,14 @@ mod split; mod textbox; mod variable_label; mod widget_arena; +mod grid; pub use self::image::Image; pub use align::Align; pub use button::Button; pub use checkbox::Checkbox; pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment}; +pub use grid::Grid; pub use label::{Label, LineBreaking}; pub use portal::Portal; pub use progress_bar::ProgressBar; From 0425d567c8b206b25534929385fc28f5e5f31037 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 20 Aug 2024 23:18:04 -0400 Subject: [PATCH 02/24] Completed minimum viable grid --- masonry/examples/grid_masonry.rs | 39 ++++++++++++++++++++++-------- masonry/src/widget/grid.rs | 41 ++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index dafdf7a6a..f533b9649 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -9,17 +9,31 @@ use masonry::app_driver::{AppDriver, DriverCtx}; use masonry::dpi::LogicalSize; -use masonry::widget::{Button, Flex, Grid, Label, RootWidget}; -use masonry::{Action, WidgetId}; +use masonry::widget::{Button, Grid, Label, RootWidget}; +use masonry::{Action, PointerButton, WidgetId}; use winit::window::Window; -const GRID_SPACING: f64 = 10.0; - -struct Driver; +struct Driver { + grid_spacing: f64, +} impl AppDriver for Driver { - fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) { + fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) { + match action { + Action::ButtonPressed(button) => { + if button == PointerButton::Primary { + self.grid_spacing += 1.0; + } else if button == PointerButton::Secondary { + self.grid_spacing -= 1.0; + } else { + self.grid_spacing += 0.5; + } + ctx.get_root::>() + .get_element().set_spacing(self.grid_spacing) + } + _ => () + } } } @@ -28,7 +42,6 @@ struct DrawnButton { y: i32, width: i32, height: i32, - // TODO: Color? } impl DrawnButton { @@ -38,7 +51,8 @@ impl DrawnButton { } pub fn main() { - let label = Label::new("A Label").with_text_size(28.0); + let label = Label::new("Change spacing by right and\n left clicking on the buttons") + .with_text_size(14.0); let button_inputs = vec![ DrawnButton{ x: 0, y: 0, width: 1, height: 1 }, DrawnButton{ x: 2, y: 0, width: 2, height: 1 }, @@ -49,8 +63,13 @@ pub fn main() { DrawnButton{ x: 0, y: 3, width: 4, height: 1 }, ]; + let driver = Driver { + grid_spacing: 1.0, + }; + // Arrange the two widgets vertically, with some padding let mut main_widget = Grid::with_dimensions(4, 4) + .with_spacing(driver.grid_spacing) .with_child(label, 1, 0, 1, 1); for button_input in button_inputs { let button = Button::new(button_input.get_label()); @@ -63,7 +82,7 @@ pub fn main() { ) } - let window_size = LogicalSize::new(600.0, 500.0); + let window_size = LogicalSize::new(800.0, 500.0); let window_attributes = Window::default_attributes() .with_title("Grid Layout") .with_resizable(true) @@ -73,7 +92,7 @@ pub fn main() { masonry::event_loop_runner::EventLoop::with_user_event(), window_attributes, RootWidget::new(main_widget), - Driver, + driver, ) .unwrap(); } diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 0d82a6e78..e224adf2f 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -1,23 +1,24 @@ -// Copyright 2024 the Xilem Authors +// Copyright 2024 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 use accesskit::Role; use smallvec::SmallVec; -use tracing::{trace, trace_span, Span}; -use vello::kurbo::{common::FloatExt, Affine, Line, Stroke, Vec2}; +use tracing::{trace_span, Span}; +use vello::kurbo::{Affine, Line, Stroke}; use vello::Scene; use crate::theme::get_debug_color; -use crate::widget::{WidgetMut}; +use crate::widget::WidgetMut; use crate::{ AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, - Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, + Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, }; pub struct Grid { children: Vec, grid_width: i32, grid_height: i32, + grid_spacing: f64, } // --- MARK: IMPL GRID --- @@ -27,9 +28,15 @@ impl Grid { children: Vec::new(), grid_width: width, grid_height: height, + grid_spacing: 0.0, } } + pub fn with_spacing(mut self, spacing: f64) -> Self { + self.grid_spacing = spacing; + self + } + /// Builder-style variant of [`WidgetMut::add_child`]. /// /// Convenient for assembling a group of widgets in a single expression. @@ -56,18 +63,18 @@ impl Grid { // --- MARK: WIDGETMUT--- impl<'a> WidgetMut<'a, Grid> { - /*/// Add a child widget. + /// Add a child widget. /// /// See also [`with_child`]. /// /// [`with_child`]: Grid::with_child pub fn add_child(&mut self, child: impl Widget, x: i32, y: i32, width: i32, height: i32) { - let child_pod = WidgetPod::new(Box::new(child)); + let child_pod: WidgetPod> = WidgetPod::new(Box::new(child)); self.insert_child_pod(child_pod, x, y, width, height); } pub fn add_child_id(&mut self, child: impl Widget, id: WidgetId, x: i32, y: i32, width: i32, height: i32) { - let child_pod = WidgetPod::new_with_id(Box::new(child), id); + let child_pod: WidgetPod> = WidgetPod::new_with_id(Box::new(child), id); self.insert_child_pod(child_pod, x, y, width, height); } @@ -82,7 +89,12 @@ impl<'a> WidgetMut<'a, Grid> { }; self.widget.children.push(child); self.ctx.children_changed(); - }*/ + } + + pub fn set_spacing(&mut self, spacing: f64) { + self.widget.grid_spacing = spacing; + self.ctx.request_layout(); + } } // --- MARK: IMPL WIDGET--- @@ -104,23 +116,22 @@ impl Widget for Grid { fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { bc.debug_check("Grid"); let total_size = bc.max(); - let width_unit = total_size.width / (self.grid_width as f64); - let height_unit = total_size.height / (self.grid_height as f64); + let width_unit = (total_size.width + self.grid_spacing) / (self.grid_width as f64); + let height_unit = (total_size.height + self.grid_spacing) / (self.grid_height as f64); for child in &mut self.children { let cell_size = Size::new( - child.width as f64 * width_unit, - child.height as f64 * height_unit, + child.width as f64 * width_unit - self.grid_spacing, + child.height as f64 * height_unit - self.grid_spacing, ); let child_bc = BoxConstraints::new(cell_size, cell_size); let _ = child.widget.layout(ctx, &child_bc); - // TODO: Baseline offset? - // TODO: Insets? ctx.place_child(&mut child.widget, Point::new(child.x as f64 *width_unit, child.y as f64 * height_unit)) } total_size } fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + // Just paint every child for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { child.paint(ctx, scene); } From ada6816cf448b5f3aca97523c58469e910f0a92e Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Wed, 21 Aug 2024 23:45:42 -0400 Subject: [PATCH 03/24] Optimize layout calls --- masonry/src/widget/grid.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index e224adf2f..e8b9edb60 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -19,6 +19,7 @@ pub struct Grid { grid_width: i32, grid_height: i32, grid_spacing: f64, + old_bc: BoxConstraints, } // --- MARK: IMPL GRID --- @@ -29,6 +30,7 @@ impl Grid { grid_width: width, grid_height: height, grid_spacing: 0.0, + old_bc: BoxConstraints::new(Size::ZERO, Size::ZERO), } } @@ -115,10 +117,17 @@ impl Widget for Grid { fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { bc.debug_check("Grid"); + let bc_changed = self.old_bc != *bc; + if bc_changed { + self.old_bc = *bc; + } let total_size = bc.max(); let width_unit = (total_size.width + self.grid_spacing) / (self.grid_width as f64); let height_unit = (total_size.height + self.grid_spacing) / (self.grid_height as f64); for child in &mut self.children { + if !bc_changed && !ctx.child_needs_layout(&child.widget) { + continue; + } let cell_size = Size::new( child.width as f64 * width_unit - self.grid_spacing, child.height as f64 * height_unit - self.grid_spacing, From 583b24c98d16f8020ba688ded43209007fe26714 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Thu, 22 Aug 2024 00:09:55 -0400 Subject: [PATCH 04/24] Fix error due to layout run needed --- masonry/src/widget/grid.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index e8b9edb60..ec3e41878 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -20,6 +20,7 @@ pub struct Grid { grid_height: i32, grid_spacing: f64, old_bc: BoxConstraints, + needs_layout: bool, } // --- MARK: IMPL GRID --- @@ -31,6 +32,7 @@ impl Grid { grid_height: height, grid_spacing: 0.0, old_bc: BoxConstraints::new(Size::ZERO, Size::ZERO), + needs_layout: true, } } @@ -91,10 +93,16 @@ impl<'a> WidgetMut<'a, Grid> { }; self.widget.children.push(child); self.ctx.children_changed(); + self.mark_needs_layout(); } pub fn set_spacing(&mut self, spacing: f64) { self.widget.grid_spacing = spacing; + self.mark_needs_layout(); + } + + fn mark_needs_layout(&mut self) { + self.widget.needs_layout = true; self.ctx.request_layout(); } } @@ -120,12 +128,15 @@ impl Widget for Grid { let bc_changed = self.old_bc != *bc; if bc_changed { self.old_bc = *bc; + if !self.needs_layout { + self.needs_layout = true; + } } let total_size = bc.max(); let width_unit = (total_size.width + self.grid_spacing) / (self.grid_width as f64); let height_unit = (total_size.height + self.grid_spacing) / (self.grid_height as f64); for child in &mut self.children { - if !bc_changed && !ctx.child_needs_layout(&child.widget) { + if !self.needs_layout && !ctx.child_needs_layout(&child.widget) { continue; } let cell_size = Size::new( @@ -136,6 +147,9 @@ impl Widget for Grid { let _ = child.widget.layout(ctx, &child_bc); ctx.place_child(&mut child.widget, Point::new(child.x as f64 *width_unit, child.y as f64 * height_unit)) } + if self.needs_layout { + self.needs_layout = false; + } total_size } From dc604f91383807ec72b8f03f04d32620fde2015d Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Mon, 2 Sep 2024 10:33:47 -0400 Subject: [PATCH 05/24] Update inputs to grid layout functions --- masonry/examples/grid_masonry.rs | 30 +++---- masonry/src/widget/flex.rs | 2 +- masonry/src/widget/grid.rs | 134 ++++++++++++++++++++++++++----- masonry/src/widget/mod.rs | 2 +- 4 files changed, 129 insertions(+), 39 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index f533b9649..1e68c0cb0 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -9,7 +9,7 @@ use masonry::app_driver::{AppDriver, DriverCtx}; use masonry::dpi::LogicalSize; -use masonry::widget::{Button, Grid, Label, RootWidget}; +use masonry::widget::{Button, Grid, GridParams, Label, RootWidget}; use masonry::{Action, PointerButton, WidgetId}; use winit::window::Window; @@ -38,15 +38,12 @@ impl AppDriver for Driver { } struct DrawnButton { - x: i32, - y: i32, - width: i32, - height: i32, + grid_params: GridParams, } impl DrawnButton { fn get_label(&self) -> String { - format!("X: {}, Y: {}, W: {}, H: {}", self.x, self.y, self.width, self.height) + format!("X: {}, Y: {}, W: {}, H: {}", self.grid_params.x, self.grid_params.y, self.grid_params.width, self.grid_params.height) } } @@ -54,13 +51,13 @@ pub fn main() { let label = Label::new("Change spacing by right and\n left clicking on the buttons") .with_text_size(14.0); let button_inputs = vec![ - DrawnButton{ x: 0, y: 0, width: 1, height: 1 }, - DrawnButton{ x: 2, y: 0, width: 2, height: 1 }, - DrawnButton{ x: 0, y: 1, width: 1, height: 2 }, - DrawnButton{ x: 1, y: 1, width: 2, height: 2 }, - DrawnButton{ x: 3, y: 1, width: 1, height: 1 }, - DrawnButton{ x: 3, y: 2, width: 1, height: 1 }, - DrawnButton{ x: 0, y: 3, width: 4, height: 1 }, + DrawnButton{ grid_params: GridParams { x: 0, y: 0, width: 1, height: 1 }}, + DrawnButton{ grid_params: GridParams { x: 2, y: 0, width: 2, height: 1 }}, + DrawnButton{ grid_params: GridParams { x: 0, y: 1, width: 1, height: 2 }}, + DrawnButton{ grid_params: GridParams { x: 1, y: 1, width: 2, height: 2 }}, + DrawnButton{ grid_params: GridParams { x: 3, y: 1, width: 1, height: 1 }}, + DrawnButton{ grid_params: GridParams { x: 3, y: 2, width: 1, height: 1 }}, + DrawnButton{ grid_params: GridParams { x: 0, y: 3, width: 4, height: 1 }}, ]; let driver = Driver { @@ -70,15 +67,12 @@ pub fn main() { // Arrange the two widgets vertically, with some padding let mut main_widget = Grid::with_dimensions(4, 4) .with_spacing(driver.grid_spacing) - .with_child(label, 1, 0, 1, 1); + .with_child(label, GridParams::new(1, 0, 1, 1)); for button_input in button_inputs { let button = Button::new(button_input.get_label()); main_widget = main_widget.with_child( button, - button_input.x, - button_input.y, - button_input.width, - button_input.height, + button_input.grid_params, ) } diff --git a/masonry/src/widget/flex.rs b/masonry/src/widget/flex.rs index b19be0d8f..190241e08 100644 --- a/masonry/src/widget/flex.rs +++ b/masonry/src/widget/flex.rs @@ -524,7 +524,7 @@ impl<'a> WidgetMut<'a, Flex> { /// /// # Panics /// - /// Panics if the the element at `idx` is not a widget. + /// Panics if the element at `idx` is not a widget. pub fn update_child_flex_params(&mut self, idx: usize, params: impl Into) { let child = &mut self.widget.children[idx]; let child_val = std::mem::replace(child, Child::FixedSpacer(0.0, 0.0)); diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index ec3e41878..4ae246b2b 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -1,4 +1,4 @@ -// Copyright 2024 the Xilem Authors and the Druid Authors +// Copyright 2024 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 use accesskit::Role; @@ -44,21 +44,21 @@ impl Grid { /// Builder-style variant of [`WidgetMut::add_child`]. /// /// Convenient for assembling a group of widgets in a single expression. - pub fn with_child(self, child: impl Widget, x: i32, y: i32, width: i32, height: i32) -> Self { - self.with_child_pod(WidgetPod::new(Box::new(child)), x, y, width, height) + pub fn with_child(self, child: impl Widget, params: GridParams) -> Self { + self.with_child_pod(WidgetPod::new(Box::new(child)), params) } - pub fn with_child_id(self, child: impl Widget, id: WidgetId, x: i32, y: i32, width: i32, height: i32) -> Self { - self.with_child_pod(WidgetPod::new_with_id(Box::new(child), id), x, y, width, height) + pub fn with_child_id(self, child: impl Widget, id: WidgetId, params: GridParams) -> Self { + self.with_child_pod(WidgetPod::new_with_id(Box::new(child), id), params) } - pub fn with_child_pod(mut self, widget: WidgetPod>, x: i32, y: i32, width: i32, height: i32) -> Self { + pub fn with_child_pod(mut self, widget: WidgetPod>, params: GridParams) -> Self { let child = Child { widget, - x, - y, - width, - height, + x: params.x, + y: params.y, + width: params.width, + height: params.height, }; self.children.push(child); self @@ -72,39 +72,109 @@ impl<'a> WidgetMut<'a, Grid> { /// See also [`with_child`]. /// /// [`with_child`]: Grid::with_child - pub fn add_child(&mut self, child: impl Widget, x: i32, y: i32, width: i32, height: i32) { + pub fn add_child(&mut self, child: impl Widget, params: GridParams) { let child_pod: WidgetPod> = WidgetPod::new(Box::new(child)); - self.insert_child_pod(child_pod, x, y, width, height); + self.insert_child_pod(child_pod, params); } - pub fn add_child_id(&mut self, child: impl Widget, id: WidgetId, x: i32, y: i32, width: i32, height: i32) { + pub fn add_child_id(&mut self, child: impl Widget, id: WidgetId, params: GridParams) { let child_pod: WidgetPod> = WidgetPod::new_with_id(Box::new(child), id); - self.insert_child_pod(child_pod, x, y, width, height); + self.insert_child_pod(child_pod, params); } /// Add a child widget. - pub fn insert_child_pod(&mut self, widget: WidgetPod>, x: i32, y: i32, width: i32, height: i32) { + pub fn insert_child_pod(&mut self, widget: WidgetPod>, params: GridParams) { let child = Child { widget, - x, - y, - width, - height, + x: params.x, + y: params.y, + width: params.width, + height: params.height, }; self.widget.children.push(child); self.ctx.children_changed(); self.mark_needs_layout(); } + pub fn insert_grid_child( + &mut self, + idx: usize, + child: impl Widget, + params: impl Into, + ) { + self.insert_grid_child_pod(idx, WidgetPod::new(Box::new(child)), params); + } + + pub fn insert_grid_child_pod( + &mut self, + idx: usize, + child: WidgetPod>, + params: impl Into, + ) { + let child = new_grid_child(params.into(), child); + self.widget.children.insert(idx, child); + self.ctx.children_changed(); + + self.mark_needs_layout(); // TODO: needed? + } + pub fn set_spacing(&mut self, spacing: f64) { self.widget.grid_spacing = spacing; self.mark_needs_layout(); } + pub fn set_width(&mut self, width: i32) { + self.widget.grid_width = width; + self.mark_needs_layout(); + } + + pub fn set_height(&mut self, height: i32) { + self.widget.grid_height = height; + self.mark_needs_layout(); + } + fn mark_needs_layout(&mut self) { self.widget.needs_layout = true; self.ctx.request_layout(); } + + pub fn child_mut(&mut self, idx: usize) -> Option>>{ + let child = match self.widget.children[idx].widget_mut() { + Some(widget) => widget, + None => return None, + }; + + Some(self.ctx.get_mut(child)) + } + + /// Updates the grid parameters for the child at `idx`, + /// + /// # Panics + /// + /// Panics if the element at `idx` is not a widget. + pub fn update_child_grid_params(&mut self, idx: usize, params: GridParams) { + let child = &mut self.widget.children[idx]; + //let new_child = new_grid_child(params.into(), widget); + //*child = new_child; + child.update_params(params); + self.mark_needs_layout(); + } + + pub fn remove_child(&mut self, idx: usize) { + let child = self.widget.children.remove(idx); + self.ctx.remove_child(child.widget); + self.mark_needs_layout(); + } +} + +fn new_grid_child(params: GridParams, widget: WidgetPod>) -> Child { + Child{ + widget, + x: params.x, + y: params.y, + width: params.width, + height: params.height, + } } // --- MARK: IMPL WIDGET--- @@ -208,4 +278,30 @@ impl Child { fn widget(&self) -> Option<&WidgetPod>> { Some(&self.widget) } + + fn update_params(&mut self, params: GridParams) { + self.x = params.x; + self.y = params.y; + self.width = params.width; + self.height = params.height; + } +} + +#[derive(Default, Debug, Copy, Clone, PartialEq)] +pub struct GridParams { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, +} + +impl GridParams { + pub fn new(x: i32, y: i32, width: i32, height: i32) -> GridParams { + GridParams{ + x, + y, + width, + height, + } + } } \ No newline at end of file diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index 8d27e648e..0750496cd 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -37,7 +37,7 @@ pub use align::Align; pub use button::Button; pub use checkbox::Checkbox; pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment}; -pub use grid::Grid; +pub use grid::{Grid, GridParams}; pub use label::{Label, LineBreaking}; pub use portal::Portal; pub use progress_bar::ProgressBar; From 9b9ecb599685d4dce27eba6cbc207a446836a525 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Mon, 2 Sep 2024 22:43:29 -0400 Subject: [PATCH 06/24] Added grid to Xilem, and made calc example use grid --- masonry/src/widget/grid.rs | 30 +-- xilem/examples/calc.rs | 74 +++---- xilem/src/view/grid.rs | 392 +++++++++++++++++++++++++++++++++++++ xilem/src/view/mod.rs | 3 + 4 files changed, 441 insertions(+), 58 deletions(-) create mode 100644 xilem/src/view/grid.rs diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 4ae246b2b..55dd6d3af 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -19,8 +19,8 @@ pub struct Grid { grid_width: i32, grid_height: i32, grid_spacing: f64, - old_bc: BoxConstraints, - needs_layout: bool, + //old_bc: BoxConstraints, + //needs_layout: bool, } // --- MARK: IMPL GRID --- @@ -31,8 +31,8 @@ impl Grid { grid_width: width, grid_height: height, grid_spacing: 0.0, - old_bc: BoxConstraints::new(Size::ZERO, Size::ZERO), - needs_layout: true, + //old_bc: BoxConstraints::new(Size::ZERO, Size::ZERO), + //needs_layout: true, } } @@ -115,7 +115,7 @@ impl<'a> WidgetMut<'a, Grid> { self.widget.children.insert(idx, child); self.ctx.children_changed(); - self.mark_needs_layout(); // TODO: needed? + self.mark_needs_layout(); } pub fn set_spacing(&mut self, spacing: f64) { @@ -133,8 +133,9 @@ impl<'a> WidgetMut<'a, Grid> { self.mark_needs_layout(); } + /// Used to force a re-layout. fn mark_needs_layout(&mut self) { - self.widget.needs_layout = true; + //self.widget.needs_layout = true; self.ctx.request_layout(); } @@ -154,8 +155,6 @@ impl<'a> WidgetMut<'a, Grid> { /// Panics if the element at `idx` is not a widget. pub fn update_child_grid_params(&mut self, idx: usize, params: GridParams) { let child = &mut self.widget.children[idx]; - //let new_child = new_grid_child(params.into(), widget); - //*child = new_child; child.update_params(params); self.mark_needs_layout(); } @@ -195,20 +194,21 @@ impl Widget for Grid { fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { bc.debug_check("Grid"); - let bc_changed = self.old_bc != *bc; + /*let bc_changed = self.old_bc != *bc; if bc_changed { self.old_bc = *bc; if !self.needs_layout { self.needs_layout = true; } - } + }*/ let total_size = bc.max(); let width_unit = (total_size.width + self.grid_spacing) / (self.grid_width as f64); let height_unit = (total_size.height + self.grid_spacing) / (self.grid_height as f64); for child in &mut self.children { - if !self.needs_layout && !ctx.child_needs_layout(&child.widget) { - continue; - } + /*if !self.needs_layout && !ctx.child_needs_layout(&child.widget) { + ctx.mark_child_as_visited(&child.widget, true); + continue; // TODO: This breaks it. This is an attempted optimization. + }*/ let cell_size = Size::new( child.width as f64 * width_unit - self.grid_spacing, child.height as f64 * height_unit - self.grid_spacing, @@ -217,9 +217,9 @@ impl Widget for Grid { let _ = child.widget.layout(ctx, &child_bc); ctx.place_child(&mut child.widget, Point::new(child.x as f64 *width_unit, child.y as f64 * height_unit)) } - if self.needs_layout { + /*if self.needs_layout { self.needs_layout = false; - } + }*/ total_size } diff --git a/xilem/examples/calc.rs b/xilem/examples/calc.rs index 2ea78f62f..0566c2145 100644 --- a/xilem/examples/calc.rs +++ b/xilem/examples/calc.rs @@ -1,14 +1,14 @@ // Copyright 2024 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 -use masonry::widget::{CrossAxisAlignment, MainAxisAlignment}; +use masonry::widget::{CrossAxisAlignment, GridParams, MainAxisAlignment}; use winit::dpi::LogicalSize; use winit::error::EventLoopError; use winit::window::Window; -use xilem::view::{Flex, FlexSequence}; +use xilem::view::{Flex, FlexSequence, FlexSpacer, grid, GridExt, GridItem}; use xilem::EventLoopBuilder; use xilem::{ - view::{button, flex, label, sized_box, Axis, FlexExt as _, FlexSpacer}, + view::{button, flex, label, sized_box, Axis}, EventLoop, WidgetView, Xilem, }; @@ -184,16 +184,20 @@ impl Calculator { } } +// TODO: Is there a more generic way of doing the return type? This doesn't allow different +// "types" of grid_pos values. For example, I couldn't add the operator button. +fn num_row(nums: [&'static str; 3], row: i32) -> Vec, Calculator, ()>> { + let mut views: Vec<_> = vec![]; + for (i, num) in nums.iter().enumerate() { + views.push(digit_button(num).grid_pos(i as i32, row)) + } + views +} + const DISPLAY_FONT_SIZE: f32 = 30.; const GRID_GAP: f64 = 2.; fn app_logic(data: &mut Calculator) -> impl WidgetView { - let num_row = |nums: [&'static str; 3], operator| { - flex_row(( - nums.map(|num| digit_button(num).flex(1.)), - operator_button(operator).flex(1.), - )) - }; - flex(( + grid(( // Display centered_flex_row(( FlexSpacer::Flex(0.1), @@ -207,32 +211,25 @@ fn app_logic(data: &mut Calculator) -> impl WidgetView { .map(|result| display_label(result.as_ref())), FlexSpacer::Flex(0.1), )) - .flex(1.0), - FlexSpacer::Fixed(10.0), + .grid_item(GridParams::new(0, 0, 4, 1)), // Top row - flex_row(( - expanded_button("CE", Calculator::clear_entry).flex(1.), - expanded_button("C", Calculator::clear_all).flex(1.), - expanded_button("DEL", Calculator::on_delete).flex(1.), - operator_button(MathOperator::Divide).flex(1.), - )) - .flex(1.0), - num_row(["7", "8", "9"], MathOperator::Multiply).flex(1.0), - num_row(["4", "5", "6"], MathOperator::Subtract).flex(1.0), - num_row(["1", "2", "3"], MathOperator::Add).flex(1.0), + expanded_button("CE", Calculator::clear_entry).grid_pos(0, 1), + expanded_button("C", Calculator::clear_all).grid_pos(1, 1), + expanded_button("DEL", Calculator::on_delete).grid_pos(2, 1), + operator_button(MathOperator::Divide).grid_pos(3, 1), + num_row(["7", "8", "9"], 2), + operator_button(MathOperator::Multiply).grid_pos(3, 2), + num_row(["4", "5", "6"], 3), + operator_button(MathOperator::Subtract).grid_pos(3, 3), + num_row(["1", "2", "3"], 4), + operator_button(MathOperator::Add).grid_pos(3, 4), // bottom row - flex_row(( - expanded_button("±", Calculator::negate).flex(1.), - digit_button("0").flex(1.), - digit_button(".").flex(1.), - expanded_button("=", Calculator::on_equals).flex(1.), - )) - .flex(1.0), - )) - .gap(GRID_GAP) - .cross_axis_alignment(CrossAxisAlignment::Fill) - .main_axis_alignment(MainAxisAlignment::End) - .must_fill_major_axis(true) + expanded_button("±", Calculator::negate).grid_pos(0, 5), + digit_button("0").grid_pos(1, 5), + digit_button(".").grid_pos(2, 5), + expanded_button("=", Calculator::on_equals).grid_pos(3, 5), + ), 4, 6) + .spacing(GRID_GAP) } /// Creates a horizontal centered flex row designed for the display portion of the calculator. @@ -244,15 +241,6 @@ pub fn centered_flex_row>(sequence: Seq) -> Flex .gap(5.) } -/// Creates a horizontal filled flex row designed to be used in a grid. -pub fn flex_row>(sequence: Seq) -> Flex { - flex(sequence) - .direction(Axis::Horizontal) - .cross_axis_alignment(CrossAxisAlignment::Fill) - .main_axis_alignment(MainAxisAlignment::SpaceEvenly) - .gap(GRID_GAP) -} - /// Returns a label intended to be used in the calculator's top display. /// The default text size is out of proportion for this use case. fn display_label(text: &str) -> impl WidgetView { diff --git a/xilem/src/view/grid.rs b/xilem/src/view/grid.rs new file mode 100644 index 000000000..8a6495a4d --- /dev/null +++ b/xilem/src/view/grid.rs @@ -0,0 +1,392 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::marker::PhantomData; + +use masonry::{ + widget::{self, WidgetMut}, + Widget, +}; +use masonry::widget::GridParams; +use xilem_core::{ + AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, + ViewMarker, ViewSequence, +}; + +use crate::{Pod, ViewCtx, WidgetView}; + +pub fn grid>( + sequence: Seq, + width: i32, + height: i32, +) -> Grid { + Grid { + sequence, + spacing: 0.0, + phantom: PhantomData, + height, + width, + } +} + +pub struct Grid { + sequence: Seq, + spacing: f64, + width: i32, + height: i32, + /// Used to associate the State and Action in the call to `.grid()` with the State and Action + /// used in the View implementation, to allow inference to flow backwards, allowing State and + /// Action to be inferred properly + phantom: PhantomData (State, Action)>, +} + +impl Grid { + #[track_caller] + pub fn spacing(mut self, spacing: f64) -> Self { + if spacing.is_finite() && spacing >= 0.0 { + self.spacing = spacing; + } else { + panic!("Invalid `spacing` {spacing}; expected a non-negative finite value.") + } + self + } +} + +impl ViewMarker for Grid {} + +impl View for Grid +where + State: 'static, + Action: 'static, + Seq: GridSequence, +{ + type Element = Pod; + + type ViewState = Seq::SeqState; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let mut elements = AppendVec::default(); + let mut widget = widget::Grid::with_dimensions(self.width, self.height); + widget = widget.with_spacing(self.spacing); + let seq_state = self.sequence.seq_build(ctx, &mut elements); + for child in elements.into_inner() { + widget = match child { + GridElement::Child(child, params) => { + widget.with_child_pod(child.inner, params) + } + } + } + (Pod::new(widget), seq_state) + } + + fn rebuild<'el>( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + if prev.height != self.height { + element.set_height(self.height); + ctx.mark_changed(); + } + if prev.width != self.width { + element.set_width(self.width); + ctx.mark_changed(); + } + if prev.spacing != self.spacing { + element.set_spacing(self.spacing); + ctx.mark_changed(); + } + + let mut splice = GridSplice::new(element); + self.sequence + .seq_rebuild(&prev.sequence, view_state, ctx, &mut splice); + debug_assert!(splice.scratch.is_empty()); + splice.element + } + + fn teardown( + &self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + element: Mut<'_, Self::Element>, + ) { + let mut splice = GridSplice::new(element); + self.sequence.seq_teardown(view_state, ctx, &mut splice); + debug_assert!(splice.scratch.into_inner().is_empty()); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[xilem_core::ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.sequence + .seq_message(view_state, id_path, message, app_state) + } +} + +// Used to become a reference form for editing. It's provided to rebuild and teardown. +impl ViewElement for GridElement { + type Mut<'w> = GridElementMut<'w>; +} + +// Used to allow the item to be used as a generic item in ViewSequence. +impl SuperElement for GridElement { + fn upcast(child: GridElement) -> Self { + child + } + + fn with_downcast_val( + mut this: Mut<'_, Self>, + f: impl FnOnce(Mut<'_, GridElement>) -> R, + ) -> (Self::Mut<'_>, R) { + let r = { + let parent = this.parent.reborrow_mut(); + let reborrow = GridElementMut { + idx: this.idx, + parent, + }; + f(reborrow) + }; + (this, r) + } +} + +impl SuperElement> for GridElement { + fn upcast(child: Pod) -> Self { + // TODO: Defaults are bad. + GridElement::Child(child.inner.boxed().into(), GridParams::default()) + } + + fn with_downcast_val( + mut this: Mut<'_, Self>, + f: impl FnOnce(Mut<'_, Pod>) -> R, + ) -> (Mut<'_, Self>, R) { + let ret = { + let mut child = this + .parent + .child_mut(this.idx) + .expect("This is supposed to be a widget"); + let downcast = child.downcast(); + f(downcast) + }; + + (this, ret) + } +} + +// Used for building and rebuilding the ViewSequence + +impl ElementSplice for GridSplice<'_> { + fn with_scratch(&mut self, f: impl FnOnce(&mut AppendVec) -> R) -> R { + let ret = f(&mut self.scratch); + for element in self.scratch.drain() { + match element { + GridElement::Child(child, params) => { + self.element + .insert_grid_child_pod(self.idx, child.inner, params); + }, + }; + self.idx += 1; + } + ret + } + + fn insert(&mut self, element: GridElement) { + match element { + GridElement::Child(child, params) => { + self.element + .insert_grid_child_pod(self.idx, child.inner, params); + }, + }; + self.idx += 1; + } + + fn mutate(&mut self, f: impl FnOnce(Mut<'_, GridElement>) -> R) -> R { + let child = GridElementMut { + parent: self.element.reborrow_mut(), + idx: self.idx, + }; + let ret = f(child); + self.idx += 1; + ret + } + + fn skip(&mut self, n: usize) { + self.idx += n; + } + + fn delete(&mut self, f: impl FnOnce(Mut<'_, GridElement>) -> R) -> R { + let ret = { + let child = GridElementMut { + parent: self.element.reborrow_mut(), + idx: self.idx, + }; + f(child) + }; + self.element.remove_child(self.idx); + ret + } +} + +// TODO: Document +pub trait GridSequence: + ViewSequence +{ +} + +impl GridSequence for Seq where + Seq: ViewSequence +{ +} + +/// A trait which extends a [`WidgetView`] with methods to provide parameters for a grid item +pub trait GridExt: WidgetView { + // TODO: Document. + fn grid_item(self, params: impl Into) -> GridItem + where + State: 'static, + Action: 'static, + Self: Sized, + { + grid_item(self, params) + } + + fn grid_pos(self, x: i32, y: i32) -> GridItem + where + State: 'static, + Action: 'static, + Self: Sized, + { + grid_item(self, GridParams::new(x, y, 1, 1)) + } +} + +impl> GridExt for V {} + +pub enum GridElement { + Child(Pod>, GridParams), +} + +pub struct GridElementMut<'w> { + parent: WidgetMut<'w, widget::Grid>, + idx: usize, +} + +// This stores the widget and a scratch vec of GridElement? +pub struct GridSplice<'w> { + idx: usize, + element: WidgetMut<'w, widget::Grid>, + scratch: AppendVec, +} + + +impl<'w> GridSplice<'w> { + fn new(element: WidgetMut<'w, widget::Grid>) -> Self { + Self { + idx: 0, + element, + scratch: AppendVec::default(), + } + } +} + + +/// A `WidgetView` that can be used within a [`Grid`] [`View`] +pub struct GridItem { + view: V, + params: GridParams, + // TODO: Document and learn what this does. + phantom: PhantomData (State, Action)>, +} + +pub fn grid_item( + view: V, + params: impl Into, +) -> GridItem +where + State: 'static, + Action: 'static, + V: WidgetView, +{ + GridItem { + view, + params: params.into(), + phantom: PhantomData, + } +} + +impl ViewMarker for GridItem {} + +impl View for GridItem +where + State: 'static, + Action: 'static, + V: WidgetView, +{ + type Element = GridElement; + + type ViewState = V::ViewState; + + fn build(&self, cx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + let (pod, state) = self.view.build(cx); + ( + GridElement::Child(pod.inner.boxed().into(), self.params), + state, + ) + } + + fn rebuild<'el>( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'el, Self::Element>, + ) -> Mut<'el, Self::Element> { + { + if self.params != prev.params { + element + .parent + .update_child_grid_params(element.idx, self.params); + } + let mut child = element + .parent + .child_mut(element.idx) + .expect("GridWrapper always has a widget child"); + self.view + .rebuild(&prev.view, view_state, ctx, child.downcast()); + } + element + } + + fn teardown( + &self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + mut element: Mut<'_, Self::Element>, + ) { + let mut child = element + .parent + .child_mut(element.idx) + .expect("GridWrapper always has a widget child"); + self.view.teardown(view_state, ctx, child.downcast()); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[xilem_core::ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.view.message(view_state, id_path, message, app_state) + } +} + + + + diff --git a/xilem/src/view/mod.rs b/xilem/src/view/mod.rs index 5b14f8d5a..f88798ba4 100644 --- a/xilem/src/view/mod.rs +++ b/xilem/src/view/mod.rs @@ -32,6 +32,9 @@ mod prose; pub use prose::*; mod textbox; +mod grid; +pub use grid::*; + pub use textbox::*; mod portal; From b99b8efbab0d4de63347b760c9ee5816bd6ca085 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Mon, 2 Sep 2024 23:02:53 -0400 Subject: [PATCH 07/24] Remove completed TODO --- xilem/src/view/grid.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/xilem/src/view/grid.rs b/xilem/src/view/grid.rs index 8a6495a4d..885d4770d 100644 --- a/xilem/src/view/grid.rs +++ b/xilem/src/view/grid.rs @@ -300,7 +300,6 @@ impl<'w> GridSplice<'w> { pub struct GridItem { view: V, params: GridParams, - // TODO: Document and learn what this does. phantom: PhantomData (State, Action)>, } From 7bdab6bd49978080829e114408cbe825ed310771 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Mon, 2 Sep 2024 23:07:24 -0400 Subject: [PATCH 08/24] Update to address changes to main --- masonry/src/widget/grid.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 55dd6d3af..f59e4954c 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -224,11 +224,6 @@ impl Widget for Grid { } fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { - // Just paint every child - for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { - child.paint(ctx, scene); - } - // paint the baseline if we're debugging layout if ctx.debug_paint && ctx.widget_state.baseline_offset != 0.0 { let color = get_debug_color(ctx.widget_id().to_raw()); @@ -244,10 +239,7 @@ impl Widget for Grid { Role::GenericContainer } - fn accessibility(&mut self, ctx: &mut AccessCtx) { - for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) { - child.accessibility(ctx); - } + fn accessibility(&mut self, _: &mut AccessCtx) { } fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { From bff8b76a315aaaf9ba3b84bfacc174dc767b2668 Mon Sep 17 00:00:00 2001 From: Jared O'Connell <46976761+jaredoconnell@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:06:32 -0400 Subject: [PATCH 09/24] Apply suggestions from code review Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> --- masonry/examples/grid_masonry.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 1e68c0cb0..8d0e99053 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -1,8 +1,7 @@ -// Copyright 2019 the Xilem Authors and the Druid Authors +// Copyright 2024 the Xilem Authors // SPDX-License-Identifier: Apache-2.0 -//! This is a very small example of how to setup a masonry application. -//! It does the almost bare minimum while still being useful. +//! Shows how to use a grid layout in Masonry. // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] @@ -48,7 +47,7 @@ impl DrawnButton { } pub fn main() { - let label = Label::new("Change spacing by right and\n left clicking on the buttons") + let label = Prose::new("Change spacing by right and\n left clicking on the buttons") .with_text_size(14.0); let button_inputs = vec![ DrawnButton{ grid_params: GridParams { x: 0, y: 0, width: 1, height: 1 }}, From b4e40b349eb76d57474b573aa0f8d3add22a449f Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 3 Sep 2024 10:29:43 -0400 Subject: [PATCH 10/24] Address review comments --- masonry/examples/grid_masonry.rs | 37 ++++++++++++++------------------ xilem/examples/calc.rs | 6 ++---- xilem/src/view/mod.rs | 8 +++---- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 8d0e99053..3d83da66d 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -8,8 +8,8 @@ use masonry::app_driver::{AppDriver, DriverCtx}; use masonry::dpi::LogicalSize; -use masonry::widget::{Button, Grid, GridParams, Label, RootWidget}; -use masonry::{Action, PointerButton, WidgetId}; +use masonry::widget::{Button, Grid, GridParams, Prose, RootWidget, SizedBox}; +use masonry::{Action, Color, PointerButton, WidgetId}; use winit::window::Window; struct Driver { @@ -36,27 +36,22 @@ impl AppDriver for Driver { } } -struct DrawnButton { - grid_params: GridParams, -} - -impl DrawnButton { - fn get_label(&self) -> String { - format!("X: {}, Y: {}, W: {}, H: {}", self.grid_params.x, self.grid_params.y, self.grid_params.width, self.grid_params.height) - } +fn grid_button(params: GridParams) -> Button { + Button::new(format!("X: {}, Y: {}, W: {}, H: {}", params.x, params.y, params.width, params.height)) } pub fn main() { - let label = Prose::new("Change spacing by right and\n left clicking on the buttons") - .with_text_size(14.0); + let label = SizedBox::new(Prose::new("Change spacing by right and\n left clicking on the buttons") + .with_text_size(14.0)) + .border(Color::rgb8(40, 40, 80), 1.0); let button_inputs = vec![ - DrawnButton{ grid_params: GridParams { x: 0, y: 0, width: 1, height: 1 }}, - DrawnButton{ grid_params: GridParams { x: 2, y: 0, width: 2, height: 1 }}, - DrawnButton{ grid_params: GridParams { x: 0, y: 1, width: 1, height: 2 }}, - DrawnButton{ grid_params: GridParams { x: 1, y: 1, width: 2, height: 2 }}, - DrawnButton{ grid_params: GridParams { x: 3, y: 1, width: 1, height: 1 }}, - DrawnButton{ grid_params: GridParams { x: 3, y: 2, width: 1, height: 1 }}, - DrawnButton{ grid_params: GridParams { x: 0, y: 3, width: 4, height: 1 }}, + GridParams { x: 0, y: 0, width: 1, height: 1 }, + GridParams { x: 2, y: 0, width: 2, height: 1 }, + GridParams { x: 0, y: 1, width: 1, height: 2 }, + GridParams { x: 1, y: 1, width: 2, height: 2 }, + GridParams { x: 3, y: 1, width: 1, height: 1 }, + GridParams { x: 3, y: 2, width: 1, height: 1 }, + GridParams { x: 0, y: 3, width: 4, height: 1 }, ]; let driver = Driver { @@ -68,10 +63,10 @@ pub fn main() { .with_spacing(driver.grid_spacing) .with_child(label, GridParams::new(1, 0, 1, 1)); for button_input in button_inputs { - let button = Button::new(button_input.get_label()); + let button = grid_button(button_input); main_widget = main_widget.with_child( button, - button_input.grid_params, + button_input, ) } diff --git a/xilem/examples/calc.rs b/xilem/examples/calc.rs index 0566c2145..88f1964ad 100644 --- a/xilem/examples/calc.rs +++ b/xilem/examples/calc.rs @@ -5,7 +5,7 @@ use masonry::widget::{CrossAxisAlignment, GridParams, MainAxisAlignment}; use winit::dpi::LogicalSize; use winit::error::EventLoopError; use winit::window::Window; -use xilem::view::{Flex, FlexSequence, FlexSpacer, grid, GridExt, GridItem}; +use xilem::view::{Flex, FlexSequence, FlexSpacer, grid, GridExt, GridItem, GridSequence}; use xilem::EventLoopBuilder; use xilem::{ view::{button, flex, label, sized_box, Axis}, @@ -184,9 +184,7 @@ impl Calculator { } } -// TODO: Is there a more generic way of doing the return type? This doesn't allow different -// "types" of grid_pos values. For example, I couldn't add the operator button. -fn num_row(nums: [&'static str; 3], row: i32) -> Vec, Calculator, ()>> { +fn num_row(nums: [&'static str; 3], row: i32) -> impl GridSequence { let mut views: Vec<_> = vec![]; for (i, num) in nums.iter().enumerate() { views.push(digit_button(num).grid_pos(i as i32, row)) diff --git a/xilem/src/view/mod.rs b/xilem/src/view/mod.rs index f88798ba4..2d7e2bb4b 100644 --- a/xilem/src/view/mod.rs +++ b/xilem/src/view/mod.rs @@ -13,6 +13,9 @@ pub use checkbox::*; mod flex; pub use flex::*; +mod grid; +pub use grid::*; + mod sized_box; pub use sized_box::*; @@ -32,10 +35,7 @@ mod prose; pub use prose::*; mod textbox; -mod grid; -pub use grid::*; - pub use textbox::*; mod portal; -pub use portal::*; +pub use portal::*; \ No newline at end of file From e437cb3c5019265b2c63cb367c6e20011b9d94a2 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 3 Sep 2024 10:34:16 -0400 Subject: [PATCH 11/24] Address linter errors --- masonry/examples/grid_masonry.rs | 78 +++++++++++++++++++++++--------- masonry/src/widget/grid.rs | 24 ++++++---- masonry/src/widget/mod.rs | 2 +- xilem/examples/calc.rs | 70 ++++++++++++++-------------- xilem/src/view/grid.rs | 16 ++----- xilem/src/view/mod.rs | 2 +- 6 files changed, 115 insertions(+), 77 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 3d83da66d..ab4362759 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -29,34 +29,73 @@ impl AppDriver for Driver { } ctx.get_root::>() - .get_element().set_spacing(self.grid_spacing) + .get_element() + .set_spacing(self.grid_spacing) } - _ => () + _ => (), } } } fn grid_button(params: GridParams) -> Button { - Button::new(format!("X: {}, Y: {}, W: {}, H: {}", params.x, params.y, params.width, params.height)) + Button::new(format!( + "X: {}, Y: {}, W: {}, H: {}", + params.x, params.y, params.width, params.height + )) } pub fn main() { - let label = SizedBox::new(Prose::new("Change spacing by right and\n left clicking on the buttons") - .with_text_size(14.0)) - .border(Color::rgb8(40, 40, 80), 1.0); + let label = SizedBox::new( + Prose::new("Change spacing by right and\n left clicking on the buttons") + .with_text_size(14.0), + ) + .border(Color::rgb8(40, 40, 80), 1.0); let button_inputs = vec![ - GridParams { x: 0, y: 0, width: 1, height: 1 }, - GridParams { x: 2, y: 0, width: 2, height: 1 }, - GridParams { x: 0, y: 1, width: 1, height: 2 }, - GridParams { x: 1, y: 1, width: 2, height: 2 }, - GridParams { x: 3, y: 1, width: 1, height: 1 }, - GridParams { x: 3, y: 2, width: 1, height: 1 }, - GridParams { x: 0, y: 3, width: 4, height: 1 }, + GridParams { + x: 0, + y: 0, + width: 1, + height: 1, + }, + GridParams { + x: 2, + y: 0, + width: 2, + height: 1, + }, + GridParams { + x: 0, + y: 1, + width: 1, + height: 2, + }, + GridParams { + x: 1, + y: 1, + width: 2, + height: 2, + }, + GridParams { + x: 3, + y: 1, + width: 1, + height: 1, + }, + GridParams { + x: 3, + y: 2, + width: 1, + height: 1, + }, + GridParams { + x: 0, + y: 3, + width: 4, + height: 1, + }, ]; - let driver = Driver { - grid_spacing: 1.0, - }; + let driver = Driver { grid_spacing: 1.0 }; // Arrange the two widgets vertically, with some padding let mut main_widget = Grid::with_dimensions(4, 4) @@ -64,10 +103,7 @@ pub fn main() { .with_child(label, GridParams::new(1, 0, 1, 1)); for button_input in button_inputs { let button = grid_button(button_input); - main_widget = main_widget.with_child( - button, - button_input, - ) + main_widget = main_widget.with_child(button, button_input) } let window_size = LogicalSize::new(800.0, 500.0); @@ -82,5 +118,5 @@ pub fn main() { RootWidget::new(main_widget), driver, ) - .unwrap(); + .unwrap(); } diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index f59e4954c..22f6b76f7 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -52,7 +52,11 @@ impl Grid { self.with_child_pod(WidgetPod::new_with_id(Box::new(child), id), params) } - pub fn with_child_pod(mut self, widget: WidgetPod>, params: GridParams) -> Self { + pub fn with_child_pod( + mut self, + widget: WidgetPod>, + params: GridParams, + ) -> Self { let child = Child { widget, x: params.x, @@ -73,7 +77,7 @@ impl<'a> WidgetMut<'a, Grid> { /// /// [`with_child`]: Grid::with_child pub fn add_child(&mut self, child: impl Widget, params: GridParams) { - let child_pod: WidgetPod> = WidgetPod::new(Box::new(child)); + let child_pod: WidgetPod> = WidgetPod::new(Box::new(child)); self.insert_child_pod(child_pod, params); } @@ -139,7 +143,7 @@ impl<'a> WidgetMut<'a, Grid> { self.ctx.request_layout(); } - pub fn child_mut(&mut self, idx: usize) -> Option>>{ + pub fn child_mut(&mut self, idx: usize) -> Option>> { let child = match self.widget.children[idx].widget_mut() { Some(widget) => widget, None => return None, @@ -167,7 +171,7 @@ impl<'a> WidgetMut<'a, Grid> { } fn new_grid_child(params: GridParams, widget: WidgetPod>) -> Child { - Child{ + Child { widget, x: params.x, y: params.y, @@ -215,7 +219,10 @@ impl Widget for Grid { ); let child_bc = BoxConstraints::new(cell_size, cell_size); let _ = child.widget.layout(ctx, &child_bc); - ctx.place_child(&mut child.widget, Point::new(child.x as f64 *width_unit, child.y as f64 * height_unit)) + ctx.place_child( + &mut child.widget, + Point::new(child.x as f64 * width_unit, child.y as f64 * height_unit), + ); } /*if self.needs_layout { self.needs_layout = false; @@ -239,8 +246,7 @@ impl Widget for Grid { Role::GenericContainer } - fn accessibility(&mut self, _: &mut AccessCtx) { - } + fn accessibility(&mut self, _: &mut AccessCtx) {} fn children_ids(&self) -> SmallVec<[WidgetId; 16]> { self.children @@ -289,11 +295,11 @@ pub struct GridParams { impl GridParams { pub fn new(x: i32, y: i32, width: i32, height: i32) -> GridParams { - GridParams{ + GridParams { x, y, width, height, } } -} \ No newline at end of file +} diff --git a/masonry/src/widget/mod.rs b/masonry/src/widget/mod.rs index 0750496cd..7a3720ca2 100644 --- a/masonry/src/widget/mod.rs +++ b/masonry/src/widget/mod.rs @@ -17,6 +17,7 @@ mod align; mod button; mod checkbox; mod flex; +mod grid; mod image; mod label; mod portal; @@ -30,7 +31,6 @@ mod split; mod textbox; mod variable_label; mod widget_arena; -mod grid; pub use self::image::Image; pub use align::Align; diff --git a/xilem/examples/calc.rs b/xilem/examples/calc.rs index 88f1964ad..a071e190f 100644 --- a/xilem/examples/calc.rs +++ b/xilem/examples/calc.rs @@ -5,7 +5,7 @@ use masonry::widget::{CrossAxisAlignment, GridParams, MainAxisAlignment}; use winit::dpi::LogicalSize; use winit::error::EventLoopError; use winit::window::Window; -use xilem::view::{Flex, FlexSequence, FlexSpacer, grid, GridExt, GridItem, GridSequence}; +use xilem::view::{grid, Flex, FlexSequence, FlexSpacer, GridExt, GridItem, GridSequence}; use xilem::EventLoopBuilder; use xilem::{ view::{button, flex, label, sized_box, Axis}, @@ -195,38 +195,42 @@ fn num_row(nums: [&'static str; 3], row: i32) -> impl GridSequence { const DISPLAY_FONT_SIZE: f32 = 30.; const GRID_GAP: f64 = 2.; fn app_logic(data: &mut Calculator) -> impl WidgetView { - grid(( - // Display - centered_flex_row(( - FlexSpacer::Flex(0.1), - display_label(data.numbers[0].as_ref()), - data.operation - .map(|operation| display_label(operation.as_str())), - display_label(data.numbers[1].as_ref()), - data.result.is_some().then(|| display_label("=")), - data.result - .as_ref() - .map(|result| display_label(result.as_ref())), - FlexSpacer::Flex(0.1), - )) - .grid_item(GridParams::new(0, 0, 4, 1)), - // Top row - expanded_button("CE", Calculator::clear_entry).grid_pos(0, 1), - expanded_button("C", Calculator::clear_all).grid_pos(1, 1), - expanded_button("DEL", Calculator::on_delete).grid_pos(2, 1), - operator_button(MathOperator::Divide).grid_pos(3, 1), - num_row(["7", "8", "9"], 2), - operator_button(MathOperator::Multiply).grid_pos(3, 2), - num_row(["4", "5", "6"], 3), - operator_button(MathOperator::Subtract).grid_pos(3, 3), - num_row(["1", "2", "3"], 4), - operator_button(MathOperator::Add).grid_pos(3, 4), - // bottom row - expanded_button("±", Calculator::negate).grid_pos(0, 5), - digit_button("0").grid_pos(1, 5), - digit_button(".").grid_pos(2, 5), - expanded_button("=", Calculator::on_equals).grid_pos(3, 5), - ), 4, 6) + grid( + ( + // Display + centered_flex_row(( + FlexSpacer::Flex(0.1), + display_label(data.numbers[0].as_ref()), + data.operation + .map(|operation| display_label(operation.as_str())), + display_label(data.numbers[1].as_ref()), + data.result.is_some().then(|| display_label("=")), + data.result + .as_ref() + .map(|result| display_label(result.as_ref())), + FlexSpacer::Flex(0.1), + )) + .grid_item(GridParams::new(0, 0, 4, 1)), + // Top row + expanded_button("CE", Calculator::clear_entry).grid_pos(0, 1), + expanded_button("C", Calculator::clear_all).grid_pos(1, 1), + expanded_button("DEL", Calculator::on_delete).grid_pos(2, 1), + operator_button(MathOperator::Divide).grid_pos(3, 1), + num_row(["7", "8", "9"], 2), + operator_button(MathOperator::Multiply).grid_pos(3, 2), + num_row(["4", "5", "6"], 3), + operator_button(MathOperator::Subtract).grid_pos(3, 3), + num_row(["1", "2", "3"], 4), + operator_button(MathOperator::Add).grid_pos(3, 4), + // bottom row + expanded_button("±", Calculator::negate).grid_pos(0, 5), + digit_button("0").grid_pos(1, 5), + digit_button(".").grid_pos(2, 5), + expanded_button("=", Calculator::on_equals).grid_pos(3, 5), + ), + 4, + 6, + ) .spacing(GRID_GAP) } diff --git a/xilem/src/view/grid.rs b/xilem/src/view/grid.rs index 885d4770d..02e19d050 100644 --- a/xilem/src/view/grid.rs +++ b/xilem/src/view/grid.rs @@ -3,11 +3,11 @@ use std::marker::PhantomData; +use masonry::widget::GridParams; use masonry::{ widget::{self, WidgetMut}, Widget, }; -use masonry::widget::GridParams; use xilem_core::{ AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement, ViewMarker, ViewSequence, @@ -71,9 +71,7 @@ where let seq_state = self.sequence.seq_build(ctx, &mut elements); for child in elements.into_inner() { widget = match child { - GridElement::Child(child, params) => { - widget.with_child_pod(child.inner, params) - } + GridElement::Child(child, params) => widget.with_child_pod(child.inner, params), } } (Pod::new(widget), seq_state) @@ -189,7 +187,7 @@ impl ElementSplice for GridSplice<'_> { GridElement::Child(child, params) => { self.element .insert_grid_child_pod(self.idx, child.inner, params); - }, + } }; self.idx += 1; } @@ -201,7 +199,7 @@ impl ElementSplice for GridSplice<'_> { GridElement::Child(child, params) => { self.element .insert_grid_child_pod(self.idx, child.inner, params); - }, + } }; self.idx += 1; } @@ -284,7 +282,6 @@ pub struct GridSplice<'w> { scratch: AppendVec, } - impl<'w> GridSplice<'w> { fn new(element: WidgetMut<'w, widget::Grid>) -> Self { Self { @@ -295,7 +292,6 @@ impl<'w> GridSplice<'w> { } } - /// A `WidgetView` that can be used within a [`Grid`] [`View`] pub struct GridItem { view: V, @@ -385,7 +381,3 @@ where self.view.message(view_state, id_path, message, app_state) } } - - - - diff --git a/xilem/src/view/mod.rs b/xilem/src/view/mod.rs index 2d7e2bb4b..7c634fa3a 100644 --- a/xilem/src/view/mod.rs +++ b/xilem/src/view/mod.rs @@ -38,4 +38,4 @@ mod textbox; pub use textbox::*; mod portal; -pub use portal::*; \ No newline at end of file +pub use portal::*; From 556a145de02426673b6b541ff1786ce6ec998402 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 3 Sep 2024 10:41:49 -0400 Subject: [PATCH 12/24] Address example linter errors --- masonry/examples/grid_masonry.rs | 27 ++++++++++++--------------- xilem/examples/calc.rs | 4 ++-- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index ab4362759..9a0612a85 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -18,21 +18,18 @@ struct Driver { impl AppDriver for Driver { fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) { - match action { - Action::ButtonPressed(button) => { - if button == PointerButton::Primary { - self.grid_spacing += 1.0; - } else if button == PointerButton::Secondary { - self.grid_spacing -= 1.0; - } else { - self.grid_spacing += 0.5; - } - - ctx.get_root::>() - .get_element() - .set_spacing(self.grid_spacing) + if let Action::ButtonPressed(button) = action { + if button == PointerButton::Primary { + self.grid_spacing += 1.0; + } else if button == PointerButton::Secondary { + self.grid_spacing -= 1.0; + } else { + self.grid_spacing += 0.5; } - _ => (), + + ctx.get_root::>() + .get_element() + .set_spacing(self.grid_spacing); } } } @@ -103,7 +100,7 @@ pub fn main() { .with_child(label, GridParams::new(1, 0, 1, 1)); for button_input in button_inputs { let button = grid_button(button_input); - main_widget = main_widget.with_child(button, button_input) + main_widget = main_widget.with_child(button, button_input); } let window_size = LogicalSize::new(800.0, 500.0); diff --git a/xilem/examples/calc.rs b/xilem/examples/calc.rs index a071e190f..a0973176d 100644 --- a/xilem/examples/calc.rs +++ b/xilem/examples/calc.rs @@ -5,7 +5,7 @@ use masonry::widget::{CrossAxisAlignment, GridParams, MainAxisAlignment}; use winit::dpi::LogicalSize; use winit::error::EventLoopError; use winit::window::Window; -use xilem::view::{grid, Flex, FlexSequence, FlexSpacer, GridExt, GridItem, GridSequence}; +use xilem::view::{grid, Flex, FlexSequence, FlexSpacer, GridExt, GridSequence}; use xilem::EventLoopBuilder; use xilem::{ view::{button, flex, label, sized_box, Axis}, @@ -187,7 +187,7 @@ impl Calculator { fn num_row(nums: [&'static str; 3], row: i32) -> impl GridSequence { let mut views: Vec<_> = vec![]; for (i, num) in nums.iter().enumerate() { - views.push(digit_button(num).grid_pos(i as i32, row)) + views.push(digit_button(num).grid_pos(i as i32, row)); } views } From e541c648c4a5f61ab98fc3295238e97a8740ec53 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 3 Sep 2024 15:30:17 -0400 Subject: [PATCH 13/24] Commit change that wasn't added --- masonry/examples/grid_masonry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 9a0612a85..d8dd36e48 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -94,7 +94,7 @@ pub fn main() { let driver = Driver { grid_spacing: 1.0 }; - // Arrange the two widgets vertically, with some padding + // Arrange widgets in a 4 by 4 grid. let mut main_widget = Grid::with_dimensions(4, 4) .with_spacing(driver.grid_spacing) .with_child(label, GridParams::new(1, 0, 1, 1)); From 19a0761514c987c9621670f8c95b4a814b97e7d1 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Wed, 4 Sep 2024 23:38:35 -0400 Subject: [PATCH 14/24] Improve documentation for Grid --- xilem/src/view/grid.rs | 47 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/xilem/src/view/grid.rs b/xilem/src/view/grid.rs index 02e19d050..35cf8c50d 100644 --- a/xilem/src/view/grid.rs +++ b/xilem/src/view/grid.rs @@ -156,8 +156,12 @@ impl SuperElement for GridElement { impl SuperElement> for GridElement { fn upcast(child: Pod) -> Self { - // TODO: Defaults are bad. - GridElement::Child(child.inner.boxed().into(), GridParams::default()) + // Getting here means that the widget didn't use .grid_item or .grid_pos. + // This currently places the widget in the top left cell. + // There is not much else, beyond purposefully failing, that can be done here, + // because there isn't enough information to determine an appropriate spot + // for the widget. + GridElement::Child(child.inner.boxed().into(), GridParams::new(1, 1, 1, 1)) } fn with_downcast_val( @@ -178,7 +182,6 @@ impl SuperElement> for GridElement { } // Used for building and rebuilding the ViewSequence - impl ElementSplice for GridSplice<'_> { fn with_scratch(&mut self, f: impl FnOnce(&mut AppendVec) -> R) -> R { let ret = f(&mut self.scratch); @@ -231,7 +234,7 @@ impl ElementSplice for GridSplice<'_> { } } -// TODO: Document +/// GridSequence is what allows an input to the grid that contains all the grid elements. pub trait GridSequence: ViewSequence { @@ -244,7 +247,22 @@ impl GridSequence for Seq where /// A trait which extends a [`WidgetView`] with methods to provide parameters for a grid item pub trait GridExt: WidgetView { - // TODO: Document. + /// Applies [`impl Into`](`GridParams`) to this view. This allows the view + /// to be placed as a child within a [`Grid`] [`View`]. + /// + /// # Examples + /// ``` + /// use masonry::widget::GridParams; + /// use xilem::{view::{button, prose, grid, GridExt}}; + /// # use xilem::{WidgetView}; + /// + /// # fn view() -> impl WidgetView { + /// grid(( + /// button("click me", |_| ()).grid_item(GridParams::new(0, 0, 2, 1)), + /// prose("a prose").grid_item(GridParams::new(1, 1, 1, 1)), + /// ), 2, 2) + /// # } + /// ``` fn grid_item(self, params: impl Into) -> GridItem where State: 'static, @@ -254,6 +272,23 @@ pub trait GridExt: WidgetView { grid_item(self, params) } + /// Applies a [`impl Into`](`GridParams`) with the specified position to this view. + /// This allows the view to be placed as a child within a [`Grid`] [`View`]. + /// For instances where a grid item is expected to take up multiple cell units, + /// use [`GridExt::grid_item`] + /// + /// # Examples + /// ``` + /// use masonry::widget::GridParams; + /// use xilem::{view::{button, prose, grid, GridExt}}; + /// # use xilem::{WidgetView}; + /// + /// # fn view() -> impl WidgetView { + /// grid(( + /// button("click me", |_| ()).grid_pos(0, 0), + /// prose("a prose").grid_pos(1, 1), + /// ), 2, 2) + /// # } fn grid_pos(self, x: i32, y: i32) -> GridItem where State: 'static, @@ -275,7 +310,7 @@ pub struct GridElementMut<'w> { idx: usize, } -// This stores the widget and a scratch vec of GridElement? +// Used for manipulating the ViewSequence. pub struct GridSplice<'w> { idx: usize, element: WidgetMut<'w, widget::Grid>, From 6430d8b61db501d3bbf4e48cada8594e076efcd9 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Wed, 4 Sep 2024 23:44:03 -0400 Subject: [PATCH 15/24] Fixed clippy linter error --- xilem/src/view/grid.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xilem/src/view/grid.rs b/xilem/src/view/grid.rs index 35cf8c50d..44ea99b64 100644 --- a/xilem/src/view/grid.rs +++ b/xilem/src/view/grid.rs @@ -234,7 +234,7 @@ impl ElementSplice for GridSplice<'_> { } } -/// GridSequence is what allows an input to the grid that contains all the grid elements. +/// `GridSequence` is what allows an input to the grid that contains all the grid elements. pub trait GridSequence: ViewSequence { From edeb58a8d4dadb2641bcaab9e8ab782283d5f2cf Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Sun, 8 Sep 2024 00:24:41 -0400 Subject: [PATCH 16/24] Added tests to grid widget --- masonry/src/widget/grid.rs | 153 ++++++++++++++++-- ...t__grid__tests__2x2_with_overlapping_b.png | 3 + ...t__grid__tests__2x2_with_overlapping_c.png | 3 + ...__grid__tests__2x2_with_removed_widget.png | 3 + ...nry__widget__grid__tests__expanded_4x1.png | 3 + ...nry__widget__grid__tests__expanded_4x4.png | 3 + ...onry__widget__grid__tests__initial_1x1.png | 3 + ...onry__widget__grid__tests__initial_2x2.png | 3 + ...onry__widget__grid__tests__moved_2x2_1.png | 3 + ...onry__widget__grid__tests__moved_2x2_2.png | 3 + ...__widget__grid__tests__with_2x2_widget.png | 3 + ...get__grid__tests__with_changed_spacing.png | 3 + ...t__grid__tests__with_horizontal_widget.png | 3 + ...get__grid__tests__with_vertical_widget.png | 3 + 14 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_b.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_c.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_removed_widget.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x1.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x4.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_1x1.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_2x2.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_1.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_2.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__with_2x2_widget.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__with_changed_spacing.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__with_horizontal_widget.png create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__with_vertical_widget.png diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 22f6b76f7..340d53333 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -88,19 +88,13 @@ impl<'a> WidgetMut<'a, Grid> { /// Add a child widget. pub fn insert_child_pod(&mut self, widget: WidgetPod>, params: GridParams) { - let child = Child { - widget, - x: params.x, - y: params.y, - width: params.width, - height: params.height, - }; + let child = new_grid_child(params.into(), widget); self.widget.children.push(child); self.ctx.children_changed(); self.mark_needs_layout(); } - pub fn insert_grid_child( + pub fn insert_grid_child_at( &mut self, idx: usize, child: impl Widget, @@ -294,7 +288,23 @@ pub struct GridParams { } impl GridParams { - pub fn new(x: i32, y: i32, width: i32, height: i32) -> GridParams { + pub fn new(mut x: i32, mut y: i32, mut width: i32, mut height: i32) -> GridParams { + if x < 0 { + debug_panic!("Grid x value should be a non-negative number; got {}", x); + x = 0; + } + if y < 0 { + debug_panic!("Grid y value should be a non-negative number; got {}", y); + y = 0; + } + if width <= 0 { + debug_panic!("Grid width value should be a positive nonzero number; got {}", width); + width = 1; + } + if height <= 0 { + debug_panic!("Grid height value should be a positive nonzero number; got {}", height); + height = 1; + } GridParams { x, y, @@ -303,3 +313,128 @@ impl GridParams { } } } + +// --- MARK: TESTS --- +#[cfg(test)] +mod tests { + use super::*; + use crate::assert_render_snapshot; + use crate::testing::TestHarness; + use crate::widget::button; + + #[test] + fn test_grid_basics() { + // Start with a 1x1 grid + let widget = Grid::with_dimensions(1, 1) + .with_child(button::Button::new("A"), GridParams::new(0, 0, 1, 1)); + let mut harness = TestHarness::create(widget); + // Snapshot with the single widget. + assert_render_snapshot!(harness, "initial_1x1"); + + // Expand it to a 4x4 grid + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.set_width(4); + }); + assert_render_snapshot!(harness, "expanded_4x1"); + + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.set_height(4); + }); + assert_render_snapshot!(harness, "expanded_4x4"); + + // Add a widget that takes up more than one horizontal cell + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.add_child(button::Button::new("B"), + GridParams::new(1, 0, 3, 1)); + }); + assert_render_snapshot!(harness, "with_horizontal_widget"); + + // Add a widget that takes up more than one vertical cell + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.add_child(button::Button::new("C"), + GridParams::new(0, 1, 1, 3)); + }); + assert_render_snapshot!(harness, "with_vertical_widget"); + + // Add a widget that takes up more than one horizontal and vertical cell + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.add_child(button::Button::new("D"), + GridParams::new(1, 1, 2, 2)); + }); + assert_render_snapshot!(harness, "with_2x2_widget"); + + // Change the spacing + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.set_spacing(7.0); + }); + assert_render_snapshot!(harness, "with_changed_spacing"); + } + + #[test] + fn test_widget_removal_and_modification() { + let widget = Grid::with_dimensions(2, 2) + .with_child(button::Button::new("A"), GridParams::new(0, 0, 1, 1)); + let mut harness = TestHarness::create(widget); + // Snapshot with the single widget. + assert_render_snapshot!(harness, "initial_2x2"); + + // Now remove the widget + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.remove_child(0); + }); + assert_render_snapshot!(harness, "2x2_with_removed_widget"); + + // Add it back + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.add_child(button::Button::new("A"), GridParams::new(0, 0, 1, 1)); + }); + assert_render_snapshot!(harness, "initial_2x2"); // Should be back to the original state + + // Change the grid params to position it on the other corner + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.update_child_grid_params(0, GridParams::new(1, 1, 1, 1)); + }); + assert_render_snapshot!(harness, "moved_2x2_1"); + + // Now make it take up the entire grid + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.update_child_grid_params(0, GridParams::new(0, 0, 2, 2)); + }); + assert_render_snapshot!(harness, "moved_2x2_2"); + } + + #[test] + fn test_widget_order() { + let widget = Grid::with_dimensions(2, 2) + .with_child(button::Button::new("A"), GridParams::new(0, 0, 1, 1)); + let mut harness = TestHarness::create(widget); + // Snapshot with the single widget. + assert_render_snapshot!(harness, "initial_2x2"); + + // Order sets the draw order, so draw a widget over A by adding it after + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.add_child(button::Button::new("B"), GridParams::new(0, 0, 1, 1)); + }); + assert_render_snapshot!(harness, "2x2_with_overlapping_b"); + + // Draw a widget under the others by putting it at index 0 + // Make it wide enough to see it stick out, with half of it under A and B. + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.insert_grid_child_at(0, button::Button::new("C"), GridParams::new(0, 0, 2, 1)); + }); + assert_render_snapshot!(harness, "2x2_with_overlapping_c"); + + } +} \ No newline at end of file diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_b.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_b.png new file mode 100644 index 000000000..646ec41f8 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4aef605773c92b01166520d3ca8065116d46540bc3880978b5f248c95c61fd8d +size 5001 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_c.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_c.png new file mode 100644 index 000000000..ee7ba265f --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_overlapping_c.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec7a39d12493409ed242cc1f41e3d4dce46a96c481d2cdc38d4da185269465e0 +size 5321 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_removed_widget.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_removed_widget.png new file mode 100644 index 000000000..793dd9c82 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__2x2_with_removed_widget.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6c1515c193d2b494e792f05b5d088d1566774285e0df448ea9bf364ce03ab712 +size 4472 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x1.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x1.png new file mode 100644 index 000000000..3d3b16bcf --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f7ce584ad5a911bb7bb8c35f3066ac3031c7ade64692896f7586e20c391b624 +size 5052 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x4.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x4.png new file mode 100644 index 000000000..2eee9b135 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__expanded_4x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58796a0ff44c059d90b9a2e424d390721b5e0052eccbd3b6f003d1739d771440 +size 5186 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_1x1.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_1x1.png new file mode 100644 index 000000000..530021941 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_1x1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f92e5d63eab954b39012854bafa8ce2d9badfdae4304108bd7e290c2b1cb7143 +size 5026 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_2x2.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_2x2.png new file mode 100644 index 000000000..f52465971 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__initial_2x2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33aabab29c8651a7efaf88d7609cb50b93e8864367ddeff54a77dc435bb746b0 +size 5312 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_1.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_1.png new file mode 100644 index 000000000..881db94da --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b741e1bcdc9ad915c2f17f3fededa3435bc089a3fea2309235c913cfce331a +size 5321 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_2.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_2.png new file mode 100644 index 000000000..530021941 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__moved_2x2_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f92e5d63eab954b39012854bafa8ce2d9badfdae4304108bd7e290c2b1cb7143 +size 5026 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_2x2_widget.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_2x2_widget.png new file mode 100644 index 000000000..d71393d96 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_2x2_widget.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e52f1f8d696af90e035cbb77f1eda99d14bf6fc2d800866040d0976a7c5d2a25 +size 6693 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_changed_spacing.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_changed_spacing.png new file mode 100644 index 000000000..b8a3b22f6 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_changed_spacing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81afa7e7ef34369d9edfe042b0c60d1eef1b911c098ac625164171b8b7e49e11 +size 6971 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_horizontal_widget.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_horizontal_widget.png new file mode 100644 index 000000000..0930eb878 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_horizontal_widget.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3602efdabe62bdb4592090afcb80256bfe9ba339d5b87f1271f36cbca7c35e0a +size 5609 diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_vertical_widget.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_vertical_widget.png new file mode 100644 index 000000000..a527d1e32 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_vertical_widget.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:871275dca581cd2201a4d88e7bf50a2497f31f87afc953446d1f80293703deee +size 6173 From 11c1fef60938965f87f2002513b191795837a2c1 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Sun, 8 Sep 2024 00:29:13 -0400 Subject: [PATCH 17/24] Fix linter errors --- masonry/src/widget/grid.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 340d53333..56ff84273 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -88,7 +88,7 @@ impl<'a> WidgetMut<'a, Grid> { /// Add a child widget. pub fn insert_child_pod(&mut self, widget: WidgetPod>, params: GridParams) { - let child = new_grid_child(params.into(), widget); + let child = new_grid_child(params, widget); self.widget.children.push(child); self.ctx.children_changed(); self.mark_needs_layout(); @@ -298,11 +298,17 @@ impl GridParams { y = 0; } if width <= 0 { - debug_panic!("Grid width value should be a positive nonzero number; got {}", width); + debug_panic!( + "Grid width value should be a positive nonzero number; got {}", + width + ); width = 1; } if height <= 0 { - debug_panic!("Grid height value should be a positive nonzero number; got {}", height); + debug_panic!( + "Grid height value should be a positive nonzero number; got {}", + height + ); height = 1; } GridParams { @@ -347,24 +353,21 @@ mod tests { // Add a widget that takes up more than one horizontal cell harness.edit_root_widget(|mut grid| { let mut grid = grid.downcast::(); - grid.add_child(button::Button::new("B"), - GridParams::new(1, 0, 3, 1)); + grid.add_child(button::Button::new("B"), GridParams::new(1, 0, 3, 1)); }); assert_render_snapshot!(harness, "with_horizontal_widget"); // Add a widget that takes up more than one vertical cell harness.edit_root_widget(|mut grid| { let mut grid = grid.downcast::(); - grid.add_child(button::Button::new("C"), - GridParams::new(0, 1, 1, 3)); + grid.add_child(button::Button::new("C"), GridParams::new(0, 1, 1, 3)); }); assert_render_snapshot!(harness, "with_vertical_widget"); // Add a widget that takes up more than one horizontal and vertical cell harness.edit_root_widget(|mut grid| { let mut grid = grid.downcast::(); - grid.add_child(button::Button::new("D"), - GridParams::new(1, 1, 2, 2)); + grid.add_child(button::Button::new("D"), GridParams::new(1, 1, 2, 2)); }); assert_render_snapshot!(harness, "with_2x2_widget"); @@ -435,6 +438,5 @@ mod tests { grid.insert_grid_child_at(0, button::Button::new("C"), GridParams::new(0, 0, 2, 1)); }); assert_render_snapshot!(harness, "2x2_with_overlapping_c"); - } -} \ No newline at end of file +} From cd44ea5506bebfa0132580d75699740c2c1ddae4 Mon Sep 17 00:00:00 2001 From: Jared O'Connell <46976761+jaredoconnell@users.noreply.github.com> Date: Tue, 10 Sep 2024 23:20:04 -0400 Subject: [PATCH 18/24] Apply suggestions from code review Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> --- masonry/examples/grid_masonry.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index d8dd36e48..0c550849f 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -43,7 +43,8 @@ fn grid_button(params: GridParams) -> Button { pub fn main() { let label = SizedBox::new( - Prose::new("Change spacing by right and\n left clicking on the buttons") + Prose::new("Change spacing by right and\n\ + left clicking on the buttons") .with_text_size(14.0), ) .border(Color::rgb8(40, 40, 80), 1.0); From aa107f0983c8d4cc34f163f0ae87b6fecb7e317f Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 10 Sep 2024 23:27:59 -0400 Subject: [PATCH 19/24] Remove commented out layout caching --- masonry/src/widget/grid.rs | 37 ++++++------------------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 56ff84273..859b4a2e1 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -19,8 +19,6 @@ pub struct Grid { grid_width: i32, grid_height: i32, grid_spacing: f64, - //old_bc: BoxConstraints, - //needs_layout: bool, } // --- MARK: IMPL GRID --- @@ -31,8 +29,6 @@ impl Grid { grid_width: width, grid_height: height, grid_spacing: 0.0, - //old_bc: BoxConstraints::new(Size::ZERO, Size::ZERO), - //needs_layout: true, } } @@ -91,7 +87,7 @@ impl<'a> WidgetMut<'a, Grid> { let child = new_grid_child(params, widget); self.widget.children.push(child); self.ctx.children_changed(); - self.mark_needs_layout(); + self.ctx.request_layout(); } pub fn insert_grid_child_at( @@ -112,28 +108,21 @@ impl<'a> WidgetMut<'a, Grid> { let child = new_grid_child(params.into(), child); self.widget.children.insert(idx, child); self.ctx.children_changed(); - - self.mark_needs_layout(); + self.ctx.request_layout(); } pub fn set_spacing(&mut self, spacing: f64) { self.widget.grid_spacing = spacing; - self.mark_needs_layout(); + self.ctx.request_layout(); } pub fn set_width(&mut self, width: i32) { self.widget.grid_width = width; - self.mark_needs_layout(); + self.ctx.request_layout(); } pub fn set_height(&mut self, height: i32) { self.widget.grid_height = height; - self.mark_needs_layout(); - } - - /// Used to force a re-layout. - fn mark_needs_layout(&mut self) { - //self.widget.needs_layout = true; self.ctx.request_layout(); } @@ -154,13 +143,13 @@ impl<'a> WidgetMut<'a, Grid> { pub fn update_child_grid_params(&mut self, idx: usize, params: GridParams) { let child = &mut self.widget.children[idx]; child.update_params(params); - self.mark_needs_layout(); + self.ctx.request_layout(); } pub fn remove_child(&mut self, idx: usize) { let child = self.widget.children.remove(idx); self.ctx.remove_child(child.widget); - self.mark_needs_layout(); + self.ctx.request_layout(); } } @@ -192,21 +181,10 @@ impl Widget for Grid { fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size { bc.debug_check("Grid"); - /*let bc_changed = self.old_bc != *bc; - if bc_changed { - self.old_bc = *bc; - if !self.needs_layout { - self.needs_layout = true; - } - }*/ let total_size = bc.max(); let width_unit = (total_size.width + self.grid_spacing) / (self.grid_width as f64); let height_unit = (total_size.height + self.grid_spacing) / (self.grid_height as f64); for child in &mut self.children { - /*if !self.needs_layout && !ctx.child_needs_layout(&child.widget) { - ctx.mark_child_as_visited(&child.widget, true); - continue; // TODO: This breaks it. This is an attempted optimization. - }*/ let cell_size = Size::new( child.width as f64 * width_unit - self.grid_spacing, child.height as f64 * height_unit - self.grid_spacing, @@ -218,9 +196,6 @@ impl Widget for Grid { Point::new(child.x as f64 * width_unit, child.y as f64 * height_unit), ); } - /*if self.needs_layout { - self.needs_layout = false; - }*/ total_size } From 9912f05553ad1693f2fb44b77c62b31173743c9b Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 10 Sep 2024 23:43:25 -0400 Subject: [PATCH 20/24] Reorder code and fix formatting --- masonry/examples/grid_masonry.rs | 8 +- masonry/src/widget/grid.rs | 152 ++++++++++++++++--------------- 2 files changed, 82 insertions(+), 78 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 0c550849f..10bb24046 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -43,9 +43,11 @@ fn grid_button(params: GridParams) -> Button { pub fn main() { let label = SizedBox::new( - Prose::new("Change spacing by right and\n\ - left clicking on the buttons") - .with_text_size(14.0), + Prose::new( + "Change spacing by right and\n\ + left clicking on the buttons", + ) + .with_text_size(14.0), ) .border(Color::rgb8(40, 40, 80), 1.0); let button_inputs = vec![ diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 859b4a2e1..7991e41b5 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -21,6 +21,22 @@ pub struct Grid { grid_spacing: f64, } +struct Child { + widget: WidgetPod>, + x: i32, + y: i32, + width: i32, + height: i32, +} + +#[derive(Default, Debug, Copy, Clone, PartialEq)] +pub struct GridParams { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, +} + // --- MARK: IMPL GRID --- impl Grid { pub fn with_dimensions(width: i32, height: i32) -> Self { @@ -65,6 +81,67 @@ impl Grid { } } +// --- MARK: IMPL CHILD --- +impl Child { + fn widget_mut(&mut self) -> Option<&mut WidgetPod>> { + Some(&mut self.widget) + } + fn widget(&self) -> Option<&WidgetPod>> { + Some(&self.widget) + } + + fn update_params(&mut self, params: GridParams) { + self.x = params.x; + self.y = params.y; + self.width = params.width; + self.height = params.height; + } +} + +fn new_grid_child(params: GridParams, widget: WidgetPod>) -> Child { + Child { + widget, + x: params.x, + y: params.y, + width: params.width, + height: params.height, + } +} + +// --- MARK: IMPL GRIDPARAMS --- +impl GridParams { + pub fn new(mut x: i32, mut y: i32, mut width: i32, mut height: i32) -> GridParams { + if x < 0 { + debug_panic!("Grid x value should be a non-negative number; got {}", x); + x = 0; + } + if y < 0 { + debug_panic!("Grid y value should be a non-negative number; got {}", y); + y = 0; + } + if width <= 0 { + debug_panic!( + "Grid width value should be a positive nonzero number; got {}", + width + ); + width = 1; + } + if height <= 0 { + debug_panic!( + "Grid height value should be a positive nonzero number; got {}", + height + ); + height = 1; + } + GridParams { + x, + y, + width, + height, + } + } +} + // --- MARK: WIDGETMUT--- impl<'a> WidgetMut<'a, Grid> { /// Add a child widget. @@ -153,16 +230,6 @@ impl<'a> WidgetMut<'a, Grid> { } } -fn new_grid_child(params: GridParams, widget: WidgetPod>) -> Child { - Child { - widget, - x: params.x, - y: params.y, - width: params.width, - height: params.height, - } -} - // --- MARK: IMPL WIDGET--- impl Widget for Grid { fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) {} @@ -230,71 +297,6 @@ impl Widget for Grid { } } -struct Child { - widget: WidgetPod>, - x: i32, - y: i32, - width: i32, - height: i32, -} - -impl Child { - fn widget_mut(&mut self) -> Option<&mut WidgetPod>> { - Some(&mut self.widget) - } - fn widget(&self) -> Option<&WidgetPod>> { - Some(&self.widget) - } - - fn update_params(&mut self, params: GridParams) { - self.x = params.x; - self.y = params.y; - self.width = params.width; - self.height = params.height; - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq)] -pub struct GridParams { - pub x: i32, - pub y: i32, - pub width: i32, - pub height: i32, -} - -impl GridParams { - pub fn new(mut x: i32, mut y: i32, mut width: i32, mut height: i32) -> GridParams { - if x < 0 { - debug_panic!("Grid x value should be a non-negative number; got {}", x); - x = 0; - } - if y < 0 { - debug_panic!("Grid y value should be a non-negative number; got {}", y); - y = 0; - } - if width <= 0 { - debug_panic!( - "Grid width value should be a positive nonzero number; got {}", - width - ); - width = 1; - } - if height <= 0 { - debug_panic!( - "Grid height value should be a positive nonzero number; got {}", - height - ); - height = 1; - } - GridParams { - x, - y, - width, - height, - } - } -} - // --- MARK: TESTS --- #[cfg(test)] mod tests { From dec9ff759521fbc065de0ae9e6b6619211584caf Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 10 Sep 2024 23:51:09 -0400 Subject: [PATCH 21/24] Update to layout pass spec --- masonry/src/widget/grid.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index 7991e41b5..e81e919d3 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -257,7 +257,7 @@ impl Widget for Grid { child.height as f64 * height_unit - self.grid_spacing, ); let child_bc = BoxConstraints::new(cell_size, cell_size); - let _ = child.widget.layout(ctx, &child_bc); + let _ = ctx.run_layout(&mut child.widget, &child_bc); ctx.place_child( &mut child.widget, Point::new(child.x as f64 * width_unit, child.y as f64 * height_unit), From cb8e9843b464460181976fd6ea5d7312b55c86d3 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Tue, 10 Sep 2024 23:54:06 -0400 Subject: [PATCH 22/24] Update grid example based on feedback --- masonry/examples/grid_masonry.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 10bb24046..29fd8df54 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -10,6 +10,7 @@ use masonry::app_driver::{AppDriver, DriverCtx}; use masonry::dpi::LogicalSize; use masonry::widget::{Button, Grid, GridParams, Prose, RootWidget, SizedBox}; use masonry::{Action, Color, PointerButton, WidgetId}; +use parley::layout::Alignment; use winit::window::Window; struct Driver { @@ -44,10 +45,11 @@ fn grid_button(params: GridParams) -> Button { pub fn main() { let label = SizedBox::new( Prose::new( - "Change spacing by right and\n\ + "Change spacing by right and \ left clicking on the buttons", ) - .with_text_size(14.0), + .with_text_size(14.0) + .with_text_alignment(Alignment::Middle), ) .border(Color::rgb8(40, 40, 80), 1.0); let button_inputs = vec![ From 5bab9c70cf7b360a6200fc059e95981234ec1080 Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Wed, 11 Sep 2024 10:45:39 -0400 Subject: [PATCH 23/24] Address review comments --- masonry/examples/grid_masonry.rs | 3 +-- masonry/src/widget/grid.rs | 7 +++++++ ...masonry__widget__grid__tests__with_negative_spacing.png | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 masonry/src/widget/screenshots/masonry__widget__grid__tests__with_negative_spacing.png diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 29fd8df54..77317238e 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -45,8 +45,7 @@ fn grid_button(params: GridParams) -> Button { pub fn main() { let label = SizedBox::new( Prose::new( - "Change spacing by right and \ - left clicking on the buttons", + "Change spacing by right and left clicking on the buttons", ) .with_text_size(14.0) .with_text_alignment(Alignment::Middle), diff --git a/masonry/src/widget/grid.rs b/masonry/src/widget/grid.rs index e81e919d3..ec0cec36a 100644 --- a/masonry/src/widget/grid.rs +++ b/masonry/src/widget/grid.rs @@ -354,6 +354,13 @@ mod tests { grid.set_spacing(7.0); }); assert_render_snapshot!(harness, "with_changed_spacing"); + + // Make the spacing negative + harness.edit_root_widget(|mut grid| { + let mut grid = grid.downcast::(); + grid.set_spacing(-4.0); + }); + assert_render_snapshot!(harness, "with_negative_spacing"); } #[test] diff --git a/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_negative_spacing.png b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_negative_spacing.png new file mode 100644 index 000000000..c8dd392d8 --- /dev/null +++ b/masonry/src/widget/screenshots/masonry__widget__grid__tests__with_negative_spacing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d1c79d02e5f0b22b6326e7926c0af2880cdb9c4e852b04fde63cab9cce10da5 +size 6897 From b5858a1f13c89e6d70731c7616d1ee0ab705ebcb Mon Sep 17 00:00:00 2001 From: jaredoconnell Date: Wed, 11 Sep 2024 10:47:12 -0400 Subject: [PATCH 24/24] Fix formatting --- masonry/examples/grid_masonry.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/masonry/examples/grid_masonry.rs b/masonry/examples/grid_masonry.rs index 77317238e..95ffdfe0d 100644 --- a/masonry/examples/grid_masonry.rs +++ b/masonry/examples/grid_masonry.rs @@ -44,11 +44,9 @@ fn grid_button(params: GridParams) -> Button { pub fn main() { let label = SizedBox::new( - Prose::new( - "Change spacing by right and left clicking on the buttons", - ) - .with_text_size(14.0) - .with_text_alignment(Alignment::Middle), + Prose::new("Change spacing by right and left clicking on the buttons") + .with_text_size(14.0) + .with_text_alignment(Alignment::Middle), ) .border(Color::rgb8(40, 40, 80), 1.0); let button_inputs = vec![