1use crate::core;
3use crate::core::border;
4use crate::core::layout;
5use crate::core::mouse;
6use crate::core::overlay;
7use crate::core::renderer;
8use crate::core::widget;
9use crate::core::widget::tree;
10use crate::core::{
11 Element, Event, Layout, Length, Rectangle, Shadow, Shell, Size, Transformation, Vector, Widget,
12};
13
14pub struct Float<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
16where
17 Theme: Catalog,
18{
19 content: Element<'a, Message, Theme, Renderer>,
20 scale: f32,
21 translate: Option<Box<dyn Fn(Rectangle, Rectangle) -> Vector + 'a>>,
22 class: Theme::Class<'a>,
23}
24
25impl<'a, Message, Theme, Renderer> Float<'a, Message, Theme, Renderer>
26where
27 Theme: Catalog,
28{
29 pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
31 Self {
32 content: content.into(),
33 scale: 1.0,
34 translate: None,
35 class: Theme::default(),
36 }
37 }
38
39 pub fn scale(mut self, scale: f32) -> Self {
41 self.scale = scale;
42 self
43 }
44
45 pub fn translate(mut self, translate: impl Fn(Rectangle, Rectangle) -> Vector + 'a) -> Self {
51 self.translate = Some(Box::new(translate));
52 self
53 }
54
55 #[must_use]
57 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
58 where
59 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
60 {
61 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
62 self
63 }
64
65 #[cfg(feature = "advanced")]
67 #[must_use]
68 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
69 self.class = class.into();
70 self
71 }
72
73 fn is_floating(&self, bounds: Rectangle, viewport: Rectangle) -> bool {
74 self.scale > 1.0
75 || self
76 .translate
77 .as_ref()
78 .is_some_and(|translate| translate(bounds, viewport) != Vector::ZERO)
79 }
80}
81
82impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
83 for Float<'_, Message, Theme, Renderer>
84where
85 Theme: Catalog,
86 Renderer: core::Renderer,
87{
88 fn tag(&self) -> tree::Tag {
89 self.content.as_widget().tag()
90 }
91
92 fn state(&self) -> tree::State {
93 self.content.as_widget().state()
94 }
95
96 fn children(&self) -> Vec<tree::Tree> {
97 self.content.as_widget().children()
98 }
99
100 fn diff(&self, tree: &mut widget::Tree) {
101 self.content.as_widget().diff(tree);
102 }
103
104 fn size(&self) -> Size<Length> {
105 self.content.as_widget().size()
106 }
107
108 fn size_hint(&self) -> Size<Length> {
109 self.content.as_widget().size_hint()
110 }
111
112 fn layout(
113 &mut self,
114 tree: &mut widget::Tree,
115 renderer: &Renderer,
116 limits: &layout::Limits,
117 ) -> layout::Node {
118 self.content.as_widget_mut().layout(tree, renderer, limits)
119 }
120
121 fn update(
122 &mut self,
123 tree: &mut widget::Tree,
124 event: &Event,
125 layout: Layout<'_>,
126 cursor: mouse::Cursor,
127 renderer: &Renderer,
128 shell: &mut Shell<'_, Message>,
129 viewport: &Rectangle,
130 ) {
131 if self.is_floating(layout.bounds(), *viewport) {
132 return;
133 }
134
135 self.content
136 .as_widget_mut()
137 .update(tree, event, layout, cursor, renderer, shell, viewport);
138 }
139
140 fn draw(
141 &self,
142 tree: &widget::Tree,
143 renderer: &mut Renderer,
144 theme: &Theme,
145 style: &renderer::Style,
146 layout: Layout<'_>,
147 cursor: mouse::Cursor,
148 viewport: &Rectangle,
149 ) {
150 if self.is_floating(layout.bounds(), *viewport) {
151 return;
152 }
153
154 {
155 let style = theme.style(&self.class);
156
157 if style.shadow.color.a > 0.0 {
158 renderer.fill_quad(
159 renderer::Quad {
160 bounds: layout.bounds().shrink(1.0),
161 shadow: style.shadow,
162 border: border::rounded(style.shadow_border_radius),
163 snap: false,
164 },
165 style.shadow.color,
166 );
167 }
168 }
169
170 self.content
171 .as_widget()
172 .draw(tree, renderer, theme, style, layout, cursor, viewport);
173 }
174
175 fn mouse_interaction(
176 &self,
177 tree: &widget::Tree,
178 layout: Layout<'_>,
179 cursor: mouse::Cursor,
180 viewport: &Rectangle,
181 renderer: &Renderer,
182 ) -> mouse::Interaction {
183 if self.is_floating(layout.bounds(), *viewport) {
184 return mouse::Interaction::None;
185 }
186
187 self.content
188 .as_widget()
189 .mouse_interaction(tree, layout, cursor, viewport, renderer)
190 }
191
192 fn operate(
193 &mut self,
194 tree: &mut widget::Tree,
195 layout: Layout<'_>,
196 renderer: &Renderer,
197 operation: &mut dyn widget::Operation,
198 ) {
199 self.content
200 .as_widget_mut()
201 .operate(tree, layout, renderer, operation);
202 }
203
204 fn overlay<'a>(
205 &'a mut self,
206 state: &'a mut widget::Tree,
207 layout: Layout<'a>,
208 renderer: &Renderer,
209 viewport: &Rectangle,
210 offset: Vector,
211 ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
212 let bounds = layout.bounds();
213
214 let translation = self
215 .translate
216 .as_ref()
217 .map(|translate| translate(bounds + offset, *viewport))
218 .unwrap_or(Vector::ZERO);
219
220 if self.scale > 1.0 || translation != Vector::ZERO {
221 let translation = translation + offset;
222
223 let transformation = Transformation::translate(
224 bounds.x + bounds.width / 2.0 + translation.x,
225 bounds.y + bounds.height / 2.0 + translation.y,
226 ) * Transformation::scale(self.scale)
227 * Transformation::translate(
228 -bounds.x - bounds.width / 2.0,
229 -bounds.y - bounds.height / 2.0,
230 );
231
232 Some(overlay::Element::new(Box::new(Overlay {
233 float: self,
234 state,
235 layout,
236 viewport: *viewport,
237 transformation,
238 })))
239 } else {
240 self.content
241 .as_widget_mut()
242 .overlay(state, layout, renderer, viewport, offset)
243 }
244 }
245}
246
247impl<'a, Message, Theme, Renderer> From<Float<'a, Message, Theme, Renderer>>
248 for Element<'a, Message, Theme, Renderer>
249where
250 Message: 'a,
251 Theme: Catalog + 'a,
252 Renderer: core::Renderer + 'a,
253{
254 fn from(float: Float<'a, Message, Theme, Renderer>) -> Self {
255 Element::new(float)
256 }
257}
258
259struct Overlay<'a, 'b, Message, Theme, Renderer>
260where
261 Theme: Catalog,
262{
263 float: &'a mut Float<'b, Message, Theme, Renderer>,
264 state: &'a mut widget::Tree,
265 layout: Layout<'a>,
266 viewport: Rectangle,
267 transformation: Transformation,
268}
269
270impl<Message, Theme, Renderer> core::Overlay<Message, Theme, Renderer>
271 for Overlay<'_, '_, Message, Theme, Renderer>
272where
273 Theme: Catalog,
274 Renderer: core::Renderer,
275{
276 fn layout(&mut self, _renderer: &Renderer, _bounds: Size) -> layout::Node {
277 let bounds = self.layout.bounds() * self.transformation;
278
279 layout::Node::new(bounds.size()).move_to(bounds.position())
280 }
281
282 fn update(
283 &mut self,
284 event: &Event,
285 _layout: Layout<'_>,
286 cursor: mouse::Cursor,
287 renderer: &Renderer,
288 shell: &mut Shell<'_, Message>,
289 ) {
290 let inverse = self.transformation.inverse();
291
292 self.float.content.as_widget_mut().update(
293 self.state,
294 event,
295 self.layout,
296 cursor * inverse,
297 renderer,
298 shell,
299 &(self.viewport * inverse),
300 );
301 }
302
303 fn draw(
304 &self,
305 renderer: &mut Renderer,
306 theme: &Theme,
307 style: &renderer::Style,
308 _layout: Layout<'_>,
309 cursor: mouse::Cursor,
310 ) {
311 let bounds = self.layout.bounds();
312 let inverse = self.transformation.inverse();
313
314 renderer.with_layer(self.viewport, |renderer| {
315 renderer.with_transformation(self.transformation, |renderer| {
316 {
317 let style = theme.style(&self.float.class);
318
319 if style.shadow.color.a > 0.0 {
320 renderer.fill_quad(
321 renderer::Quad {
322 bounds: bounds.shrink(1.0),
323 shadow: style.shadow,
324 border: border::rounded(style.shadow_border_radius),
325 snap: false,
326 },
327 style.shadow.color,
328 );
329 }
330 }
331
332 self.float.content.as_widget().draw(
333 self.state,
334 renderer,
335 theme,
336 style,
337 self.layout,
338 cursor * inverse,
339 &(self.viewport * inverse),
340 );
341 });
342 });
343 }
344
345 fn mouse_interaction(
346 &self,
347 layout: Layout<'_>,
348 cursor: mouse::Cursor,
349 renderer: &Renderer,
350 ) -> mouse::Interaction {
351 if !cursor.is_over(layout.bounds()) {
352 return mouse::Interaction::None;
353 }
354
355 let inverse = self.transformation.inverse();
356
357 self.float.content.as_widget().mouse_interaction(
358 self.state,
359 self.layout,
360 cursor * inverse,
361 &(self.viewport * inverse),
362 renderer,
363 )
364 }
365
366 fn index(&self) -> f32 {
367 self.float.scale * 0.5
368 }
369
370 fn overlay<'a>(
371 &'a mut self,
372 _layout: Layout<'_>,
373 renderer: &Renderer,
374 ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
375 self.float.content.as_widget_mut().overlay(
376 self.state,
377 self.layout,
378 renderer,
379 &(self.viewport * self.transformation.inverse()),
380 self.transformation.translation(),
381 )
382 }
383}
384
385pub trait Catalog {
390 type Class<'a>;
392
393 fn default<'a>() -> Self::Class<'a>;
395
396 fn style(&self, class: &Self::Class<'_>) -> Style;
398}
399
400pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
402
403impl Catalog for crate::Theme {
404 type Class<'a> = StyleFn<'a, Self>;
405
406 fn default<'a>() -> Self::Class<'a> {
407 Box::new(|_| Style::default())
408 }
409
410 fn style(&self, class: &Self::Class<'_>) -> Style {
411 class(self)
412 }
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Default)]
417pub struct Style {
418 pub shadow: Shadow,
420 pub shadow_border_radius: border::Radius,
422}