1use std::borrow::Cow;
5
6use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text};
7use crate::{Apply, Element, Renderer, Theme};
8
9use super::overlay::Overlay;
10
11use iced_core::Alignment;
12use iced_core::event::{self, Event};
13use iced_core::widget::{Operation, Tree};
14use iced_core::{
15 Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse,
16 overlay as iced_overlay, renderer,
17};
18
19#[must_use]
20pub struct ContextDrawer<'a, Message> {
21 id: Option<iced_core::widget::Id>,
22 content: Element<'a, Message>,
23 drawer: Element<'a, Message>,
24 on_close: Option<Message>,
25}
26
27impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
28 pub fn new_inner<Drawer>(
29 title: Option<Cow<'a, str>>,
30 header_actions: Vec<Element<'a, Message>>,
31 header_opt: Option<Element<'a, Message>>,
32 footer_opt: Option<Element<'a, Message>>,
33 drawer: Drawer,
34 on_close: Message,
35 max_width: f32,
36 ) -> Element<'a, Message>
37 where
38 Drawer: Into<Element<'a, Message>>,
39 {
40 #[inline(never)]
41 fn inner<'a, Message: Clone + 'static>(
42 title: Option<Cow<'a, str>>,
43 header_actions: Vec<Element<'a, Message>>,
44 header_opt: Option<Element<'a, Message>>,
45 footer_opt: Option<Element<'a, Message>>,
46 drawer: Element<'a, Message>,
47 on_close: Message,
48 max_width: f32,
49 ) -> Element<'a, Message> {
50 let cosmic_theme::Spacing {
51 space_xxs,
52 space_s,
53 space_m,
54 space_l,
55 ..
56 } = crate::theme::spacing();
57
58 let horizontal_padding = if max_width < 392.0 { space_s } else { space_l };
59
60 let header_row = row::with_capacity(3)
61 .width(Length::Fixed(480.0))
62 .align_y(Alignment::Center)
63 .push(
64 row::with_children(header_actions)
65 .spacing(space_xxs)
66 .width(Length::FillPortion(1)),
67 )
68 .push_maybe(
69 title.map(|title| text::heading(title).width(Length::FillPortion(1)).center()),
70 )
71 .push(
72 button::text("Close")
73 .trailing_icon(icon::from_name("go-next-symbolic"))
74 .on_press(on_close)
75 .apply(container)
76 .width(Length::FillPortion(1))
77 .align_x(Alignment::End),
78 );
79 let header = column::with_capacity(2)
80 .width(Length::Fixed(480.0))
81 .align_x(Alignment::Center)
82 .spacing(space_m)
83 .padding([space_m, horizontal_padding])
84 .push(header_row)
85 .push_maybe(header_opt);
86 let footer = footer_opt.map(|element| {
87 container(element)
88 .width(Length::Fixed(480.0))
89 .align_y(Alignment::Center)
90 .padding([space_xxs, horizontal_padding])
91 });
92 let pane = column::with_capacity(3)
93 .push(header)
94 .push(
95 scrollable(container(drawer).padding([
96 0,
97 horizontal_padding,
98 if footer.is_some() { 0 } else { space_l },
99 horizontal_padding,
100 ]))
101 .height(Length::Fill)
102 .width(Length::Shrink),
103 )
104 .push_maybe(footer);
105
106 container(
109 LayerContainer::new(pane)
110 .layer(cosmic_theme::Layer::Primary)
111 .class(crate::style::Container::ContextDrawer)
112 .width(Length::Fill)
113 .height(Length::Fill)
114 .max_width(max_width),
115 )
116 .width(Length::Fill)
117 .height(Length::Fill)
118 .align_x(Alignment::End)
119 .into()
120 }
121
122 inner(
123 title,
124 header_actions,
125 header_opt,
126 footer_opt,
127 drawer.into(),
128 on_close,
129 max_width,
130 )
131 }
132
133 pub fn new<Content, Drawer>(
135 title: Option<Cow<'a, str>>,
136 header_actions: Vec<Element<'a, Message>>,
137 header_opt: Option<Element<'a, Message>>,
138 footer_opt: Option<Element<'a, Message>>,
139 content: Content,
140 drawer: Drawer,
141 on_close: Message,
142 max_width: f32,
143 ) -> Self
144 where
145 Content: Into<Element<'a, Message>>,
146 Drawer: Into<Element<'a, Message>>,
147 {
148 let drawer = Self::new_inner(
149 title,
150 header_actions,
151 header_opt,
152 footer_opt,
153 drawer,
154 on_close,
155 max_width,
156 );
157
158 ContextDrawer {
159 id: None,
160 content: content.into(),
161 drawer,
162 on_close: None,
163 }
164 }
165
166 pub fn id(mut self, id: iced_core::widget::Id) -> Self {
168 self.id = Some(id);
169 self
170 }
171
172 #[inline]
174 pub fn on_close_maybe(mut self, message: Option<Message>) -> Self {
175 self.on_close = message;
176 self
177 }
178}
179
180impl<Message: Clone> Widget<Message, crate::Theme, Renderer> for ContextDrawer<'_, Message> {
181 fn children(&self) -> Vec<Tree> {
182 vec![Tree::new(&self.content), Tree::new(&self.drawer)]
183 }
184
185 fn diff(&mut self, tree: &mut Tree) {
186 tree.diff_children(&mut [&mut self.content, &mut self.drawer]);
187 }
188
189 fn size(&self) -> iced_core::Size<Length> {
190 self.content.as_widget().size()
191 }
192
193 fn layout(
194 &self,
195 tree: &mut Tree,
196 renderer: &Renderer,
197 limits: &layout::Limits,
198 ) -> layout::Node {
199 self.content
200 .as_widget()
201 .layout(&mut tree.children[0], renderer, limits)
202 }
203
204 fn operate(
205 &self,
206 tree: &mut Tree,
207 layout: Layout<'_>,
208 renderer: &Renderer,
209 operation: &mut dyn Operation<()>,
210 ) {
211 self.content
212 .as_widget()
213 .operate(&mut tree.children[0], layout, renderer, operation);
214 }
215
216 fn on_event(
217 &mut self,
218 tree: &mut Tree,
219 event: Event,
220 layout: Layout<'_>,
221 cursor: mouse::Cursor,
222 renderer: &Renderer,
223 clipboard: &mut dyn Clipboard,
224 shell: &mut Shell<'_, Message>,
225 viewport: &Rectangle,
226 ) -> event::Status {
227 self.content.as_widget_mut().on_event(
228 &mut tree.children[0],
229 event,
230 layout,
231 cursor,
232 renderer,
233 clipboard,
234 shell,
235 viewport,
236 )
237 }
238
239 fn mouse_interaction(
240 &self,
241 tree: &Tree,
242 layout: Layout<'_>,
243 cursor: mouse::Cursor,
244 viewport: &Rectangle,
245 renderer: &Renderer,
246 ) -> mouse::Interaction {
247 self.content.as_widget().mouse_interaction(
248 &tree.children[0],
249 layout,
250 cursor,
251 viewport,
252 renderer,
253 )
254 }
255
256 fn draw(
257 &self,
258 tree: &Tree,
259 renderer: &mut Renderer,
260 theme: &Theme,
261 renderer_style: &renderer::Style,
262 layout: Layout<'_>,
263 cursor: mouse::Cursor,
264 viewport: &Rectangle,
265 ) {
266 self.content.as_widget().draw(
267 &tree.children[0],
268 renderer,
269 theme,
270 renderer_style,
271 layout,
272 cursor,
273 viewport,
274 );
275 }
276
277 fn overlay<'b>(
278 &'b mut self,
279 tree: &'b mut Tree,
280 layout: Layout<'_>,
281 _renderer: &Renderer,
282 translation: Vector,
283 ) -> Option<iced_overlay::Element<'b, Message, crate::Theme, Renderer>> {
284 let bounds = layout.bounds();
285
286 let mut position = layout.position();
287 position.x += translation.x;
288 position.y += translation.y;
289
290 Some(iced_overlay::Element::new(Box::new(Overlay {
291 content: &mut self.drawer,
292 tree: &mut tree.children[1],
293 width: bounds.width,
294 position,
295 })))
296 }
297
298 #[cfg(feature = "a11y")]
299 fn a11y_nodes(
301 &self,
302 layout: Layout<'_>,
303 state: &Tree,
304 p: mouse::Cursor,
305 ) -> iced_accessibility::A11yTree {
306 let c_state = &state.children[0];
307 self.content.as_widget().a11y_nodes(layout, c_state, p)
308 }
309
310 fn drag_destinations(
311 &self,
312 state: &Tree,
313 layout: Layout<'_>,
314 renderer: &Renderer,
315 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
316 ) {
317 self.content.as_widget().drag_destinations(
318 &state.children[0],
319 layout,
320 renderer,
321 dnd_rectangles,
322 );
323 }
324
325 fn id(&self) -> Option<iced_core::widget::Id> {
326 self.id.clone()
327 }
328
329 fn set_id(&mut self, id: iced_core::widget::Id) {
330 self.id = Some(id);
331 }
332}
333
334impl<'a, Message: 'a + Clone> From<ContextDrawer<'a, Message>> for Element<'a, Message> {
335 fn from(widget: ContextDrawer<'a, Message>) -> Element<'a, Message> {
336 Element::new(widget)
337 }
338}