iced_native/widget/pane_grid/
title_bar.rs

1use crate::event::{self, Event};
2use crate::layout;
3use crate::mouse;
4use crate::overlay;
5use crate::renderer;
6use crate::widget::container;
7use crate::widget::{self, Tree};
8use crate::{
9    Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
10};
11
12/// The title bar of a [`Pane`].
13///
14/// [`Pane`]: crate::widget::pane_grid::Pane
15#[allow(missing_debug_implementations)]
16pub struct TitleBar<'a, Message, Renderer>
17where
18    Renderer: crate::Renderer,
19    Renderer::Theme: container::StyleSheet,
20{
21    content: Element<'a, Message, Renderer>,
22    controls: Option<Element<'a, Message, Renderer>>,
23    padding: Padding,
24    always_show_controls: bool,
25    style: <Renderer::Theme as container::StyleSheet>::Style,
26}
27
28impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
29where
30    Renderer: crate::Renderer,
31    Renderer::Theme: container::StyleSheet,
32{
33    /// Creates a new [`TitleBar`] with the given content.
34    pub fn new<E>(content: E) -> Self
35    where
36        E: Into<Element<'a, Message, Renderer>>,
37    {
38        Self {
39            content: content.into(),
40            controls: None,
41            padding: Padding::ZERO,
42            always_show_controls: false,
43            style: Default::default(),
44        }
45    }
46
47    /// Sets the controls of the [`TitleBar`].
48    pub fn controls(
49        mut self,
50        controls: impl Into<Element<'a, Message, Renderer>>,
51    ) -> Self {
52        self.controls = Some(controls.into());
53        self
54    }
55
56    /// Sets the [`Padding`] of the [`TitleBar`].
57    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
58        self.padding = padding.into();
59        self
60    }
61
62    /// Sets the style of the [`TitleBar`].
63    pub fn style(
64        mut self,
65        style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>,
66    ) -> Self {
67        self.style = style.into();
68        self
69    }
70
71    /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are
72    /// always visible.
73    ///
74    /// By default, the controls are only visible when the [`Pane`] of this
75    /// [`TitleBar`] is hovered.
76    ///
77    /// [`controls`]: Self::controls
78    /// [`Pane`]: crate::widget::pane_grid::Pane
79    pub fn always_show_controls(mut self) -> Self {
80        self.always_show_controls = true;
81        self
82    }
83}
84
85impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
86where
87    Renderer: crate::Renderer,
88    Renderer::Theme: container::StyleSheet,
89{
90    pub(super) fn state(&self) -> Tree {
91        let children = if let Some(controls) = self.controls.as_ref() {
92            vec![Tree::new(&self.content), Tree::new(controls)]
93        } else {
94            vec![Tree::new(&self.content), Tree::empty()]
95        };
96
97        Tree {
98            children,
99            ..Tree::empty()
100        }
101    }
102
103    pub(super) fn diff(&self, tree: &mut Tree) {
104        if tree.children.len() == 2 {
105            if let Some(controls) = self.controls.as_ref() {
106                tree.children[1].diff(controls);
107            }
108
109            tree.children[0].diff(&self.content);
110        } else {
111            *tree = self.state();
112        }
113    }
114
115    /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
116    ///
117    /// [`Renderer`]: crate::Renderer
118    pub fn draw(
119        &self,
120        tree: &Tree,
121        renderer: &mut Renderer,
122        theme: &Renderer::Theme,
123        inherited_style: &renderer::Style,
124        layout: Layout<'_>,
125        cursor_position: Point,
126        viewport: &Rectangle,
127        show_controls: bool,
128    ) {
129        use container::StyleSheet;
130
131        let bounds = layout.bounds();
132        let style = theme.appearance(&self.style);
133        let inherited_style = renderer::Style {
134            text_color: style.text_color.unwrap_or(inherited_style.text_color),
135        };
136
137        container::draw_background(renderer, &style, bounds);
138
139        let mut children = layout.children();
140        let padded = children.next().unwrap();
141
142        let mut children = padded.children();
143        let title_layout = children.next().unwrap();
144        let mut show_title = true;
145
146        if let Some(controls) = &self.controls {
147            if show_controls || self.always_show_controls {
148                let controls_layout = children.next().unwrap();
149                if title_layout.bounds().width + controls_layout.bounds().width
150                    > padded.bounds().width
151                {
152                    show_title = false;
153                }
154
155                controls.as_widget().draw(
156                    &tree.children[1],
157                    renderer,
158                    theme,
159                    &inherited_style,
160                    controls_layout,
161                    cursor_position,
162                    viewport,
163                );
164            }
165        }
166
167        if show_title {
168            self.content.as_widget().draw(
169                &tree.children[0],
170                renderer,
171                theme,
172                &inherited_style,
173                title_layout,
174                cursor_position,
175                viewport,
176            );
177        }
178    }
179
180    /// Returns whether the mouse cursor is over the pick area of the
181    /// [`TitleBar`] or not.
182    ///
183    /// The whole [`TitleBar`] is a pick area, except its controls.
184    pub fn is_over_pick_area(
185        &self,
186        layout: Layout<'_>,
187        cursor_position: Point,
188    ) -> bool {
189        if layout.bounds().contains(cursor_position) {
190            let mut children = layout.children();
191            let padded = children.next().unwrap();
192            let mut children = padded.children();
193            let title_layout = children.next().unwrap();
194
195            if self.controls.is_some() {
196                let controls_layout = children.next().unwrap();
197
198                if title_layout.bounds().width + controls_layout.bounds().width
199                    > padded.bounds().width
200                {
201                    !controls_layout.bounds().contains(cursor_position)
202                } else {
203                    !controls_layout.bounds().contains(cursor_position)
204                        && !title_layout.bounds().contains(cursor_position)
205                }
206            } else {
207                !title_layout.bounds().contains(cursor_position)
208            }
209        } else {
210            false
211        }
212    }
213
214    pub(crate) fn layout(
215        &self,
216        renderer: &Renderer,
217        limits: &layout::Limits,
218    ) -> layout::Node {
219        let limits = limits.pad(self.padding);
220        let max_size = limits.max();
221
222        let title_layout = self
223            .content
224            .as_widget()
225            .layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
226
227        let title_size = title_layout.size();
228
229        let mut node = if let Some(controls) = &self.controls {
230            let mut controls_layout = controls
231                .as_widget()
232                .layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
233
234            let controls_size = controls_layout.size();
235            let space_before_controls = max_size.width - controls_size.width;
236
237            let height = title_size.height.max(controls_size.height);
238
239            controls_layout.move_to(Point::new(space_before_controls, 0.0));
240
241            layout::Node::with_children(
242                Size::new(max_size.width, height),
243                vec![title_layout, controls_layout],
244            )
245        } else {
246            layout::Node::with_children(
247                Size::new(max_size.width, title_size.height),
248                vec![title_layout],
249            )
250        };
251
252        node.move_to(Point::new(self.padding.left, self.padding.top));
253
254        layout::Node::with_children(node.size().pad(self.padding), vec![node])
255    }
256
257    pub(crate) fn operate(
258        &self,
259        tree: &mut Tree,
260        layout: Layout<'_>,
261        renderer: &Renderer,
262        operation: &mut dyn widget::Operation<Message>,
263    ) {
264        let mut children = layout.children();
265        let padded = children.next().unwrap();
266
267        let mut children = padded.children();
268        let title_layout = children.next().unwrap();
269        let mut show_title = true;
270
271        if let Some(controls) = &self.controls {
272            let controls_layout = children.next().unwrap();
273
274            if title_layout.bounds().width + controls_layout.bounds().width
275                > padded.bounds().width
276            {
277                show_title = false;
278            }
279
280            controls.as_widget().operate(
281                &mut tree.children[1],
282                controls_layout,
283                renderer,
284                operation,
285            )
286        };
287
288        if show_title {
289            self.content.as_widget().operate(
290                &mut tree.children[0],
291                title_layout,
292                renderer,
293                operation,
294            )
295        }
296    }
297
298    pub(crate) fn on_event(
299        &mut self,
300        tree: &mut Tree,
301        event: Event,
302        layout: Layout<'_>,
303        cursor_position: Point,
304        renderer: &Renderer,
305        clipboard: &mut dyn Clipboard,
306        shell: &mut Shell<'_, Message>,
307    ) -> event::Status {
308        let mut children = layout.children();
309        let padded = children.next().unwrap();
310
311        let mut children = padded.children();
312        let title_layout = children.next().unwrap();
313        let mut show_title = true;
314
315        let control_status = if let Some(controls) = &mut self.controls {
316            let controls_layout = children.next().unwrap();
317            if title_layout.bounds().width + controls_layout.bounds().width
318                > padded.bounds().width
319            {
320                show_title = false;
321            }
322
323            controls.as_widget_mut().on_event(
324                &mut tree.children[1],
325                event.clone(),
326                controls_layout,
327                cursor_position,
328                renderer,
329                clipboard,
330                shell,
331            )
332        } else {
333            event::Status::Ignored
334        };
335
336        let title_status = if show_title {
337            self.content.as_widget_mut().on_event(
338                &mut tree.children[0],
339                event,
340                title_layout,
341                cursor_position,
342                renderer,
343                clipboard,
344                shell,
345            )
346        } else {
347            event::Status::Ignored
348        };
349
350        control_status.merge(title_status)
351    }
352
353    pub(crate) fn mouse_interaction(
354        &self,
355        tree: &Tree,
356        layout: Layout<'_>,
357        cursor_position: Point,
358        viewport: &Rectangle,
359        renderer: &Renderer,
360    ) -> mouse::Interaction {
361        let mut children = layout.children();
362        let padded = children.next().unwrap();
363
364        let mut children = padded.children();
365        let title_layout = children.next().unwrap();
366
367        let title_interaction = self.content.as_widget().mouse_interaction(
368            &tree.children[0],
369            title_layout,
370            cursor_position,
371            viewport,
372            renderer,
373        );
374
375        if let Some(controls) = &self.controls {
376            let controls_layout = children.next().unwrap();
377            let controls_interaction = controls.as_widget().mouse_interaction(
378                &tree.children[1],
379                controls_layout,
380                cursor_position,
381                viewport,
382                renderer,
383            );
384
385            if title_layout.bounds().width + controls_layout.bounds().width
386                > padded.bounds().width
387            {
388                controls_interaction
389            } else {
390                controls_interaction.max(title_interaction)
391            }
392        } else {
393            title_interaction
394        }
395    }
396
397    pub(crate) fn overlay<'b>(
398        &'b mut self,
399        tree: &'b mut Tree,
400        layout: Layout<'_>,
401        renderer: &Renderer,
402    ) -> Option<overlay::Element<'b, Message, Renderer>> {
403        let mut children = layout.children();
404        let padded = children.next()?;
405
406        let mut children = padded.children();
407        let title_layout = children.next()?;
408
409        let Self {
410            content, controls, ..
411        } = self;
412
413        let mut states = tree.children.iter_mut();
414        let title_state = states.next().unwrap();
415        let controls_state = states.next().unwrap();
416
417        content
418            .as_widget_mut()
419            .overlay(title_state, title_layout, renderer)
420            .or_else(move || {
421                controls.as_mut().and_then(|controls| {
422                    let controls_layout = children.next()?;
423
424                    controls.as_widget_mut().overlay(
425                        controls_state,
426                        controls_layout,
427                        renderer,
428                    )
429                })
430            })
431    }
432}