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 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
377/// The theme catalog of a [`Float`].
378///
379/// All themes that can be used with [`Float`]
380/// must implement this trait.
381pub trait Catalog {
382    /// The item class of the [`Catalog`].
383    type Class<'a>;
384
385    /// The default class produced by the [`Catalog`].
386    fn default<'a>() -> Self::Class<'a>;
387
388    /// The [`Style`] of a class with the given status.
389    fn style(&self, class: &Self::Class<'_>) -> Style;
390}
391
392/// A styling function for a [`Float`].
393pub 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/// The style of a [`Float`].
408#[derive(Debug, Clone, Copy, PartialEq, Default)]
409pub struct Style {
410    /// The [`Shadow`] of the [`Float`].
411    pub shadow: Shadow,
412    /// The border radius of the shadow.
413    pub shadow_border_radius: border::Radius,
414}