Skip to main content

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