iced_widget/pane_grid/
title_bar.rs

1use crate::container;
2use crate::core::layout;
3use crate::core::mouse;
4use crate::core::overlay;
5use crate::core::renderer;
6use crate::core::widget::{self, Tree};
7use crate::core::{
8    self, Clipboard, Element, Event, Layout, Padding, Point, Rectangle, Shell, Size, Vector,
9};
10use crate::pane_grid::controls::Controls;
11
12/// The title bar of a [`Pane`].
13///
14/// [`Pane`]: super::Pane
15pub struct TitleBar<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
16where
17    Theme: container::Catalog,
18    Renderer: core::Renderer,
19{
20    content: Element<'a, Message, Theme, Renderer>,
21    controls: Option<Controls<'a, Message, Theme, Renderer>>,
22    padding: Padding,
23    always_show_controls: bool,
24    class: Theme::Class<'a>,
25}
26
27impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
28where
29    Theme: container::Catalog,
30    Renderer: core::Renderer,
31{
32    /// Creates a new [`TitleBar`] with the given content.
33    pub fn new(content: impl Into<Element<'a, Message, Theme, Renderer>>) -> Self {
34        Self {
35            content: content.into(),
36            controls: None,
37            padding: Padding::ZERO,
38            always_show_controls: false,
39            class: Theme::default(),
40        }
41    }
42
43    /// Sets the controls of the [`TitleBar`].
44    pub fn controls(mut self, controls: impl Into<Controls<'a, Message, Theme, Renderer>>) -> Self {
45        self.controls = Some(controls.into());
46        self
47    }
48
49    /// Sets the [`Padding`] of the [`TitleBar`].
50    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
51        self.padding = padding.into();
52        self
53    }
54
55    /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are
56    /// always visible.
57    ///
58    /// By default, the controls are only visible when the [`Pane`] of this
59    /// [`TitleBar`] is hovered.
60    ///
61    /// [`controls`]: Self::controls
62    /// [`Pane`]: super::Pane
63    pub fn always_show_controls(mut self) -> Self {
64        self.always_show_controls = true;
65        self
66    }
67
68    /// Sets the style of the [`TitleBar`].
69    #[must_use]
70    pub fn style(mut self, style: impl Fn(&Theme) -> container::Style + 'a) -> Self
71    where
72        Theme::Class<'a>: From<container::StyleFn<'a, Theme>>,
73    {
74        self.class = (Box::new(style) as container::StyleFn<'a, Theme>).into();
75        self
76    }
77
78    /// Sets the style class of the [`TitleBar`].
79    #[cfg(feature = "advanced")]
80    #[must_use]
81    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
82        self.class = class.into();
83        self
84    }
85}
86
87impl<Message, Theme, Renderer> TitleBar<'_, Message, Theme, Renderer>
88where
89    Theme: container::Catalog,
90    Renderer: core::Renderer,
91{
92    pub(super) fn state(&self) -> Tree {
93        let children = match self.controls.as_ref() {
94            Some(controls) => match controls.compact.as_ref() {
95                Some(compact) => vec![
96                    Tree::new(&self.content),
97                    Tree::new(&controls.full),
98                    Tree::new(compact),
99                ],
100                None => vec![
101                    Tree::new(&self.content),
102                    Tree::new(&controls.full),
103                    Tree::empty(),
104                ],
105            },
106            None => {
107                vec![Tree::new(&self.content), Tree::empty(), Tree::empty()]
108            }
109        };
110
111        Tree {
112            children,
113            ..Tree::empty()
114        }
115    }
116
117    pub(super) fn diff(&self, tree: &mut Tree) {
118        if tree.children.len() == 3 {
119            if let Some(controls) = self.controls.as_ref() {
120                if let Some(compact) = controls.compact.as_ref() {
121                    tree.children[2].diff(compact);
122                }
123
124                tree.children[1].diff(&controls.full);
125            }
126
127            tree.children[0].diff(&self.content);
128        } else {
129            *tree = self.state();
130        }
131    }
132
133    /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
134    ///
135    /// [`Renderer`]: core::Renderer
136    pub fn draw(
137        &self,
138        tree: &Tree,
139        renderer: &mut Renderer,
140        theme: &Theme,
141        inherited_style: &renderer::Style,
142        layout: Layout<'_>,
143        cursor: mouse::Cursor,
144        viewport: &Rectangle,
145        show_controls: bool,
146    ) {
147        let bounds = layout.bounds();
148        let style = theme.style(&self.class);
149
150        let inherited_style = renderer::Style {
151            text_color: style.text_color.unwrap_or(inherited_style.text_color),
152        };
153
154        container::draw_background(renderer, &style, bounds);
155
156        let mut children = layout.children();
157        let padded = children.next().unwrap();
158
159        let mut children = padded.children();
160        let title_layout = children.next().unwrap();
161        let mut show_title = true;
162
163        if let Some(controls) = &self.controls
164            && (show_controls || self.always_show_controls)
165        {
166            let controls_layout = children.next().unwrap();
167            if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width
168            {
169                if let Some(compact) = controls.compact.as_ref() {
170                    let compact_layout = children.next().unwrap();
171
172                    compact.as_widget().draw(
173                        &tree.children[2],
174                        renderer,
175                        theme,
176                        &inherited_style,
177                        compact_layout,
178                        cursor,
179                        viewport,
180                    );
181                } else {
182                    show_title = false;
183
184                    controls.full.as_widget().draw(
185                        &tree.children[1],
186                        renderer,
187                        theme,
188                        &inherited_style,
189                        controls_layout,
190                        cursor,
191                        viewport,
192                    );
193                }
194            } else {
195                controls.full.as_widget().draw(
196                    &tree.children[1],
197                    renderer,
198                    theme,
199                    &inherited_style,
200                    controls_layout,
201                    cursor,
202                    viewport,
203                );
204            }
205        }
206
207        if show_title {
208            self.content.as_widget().draw(
209                &tree.children[0],
210                renderer,
211                theme,
212                &inherited_style,
213                title_layout,
214                cursor,
215                viewport,
216            );
217        }
218    }
219
220    /// Returns whether the mouse cursor is over the pick area of the
221    /// [`TitleBar`] or not.
222    ///
223    /// The whole [`TitleBar`] is a pick area, except its controls.
224    pub fn is_over_pick_area(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
225        if layout.bounds().contains(cursor_position) {
226            let mut children = layout.children();
227            let padded = children.next().unwrap();
228            let mut children = padded.children();
229            let title_layout = children.next().unwrap();
230
231            if let Some(controls) = self.controls.as_ref() {
232                let controls_layout = children.next().unwrap();
233
234                if title_layout.bounds().width + controls_layout.bounds().width
235                    > padded.bounds().width
236                {
237                    if controls.compact.is_some() {
238                        let compact_layout = children.next().unwrap();
239
240                        !compact_layout.bounds().contains(cursor_position)
241                            && !title_layout.bounds().contains(cursor_position)
242                    } else {
243                        !controls_layout.bounds().contains(cursor_position)
244                    }
245                } else {
246                    !controls_layout.bounds().contains(cursor_position)
247                        && !title_layout.bounds().contains(cursor_position)
248                }
249            } else {
250                !title_layout.bounds().contains(cursor_position)
251            }
252        } else {
253            false
254        }
255    }
256
257    pub(crate) fn layout(
258        &mut self,
259        tree: &mut Tree,
260        renderer: &Renderer,
261        limits: &layout::Limits,
262    ) -> layout::Node {
263        let limits = limits.shrink(self.padding);
264        let max_size = limits.max();
265
266        let title_layout = self.content.as_widget_mut().layout(
267            &mut tree.children[0],
268            renderer,
269            &layout::Limits::new(Size::ZERO, max_size),
270        );
271
272        let title_size = title_layout.size();
273
274        let node = if let Some(controls) = &mut self.controls {
275            let controls_layout = controls.full.as_widget_mut().layout(
276                &mut tree.children[1],
277                renderer,
278                &layout::Limits::new(Size::ZERO, max_size),
279            );
280
281            if title_layout.bounds().width + controls_layout.bounds().width > max_size.width {
282                if let Some(compact) = controls.compact.as_mut() {
283                    let compact_layout = compact.as_widget_mut().layout(
284                        &mut tree.children[2],
285                        renderer,
286                        &layout::Limits::new(Size::ZERO, max_size),
287                    );
288
289                    let compact_size = compact_layout.size();
290                    let space_before_controls = max_size.width - compact_size.width;
291
292                    let height = title_size.height.max(compact_size.height);
293
294                    layout::Node::with_children(
295                        Size::new(max_size.width, height),
296                        vec![
297                            title_layout,
298                            controls_layout,
299                            compact_layout.move_to(Point::new(space_before_controls, 0.0)),
300                        ],
301                    )
302                } else {
303                    let controls_size = controls_layout.size();
304                    let space_before_controls = max_size.width - controls_size.width;
305
306                    let height = title_size.height.max(controls_size.height);
307
308                    layout::Node::with_children(
309                        Size::new(max_size.width, height),
310                        vec![
311                            title_layout,
312                            controls_layout.move_to(Point::new(space_before_controls, 0.0)),
313                        ],
314                    )
315                }
316            } else {
317                let controls_size = controls_layout.size();
318                let space_before_controls = max_size.width - controls_size.width;
319
320                let height = title_size.height.max(controls_size.height);
321
322                layout::Node::with_children(
323                    Size::new(max_size.width, height),
324                    vec![
325                        title_layout,
326                        controls_layout.move_to(Point::new(space_before_controls, 0.0)),
327                    ],
328                )
329            }
330        } else {
331            layout::Node::with_children(
332                Size::new(max_size.width, title_size.height),
333                vec![title_layout],
334            )
335        };
336
337        layout::Node::container(node, self.padding)
338    }
339
340    pub(crate) fn operate(
341        &mut self,
342        tree: &mut Tree,
343        layout: Layout<'_>,
344        renderer: &Renderer,
345        operation: &mut dyn widget::Operation,
346    ) {
347        let mut children = layout.children();
348        let padded = children.next().unwrap();
349
350        let mut children = padded.children();
351        let title_layout = children.next().unwrap();
352        let mut show_title = true;
353
354        if let Some(controls) = &mut self.controls {
355            let controls_layout = children.next().unwrap();
356
357            if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width
358            {
359                if let Some(compact) = controls.compact.as_mut() {
360                    let compact_layout = children.next().unwrap();
361
362                    compact.as_widget_mut().operate(
363                        &mut tree.children[2],
364                        compact_layout,
365                        renderer,
366                        operation,
367                    );
368                } else {
369                    show_title = false;
370
371                    controls.full.as_widget_mut().operate(
372                        &mut tree.children[1],
373                        controls_layout,
374                        renderer,
375                        operation,
376                    );
377                }
378            } else {
379                controls.full.as_widget_mut().operate(
380                    &mut tree.children[1],
381                    controls_layout,
382                    renderer,
383                    operation,
384                );
385            }
386        };
387
388        if show_title {
389            self.content.as_widget_mut().operate(
390                &mut tree.children[0],
391                title_layout,
392                renderer,
393                operation,
394            );
395        }
396    }
397
398    pub(crate) fn update(
399        &mut self,
400        tree: &mut Tree,
401        event: &Event,
402        layout: Layout<'_>,
403        cursor: mouse::Cursor,
404        renderer: &Renderer,
405        clipboard: &mut dyn Clipboard,
406        shell: &mut Shell<'_, Message>,
407        viewport: &Rectangle,
408    ) {
409        let mut children = layout.children();
410        let padded = children.next().unwrap();
411
412        let mut children = padded.children();
413        let title_layout = children.next().unwrap();
414        let mut show_title = true;
415
416        if let Some(controls) = &mut self.controls {
417            let controls_layout = children.next().unwrap();
418
419            if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width
420            {
421                if let Some(compact) = controls.compact.as_mut() {
422                    let compact_layout = children.next().unwrap();
423
424                    compact.as_widget_mut().update(
425                        &mut tree.children[2],
426                        event,
427                        compact_layout,
428                        cursor,
429                        renderer,
430                        clipboard,
431                        shell,
432                        viewport,
433                    );
434                } else {
435                    show_title = false;
436
437                    controls.full.as_widget_mut().update(
438                        &mut tree.children[1],
439                        event,
440                        controls_layout,
441                        cursor,
442                        renderer,
443                        clipboard,
444                        shell,
445                        viewport,
446                    );
447                }
448            } else {
449                controls.full.as_widget_mut().update(
450                    &mut tree.children[1],
451                    event,
452                    controls_layout,
453                    cursor,
454                    renderer,
455                    clipboard,
456                    shell,
457                    viewport,
458                );
459            }
460        }
461
462        if show_title {
463            self.content.as_widget_mut().update(
464                &mut tree.children[0],
465                event,
466                title_layout,
467                cursor,
468                renderer,
469                clipboard,
470                shell,
471                viewport,
472            );
473        }
474    }
475
476    pub(crate) fn mouse_interaction(
477        &self,
478        tree: &Tree,
479        layout: Layout<'_>,
480        cursor: mouse::Cursor,
481        viewport: &Rectangle,
482        renderer: &Renderer,
483    ) -> mouse::Interaction {
484        let mut children = layout.children();
485        let padded = children.next().unwrap();
486
487        let mut children = padded.children();
488        let title_layout = children.next().unwrap();
489
490        let title_interaction = self.content.as_widget().mouse_interaction(
491            &tree.children[0],
492            title_layout,
493            cursor,
494            viewport,
495            renderer,
496        );
497
498        if let Some(controls) = &self.controls {
499            let controls_layout = children.next().unwrap();
500            let controls_interaction = controls.full.as_widget().mouse_interaction(
501                &tree.children[1],
502                controls_layout,
503                cursor,
504                viewport,
505                renderer,
506            );
507
508            if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width
509            {
510                if let Some(compact) = controls.compact.as_ref() {
511                    let compact_layout = children.next().unwrap();
512                    let compact_interaction = compact.as_widget().mouse_interaction(
513                        &tree.children[2],
514                        compact_layout,
515                        cursor,
516                        viewport,
517                        renderer,
518                    );
519
520                    compact_interaction.max(title_interaction)
521                } else {
522                    controls_interaction
523                }
524            } else {
525                controls_interaction.max(title_interaction)
526            }
527        } else {
528            title_interaction
529        }
530    }
531
532    pub(crate) fn overlay<'b>(
533        &'b mut self,
534        tree: &'b mut Tree,
535        layout: Layout<'b>,
536        renderer: &Renderer,
537        viewport: &Rectangle,
538        translation: Vector,
539    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
540        let mut children = layout.children();
541        let padded = children.next()?;
542
543        let mut children = padded.children();
544        let title_layout = children.next()?;
545
546        let Self {
547            content, controls, ..
548        } = self;
549
550        let mut states = tree.children.iter_mut();
551        let title_state = states.next().unwrap();
552        let controls_state = states.next().unwrap();
553
554        content
555            .as_widget_mut()
556            .overlay(title_state, title_layout, renderer, viewport, translation)
557            .or_else(move || {
558                controls.as_mut().and_then(|controls| {
559                    let controls_layout = children.next()?;
560
561                    if title_layout.bounds().width + controls_layout.bounds().width
562                        > padded.bounds().width
563                    {
564                        if let Some(compact) = controls.compact.as_mut() {
565                            let compact_state = states.next().unwrap();
566                            let compact_layout = children.next()?;
567
568                            compact.as_widget_mut().overlay(
569                                compact_state,
570                                compact_layout,
571                                renderer,
572                                viewport,
573                                translation,
574                            )
575                        } else {
576                            controls.full.as_widget_mut().overlay(
577                                controls_state,
578                                controls_layout,
579                                renderer,
580                                viewport,
581                                translation,
582                            )
583                        }
584                    } else {
585                        controls.full.as_widget_mut().overlay(
586                            controls_state,
587                            controls_layout,
588                            renderer,
589                            viewport,
590                            translation,
591                        )
592                    }
593                })
594            })
595    }
596}