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/// > `float!` `[` _Items_<sup>\?</sup> `]`
17/// >
18/// > _Items_ :\
19/// > (_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}