diff options
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, + ); + } + } +} |