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