kas_widgets/
float.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! A pile of widgets "floating" over the top of each other
7
8use kas::Collection;
9use kas::prelude::*;
10
11/// Make a [`Float`] widget
12///
13/// # Syntax
14///
15/// > _Collection_ :\
16/// > &nbsp;&nbsp; `float!` `[` _Items_<sup>\?</sup> `]`
17/// >
18/// > _Items_ :\
19/// > &nbsp;&nbsp; (_Item_ `,`)<sup>\*</sup> _Item_ `,`<sup>\?</sup>
20///
21/// ## Stand-alone usage
22///
23/// When used as a stand-alone macro, `float! [/* ... */]` is just syntactic sugar
24/// for `Float::new(kas::collection! [/* ... */])`.
25///
26/// In this case, _Item_ may be:
27///
28/// -   A string literal (interpreted as a label widget), optionally followed by
29///     an [`align`] or [`pack`] method call
30/// -   An expression yielding an object implementing `Widget<Data = _A>`
31///
32/// In case all _Item_ instances are a string literal, the data type of the
33/// `float!` widget will be `()`; otherwise the data type of the widget is `_A`
34/// where `_A` is a generic type parameter of the widget.
35///
36/// ## Usage within widget layout syntax
37///
38/// When called within [widget layout syntax], `float!` may be evaluated as a
39/// recursive macro and the result does not have a specified type, except that
40/// methods [`map_any`], [`align`] and [`pack`] are supported via emulation.
41///
42/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
43/// broadly similar to the above with a couple of exceptions:
44///
45/// -   Supported layout macros do not need to be imported to the module scope
46/// -   An _Item_ may be a `#[widget]` field of the widget
47///
48/// # Example
49///
50/// ```
51/// # use kas::prelude::*;
52/// let my_widget = kas_widgets::float! [
53///     "one".pack(AlignHints::TOP_LEFT),
54///     "two".pack(AlignHints::BOTTOM_RIGHT),
55///     "some text\nin the\nbackground"
56/// ];
57/// ```
58///
59/// # Cost
60///
61/// Warning: each child beyond the first requires a new draw pass. The
62/// number of passes used can have a substantial performance impact,
63/// potentially more on GPU communication than CPU usage.
64///
65/// [widget layout syntax]: macro@kas::layout
66/// [`map_any`]: crate::AdaptWidgetAny::map_any
67/// [`align`]: crate::AdaptWidget::align
68/// [`pack`]: crate::AdaptWidget::pack
69#[macro_export]
70macro_rules! float {
71    ( $( $ee:expr ),* ) => {
72        $crate::Float::new( ::kas::collection! [ $( $ee ),* ] )
73    };
74    ( $( $ee:expr ),+ , ) => {
75        $crate::Float::new( ::kas::collection! [ $( $ee ),+ ] )
76    };
77}
78
79#[impl_self]
80mod Float {
81    /// A float widget
82    ///
83    /// All widgets occupy the same space with the first child on top.
84    ///
85    /// Size is determined as the maximum required by any child for each axis.
86    /// All widgets are assigned this size. It is usually necessary to use
87    /// [`pack`] or a similar mechanism to constrain a child to avoid it hiding
88    /// the content underneath (note that even if an unconstrained child does
89    /// not *visually* hide everything beneath, it may still "occupy" the
90    /// assigned area, preventing mouse clicks from reaching the widget
91    /// beneath).
92    ///
93    /// Warning: each child beyond the first requires a new draw pass. The
94    /// number of passes used can have a substantial performance impact,
95    /// potentially more on GPU communication than CPU usage.
96    ///
97    /// [`pack`]: crate::AdaptWidget::pack
98    #[derive(Clone, Default)]
99    #[widget]
100    pub struct Float<C: Collection> {
101        core: widget_core!(),
102        #[collection]
103        widgets: C,
104    }
105
106    impl Self {
107        /// Construct a float
108        #[inline]
109        pub fn new(widgets: C) -> Self {
110            Float {
111                core: Default::default(),
112                widgets,
113            }
114        }
115    }
116
117    impl Layout for Self {
118        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
119            let mut rules = SizeRules::EMPTY;
120            for i in 0..self.widgets.len() {
121                if let Some(child) = self.widgets.get_mut_tile(i) {
122                    rules = rules.max(child.size_rules(sizer.re(), axis));
123                }
124            }
125            rules
126        }
127
128        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
129            widget_set_rect!(rect);
130            for i in 0..self.widgets.len() {
131                if let Some(child) = self.widgets.get_mut_tile(i) {
132                    child.set_rect(cx, rect, hints);
133                }
134            }
135        }
136
137        fn draw(&self, mut draw: DrawCx) {
138            let mut iter = (0..self.widgets.len()).rev();
139            if let Some(first) = iter.next() {
140                if let Some(child) = self.widgets.get_tile(first) {
141                    child.draw(draw.re());
142                }
143            }
144            for i in iter {
145                if let Some(child) = self.widgets.get_tile(i) {
146                    // We use a new pass to control draw order and clip content:
147                    draw.with_pass(|draw| child.draw(draw));
148                }
149            }
150        }
151    }
152
153    impl Tile for Self {
154        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
155            Role::None
156        }
157
158        fn probe(&self, coord: Coord) -> Id {
159            for i in 0..self.widgets.len() {
160                if let Some(child) = self.widgets.get_tile(i) {
161                    if let Some(id) = child.try_probe(coord) {
162                        return id;
163                    }
164                }
165            }
166            self.id()
167        }
168    }
169}