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        &self,
49        tree: &mut Tree,
50        renderer: &Renderer,
51        limits: &layout::Limits,
52    ) -> layout::Node {
53        self.content
54            .as_widget()
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 self,
89        state: &'b mut Tree,
90        layout: Layout<'_>,
91        renderer: &Renderer,
92        operation: &mut dyn Operation<()>,
93    ) {
94        self.content
95            .as_widget()
96            .operate(&mut state.children[0], layout, renderer, operation);
97    }
98
99    fn on_event(
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    ) -> event::Status {
110        self.content.as_widget_mut().on_event(
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<'_>,
143        renderer: &Renderer,
144        translation: Vector,
145    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
146        //TODO: this hides the overlay of the content during the toast
147        if self.is_empty {
148            self.content.as_widget_mut().overlay(
149                &mut state.children[0],
150                layout,
151                renderer,
152                translation,
153            )
154        } else {
155            let bounds = layout.bounds();
156
157            Some(overlay::Element::new(Box::new(ToasterOverlay::new(
158                &mut state.children[1],
159                &mut self.toasts,
160            ))))
161        }
162    }
163
164    fn drag_destinations(
165        &self,
166        state: &Tree,
167        layout: Layout<'_>,
168        renderer: &Renderer,
169        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
170    ) {
171        self.content.as_widget().drag_destinations(
172            &state.children[0],
173            layout,
174            renderer,
175            dnd_rectangles,
176        );
177    }
178}
179
180struct ToasterOverlay<'a, 'b, Message, Theme = iced::Theme, Renderer = iced::Renderer> {
181    state: &'b mut Tree,
182    element: &'b mut Element<'a, Message, Theme, Renderer>,
183}
184
185impl<'a, 'b, Message, Theme, Renderer> ToasterOverlay<'a, 'b, Message, Theme, Renderer>
186where
187    Renderer: renderer::Renderer,
188{
189    fn new(state: &'b mut Tree, element: &'b mut Element<'a, Message, Theme, Renderer>) -> Self {
190        Self { state, element }
191    }
192}
193
194impl<Message, Theme, Renderer> Overlay<Message, Theme, Renderer>
195    for ToasterOverlay<'_, '_, Message, Theme, Renderer>
196where
197    Renderer: renderer::Renderer,
198{
199    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node {
200        let limits = Limits::new(Size::ZERO, bounds);
201
202        let mut node = self
203            .element
204            .as_widget()
205            .layout(self.state, renderer, &limits);
206
207        let offset = 15.;
208
209        let position = Point::new(
210            (bounds.width / 2.) - (node.size().width / 2.),
211            bounds.height - (node.size().height + offset),
212        );
213
214        node.move_to_mut(position);
215
216        node
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 on_event(
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    ) -> event::Status {
242        self.element.as_widget_mut().on_event(
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        viewport: &Rectangle,
259        renderer: &Renderer,
260    ) -> mouse::Interaction {
261        self.element
262            .as_widget()
263            .mouse_interaction(self.state, layout, cursor, viewport, renderer)
264    }
265
266    fn overlay<'c>(
267        &'c mut self,
268        layout: Layout<'_>,
269        renderer: &Renderer,
270    ) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
271        self.element
272            .as_widget_mut()
273            .overlay(self.state, layout, renderer, Default::default())
274    }
275}
276
277impl<'a, Message, Theme, Renderer> From<Toaster<'a, Message, Theme, Renderer>>
278    for Element<'a, Message, Theme, Renderer>
279where
280    Renderer: renderer::Renderer + 'a,
281    Theme: 'a,
282    Message: 'a,
283{
284    fn from(
285        toaster: Toaster<'a, Message, Theme, Renderer>,
286    ) -> Element<'a, Message, Theme, Renderer> {
287        Element::new(toaster)
288    }
289}