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/// 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}