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