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 diff(&mut self, tree: &mut widget::Tree) {
97 self.content.as_widget_mut().diff(tree);
98 }
99
100 fn size(&self) -> Size<Length> {
101 self.content.as_widget().size()
102 }
103
104 fn layout(
105 &mut self,
106 tree: &mut widget::Tree,
107 renderer: &Renderer,
108 limits: &layout::Limits,
109 ) -> layout::Node {
110 self.content.as_widget_mut().layout(tree, renderer, limits)
111 }
112
113 fn update(
114 &mut self,
115 tree: &mut widget::Tree,
116 event: &Event,
117 layout: Layout<'_>,
118 cursor: mouse::Cursor,
119 renderer: &Renderer,
120 shell: &mut Shell<'_, Message>,
121 viewport: &Rectangle,
122 ) {
123 if self.is_floating(layout.bounds(), *viewport) {
124 return;
125 }
126
127 self.content
128 .as_widget_mut()
129 .update(tree, event, layout, cursor, renderer, shell, viewport);
130 }
131
132 fn draw(
133 &self,
134 tree: &widget::Tree,
135 renderer: &mut Renderer,
136 theme: &Theme,
137 style: &renderer::Style,
138 layout: Layout<'_>,
139 cursor: mouse::Cursor,
140 viewport: &Rectangle,
141 ) {
142 if self.is_floating(layout.bounds(), *viewport) {
143 return;
144 }
145
146 {
147 let style = theme.style(&self.class);
148
149 if style.shadow.color.a > 0.0 {
150 renderer.fill_quad(
151 renderer::Quad {
152 bounds: layout.bounds().shrink(1.0),
153 shadow: style.shadow,
154 border: border::rounded(style.shadow_border_radius),
155 snap: false,
156 },
157 style.shadow.color,
158 );
159 }
160 }
161
162 self.content
163 .as_widget()
164 .draw(tree, renderer, theme, style, layout, cursor, viewport);
165 }
166
167 fn mouse_interaction(
168 &self,
169 tree: &widget::Tree,
170 layout: Layout<'_>,
171 cursor: mouse::Cursor,
172 viewport: &Rectangle,
173 renderer: &Renderer,
174 ) -> mouse::Interaction {
175 if self.is_floating(layout.bounds(), *viewport) {
176 return mouse::Interaction::None;
177 }
178
179 self.content
180 .as_widget()
181 .mouse_interaction(tree, layout, cursor, viewport, renderer)
182 }
183
184 fn operate(
185 &mut self,
186 tree: &mut widget::Tree,
187 layout: Layout<'_>,
188 renderer: &Renderer,
189 operation: &mut dyn widget::Operation,
190 ) {
191 self.content
192 .as_widget_mut()
193 .operate(tree, layout, renderer, operation);
194 }
195
196 fn overlay<'a>(
197 &'a mut self,
198 state: &'a mut widget::Tree,
199 layout: Layout<'a>,
200 renderer: &Renderer,
201 viewport: &Rectangle,
202 offset: Vector,
203 ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
204 let bounds = layout.bounds();
205
206 let translation = self
207 .translate
208 .as_ref()
209 .map(|translate| translate(bounds + offset, *viewport))
210 .unwrap_or(Vector::ZERO);
211
212 if self.scale > 1.0 || translation != Vector::ZERO {
213 let translation = translation + offset;
214
215 let transformation = Transformation::translate(
216 bounds.x + bounds.width / 2.0 + translation.x,
217 bounds.y + bounds.height / 2.0 + translation.y,
218 ) * Transformation::scale(self.scale)
219 * Transformation::translate(
220 -bounds.x - bounds.width / 2.0,
221 -bounds.y - bounds.height / 2.0,
222 );
223
224 Some(overlay::Element::new(Box::new(Overlay {
225 float: self,
226 state,
227 layout,
228 viewport: *viewport,
229 transformation,
230 })))
231 } else {
232 self.content
233 .as_widget_mut()
234 .overlay(state, layout, renderer, viewport, offset)
235 }
236 }
237}
238
239impl<'a, Message, Theme, Renderer> From<Float<'a, Message, Theme, Renderer>>
240 for Element<'a, Message, Theme, Renderer>
241where
242 Message: 'a,
243 Theme: Catalog + 'a,
244 Renderer: core::Renderer + 'a,
245{
246 fn from(float: Float<'a, Message, Theme, Renderer>) -> Self {
247 Element::new(float)
248 }
249}
250
251struct Overlay<'a, 'b, Message, Theme, Renderer>
252where
253 Theme: Catalog,
254{
255 float: &'a mut Float<'b, Message, Theme, Renderer>,
256 state: &'a mut widget::Tree,
257 layout: Layout<'a>,
258 viewport: Rectangle,
259 transformation: Transformation,
260}
261
262impl<Message, Theme, Renderer> core::Overlay<Message, Theme, Renderer>
263 for Overlay<'_, '_, Message, Theme, Renderer>
264where
265 Theme: Catalog,
266 Renderer: core::Renderer,
267{
268 fn layout(&mut self, _renderer: &Renderer, _bounds: Size) -> layout::Node {
269 let bounds = self.layout.bounds() * self.transformation;
270
271 layout::Node::new(bounds.size()).move_to(bounds.position())
272 }
273
274 fn update(
275 &mut self,
276 event: &Event,
277 _layout: Layout<'_>,
278 cursor: mouse::Cursor,
279 renderer: &Renderer,
280 shell: &mut Shell<'_, Message>,
281 ) {
282 let inverse = self.transformation.inverse();
283
284 self.float.content.as_widget_mut().update(
285 self.state,
286 event,
287 self.layout,
288 cursor * inverse,
289 renderer,
290 shell,
291 &(self.viewport * inverse),
292 );
293 }
294
295 fn draw(
296 &self,
297 renderer: &mut Renderer,
298 theme: &Theme,
299 style: &renderer::Style,
300 _layout: Layout<'_>,
301 cursor: mouse::Cursor,
302 ) {
303 let bounds = self.layout.bounds();
304 let inverse = self.transformation.inverse();
305
306 renderer.with_layer(self.viewport, |renderer| {
307 renderer.with_transformation(self.transformation, |renderer| {
308 {
309 let style = theme.style(&self.float.class);
310
311 if style.shadow.color.a > 0.0 {
312 renderer.fill_quad(
313 renderer::Quad {
314 bounds: bounds.shrink(1.0),
315 shadow: style.shadow,
316 border: border::rounded(style.shadow_border_radius),
317 snap: false,
318 },
319 style.shadow.color,
320 );
321 }
322 }
323
324 self.float.content.as_widget().draw(
325 self.state,
326 renderer,
327 theme,
328 style,
329 self.layout,
330 cursor * inverse,
331 &(self.viewport * inverse),
332 );
333 });
334 });
335 }
336
337 fn mouse_interaction(
338 &self,
339 layout: Layout<'_>,
340 cursor: mouse::Cursor,
341 renderer: &Renderer,
342 ) -> mouse::Interaction {
343 if !cursor.is_over(layout.bounds()) {
344 return mouse::Interaction::None;
345 }
346
347 let inverse = self.transformation.inverse();
348
349 self.float.content.as_widget().mouse_interaction(
350 self.state,
351 self.layout,
352 cursor * inverse,
353 &(self.viewport * inverse),
354 renderer,
355 )
356 }
357
358 fn index(&self) -> f32 {
359 self.float.scale * 0.5
360 }
361
362 fn overlay<'a>(
363 &'a mut self,
364 _layout: Layout<'_>,
365 renderer: &Renderer,
366 ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
367 self.float.content.as_widget_mut().overlay(
368 self.state,
369 self.layout,
370 renderer,
371 &(self.viewport * self.transformation.inverse()),
372 self.transformation.translation(),
373 )
374 }
375}
376
377pub trait Catalog {
382 type Class<'a>;
384
385 fn default<'a>() -> Self::Class<'a>;
387
388 fn style(&self, class: &Self::Class<'_>) -> Style;
390}
391
392pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
394
395impl Catalog for crate::Theme {
396 type Class<'a> = StyleFn<'a, Self>;
397
398 fn default<'a>() -> Self::Class<'a> {
399 Box::new(|_| Style::default())
400 }
401
402 fn style(&self, class: &Self::Class<'_>) -> Style {
403 class(self)
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Default)]
409pub struct Style {
410 pub shadow: Shadow,
412 pub shadow_border_radius: border::Radius,
414}