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
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(
32 content: impl Into<Element<'a, Message, Theme, Renderer>>,
33 ) -> Self {
34 Self {
35 content: content.into(),
36 scale: 1.0,
37 translate: None,
38 class: Theme::default(),
39 }
40 }
41
42 pub fn scale(mut self, scale: f32) -> Self {
44 self.scale = scale;
45 self
46 }
47
48 pub fn translate(
54 mut self,
55 translate: impl Fn(Rectangle, Rectangle) -> Vector + 'a,
56 ) -> Self {
57 self.translate = Some(Box::new(translate));
58 self
59 }
60
61 #[must_use]
63 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
64 where
65 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
66 {
67 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
68 self
69 }
70
71 #[cfg(feature = "advanced")]
73 #[must_use]
74 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
75 self.class = class.into();
76 self
77 }
78
79 fn is_floating(&self, bounds: Rectangle, viewport: Rectangle) -> bool {
80 self.scale > 1.0
81 || self.translate.as_ref().is_some_and(|translate| {
82 translate(bounds, viewport) != Vector::ZERO
83 })
84 }
85}
86
87impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
88 for Float<'_, Message, Theme, Renderer>
89where
90 Theme: Catalog,
91 Renderer: core::Renderer,
92{
93 fn tag(&self) -> tree::Tag {
94 self.content.as_widget().tag()
95 }
96
97 fn state(&self) -> tree::State {
98 self.content.as_widget().state()
99 }
100
101 fn children(&self) -> Vec<tree::Tree> {
102 self.content.as_widget().children()
103 }
104
105 fn diff(&self, tree: &mut widget::Tree) {
106 self.content.as_widget().diff(tree);
107 }
108
109 fn size(&self) -> Size<Length> {
110 self.content.as_widget().size()
111 }
112
113 fn size_hint(&self) -> Size<Length> {
114 self.content.as_widget().size_hint()
115 }
116
117 fn layout(
118 &mut self,
119 tree: &mut widget::Tree,
120 renderer: &Renderer,
121 limits: &layout::Limits,
122 ) -> layout::Node {
123 self.content.as_widget_mut().layout(tree, renderer, limits)
124 }
125
126 fn update(
127 &mut self,
128 state: &mut widget::Tree,
129 event: &Event,
130 layout: Layout<'_>,
131 cursor: mouse::Cursor,
132 renderer: &Renderer,
133 clipboard: &mut dyn Clipboard,
134 shell: &mut Shell<'_, Message>,
135 viewport: &Rectangle,
136 ) {
137 if self.is_floating(layout.bounds(), *viewport) {
138 return;
139 }
140
141 self.content.as_widget_mut().update(
142 state, event, layout, cursor, renderer, clipboard, shell, viewport,
143 );
144 }
145
146 fn draw(
147 &self,
148 tree: &widget::Tree,
149 renderer: &mut Renderer,
150 theme: &Theme,
151 style: &renderer::Style,
152 layout: Layout<'_>,
153 cursor: mouse::Cursor,
154 viewport: &Rectangle,
155 ) {
156 if self.is_floating(layout.bounds(), *viewport) {
157 return;
158 }
159
160 {
161 let style = theme.style(&self.class);
162
163 if style.shadow.color.a > 0.0 {
164 renderer.fill_quad(
165 renderer::Quad {
166 bounds: layout.bounds().shrink(1.0),
167 shadow: style.shadow,
168 border: border::rounded(style.shadow_border_radius),
169 snap: false,
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 &mut 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_mut()
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 snap: false,
336 },
337 style.shadow.color,
338 );
339 }
340 }
341
342 self.float.content.as_widget().draw(
343 self.state,
344 renderer,
345 theme,
346 style,
347 self.layout,
348 cursor * inverse,
349 &(self.viewport * inverse),
350 );
351 });
352 });
353 }
354
355 fn mouse_interaction(
356 &self,
357 layout: Layout<'_>,
358 cursor: mouse::Cursor,
359 renderer: &Renderer,
360 ) -> mouse::Interaction {
361 if !cursor.is_over(layout.bounds()) {
362 return mouse::Interaction::None;
363 }
364
365 let inverse = self.transformation.inverse();
366
367 self.float.content.as_widget().mouse_interaction(
368 self.state,
369 self.layout,
370 cursor * inverse,
371 &(self.viewport * inverse),
372 renderer,
373 )
374 }
375
376 fn index(&self) -> f32 {
377 self.float.scale * 0.5
378 }
379
380 fn overlay<'a>(
381 &'a mut self,
382 _layout: Layout<'_>,
383 renderer: &Renderer,
384 ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
385 self.float.content.as_widget_mut().overlay(
386 self.state,
387 self.layout,
388 renderer,
389 &(self.viewport * self.transformation.inverse()),
390 self.transformation.translation(),
391 )
392 }
393}
394
395pub trait Catalog {
400 type Class<'a>;
402
403 fn default<'a>() -> Self::Class<'a>;
405
406 fn style(&self, class: &Self::Class<'_>) -> Style;
408}
409
410pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
412
413impl Catalog for crate::Theme {
414 type Class<'a> = StyleFn<'a, Self>;
415
416 fn default<'a>() -> Self::Class<'a> {
417 Box::new(|_| Style::default())
418 }
419
420 fn style(&self, class: &Self::Class<'_>) -> Style {
421 class(self)
422 }
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Default)]
427pub struct Style {
428 pub shadow: Shadow,
430 pub shadow_border_radius: border::Radius,
432}