diff options
| author | 2020-07-10 00:35:15 +0200 | |
|---|---|---|
| committer | 2020-07-10 00:35:15 +0200 | |
| commit | 46ce3a1f0004ddc527ba3de1ffe3dac3f41a06c3 (patch) | |
| tree | 66449eec70609f8fc03a5e4e90ce39a7346d02fb /native/src/widget/pane_grid | |
| parent | 5c4f5ae5ecb36703a95cafb2cd58692529c9466d (diff) | |
| parent | 3c5921f30c17bcbe0af74a30b6a256a3fa03515c (diff) | |
| download | iced-46ce3a1f0004ddc527ba3de1ffe3dac3f41a06c3.tar.gz iced-46ce3a1f0004ddc527ba3de1ffe3dac3f41a06c3.tar.bz2 iced-46ce3a1f0004ddc527ba3de1ffe3dac3f41a06c3.zip  | |
Merge pull request #442 from hecrj/feature/pane-grid-titlebar
Title bar support for `PaneGrid`
Diffstat (limited to 'native/src/widget/pane_grid')
| -rw-r--r-- | native/src/widget/pane_grid/configuration.rs | 30 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/content.rs | 219 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/state.rs | 40 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/title_bar.rs | 240 | 
4 files changed, 485 insertions, 44 deletions
diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs new file mode 100644 index 00000000..1fed98b7 --- /dev/null +++ b/native/src/widget/pane_grid/configuration.rs @@ -0,0 +1,30 @@ +use crate::pane_grid::Axis; + +/// The arrangement of a [`PaneGrid`]. +/// +/// [`PaneGrid`]: struct.PaneGrid.html +#[derive(Debug, Clone)] +pub enum Configuration<T> { +    /// A split of the available space. +    Split { +        /// The direction of the split. +        axis: Axis, + +        /// The ratio of the split in [0.0, 1.0]. +        ratio: f32, + +        /// The left/top [`Content`] of the split. +        /// +        /// [`Configuration`]: enum.Node.html +        a: Box<Configuration<T>>, + +        /// The right/bottom [`Content`] of the split. +        /// +        /// [`Configuration`]: enum.Node.html +        b: Box<Configuration<T>>, +    }, +    /// A [`Pane`]. +    /// +    /// [`Pane`]: struct.Pane.html +    Pane(T), +} diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 8822083e..1f5ce640 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,30 +1,197 @@ -use crate::pane_grid::Axis; +use crate::container; +use crate::layout; +use crate::pane_grid::{self, TitleBar}; +use crate::{Clipboard, Element, Event, Hasher, Layout, Point, Size}; -/// The content of a [`PaneGrid`]. +/// The content of a [`Pane`].  /// -/// [`PaneGrid`]: struct.PaneGrid.html -#[derive(Debug, Clone)] -pub enum Content<T> { -    /// A split of the available space. -    Split { -        /// The direction of the split. -        axis: Axis, - -        /// The ratio of the split in [0.0, 1.0]. -        ratio: f32, - -        /// The left/top [`Content`] of the split. -        /// -        /// [`Content`]: enum.Node.html -        a: Box<Content<T>>, - -        /// The right/bottom [`Content`] of the split. -        /// -        /// [`Content`]: enum.Node.html -        b: Box<Content<T>>, -    }, -    /// A [`Pane`]. +/// [`Pane`]: struct.Pane.html +#[allow(missing_debug_implementations)] +pub struct Content<'a, Message, Renderer: pane_grid::Renderer> { +    title_bar: Option<TitleBar<'a, Message, Renderer>>, +    body: Element<'a, Message, Renderer>, +    style: Renderer::Style, +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where +    Renderer: pane_grid::Renderer, +{ +    /// Creates a new [`Content`] with the provided body. +    /// +    /// [`Content`]: struct.Content.html +    pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self { +        Self { +            title_bar: None, +            body: body.into(), +            style: Renderer::Style::default(), +        } +    } + +    /// Sets the [`TitleBar`] of this [`Content`].      /// -    /// [`Pane`]: struct.Pane.html -    Pane(T), +    /// [`TitleBar`]: struct.TitleBar.html +    /// [`Content`]: struct.Content.html +    pub fn title_bar( +        mut self, +        title_bar: TitleBar<'a, Message, Renderer>, +    ) -> Self { +        self.title_bar = Some(title_bar); +        self +    } + +    /// Sets the style of the [`TitleBar`]. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    } +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where +    Renderer: pane_grid::Renderer, +{ +    /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. +    /// +    /// [`Content`]: struct.Content.html +    /// [`Renderer`]: trait.Renderer.html +    /// [`Layout`]: ../layout/struct.Layout.html +    pub fn draw( +        &self, +        renderer: &mut Renderer, +        defaults: &Renderer::Defaults, +        layout: Layout<'_>, +        cursor_position: Point, +    ) -> Renderer::Output { +        if let Some(title_bar) = &self.title_bar { +            let mut children = layout.children(); +            let title_bar_layout = children.next().unwrap(); +            let body_layout = children.next().unwrap(); + +            renderer.draw_pane( +                defaults, +                layout.bounds(), +                &self.style, +                Some((title_bar, title_bar_layout)), +                (&self.body, body_layout), +                cursor_position, +            ) +        } else { +            renderer.draw_pane( +                defaults, +                layout.bounds(), +                &self.style, +                None, +                (&self.body, layout), +                cursor_position, +            ) +        } +    } + +    /// Returns whether the [`Content`] with the given [`Layout`] can be picked +    /// at the provided cursor position. +    /// +    /// [`Content`]: struct.Content.html +    /// [`Layout`]: ../layout/struct.Layout.html +    pub fn can_be_picked_at( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +    ) -> bool { +        if let Some(title_bar) = &self.title_bar { +            let mut children = layout.children(); +            let title_bar_layout = children.next().unwrap(); + +            title_bar.is_over_pick_area(title_bar_layout, cursor_position) +        } else { +            false +        } +    } + +    pub(crate) fn layout( +        &self, +        renderer: &Renderer, +        limits: &layout::Limits, +    ) -> layout::Node { +        if let Some(title_bar) = &self.title_bar { +            let max_size = limits.max(); + +            let title_bar_layout = title_bar +                .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + +            let title_bar_size = title_bar_layout.size(); + +            let mut body_layout = self.body.layout( +                renderer, +                &layout::Limits::new( +                    Size::ZERO, +                    Size::new( +                        max_size.width, +                        max_size.height - title_bar_size.height, +                    ), +                ), +            ); + +            body_layout.move_to(Point::new(0.0, title_bar_size.height)); + +            layout::Node::with_children( +                max_size, +                vec![title_bar_layout, body_layout], +            ) +        } else { +            self.body.layout(renderer, limits) +        } +    } + +    pub(crate) fn on_event( +        &mut self, +        event: Event, +        layout: Layout<'_>, +        cursor_position: Point, +        messages: &mut Vec<Message>, +        renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>, +    ) { +        let body_layout = if let Some(title_bar) = &mut self.title_bar { +            let mut children = layout.children(); + +            title_bar.on_event( +                event.clone(), +                children.next().unwrap(), +                cursor_position, +                messages, +                renderer, +                clipboard, +            ); + +            children.next().unwrap() +        } else { +            layout +        }; + +        self.body.on_event( +            event, +            body_layout, +            cursor_position, +            messages, +            renderer, +            clipboard, +        ); +    } + +    pub(crate) fn hash_layout(&self, state: &mut Hasher) { +        self.body.hash_layout(state); +    } +} + +impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer> +where +    T: Into<Element<'a, Message, Renderer>>, +    Renderer: pane_grid::Renderer + container::Renderer, +{ +    fn from(element: T) -> Self { +        Self::new(element) +    }  } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index a4cfb6f6..e1585968 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,6 +1,5 @@  use crate::{ -    keyboard, -    pane_grid::{Axis, Content, Direction, Node, Pane, Split}, +    pane_grid::{Axis, Configuration, Direction, Node, Pane, Split},      Hasher, Point, Rectangle, Size,  }; @@ -25,7 +24,6 @@ use std::collections::HashMap;  pub struct State<T> {      pub(super) panes: HashMap<Pane, T>,      pub(super) internal: Internal, -    pub(super) modifiers: keyboard::ModifiersState,  }  /// The current focus of a [`Pane`]. @@ -53,18 +51,21 @@ impl<T> State<T> {      /// [`State`]: struct.State.html      /// [`Pane`]: struct.Pane.html      pub fn new(first_pane_state: T) -> (Self, Pane) { -        (Self::with_content(Content::Pane(first_pane_state)), Pane(0)) +        ( +            Self::with_configuration(Configuration::Pane(first_pane_state)), +            Pane(0), +        )      } -    /// Creates a new [`State`] with the given [`Content`]. +    /// Creates a new [`State`] with the given [`Configuration`].      ///      /// [`State`]: struct.State.html -    /// [`Content`]: enum.Content.html -    pub fn with_content(content: impl Into<Content<T>>) -> Self { +    /// [`Configuration`]: enum.Configuration.html +    pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {          let mut panes = HashMap::new();          let (layout, last_id) = -            Self::distribute_content(&mut panes, content.into(), 0); +            Self::distribute_content(&mut panes, config.into(), 0);          State {              panes, @@ -73,7 +74,6 @@ impl<T> State<T> {                  last_id,                  action: Action::Idle { focus: None },              }, -            modifiers: keyboard::ModifiersState::default(),          }      } @@ -283,11 +283,11 @@ impl<T> State<T> {      fn distribute_content(          panes: &mut HashMap<Pane, T>, -        content: Content<T>, +        content: Configuration<T>,          next_id: usize,      ) -> (Node, usize) {          match content { -            Content::Split { axis, ratio, a, b } => { +            Configuration::Split { axis, ratio, a, b } => {                  let (a, next_id) = Self::distribute_content(panes, *a, next_id);                  let (b, next_id) = Self::distribute_content(panes, *b, next_id); @@ -302,7 +302,7 @@ impl<T> State<T> {                      next_id + 1,                  )              } -            Content::Pane(state) => { +            Configuration::Pane(state) => {                  let id = Pane(next_id);                  let _ = panes.insert(id, state); @@ -319,13 +319,14 @@ pub struct Internal {      action: Action,  } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq)]  pub enum Action {      Idle {          focus: Option<Pane>,      },      Dragging {          pane: Pane, +        origin: Point,      },      Resizing {          split: Split, @@ -340,7 +341,7 @@ impl Action {              Action::Idle { focus } | Action::Resizing { focus, .. } => {                  focus.map(|pane| (pane, Focus::Idle))              } -            Action::Dragging { pane } => Some((*pane, Focus::Dragging)), +            Action::Dragging { pane, .. } => Some((*pane, Focus::Dragging)),          }      }  } @@ -357,9 +358,9 @@ impl Internal {          }      } -    pub fn picked_pane(&self) -> Option<Pane> { +    pub fn picked_pane(&self) -> Option<(Pane, Point)> {          match self.action { -            Action::Dragging { pane } => Some(pane), +            Action::Dragging { pane, origin } => Some((pane, origin)),              _ => None,          }      } @@ -391,8 +392,11 @@ impl Internal {          self.action = Action::Idle { focus: Some(*pane) };      } -    pub fn pick_pane(&mut self, pane: &Pane) { -        self.action = Action::Dragging { pane: *pane }; +    pub fn pick_pane(&mut self, pane: &Pane, origin: Point) { +        self.action = Action::Dragging { +            pane: *pane, +            origin, +        };      }      pub fn pick_split(&mut self, split: &Split, axis: Axis) { diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs new file mode 100644 index 00000000..c74c9e20 --- /dev/null +++ b/native/src/widget/pane_grid/title_bar.rs @@ -0,0 +1,240 @@ +use crate::layout; +use crate::pane_grid; +use crate::{Clipboard, Element, Event, Layout, Point, Rectangle, Size}; + +/// The title bar of a [`Pane`]. +/// +/// [`Pane`]: struct.Pane.html +#[allow(missing_debug_implementations)] +pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> { +    title: String, +    title_size: Option<u16>, +    controls: Option<Element<'a, Message, Renderer>>, +    padding: u16, +    style: Renderer::Style, +} + +impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> +where +    Renderer: pane_grid::Renderer, +{ +    /// Cretes a new [`TitleBar`] with the given title. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    pub fn new(title: impl Into<String>) -> Self { +        Self { +            title: title.into(), +            title_size: None, +            controls: None, +            padding: 0, +            style: Renderer::Style::default(), +        } +    } + +    /// Sets the size of the title of the [`TitleBar`]. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    pub fn title_size(mut self, size: u16) -> Self { +        self.title_size = Some(size); +        self +    } + +    /// Sets the controls of the [`TitleBar`]. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    pub fn controls( +        mut self, +        controls: impl Into<Element<'a, Message, Renderer>>, +    ) -> Self { +        self.controls = Some(controls.into()); +        self +    } + +    /// Sets the padding of the [`TitleBar`]. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    pub fn padding(mut self, units: u16) -> Self { +        self.padding = units; +        self +    } + +    /// Sets the style of the [`TitleBar`]. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    } +} + +impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> +where +    Renderer: pane_grid::Renderer, +{ +    /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    /// [`Renderer`]: trait.Renderer.html +    /// [`Layout`]: ../layout/struct.Layout.html +    pub fn draw( +        &self, +        renderer: &mut Renderer, +        defaults: &Renderer::Defaults, +        layout: Layout<'_>, +        cursor_position: Point, +        show_controls: bool, +    ) -> Renderer::Output { +        let mut children = layout.children(); +        let padded = children.next().unwrap(); + +        if let Some(controls) = &self.controls { +            let mut children = padded.children(); +            let title_layout = children.next().unwrap(); +            let controls_layout = children.next().unwrap(); + +            let (title_bounds, controls) = if show_controls { +                (title_layout.bounds(), Some((controls, controls_layout))) +            } else { +                ( +                    Rectangle { +                        width: padded.bounds().width, +                        ..title_layout.bounds() +                    }, +                    None, +                ) +            }; + +            renderer.draw_title_bar( +                defaults, +                layout.bounds(), +                &self.style, +                &self.title, +                self.title_size.unwrap_or(renderer.default_size()), +                Renderer::Font::default(), +                title_bounds, +                controls, +                cursor_position, +            ) +        } else { +            renderer.draw_title_bar::<()>( +                defaults, +                layout.bounds(), +                &self.style, +                &self.title, +                self.title_size.unwrap_or(renderer.default_size()), +                Renderer::Font::default(), +                padded.bounds(), +                None, +                cursor_position, +            ) +        } +    } + +    /// 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. +    /// +    /// [`TitleBar`]: struct.TitleBar.html +    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(); + +            if self.controls.is_some() { +                let mut children = padded.children(); +                let _ = children.next().unwrap(); +                let controls_layout = children.next().unwrap(); + +                !controls_layout.bounds().contains(cursor_position) +            } else { +                true +            } +        } else { +            false +        } +    } + +    pub(crate) fn layout( +        &self, +        renderer: &Renderer, +        limits: &layout::Limits, +    ) -> layout::Node { +        let padding = f32::from(self.padding); +        let limits = limits.pad(padding); +        let max_size = limits.max(); + +        let title_size = self.title_size.unwrap_or(renderer.default_size()); +        let title_font = Renderer::Font::default(); + +        let (title_width, title_height) = renderer.measure( +            &self.title, +            title_size, +            title_font, +            Size::new(f32::INFINITY, max_size.height), +        ); + +        let mut node = if let Some(controls) = &self.controls { +            let mut controls_layout = controls +                .layout(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 mut title_layout = layout::Node::new(Size::new( +                title_width.min(space_before_controls), +                title_height, +            )); + +            let title_size = title_layout.size(); +            let height = title_size.height.max(controls_size.height); + +            title_layout +                .move_to(Point::new(0.0, (height - title_size.height) / 2.0)); +            controls_layout.move_to(Point::new(space_before_controls, 0.0)); + +            layout::Node::with_children( +                Size::new(max_size.width, height), +                vec![title_layout, controls_layout], +            ) +        } else { +            layout::Node::new(Size::new(max_size.width, title_height)) +        }; + +        node.move_to(Point::new(padding, padding)); + +        layout::Node::with_children(node.size().pad(padding), vec![node]) +    } + +    pub(crate) fn on_event( +        &mut self, +        event: Event, +        layout: Layout<'_>, +        cursor_position: Point, +        messages: &mut Vec<Message>, +        renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>, +    ) { +        if let Some(controls) = &mut self.controls { +            let mut children = layout.children(); +            let padded = children.next().unwrap(); + +            let mut children = padded.children(); +            let _ = children.next(); +            let controls_layout = children.next().unwrap(); + +            controls.on_event( +                event, +                controls_layout, +                cursor_position, +                messages, +                renderer, +                clipboard, +            ); +        } +    } +}  | 
