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