Skip to main content

xilem/view/
grid.rs

1// Copyright 2024 the Xilem Authors
2// SPDX-License-Identifier: Apache-2.0
3
4use std::marker::PhantomData;
5
6use masonry::core::{FromDynWidget, Widget, WidgetMut};
7use masonry::properties::types::Length;
8use masonry::widgets;
9
10use crate::core::{
11    AppendVec, ElementSplice, MessageContext, MessageResult, Mut, SuperElement, View, ViewElement,
12    ViewMarker, ViewSequence,
13};
14use crate::{Pod, ViewCtx, WidgetView};
15
16pub use masonry::widgets::GridParams;
17/// A Grid layout divides a window into regions and defines the relationship
18/// between inner elements in terms of size and position.
19///
20/// # Example
21/// ```ignore
22/// use masonry::widgets::GridParams;
23/// use xilem::view::{
24///     text_button, grid, label, GridExt,
25/// };
26///
27/// const GRID_GAP: f64 = 2.;
28///
29/// #[derive(Default)]
30/// struct State {
31///     int: i32,
32/// }
33///
34/// let mut state = State::default();
35///
36/// grid(
37///     (
38///         label(state.int.to_string()).grid_item(GridParams::new(0, 0, 3, 1)),
39///         text_button("Decrease by 1", |state: &mut State| state.int -= 1).grid_pos(1, 1),
40///         text_button("To zero", |state: &mut State| state.int = 0).grid_pos(2, 1),
41///         text_button("Increase by 1", |state: &mut State| state.int += 1).grid_pos(3, 1),
42///     ),
43///     3,
44///     2,
45/// )
46/// .spacing(GRID_GAP)
47/// ```
48/// Also see Calculator example [here](https://github.com/linebender/xilem/blob/main/xilem/examples/calc.rs) to learn more about grid layout.
49pub fn grid<State, Action, Seq: GridSequence<State, Action>>(
50    sequence: Seq,
51    width: i32,
52    height: i32,
53) -> Grid<Seq, State, Action> {
54    Grid {
55        sequence,
56        spacing: Length::ZERO,
57        height,
58        width,
59        phantom: PhantomData,
60    }
61}
62
63/// The [`View`] created by [`grid`] from a sequence, which also consumes custom width and height.
64///
65/// See `grid` documentation for more context.
66#[must_use = "View values do nothing unless provided to Xilem."]
67pub struct Grid<Seq, State, Action = ()> {
68    sequence: Seq,
69    spacing: Length,
70    width: i32,
71    height: i32,
72
73    /// Used to associate the State and Action in the call to `.grid()` with the State and Action
74    /// used in the View implementation, to allow inference to flow backwards, allowing State and
75    /// Action to be inferred properly
76    phantom: PhantomData<fn() -> (State, Action)>,
77}
78
79impl<Seq, State, Action> Grid<Seq, State, Action> {
80    /// Set the spacing (both vertical and horizontal) between grid items.
81    #[track_caller]
82    pub fn spacing(mut self, spacing: Length) -> Self {
83        self.spacing = spacing;
84        self
85    }
86}
87
88mod hidden {
89    use super::GridElement;
90    use crate::core::AppendVec;
91
92    #[doc(hidden)]
93    #[expect(
94        unnameable_types,
95        reason = "Implementation detail, public because of trait visibility rules"
96    )]
97    pub struct GridState<SeqState> {
98        pub(crate) seq_state: SeqState,
99        pub(crate) scratch: AppendVec<GridElement>,
100    }
101}
102
103use hidden::GridState;
104
105impl<Seq, State, Action> ViewMarker for Grid<Seq, State, Action> {}
106
107impl<State, Action, Seq> View<State, Action, ViewCtx> for Grid<Seq, State, Action>
108where
109    State: 'static,
110    Action: 'static,
111    Seq: GridSequence<State, Action>,
112{
113    type Element = Pod<widgets::Grid>;
114
115    type ViewState = GridState<Seq::SeqState>;
116
117    fn build(&self, ctx: &mut ViewCtx, app_state: &mut State) -> (Self::Element, Self::ViewState) {
118        let mut elements = AppendVec::default();
119        let mut widget = widgets::Grid::with_dimensions(self.width, self.height);
120        widget = widget.with_spacing(self.spacing);
121        let seq_state = self.sequence.seq_build(ctx, &mut elements, app_state);
122        for element in elements.drain() {
123            widget = widget.with_child(element.child.new_widget, element.params);
124        }
125        let pod = ctx.create_pod(widget);
126        (
127            pod,
128            GridState {
129                seq_state,
130                scratch: elements,
131            },
132        )
133    }
134
135    fn rebuild(
136        &self,
137        prev: &Self,
138        GridState { seq_state, scratch }: &mut Self::ViewState,
139        ctx: &mut ViewCtx,
140        mut element: Mut<'_, Self::Element>,
141        app_state: &mut State,
142    ) {
143        if prev.height != self.height {
144            widgets::Grid::set_height(&mut element, self.height);
145        }
146        if prev.width != self.width {
147            widgets::Grid::set_width(&mut element, self.width);
148        }
149        if prev.spacing != self.spacing {
150            widgets::Grid::set_spacing(&mut element, self.spacing);
151        }
152
153        let mut splice = GridSplice::new(element, scratch);
154        self.sequence
155            .seq_rebuild(&prev.sequence, seq_state, ctx, &mut splice, app_state);
156        debug_assert!(scratch.is_empty());
157    }
158
159    fn teardown(
160        &self,
161        GridState { seq_state, scratch }: &mut Self::ViewState,
162        ctx: &mut ViewCtx,
163        element: Mut<'_, Self::Element>,
164    ) {
165        let mut splice = GridSplice::new(element, scratch);
166        self.sequence.seq_teardown(seq_state, ctx, &mut splice);
167        debug_assert!(scratch.is_empty());
168    }
169
170    fn message(
171        &self,
172        GridState { seq_state, scratch }: &mut Self::ViewState,
173        message: &mut MessageContext,
174        element: Mut<'_, Self::Element>,
175        app_state: &mut State,
176    ) -> MessageResult<Action> {
177        let mut splice = GridSplice::new(element, scratch);
178        let result = self
179            .sequence
180            .seq_message(seq_state, message, &mut splice, app_state);
181        debug_assert!(scratch.is_empty());
182        result
183    }
184}
185
186// Used to become a reference form for editing. It's provided to rebuild and teardown.
187impl ViewElement for GridElement {
188    type Mut<'w> = GridElementMut<'w>;
189}
190
191// Used to allow the item to be used as a generic item in ViewSequence.
192impl SuperElement<Self, ViewCtx> for GridElement {
193    fn upcast(_ctx: &mut ViewCtx, child: Self) -> Self {
194        child
195    }
196
197    fn with_downcast_val<R>(
198        mut this: Mut<'_, Self>,
199        f: impl FnOnce(Mut<'_, Self>) -> R,
200    ) -> (Self::Mut<'_>, R) {
201        let r = {
202            let parent = this.parent.reborrow_mut();
203            let reborrow = GridElementMut {
204                idx: this.idx,
205                parent,
206            };
207            f(reborrow)
208        };
209        (this, r)
210    }
211}
212
213impl<W: Widget + FromDynWidget + ?Sized> SuperElement<Pod<W>, ViewCtx> for GridElement {
214    fn upcast(_: &mut ViewCtx, child: Pod<W>) -> Self {
215        // Getting here means that the widget didn't use .grid_item or .grid_pos.
216        // This currently places the widget in the top left cell.
217        // There is not much else, beyond purposefully failing, that can be done here,
218        // because there isn't enough information to determine an appropriate spot
219        // for the widget.
220        Self {
221            child: child.erased(),
222            // TODO - Should be 0, 0?
223            params: GridParams::new(1, 1, 1, 1),
224        }
225    }
226
227    fn with_downcast_val<R>(
228        mut this: Mut<'_, Self>,
229        f: impl FnOnce(Mut<'_, Pod<W>>) -> R,
230    ) -> (Mut<'_, Self>, R) {
231        let ret = {
232            let mut child = widgets::Grid::child_mut(&mut this.parent, this.idx);
233            let downcast = child.downcast();
234            f(downcast)
235        };
236
237        (this, ret)
238    }
239}
240
241// Used for building and rebuilding the ViewSequence
242impl ElementSplice<GridElement> for GridSplice<'_, '_> {
243    fn with_scratch<R>(&mut self, f: impl FnOnce(&mut AppendVec<GridElement>) -> R) -> R {
244        let ret = f(self.scratch);
245        for element in self.scratch.drain() {
246            widgets::Grid::insert_grid_child_at(
247                &mut self.element,
248                self.idx,
249                element.child.new_widget,
250                element.params,
251            );
252            self.idx += 1;
253        }
254        ret
255    }
256
257    fn insert(&mut self, element: GridElement) {
258        widgets::Grid::insert_grid_child_at(
259            &mut self.element,
260            self.idx,
261            element.child.new_widget,
262            element.params,
263        );
264        self.idx += 1;
265    }
266
267    fn mutate<R>(&mut self, f: impl FnOnce(Mut<'_, GridElement>) -> R) -> R {
268        let child = GridElementMut {
269            parent: self.element.reborrow_mut(),
270            idx: self.idx,
271        };
272        let ret = f(child);
273        self.idx += 1;
274        ret
275    }
276
277    fn skip(&mut self, n: usize) {
278        self.idx += n;
279    }
280
281    fn index(&self) -> usize {
282        self.idx
283    }
284
285    fn delete<R>(&mut self, f: impl FnOnce(Mut<'_, GridElement>) -> R) -> R {
286        let ret = {
287            let child = GridElementMut {
288                parent: self.element.reborrow_mut(),
289                idx: self.idx,
290            };
291            f(child)
292        };
293        widgets::Grid::remove_child(&mut self.element, self.idx);
294        ret
295    }
296}
297
298/// `GridSequence` is what allows an input to the grid that contains all the grid elements.
299pub trait GridSequence<State, Action = ()>:
300    ViewSequence<State, Action, ViewCtx, GridElement>
301{
302}
303
304impl<Seq, State, Action> GridSequence<State, Action> for Seq where
305    Seq: ViewSequence<State, Action, ViewCtx, GridElement>
306{
307}
308
309/// A trait which extends a [`WidgetView`] with methods to provide parameters for a grid item
310pub trait GridExt<State, Action>: WidgetView<State, Action> {
311    /// Applies [`impl Into<GridParams>`](`GridParams`) to this view. This allows the view
312    /// to be placed as a child within a [`Grid`] [`View`].
313    ///
314    /// # Examples
315    /// ```
316    /// use masonry::widgets::GridParams;
317    /// use xilem::view::{text_button, prose, grid, GridExt};
318    /// # use xilem::{WidgetView};
319    ///
320    /// # fn view<State: 'static>() -> impl WidgetView<State> {
321    /// grid((
322    ///     text_button("click me", |_| ()).grid_item(GridParams::new(0, 0, 2, 1)),
323    ///     prose("a prose").grid_item(GridParams::new(1, 1, 1, 1)),
324    /// ), 2, 2)
325    /// # }
326    /// ```
327    fn grid_item(self, params: impl Into<GridParams>) -> GridItem<Self, State, Action>
328    where
329        State: 'static,
330        Action: 'static,
331        Self: Sized,
332    {
333        grid_item(self, params)
334    }
335
336    /// Applies a [`impl Into<GridParams>`](`GridParams`) with the specified position to this view.
337    /// This allows the view to be placed as a child within a [`Grid`] [`View`].
338    /// For instances where a grid item is expected to take up multiple cell units,
339    /// use [`GridExt::grid_item`]
340    ///
341    /// # Examples
342    /// ```
343    /// use masonry::widgets::GridParams;
344    /// use xilem::{view::{text_button, prose, grid, GridExt}};
345    /// # use xilem::{WidgetView};
346    ///
347    /// # fn view<State: 'static>() -> impl WidgetView<State> {
348    /// grid((
349    ///     text_button("click me", |_| ()).grid_pos(0, 0),
350    ///     prose("a prose").grid_pos(1, 1),
351    /// ), 2, 2)
352    /// # }
353    /// ```
354    fn grid_pos(self, x: i32, y: i32) -> GridItem<Self, State, Action>
355    where
356        State: 'static,
357        Action: 'static,
358        Self: Sized,
359    {
360        grid_item(self, GridParams::new(x, y, 1, 1))
361    }
362}
363
364impl<State, Action, V: WidgetView<State, Action>> GridExt<State, Action> for V {}
365
366/// A child widget within a [`Grid`] view.
367pub struct GridElement {
368    /// The child widget.
369    child: Pod<dyn Widget>,
370    /// The grid parameters of the child widget.
371    params: GridParams,
372}
373
374/// A mutable reference to a [`GridElement`], used internally by Xilem traits.
375pub struct GridElementMut<'w> {
376    parent: WidgetMut<'w, widgets::Grid>,
377    idx: usize,
378}
379
380// Used for manipulating the ViewSequence.
381struct GridSplice<'w, 's> {
382    idx: usize,
383    element: WidgetMut<'w, widgets::Grid>,
384    scratch: &'s mut AppendVec<GridElement>,
385}
386
387impl<'w, 's> GridSplice<'w, 's> {
388    fn new(element: WidgetMut<'w, widgets::Grid>, scratch: &'s mut AppendVec<GridElement>) -> Self {
389        Self {
390            idx: 0,
391            element,
392            scratch,
393        }
394    }
395}
396
397/// A `WidgetView` that can be used within a [`Grid`] [`View`]
398pub struct GridItem<V, State, Action> {
399    view: V,
400    params: GridParams,
401    phantom: PhantomData<fn() -> (State, Action)>,
402}
403
404/// Creates a [`GridItem`] from a view and [`GridParams`].
405pub fn grid_item<V, State, Action>(
406    view: V,
407    params: impl Into<GridParams>,
408) -> GridItem<V, State, Action>
409where
410    State: 'static,
411    Action: 'static,
412    V: WidgetView<State, Action>,
413{
414    GridItem {
415        view,
416        params: params.into(),
417        phantom: PhantomData,
418    }
419}
420
421impl<V, State, Action> ViewMarker for GridItem<V, State, Action> {}
422
423impl<State, Action, V> View<State, Action, ViewCtx> for GridItem<V, State, Action>
424where
425    State: 'static,
426    Action: 'static,
427    V: WidgetView<State, Action>,
428{
429    type Element = GridElement;
430
431    type ViewState = V::ViewState;
432
433    fn build(&self, ctx: &mut ViewCtx, app_state: &mut State) -> (Self::Element, Self::ViewState) {
434        let (pod, state) = self.view.build(ctx, app_state);
435        (
436            GridElement {
437                child: pod.erased(),
438                params: self.params,
439            },
440            state,
441        )
442    }
443
444    fn rebuild(
445        &self,
446        prev: &Self,
447        view_state: &mut Self::ViewState,
448        ctx: &mut ViewCtx,
449        mut element: Mut<'_, Self::Element>,
450        app_state: &mut State,
451    ) {
452        {
453            if self.params != prev.params {
454                widgets::Grid::update_child_grid_params(
455                    &mut element.parent,
456                    element.idx,
457                    self.params,
458                );
459            }
460            let mut child = widgets::Grid::child_mut(&mut element.parent, element.idx);
461            self.view
462                .rebuild(&prev.view, view_state, ctx, child.downcast(), app_state);
463        }
464    }
465
466    fn teardown(
467        &self,
468        view_state: &mut Self::ViewState,
469        ctx: &mut ViewCtx,
470        mut element: Mut<'_, Self::Element>,
471    ) {
472        let mut child = widgets::Grid::child_mut(&mut element.parent, element.idx);
473        self.view.teardown(view_state, ctx, child.downcast());
474    }
475
476    fn message(
477        &self,
478        view_state: &mut Self::ViewState,
479        message: &mut MessageContext,
480        mut element: Mut<'_, Self::Element>,
481        app_state: &mut State,
482    ) -> MessageResult<Action> {
483        let mut child = widgets::Grid::child_mut(&mut element.parent, element.idx);
484        self.view
485            .message(view_state, message, child.downcast(), app_state)
486    }
487}