use crate::container; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{self, Tree}; use crate::core::{ Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, Vector, }; /// The title bar of a [`Pane`]. /// /// [`Pane`]: super::Pane #[allow(missing_debug_implementations)] pub struct TitleBar< 'a, Message, Theme = crate::Theme, Renderer = crate::Renderer, > where Renderer: crate::core::Renderer, { content: Element<'a, Message, Theme, Renderer>, controls: Option>, padding: Padding, always_show_controls: bool, style: container::Style<'a, Theme>, } impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { /// Creates a new [`TitleBar`] with the given content. pub fn new( content: impl Into>, ) -> Self where Theme: container::DefaultStyle + 'a, { Self { content: content.into(), controls: None, padding: Padding::ZERO, always_show_controls: false, style: Box::new(Theme::default_style), } } /// Sets the controls of the [`TitleBar`]. pub fn controls( mut self, controls: impl Into>, ) -> Self { self.controls = Some(controls.into()); self } /// Sets the [`Padding`] of the [`TitleBar`]. pub fn padding>(mut self, padding: P) -> Self { self.padding = padding.into(); self } /// Sets the style of the [`TitleBar`]. pub fn style( mut self, style: impl Fn(&Theme, container::Status) -> container::Appearance + 'a, ) -> Self { self.style = Box::new(style); self } /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are /// always visible. /// /// By default, the controls are only visible when the [`Pane`] of this /// [`TitleBar`] is hovered. /// /// [`controls`]: Self::controls /// [`Pane`]: super::Pane pub fn always_show_controls(mut self) -> Self { self.always_show_controls = true; self } } impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, { pub(super) fn state(&self) -> Tree { let children = if let Some(controls) = self.controls.as_ref() { vec![Tree::new(&self.content), Tree::new(controls)] } else { vec![Tree::new(&self.content), Tree::empty()] }; Tree { children, ..Tree::empty() } } pub(super) fn diff(&self, tree: &mut Tree) { if tree.children.len() == 2 { if let Some(controls) = self.controls.as_ref() { tree.children[1].diff(controls); } tree.children[0].diff(&self.content); } else { *tree = self.state(); } } /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. /// /// [`Renderer`]: crate::core::Renderer pub fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, inherited_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, show_controls: bool, ) { let bounds = layout.bounds(); let style = { let status = if cursor.is_over(bounds) { container::Status::Hovered } else { container::Status::Idle }; (self.style)(theme, status) }; let inherited_style = renderer::Style { text_color: style.text_color.unwrap_or(inherited_style.text_color), }; container::draw_background(renderer, &style, bounds); let mut children = layout.children(); let padded = children.next().unwrap(); let mut children = padded.children(); let title_layout = children.next().unwrap(); let mut show_title = true; if let Some(controls) = &self.controls { if show_controls || self.always_show_controls { let controls_layout = children.next().unwrap(); if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width { show_title = false; } controls.as_widget().draw( &tree.children[1], renderer, theme, &inherited_style, controls_layout, cursor, viewport, ); } } if show_title { self.content.as_widget().draw( &tree.children[0], renderer, theme, &inherited_style, title_layout, cursor, viewport, ); } } /// Returns whether the mouse cursor is over the pick area of the /// [`TitleBar`] or not. /// /// The whole [`TitleBar`] is a pick area, except its controls. pub fn is_over_pick_area( &self, layout: Layout<'_>, cursor_position: Point, ) -> bool { if layout.bounds().contains(cursor_position) { let mut children = layout.children(); let padded = children.next().unwrap(); let mut children = padded.children(); let title_layout = children.next().unwrap(); if self.controls.is_some() { let controls_layout = children.next().unwrap(); if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width { !controls_layout.bounds().contains(cursor_position) } else { !controls_layout.bounds().contains(cursor_position) && !title_layout.bounds().contains(cursor_position) } } else { !title_layout.bounds().contains(cursor_position) } } else { false } } pub(crate) fn layout( &self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let limits = limits.shrink(self.padding); let max_size = limits.max(); let title_layout = self.content.as_widget().layout( &mut tree.children[0], renderer, &layout::Limits::new(Size::ZERO, max_size), ); let title_size = title_layout.size(); let node = if let Some(controls) = &self.controls { let controls_layout = controls.as_widget().layout( &mut tree.children[1], renderer, &layout::Limits::new(Size::ZERO, max_size), ); let controls_size = controls_layout.size(); let space_before_controls = max_size.width - controls_size.width; let height = title_size.height.max(controls_size.height); layout::Node::with_children( Size::new(max_size.width, height), vec![ title_layout, controls_layout .move_to(Point::new(space_before_controls, 0.0)), ], ) } else { layout::Node::with_children( Size::new(max_size.width, title_size.height), vec![title_layout], ) }; layout::Node::container(node, self.padding) } pub(crate) fn operate( &self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { let mut children = layout.children(); let padded = children.next().unwrap(); let mut children = padded.children(); let title_layout = children.next().unwrap(); let mut show_title = true; if let Some(controls) = &self.controls { let controls_layout = children.next().unwrap(); if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width { show_title = false; } controls.as_widget().operate( &mut tree.children[1], controls_layout, renderer, operation, ); }; if show_title { self.content.as_widget().operate( &mut tree.children[0], title_layout, renderer, operation, ); } } pub(crate) fn on_event( &mut self, tree: &mut Tree, event: Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { let mut children = layout.children(); let padded = children.next().unwrap(); let mut children = padded.children(); let title_layout = children.next().unwrap(); let mut show_title = true; let control_status = if let Some(controls) = &mut self.controls { let controls_layout = children.next().unwrap(); if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width { show_title = false; } controls.as_widget_mut().on_event( &mut tree.children[1], event.clone(), controls_layout, cursor, renderer, clipboard, shell, viewport, ) } else { event::Status::Ignored }; let title_status = if show_title { self.content.as_widget_mut().on_event( &mut tree.children[0], event, title_layout, cursor, renderer, clipboard, shell, viewport, ) } else { event::Status::Ignored }; control_status.merge(title_status) } pub(crate) fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { let mut children = layout.children(); let padded = children.next().unwrap(); let mut children = padded.children(); let title_layout = children.next().unwrap(); let title_interaction = self.content.as_widget().mouse_interaction( &tree.children[0], title_layout, cursor, viewport, renderer, ); if let Some(controls) = &self.controls { let controls_layout = children.next().unwrap(); let controls_interaction = controls.as_widget().mouse_interaction( &tree.children[1], controls_layout, cursor, viewport, renderer, ); if title_layout.bounds().width + controls_layout.bounds().width > padded.bounds().width { controls_interaction } else { controls_interaction.max(title_interaction) } } else { title_interaction } } pub(crate) fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, translation: Vector, ) -> Option> { let mut children = layout.children(); let padded = children.next()?; let mut children = padded.children(); let title_layout = children.next()?; let Self { content, controls, .. } = self; let mut states = tree.children.iter_mut(); let title_state = states.next().unwrap(); let controls_state = states.next().unwrap(); content .as_widget_mut() .overlay(title_state, title_layout, renderer, translation) .or_else(move || { controls.as_mut().and_then(|controls| { let controls_layout = children.next()?; controls.as_widget_mut().overlay( controls_state, controls_layout, renderer, translation, ) }) }) } }