Skip to main content

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///     any of the following method calls: [`align`], [`pack`], [`with_stretch`]
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`], [`pack`] and [`with_stretch`] are supported via
41/// emulation.
42///
43/// In this case, _Item_ is evaluated using [widget layout syntax]. This is
44/// broadly similar to the above with a couple of exceptions:
45///
46/// -   Supported layout macros do not need to be imported to the module scope
47/// -   An _Item_ may be a `#[widget]` field of the widget
48///
49/// # Example
50///
51/// ```
52/// # use kas::prelude::*;
53/// let my_widget = kas_widgets::float! [
54///     "one".pack(AlignHints::TOP_LEFT),
55///     "two".pack(AlignHints::BOTTOM_RIGHT),
56///     "some text\nin the\nbackground"
57/// ];
58/// ```
59///
60/// # Cost
61///
62/// Warning: each child beyond the first requires a new draw pass. The
63/// number of passes used can have a substantial performance impact,
64/// potentially more on GPU communication than CPU usage.
65///
66/// [widget layout syntax]: macro@kas::layout
67/// [`map_any`]: crate::AdaptWidgetAny::map_any
68/// [`align`]: crate::AdaptWidget::align
69/// [`pack`]: crate::AdaptWidget::pack
70/// [`with_stretch`]: crate::AdaptWidget::with_stretch
71#[macro_export]
72macro_rules! float {
73    ( $( $ee:expr ),* ) => {
74        $crate::Float::new( ::kas::collection! [ $( $ee ),* ] )
75    };
76    ( $( $ee:expr ),+ , ) => {
77        $crate::Float::new( ::kas::collection! [ $( $ee ),+ ] )
78    };
79}
80
81#[impl_self]
82mod Float {
83    /// A float widget
84    ///
85    /// All widgets occupy the same space with the first child on top.
86    ///
87    /// Size is determined as the maximum required by any child for each axis.
88    /// All widgets are assigned this size. It is usually necessary to use
89    /// [`pack`] or a similar mechanism to constrain a child to avoid it hiding
90    /// the content underneath (note that even if an unconstrained child does
91    /// not *visually* hide everything beneath, it may still "occupy" the
92    /// assigned area, preventing mouse clicks from reaching the widget
93    /// beneath).
94    ///
95    /// Warning: each child beyond the first requires a new draw pass. The
96    /// number of passes used can have a substantial performance impact,
97    /// potentially more on GPU communication than CPU usage.
98    ///
99    /// [`pack`]: crate::AdaptWidget::pack
100    #[derive(Default)]
101    #[widget]
102    pub struct Float<C: Collection> {
103        core: widget_core!(),
104        #[collection]
105        widgets: C,
106    }
107
108    impl Self {
109        /// Construct a float
110        #[inline]
111        pub fn new(widgets: C) -> Self {
112            Float {
113                core: Default::default(),
114                widgets,
115            }
116        }
117    }
118
119    impl Layout for Self {
120        fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
121            let mut rules = SizeRules::EMPTY;
122            for i in 0..self.widgets.len() {
123                if let Some(child) = self.widgets.get_mut_tile(i) {
124                    rules = rules.max(child.size_rules(cx, axis));
125                }
126            }
127            rules
128        }
129
130        fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, hints: AlignHints) {
131            self.core.set_rect(rect);
132            for i in 0..self.widgets.len() {
133                if let Some(child) = self.widgets.get_mut_tile(i) {
134                    child.set_rect(cx, rect, hints);
135                }
136            }
137        }
138
139        fn draw(&self, mut draw: DrawCx) {
140            let mut iter = (0..self.widgets.len()).rev();
141            if let Some(first) = iter.next() {
142                if let Some(child) = self.widgets.get_tile(first) {
143                    child.draw(draw.re());
144                }
145            }
146            for i in iter {
147                if let Some(child) = self.widgets.get_tile(i) {
148                    // We use a new pass to control draw order and clip content:
149                    draw.with_pass(|draw| child.draw(draw));
150                }
151            }
152        }
153    }
154
155    impl Tile for Self {
156        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
157            Role::None
158        }
159    }
160
161    impl Events for Self {
162        type Data = C::Data;
163
164        fn probe(&self, coord: Coord) -> Id {
165            for i in 0..self.widgets.len() {
166                if let Some(child) = self.widgets.get_tile(i) {
167                    if let Some(id) = child.try_probe(coord) {
168                        return id;
169                    }
170                }
171            }
172            self.id()
173        }
174    }
175}