mirror of https://github.com/linebender/xilem
Grid layout (#570)
This PR adds a basic grid layout to Masonry and Xilem. The way this layout works is it has a fixed grid based on the initial size passed in, and grid items are placed based on the position requested. Grid items are allowed to span more than one cell, if requested. There are potential improvements that could be done, like the use of intrinsic sizing for varied column width based on content size. Though that could be done in the future taffy layout if we want to keep this one simple. ~~This PR is still a draft because of one remaining concern. I was not able to successfully optimize with conditional calls to child widgets for layout. It led to crashes about the paint rects not being within the widget's paint rect. `Error in 'Grid' #16: paint_rect Rect { x0: 0.0, y0: 0.0, x1: 800.0, y1: 610.0 } doesn't contain paint_rect Rect { x0: 400.5, y0: 0.0, x1: 800.5, y1: 150.0 } of child widget 'Button' #5`. My failed attempt at fixing it is commented out.~~ Since I am rusty on View Sequences, a lot of that code is based on the Flex implementation. Let me know if I did anything incorrectly or if any of it is unnecessary, or if anything is missing. --------- Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
This commit is contained in:
parent
9a3c8e308c
commit
3726e91a48
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2024 the Xilem Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Shows how to use a grid layout in Masonry.
|
||||
|
||||
// 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, Grid, GridParams, Prose, RootWidget, SizedBox};
|
||||
use masonry::{Action, Color, PointerButton, WidgetId};
|
||||
use parley::layout::Alignment;
|
||||
use winit::window::Window;
|
||||
|
||||
struct Driver {
|
||||
grid_spacing: f64,
|
||||
}
|
||||
|
||||
impl AppDriver for Driver {
|
||||
fn on_action(&mut self, ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
|
||||
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::<RootWidget<Grid>>()
|
||||
.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
|
||||
))
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
.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,
|
||||
},
|
||||
];
|
||||
|
||||
let driver = Driver { grid_spacing: 1.0 };
|
||||
|
||||
// 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));
|
||||
for button_input in button_inputs {
|
||||
let button = grid_button(button_input);
|
||||
main_widget = main_widget.with_child(button, button_input);
|
||||
}
|
||||
|
||||
let window_size = LogicalSize::new(800.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();
|
||||
}
|
|
@ -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<FlexParams>) {
|
||||
let child = &mut self.widget.children[idx];
|
||||
let child_val = std::mem::replace(child, Child::FixedSpacer(0.0, 0.0));
|
||||
|
|
|
@ -0,0 +1,426 @@
|
|||
// Copyright 2024 the Xilem Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use accesskit::Role;
|
||||
use smallvec::SmallVec;
|
||||
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::{
|
||||
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
|
||||
Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
|
||||
};
|
||||
|
||||
pub struct Grid {
|
||||
children: Vec<Child>,
|
||||
grid_width: i32,
|
||||
grid_height: i32,
|
||||
grid_spacing: f64,
|
||||
}
|
||||
|
||||
struct Child {
|
||||
widget: WidgetPod<Box<dyn Widget>>,
|
||||
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 {
|
||||
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.
|
||||
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, 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<Box<dyn Widget>>,
|
||||
params: GridParams,
|
||||
) -> Self {
|
||||
let child = Child {
|
||||
widget,
|
||||
x: params.x,
|
||||
y: params.y,
|
||||
width: params.width,
|
||||
height: params.height,
|
||||
};
|
||||
self.children.push(child);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// --- MARK: IMPL CHILD ---
|
||||
impl Child {
|
||||
fn widget_mut(&mut self) -> Option<&mut WidgetPod<Box<dyn Widget>>> {
|
||||
Some(&mut self.widget)
|
||||
}
|
||||
fn widget(&self) -> Option<&WidgetPod<Box<dyn Widget>>> {
|
||||
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<Box<dyn Widget>>) -> 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.
|
||||
///
|
||||
/// See also [`with_child`].
|
||||
///
|
||||
/// [`with_child`]: Grid::with_child
|
||||
pub fn add_child(&mut self, child: impl Widget, params: GridParams) {
|
||||
let child_pod: WidgetPod<Box<dyn Widget>> = WidgetPod::new(Box::new(child));
|
||||
self.insert_child_pod(child_pod, params);
|
||||
}
|
||||
|
||||
pub fn add_child_id(&mut self, child: impl Widget, id: WidgetId, params: GridParams) {
|
||||
let child_pod: WidgetPod<Box<dyn Widget>> = WidgetPod::new_with_id(Box::new(child), id);
|
||||
self.insert_child_pod(child_pod, params);
|
||||
}
|
||||
|
||||
/// Add a child widget.
|
||||
pub fn insert_child_pod(&mut self, widget: WidgetPod<Box<dyn Widget>>, params: GridParams) {
|
||||
let child = new_grid_child(params, widget);
|
||||
self.widget.children.push(child);
|
||||
self.ctx.children_changed();
|
||||
self.ctx.request_layout();
|
||||
}
|
||||
|
||||
pub fn insert_grid_child_at(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
child: impl Widget,
|
||||
params: impl Into<GridParams>,
|
||||
) {
|
||||
self.insert_grid_child_pod(idx, WidgetPod::new(Box::new(child)), params);
|
||||
}
|
||||
|
||||
pub fn insert_grid_child_pod(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
child: WidgetPod<Box<dyn Widget>>,
|
||||
params: impl Into<GridParams>,
|
||||
) {
|
||||
let child = new_grid_child(params.into(), child);
|
||||
self.widget.children.insert(idx, child);
|
||||
self.ctx.children_changed();
|
||||
self.ctx.request_layout();
|
||||
}
|
||||
|
||||
pub fn set_spacing(&mut self, spacing: f64) {
|
||||
self.widget.grid_spacing = spacing;
|
||||
self.ctx.request_layout();
|
||||
}
|
||||
|
||||
pub fn set_width(&mut self, width: i32) {
|
||||
self.widget.grid_width = width;
|
||||
self.ctx.request_layout();
|
||||
}
|
||||
|
||||
pub fn set_height(&mut self, height: i32) {
|
||||
self.widget.grid_height = height;
|
||||
self.ctx.request_layout();
|
||||
}
|
||||
|
||||
pub fn child_mut(&mut self, idx: usize) -> Option<WidgetMut<'_, Box<dyn Widget>>> {
|
||||
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];
|
||||
child.update_params(params);
|
||||
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.ctx.request_layout();
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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_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 - self.grid_spacing,
|
||||
child.height as f64 * height_unit - self.grid_spacing,
|
||||
);
|
||||
let child_bc = BoxConstraints::new(cell_size, cell_size);
|
||||
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),
|
||||
);
|
||||
}
|
||||
total_size
|
||||
}
|
||||
|
||||
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut 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, _: &mut AccessCtx) {}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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>();
|
||||
grid.set_width(4);
|
||||
});
|
||||
assert_render_snapshot!(harness, "expanded_4x1");
|
||||
|
||||
harness.edit_root_widget(|mut grid| {
|
||||
let mut grid = grid.downcast::<Grid>();
|
||||
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>();
|
||||
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>();
|
||||
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>();
|
||||
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>();
|
||||
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>();
|
||||
grid.set_spacing(-4.0);
|
||||
});
|
||||
assert_render_snapshot!(harness, "with_negative_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>();
|
||||
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>();
|
||||
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>();
|
||||
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>();
|
||||
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>();
|
||||
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>();
|
||||
grid.insert_grid_child_at(0, button::Button::new("C"), GridParams::new(0, 0, 2, 1));
|
||||
});
|
||||
assert_render_snapshot!(harness, "2x2_with_overlapping_c");
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ mod align;
|
|||
mod button;
|
||||
mod checkbox;
|
||||
mod flex;
|
||||
mod grid;
|
||||
mod image;
|
||||
mod label;
|
||||
mod portal;
|
||||
|
@ -36,6 +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, GridParams};
|
||||
pub use label::{Label, LineBreaking};
|
||||
pub use portal::Portal;
|
||||
pub use progress_bar::ProgressBar;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4aef605773c92b01166520d3ca8065116d46540bc3880978b5f248c95c61fd8d
|
||||
size 5001
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ec7a39d12493409ed242cc1f41e3d4dce46a96c481d2cdc38d4da185269465e0
|
||||
size 5321
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6c1515c193d2b494e792f05b5d088d1566774285e0df448ea9bf364ce03ab712
|
||||
size 4472
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2f7ce584ad5a911bb7bb8c35f3066ac3031c7ade64692896f7586e20c391b624
|
||||
size 5052
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:58796a0ff44c059d90b9a2e424d390721b5e0052eccbd3b6f003d1739d771440
|
||||
size 5186
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f92e5d63eab954b39012854bafa8ce2d9badfdae4304108bd7e290c2b1cb7143
|
||||
size 5026
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:33aabab29c8651a7efaf88d7609cb50b93e8864367ddeff54a77dc435bb746b0
|
||||
size 5312
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:47b741e1bcdc9ad915c2f17f3fededa3435bc089a3fea2309235c913cfce331a
|
||||
size 5321
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f92e5d63eab954b39012854bafa8ce2d9badfdae4304108bd7e290c2b1cb7143
|
||||
size 5026
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e52f1f8d696af90e035cbb77f1eda99d14bf6fc2d800866040d0976a7c5d2a25
|
||||
size 6693
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:81afa7e7ef34369d9edfe042b0c60d1eef1b911c098ac625164171b8b7e49e11
|
||||
size 6971
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3602efdabe62bdb4592090afcb80256bfe9ba339d5b87f1271f36cbca7c35e0a
|
||||
size 5609
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1d1c79d02e5f0b22b6326e7926c0af2880cdb9c4e852b04fde63cab9cce10da5
|
||||
size 6897
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:871275dca581cd2201a4d88e7bf50a2497f31f87afc953446d1f80293703deee
|
||||
size 6173
|
|
@ -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::{grid, Flex, FlexSequence, FlexSpacer, GridExt, GridSequence};
|
||||
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,55 +184,54 @@ impl Calculator {
|
|||
}
|
||||
}
|
||||
|
||||
fn num_row(nums: [&'static str; 3], row: i32) -> impl GridSequence<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<Calculator> {
|
||||
let num_row = |nums: [&'static str; 3], operator| {
|
||||
flex_row((
|
||||
nums.map(|num| digit_button(num).flex(1.)),
|
||||
operator_button(operator).flex(1.),
|
||||
))
|
||||
};
|
||||
flex((
|
||||
// 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),
|
||||
))
|
||||
.flex(1.0),
|
||||
FlexSpacer::Fixed(10.0),
|
||||
// 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),
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
|
||||
/// Creates a horizontal centered flex row designed for the display portion of the calculator.
|
||||
|
@ -244,15 +243,6 @@ pub fn centered_flex_row<State, Seq: FlexSequence<State>>(sequence: Seq) -> Flex
|
|||
.gap(5.)
|
||||
}
|
||||
|
||||
/// Creates a horizontal filled flex row designed to be used in a grid.
|
||||
pub fn flex_row<State, Seq: FlexSequence<State>>(sequence: Seq) -> Flex<Seq, State> {
|
||||
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<Calculator> {
|
||||
|
|
|
@ -0,0 +1,418 @@
|
|||
// Copyright 2024 the Xilem Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use masonry::widget::GridParams;
|
||||
use masonry::{
|
||||
widget::{self, WidgetMut},
|
||||
Widget,
|
||||
};
|
||||
use xilem_core::{
|
||||
AppendVec, DynMessage, ElementSplice, MessageResult, Mut, SuperElement, View, ViewElement,
|
||||
ViewMarker, ViewSequence,
|
||||
};
|
||||
|
||||
use crate::{Pod, ViewCtx, WidgetView};
|
||||
|
||||
pub fn grid<State, Action, Seq: GridSequence<State, Action>>(
|
||||
sequence: Seq,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> Grid<Seq, State, Action> {
|
||||
Grid {
|
||||
sequence,
|
||||
spacing: 0.0,
|
||||
phantom: PhantomData,
|
||||
height,
|
||||
width,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Grid<Seq, State, Action = ()> {
|
||||
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<fn() -> (State, Action)>,
|
||||
}
|
||||
|
||||
impl<Seq, State, Action> Grid<Seq, State, Action> {
|
||||
#[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<Seq, State, Action> ViewMarker for Grid<Seq, State, Action> {}
|
||||
|
||||
impl<State, Action, Seq> View<State, Action, ViewCtx> for Grid<Seq, State, Action>
|
||||
where
|
||||
State: 'static,
|
||||
Action: 'static,
|
||||
Seq: GridSequence<State, Action>,
|
||||
{
|
||||
type Element = Pod<widget::Grid>;
|
||||
|
||||
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<Action> {
|
||||
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<GridElement> for GridElement {
|
||||
fn upcast(child: GridElement) -> Self {
|
||||
child
|
||||
}
|
||||
|
||||
fn with_downcast_val<R>(
|
||||
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<W: Widget> SuperElement<Pod<W>> for GridElement {
|
||||
fn upcast(child: Pod<W>) -> Self {
|
||||
// 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<R>(
|
||||
mut this: Mut<'_, Self>,
|
||||
f: impl FnOnce(Mut<'_, Pod<W>>) -> 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<GridElement> for GridSplice<'_> {
|
||||
fn with_scratch<R>(&mut self, f: impl FnOnce(&mut AppendVec<GridElement>) -> 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<R>(&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<R>(&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
|
||||
}
|
||||
}
|
||||
|
||||
/// `GridSequence` is what allows an input to the grid that contains all the grid elements.
|
||||
pub trait GridSequence<State, Action = ()>:
|
||||
ViewSequence<State, Action, ViewCtx, GridElement>
|
||||
{
|
||||
}
|
||||
|
||||
impl<Seq, State, Action> GridSequence<State, Action> for Seq where
|
||||
Seq: ViewSequence<State, Action, ViewCtx, GridElement>
|
||||
{
|
||||
}
|
||||
|
||||
/// A trait which extends a [`WidgetView`] with methods to provide parameters for a grid item
|
||||
pub trait GridExt<State, Action>: WidgetView<State, Action> {
|
||||
/// Applies [`impl Into<GridParams>`](`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<State: 'static>() -> impl WidgetView<State> {
|
||||
/// 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<GridParams>) -> GridItem<Self, State, Action>
|
||||
where
|
||||
State: 'static,
|
||||
Action: 'static,
|
||||
Self: Sized,
|
||||
{
|
||||
grid_item(self, params)
|
||||
}
|
||||
|
||||
/// Applies a [`impl Into<GridParams>`](`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<State: 'static>() -> impl WidgetView<State> {
|
||||
/// 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<Self, State, Action>
|
||||
where
|
||||
State: 'static,
|
||||
Action: 'static,
|
||||
Self: Sized,
|
||||
{
|
||||
grid_item(self, GridParams::new(x, y, 1, 1))
|
||||
}
|
||||
}
|
||||
|
||||
impl<State, Action, V: WidgetView<State, Action>> GridExt<State, Action> for V {}
|
||||
|
||||
pub enum GridElement {
|
||||
Child(Pod<Box<dyn Widget>>, GridParams),
|
||||
}
|
||||
|
||||
pub struct GridElementMut<'w> {
|
||||
parent: WidgetMut<'w, widget::Grid>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
// Used for manipulating the ViewSequence.
|
||||
pub struct GridSplice<'w> {
|
||||
idx: usize,
|
||||
element: WidgetMut<'w, widget::Grid>,
|
||||
scratch: AppendVec<GridElement>,
|
||||
}
|
||||
|
||||
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<V, State, Action> {
|
||||
view: V,
|
||||
params: GridParams,
|
||||
phantom: PhantomData<fn() -> (State, Action)>,
|
||||
}
|
||||
|
||||
pub fn grid_item<V, State, Action>(
|
||||
view: V,
|
||||
params: impl Into<GridParams>,
|
||||
) -> GridItem<V, State, Action>
|
||||
where
|
||||
State: 'static,
|
||||
Action: 'static,
|
||||
V: WidgetView<State, Action>,
|
||||
{
|
||||
GridItem {
|
||||
view,
|
||||
params: params.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, State, Action> ViewMarker for GridItem<V, State, Action> {}
|
||||
|
||||
impl<State, Action, V> View<State, Action, ViewCtx> for GridItem<V, State, Action>
|
||||
where
|
||||
State: 'static,
|
||||
Action: 'static,
|
||||
V: WidgetView<State, Action>,
|
||||
{
|
||||
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<Action> {
|
||||
self.view.message(view_state, id_path, message, app_state)
|
||||
}
|
||||
}
|
|
@ -16,6 +16,9 @@ pub use checkbox::*;
|
|||
mod flex;
|
||||
pub use flex::*;
|
||||
|
||||
mod grid;
|
||||
pub use grid::*;
|
||||
|
||||
mod sized_box;
|
||||
pub use sized_box::*;
|
||||
|
||||
|
|
Loading…
Reference in New Issue