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