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 },
171 style.shadow.color,
172 );
173 }
174 }
175
176 self.content
177 .as_widget()
178 .draw(tree, renderer, theme, style, layout, cursor, viewport);
179 }
180
181 fn mouse_interaction(
182 &self,
183 state: &widget::Tree,
184 layout: Layout<'_>,
185 cursor: mouse::Cursor,
186 viewport: &Rectangle,
187 renderer: &Renderer,
188 ) -> mouse::Interaction {
189 if self.is_floating(layout.bounds(), *viewport) {
190 return mouse::Interaction::None;
191 }
192
193 self.content
194 .as_widget()
195 .mouse_interaction(state, layout, cursor, viewport, renderer)
196 }
197
198 fn operate(
199 &self,
200 state: &mut widget::Tree,
201 layout: Layout<'_>,
202 renderer: &Renderer,
203 operation: &mut dyn widget::Operation,
204 ) {
205 self.content
206 .as_widget()
207 .operate(state, layout, renderer, operation);
208 }
209
210 fn overlay<'a>(
211 &'a mut self,
212 state: &'a mut widget::Tree,
213 layout: Layout<'a>,
214 renderer: &Renderer,
215 viewport: &Rectangle,
216 offset: Vector,
217 ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
218 let bounds = layout.bounds();
219
220 let translation = self
221 .translate
222 .as_ref()
223 .map(|translate| translate(bounds + offset, *viewport))
224 .unwrap_or(Vector::ZERO);
225
226 if self.scale > 1.0 || translation != Vector::ZERO {
227 let translation = translation + offset;
228
229 let transformation = Transformation::translate(
230 bounds.x + bounds.width / 2.0 + translation.x,
231 bounds.y + bounds.height / 2.0 + translation.y,
232 ) * Transformation::scale(self.scale)
233 * Transformation::translate(
234 -bounds.x - bounds.width / 2.0,
235 -bounds.y - bounds.height / 2.0,
236 );
237
238 Some(overlay::Element::new(Box::new(Overlay {
239 float: self,
240 state,
241 layout,
242 viewport: *viewport,
243 transformation,
244 })))
245 } else {
246 self.content
247 .as_widget_mut()
248 .overlay(state, layout, renderer, viewport, offset)
249 }
250 }
251}
252
253impl<'a, Message, Theme, Renderer> From<Float<'a, Message, Theme, Renderer>>
254 for Element<'a, Message, Theme, Renderer>
255where
256 Message: 'a,
257 Theme: Catalog + 'a,
258 Renderer: core::Renderer + 'a,
259{
260 fn from(float: Float<'a, Message, Theme, Renderer>) -> Self {
261 Element::new(float)
262 }
263}
264
265struct Overlay<'a, 'b, Message, Theme, Renderer>
266where
267 Theme: Catalog,
268{
269 float: &'a mut Float<'b, Message, Theme, Renderer>,
270 state: &'a mut widget::Tree,
271 layout: Layout<'a>,
272 viewport: Rectangle,
273 transformation: Transformation,
274}
275
276impl<Message, Theme, Renderer> core::Overlay<Message, Theme, Renderer>
277 for Overlay<'_, '_, Message, Theme, Renderer>
278where
279 Theme: Catalog,
280 Renderer: core::Renderer,
281{
282 fn layout(&mut self, _renderer: &Renderer, _bounds: Size) -> layout::Node {
283 let bounds = self.layout.bounds() * self.transformation;
284
285 layout::Node::new(bounds.size()).move_to(bounds.position())
286 }
287
288 fn update(
289 &mut self,
290 event: &Event,
291 _layout: Layout<'_>,
292 cursor: mouse::Cursor,
293 renderer: &Renderer,
294 clipboard: &mut dyn Clipboard,
295 shell: &mut Shell<'_, Message>,
296 ) {
297 let inverse = self.transformation.inverse();
298
299 self.float.content.as_widget_mut().update(
300 self.state,
301 event,
302 self.layout,
303 cursor * inverse,
304 renderer,
305 clipboard,
306 shell,
307 &(self.viewport * inverse),
308 );
309 }
310
311 fn draw(
312 &self,
313 renderer: &mut Renderer,
314 theme: &Theme,
315 style: &renderer::Style,
316 _layout: Layout<'_>,
317 cursor: mouse::Cursor,
318 ) {
319 let bounds = self.layout.bounds();
320 let inverse = self.transformation.inverse();
321
322 renderer.with_layer(self.viewport, |renderer| {
323 renderer.with_transformation(self.transformation, |renderer| {
324 {
325 let style = theme.style(&self.float.class);
326
327 if style.shadow.color.a > 0.0 {
328 renderer.fill_quad(
329 renderer::Quad {
330 bounds: bounds.shrink(1.0),
331 shadow: style.shadow,
332 border: border::rounded(
333 style.shadow_border_radius,
334 ),
335 },
336 style.shadow.color,
337 );
338 }
339 }
340
341 self.float.content.as_widget().draw(
342 self.state,
343 renderer,
344 theme,
345 style,
346 self.layout,
347 cursor * inverse,
348 &(self.viewport * inverse),
349 );
350 });
351 });
352 }
353
354 fn mouse_interaction(
355 &self,
356 layout: Layout<'_>,
357 cursor: mouse::Cursor,
358 renderer: &Renderer,
359 ) -> mouse::Interaction {
360 if !cursor.is_over(layout.bounds()) {
361 return mouse::Interaction::None;
362 }
363
364 let inverse = self.transformation.inverse();
365
366 self.float.content.as_widget().mouse_interaction(
367 self.state,
368 self.layout,
369 cursor * inverse,
370 &(self.viewport * inverse),
371 renderer,
372 )
373 }
374
375 fn index(&self) -> f32 {
376 self.float.scale * 0.5
377 }
378
379 fn overlay<'a>(
380 &'a mut self,
381 _layout: Layout<'_>,
382 renderer: &Renderer,
383 ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
384 self.float.content.as_widget_mut().overlay(
385 self.state,
386 self.layout,
387 renderer,
388 &(self.viewport * self.transformation.inverse()),
389 self.transformation.translation(),
390 )
391 }
392}
393
394pub trait Catalog {
399 type Class<'a>;
401
402 fn default<'a>() -> Self::Class<'a>;
404
405 fn style(&self, class: &Self::Class<'_>) -> Style;
407}
408
409pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
411
412impl Catalog for crate::Theme {
413 type Class<'a> = StyleFn<'a, Self>;
414
415 fn default<'a>() -> Self::Class<'a> {
416 Box::new(|_| Style::default())
417 }
418
419 fn style(&self, class: &Self::Class<'_>) -> Style {
420 class(self)
421 }
422}
423
424#[derive(Debug, Clone, Copy, PartialEq, Default)]
426pub struct Style {
427 pub shadow: Shadow,
429 pub shadow_border_radius: border::Radius,
431}