iced_widget/pane_grid/
title_bar.rs

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