iced_widget/
float.rs

1//! Make elements float!
2use 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
15/// A widget that can make its contents float over other widgets.
16pub 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    /// Creates a new [`Float`] widget with the given content.
31    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    /// Sets the scale to be applied to the contents of the [`Float`].
41    pub fn scale(mut self, scale: f32) -> Self {
42        self.scale = scale;
43        self
44    }
45
46    /// Sets the translation logic to be applied to the contents of the [`Float`].
47    ///
48    /// The logic takes the original (non-scaled) bounds of the contents and the
49    /// viewport bounds. These bounds can be useful to ensure the floating elements
50    /// always stay on screen.
51    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    /// Sets the style of the [`Float`].
57    #[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    /// Sets the style class of the [`Float`].
67    #[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
389/// The theme catalog of a [`Float`].
390///
391/// All themes that can be used with [`Float`]
392/// must implement this trait.
393pub trait Catalog {
394    /// The item class of the [`Catalog`].
395    type Class<'a>;
396
397    /// The default class produced by the [`Catalog`].
398    fn default<'a>() -> Self::Class<'a>;
399
400    /// The [`Style`] of a class with the given status.
401    fn style(&self, class: &Self::Class<'_>) -> Style;
402}
403
404/// A styling function for a [`Float`].
405pub 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/// The style of a [`Float`].
420#[derive(Debug, Clone, Copy, PartialEq, Default)]
421pub struct Style {
422    /// The [`Shadow`] of the [`Float`].
423    pub shadow: Shadow,
424    /// The border radius of the shadow.
425    pub shadow_border_radius: border::Radius,
426}