cosmic/widget/toaster/
widget.rs

1// Copyright 2024 wiiznokes
2// SPDX-License-Identifier: MPL-2.0
3
4use iced::{Limits, Size};
5use iced_core::layout::Node;
6
7use iced_core::Element;
8use iced_core::Overlay;
9use iced_core::event::{self, Event};
10use iced_core::layout;
11use iced_core::mouse;
12use iced_core::overlay;
13use iced_core::renderer::{self};
14use iced_core::widget::Operation;
15use iced_core::widget::tree::Tree;
16use iced_core::{Clipboard, Layout, Length, Point, Rectangle, Shell, Vector, Widget};
17
18pub struct Toaster<'a, Message, Theme, Renderer> {
19    toasts: Element<'a, Message, Theme, Renderer>,
20    content: Element<'a, Message, Theme, Renderer>,
21    is_empty: bool,
22}
23
24impl<'a, Message, Theme, Renderer> Toaster<'a, Message, Theme, Renderer> {
25    pub fn new(
26        toasts: Element<'a, Message, Theme, Renderer>,
27        content: Element<'a, Message, Theme, Renderer>,
28        is_empty: bool,
29    ) -> Self {
30        Self {
31            toasts,
32            content,
33            is_empty,
34        }
35    }
36}
37
38impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
39    for Toaster<'_, Message, Theme, Renderer>
40where
41    Renderer: iced_core::Renderer,
42{
43    fn size(&self) -> Size<Length> {
44        self.content.as_widget().size()
45    }
46
47    fn layout(
48        &mut self,
49        tree: &mut Tree,
50        renderer: &Renderer,
51        limits: &layout::Limits,
52    ) -> layout::Node {
53        self.content
54            .as_widget_mut()
55            .layout(&mut tree.children[0], renderer, limits)
56    }
57
58    fn draw(
59        &self,
60        tree: &Tree,
61        renderer: &mut Renderer,
62        theme: &Theme,
63        style: &renderer::Style,
64        layout: Layout<'_>,
65        cursor: mouse::Cursor,
66        viewport: &Rectangle,
67    ) {
68        self.content.as_widget().draw(
69            &tree.children[0],
70            renderer,
71            theme,
72            style,
73            layout,
74            cursor,
75            viewport,
76        );
77    }
78
79    fn children(&self) -> Vec<Tree> {
80        vec![Tree::new(&self.content), Tree::new(&self.toasts)]
81    }
82
83    fn diff(&mut self, tree: &mut Tree) {
84        tree.diff_children(&mut [&mut self.content, &mut self.toasts]);
85    }
86
87    fn operate<'b>(
88        &'b mut self,
89        state: &'b mut Tree,
90        layout: Layout<'_>,
91        renderer: &Renderer,
92        operation: &mut dyn Operation<()>,
93    ) {
94        self.content
95            .as_widget_mut()
96            .operate(&mut state.children[0], layout, renderer, operation);
97    }
98
99    fn update(
100        &mut self,
101        state: &mut Tree,
102        event: &Event,
103        layout: Layout<'_>,
104        cursor: mouse::Cursor,
105        renderer: &Renderer,
106        clipboard: &mut dyn Clipboard,
107        shell: &mut Shell<'_, Message>,
108        viewport: &Rectangle,
109    ) {
110        self.content.as_widget_mut().update(
111            &mut state.children[0],
112            event,
113            layout,
114            cursor,
115            renderer,
116            clipboard,
117            shell,
118            viewport,
119        )
120    }
121
122    fn mouse_interaction(
123        &self,
124        state: &Tree,
125        layout: Layout<'_>,
126        cursor: mouse::Cursor,
127        viewport: &Rectangle,
128        renderer: &Renderer,
129    ) -> mouse::Interaction {
130        self.content.as_widget().mouse_interaction(
131            &state.children[0],
132            layout,
133            cursor,
134            viewport,
135            renderer,
136        )
137    }
138
139    fn overlay<'b>(
140        &'b mut self,
141        state: &'b mut Tree,
142        layout: Layout<'b>,
143        renderer: &Renderer,
144        viewport: &Rectangle,
145        translation: Vector,
146    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
147        //TODO: this hides the overlay of the content during the toast
148        if self.is_empty {
149            self.content.as_widget_mut().overlay(
150                &mut state.children[0],
151                layout,
152                renderer,
153                viewport,
154                translation,
155            )
156        } else {
157            let bounds = layout.bounds();
158
159            Some(overlay::Element::new(Box::new(ToasterOverlay::new(
160                &mut state.children[1],
161                &mut self.toasts,
162            ))))
163        }
164    }
165
166    fn drag_destinations(
167        &self,
168        state: &Tree,
169        layout: Layout<'_>,
170        renderer: &Renderer,
171        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
172    ) {
173        self.content.as_widget().drag_destinations(
174            &state.children[0],
175            layout,
176            renderer,
177            dnd_rectangles,
178        );
179    }
180}
181
182struct ToasterOverlay<'a, 'b, Message, Theme = iced::Theme, Renderer = iced::Renderer> {
183    state: &'b mut Tree,
184    element: &'b mut Element<'a, Message, Theme, Renderer>,
185}
186
187impl<'a, 'b, Message, Theme, Renderer> ToasterOverlay<'a, 'b, Message, Theme, Renderer>
188where
189    Renderer: renderer::Renderer,
190{
191    fn new(state: &'b mut Tree, element: &'b mut Element<'a, Message, Theme, Renderer>) -> Self {
192        Self { state, element }
193    }
194}
195
196impl<Message, Theme, Renderer> Overlay<Message, Theme, Renderer>
197    for ToasterOverlay<'_, '_, Message, Theme, Renderer>
198where
199    Renderer: renderer::Renderer,
200{
201    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
202        let limits = Limits::new(Size::ZERO, bounds);
203
204        let node = self
205            .element
206            .as_widget_mut()
207            .layout(self.state, renderer, &limits);
208
209        let offset = 15.;
210
211        let position = Point::new(
212            (bounds.width / 2.) - (node.size().width / 2.),
213            bounds.height - (node.size().height + offset),
214        );
215
216        node.move_to(position)
217    }
218
219    fn draw(
220        &self,
221        renderer: &mut Renderer,
222        theme: &Theme,
223        style: &renderer::Style,
224        layout: Layout<'_>,
225        cursor: mouse::Cursor,
226    ) {
227        let bounds = layout.bounds();
228        self.element
229            .as_widget()
230            .draw(self.state, renderer, theme, style, layout, cursor, &bounds);
231    }
232
233    fn update(
234        &mut self,
235        event: &Event,
236        layout: Layout<'_>,
237        cursor: mouse::Cursor,
238        renderer: &Renderer,
239        clipboard: &mut dyn Clipboard,
240        shell: &mut Shell<Message>,
241    ) {
242        self.element.as_widget_mut().update(
243            self.state,
244            event,
245            layout,
246            cursor,
247            renderer,
248            clipboard,
249            shell,
250            &layout.bounds(),
251        );
252    }
253
254    fn mouse_interaction(
255        &self,
256        layout: Layout<'_>,
257        cursor: mouse::Cursor,
258        renderer: &Renderer,
259    ) -> mouse::Interaction {
260        self.element.as_widget().mouse_interaction(
261            self.state,
262            layout,
263            cursor,
264            &layout.bounds(),
265            renderer,
266        )
267    }
268
269    fn overlay<'c>(
270        &'c mut self,
271        layout: Layout<'c>,
272        renderer: &Renderer,
273    ) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
274        self.element.as_widget_mut().overlay(
275            self.state,
276            layout,
277            renderer,
278            &layout.bounds(),
279            Default::default(),
280        )
281    }
282}
283
284impl<'a, Message, Theme, Renderer> From<Toaster<'a, Message, Theme, Renderer>>
285    for Element<'a, Message, Theme, Renderer>
286where
287    Renderer: renderer::Renderer + 'a,
288    Theme: 'a,
289    Message: 'a,
290{
291    fn from(
292        toaster: Toaster<'a, Message, Theme, Renderer>,
293    ) -> Element<'a, Message, Theme, Renderer> {
294        Element::new(toaster)
295    }
296}