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