diff options
Diffstat (limited to 'pure/src/widget/pane_grid')
-rw-r--r-- | pure/src/widget/pane_grid/content.rs | 331 | ||||
-rw-r--r-- | pure/src/widget/pane_grid/title_bar.rs | 355 |
2 files changed, 686 insertions, 0 deletions
diff --git a/pure/src/widget/pane_grid/content.rs b/pure/src/widget/pane_grid/content.rs new file mode 100644 index 00000000..a928b28c --- /dev/null +++ b/pure/src/widget/pane_grid/content.rs @@ -0,0 +1,331 @@ +use crate::widget::pane_grid::TitleBar; +use crate::widget::tree::Tree; +use crate::Element; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::container; +use iced_native::widget::pane_grid::Draggable; +use iced_native::{Clipboard, Layout, Point, Rectangle, Shell, Size}; + +/// The content of a [`Pane`]. +/// +/// [`Pane`]: crate::widget::pane_grid::Pane +#[allow(missing_debug_implementations)] +pub struct Content<'a, Message, Renderer> { + title_bar: Option<TitleBar<'a, Message, Renderer>>, + body: Element<'a, Message, Renderer>, + style_sheet: Box<dyn container::StyleSheet + 'a>, +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + /// Creates a new [`Content`] with the provided body. + pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self { + Self { + title_bar: None, + body: body.into(), + style_sheet: Default::default(), + } + } + + /// Sets the [`TitleBar`] of this [`Content`]. + 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 [`Content`]. + pub fn style( + mut self, + style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>, + ) -> Self { + self.style_sheet = style_sheet.into(); + self + } +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + pub fn state(&self) -> Tree { + let children = if let Some(title_bar) = self.title_bar.as_ref() { + vec![Tree::new(&self.body), title_bar.state()] + } else { + vec![Tree::new(&self.body), Tree::empty()] + }; + + Tree { + children, + ..Tree::empty() + } + } + + pub fn diff(&self, tree: &mut Tree) { + if tree.children.len() == 2 { + if let Some(title_bar) = self.title_bar.as_ref() { + title_bar.diff(&mut tree.children[1]); + } + + tree.children[0].diff(&self.body); + } else { + *tree = self.state(); + } + } + + /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. + /// + /// [`Renderer`]: crate::widget::pane_grid::Renderer + pub fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + + { + let style = self.style_sheet.style(); + + container::draw_background(renderer, &style, bounds); + } + + 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(); + + let show_controls = bounds.contains(cursor_position); + + title_bar.draw( + &tree.children[1], + renderer, + style, + title_bar_layout, + cursor_position, + viewport, + show_controls, + ); + + self.body.as_widget().draw( + &tree.children[0], + renderer, + style, + body_layout, + cursor_position, + viewport, + ); + } else { + self.body.as_widget().draw( + &tree.children[0], + renderer, + style, + layout, + cursor_position, + viewport, + ); + } + } + + 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.as_widget().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.as_widget().layout(renderer, limits) + } + } + + pub(crate) fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + is_picked: bool, + ) -> event::Status { + let mut event_status = event::Status::Ignored; + + let body_layout = if let Some(title_bar) = &mut self.title_bar { + let mut children = layout.children(); + + event_status = title_bar.on_event( + &mut tree.children[1], + event.clone(), + children.next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ); + + children.next().unwrap() + } else { + layout + }; + + let body_status = if is_picked { + event::Status::Ignored + } else { + self.body.as_widget_mut().on_event( + &mut tree.children[0], + event, + body_layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }; + + event_status.merge(body_status) + } + + pub(crate) fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let (body_layout, title_bar_interaction) = + if let Some(title_bar) = &self.title_bar { + let mut children = layout.children(); + let title_bar_layout = children.next().unwrap(); + + let is_over_pick_area = title_bar + .is_over_pick_area(title_bar_layout, cursor_position); + + if is_over_pick_area { + return mouse::Interaction::Grab; + } + + let mouse_interaction = title_bar.mouse_interaction( + &tree.children[1], + title_bar_layout, + cursor_position, + viewport, + renderer, + ); + + (children.next().unwrap(), mouse_interaction) + } else { + (layout, mouse::Interaction::default()) + }; + + self.body + .as_widget() + .mouse_interaction( + &tree.children[0], + body_layout, + cursor_position, + viewport, + renderer, + ) + .max(title_bar_interaction) + } + + pub(crate) fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option<overlay::Element<'b, Message, Renderer>> { + if let Some(title_bar) = self.title_bar.as_ref() { + let mut children = layout.children(); + let title_bar_layout = children.next()?; + + let mut states = tree.children.iter_mut(); + let body_state = states.next().unwrap(); + let title_bar_state = states.next().unwrap(); + + match title_bar.overlay(title_bar_state, title_bar_layout, renderer) + { + Some(overlay) => Some(overlay), + None => self.body.as_widget().overlay( + body_state, + children.next()?, + renderer, + ), + } + } else { + self.body.as_widget().overlay( + &mut tree.children[0], + layout, + renderer, + ) + } + } +} + +impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn can_be_dragged_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 + } + } +} + +impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer> +where + T: Into<Element<'a, Message, Renderer>>, + Renderer: iced_native::Renderer, +{ + fn from(element: T) -> Self { + Self::new(element) + } +} diff --git a/pure/src/widget/pane_grid/title_bar.rs b/pure/src/widget/pane_grid/title_bar.rs new file mode 100644 index 00000000..dd68b073 --- /dev/null +++ b/pure/src/widget/pane_grid/title_bar.rs @@ -0,0 +1,355 @@ +use crate::widget::Tree; +use crate::Element; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::container; +use iced_native::{Clipboard, Layout, Padding, Point, Rectangle, Shell, Size}; + +/// The title bar of a [`Pane`]. +/// +/// [`Pane`]: crate::widget::pane_grid::Pane +#[allow(missing_debug_implementations)] +pub struct TitleBar<'a, Message, Renderer> { + content: Element<'a, Message, Renderer>, + controls: Option<Element<'a, Message, Renderer>>, + padding: Padding, + always_show_controls: bool, + style_sheet: Box<dyn container::StyleSheet + 'a>, +} + +impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + /// Creates a new [`TitleBar`] with the given content. + pub fn new<E>(content: E) -> Self + where + E: Into<Element<'a, Message, Renderer>>, + { + Self { + content: content.into(), + controls: None, + padding: Padding::ZERO, + always_show_controls: false, + style_sheet: Default::default(), + } + } + + /// Sets the controls of the [`TitleBar`]. + 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`]. + pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the style of the [`TitleBar`]. + pub fn style( + mut self, + style: impl Into<Box<dyn container::StyleSheet + 'a>>, + ) -> Self { + self.style_sheet = style.into(); + 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`]: crate::widget::pane_grid::Pane + pub fn always_show_controls(mut self) -> Self { + self.always_show_controls = true; + self + } +} + +impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + pub 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 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::widget::pane_grid::Renderer + pub fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + inherited_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + show_controls: bool, + ) { + let bounds = layout.bounds(); + let style = self.style_sheet.style(); + 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(); + + self.content.as_widget().draw( + &tree.children[0], + renderer, + &inherited_style, + title_layout, + cursor_position, + viewport, + ); + + if let Some(controls) = &self.controls { + let controls_layout = children.next().unwrap(); + + if show_controls || self.always_show_controls { + controls.as_widget().draw( + &tree.children[1], + renderer, + &inherited_style, + controls_layout, + cursor_position, + 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(); + + !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, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.pad(self.padding); + let max_size = limits.max(); + + let title_layout = self + .content + .as_widget() + .layout(renderer, &layout::Limits::new(Size::ZERO, max_size)); + + let title_size = title_layout.size(); + + let mut node = if let Some(controls) = &self.controls { + let mut controls_layout = controls + .as_widget() + .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 height = title_size.height.max(controls_size.height); + + 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::with_children( + Size::new(max_size.width, title_size.height), + vec![title_layout], + ) + }; + + node.move_to(Point::new( + self.padding.left.into(), + self.padding.top.into(), + )); + + layout::Node::with_children(node.size().pad(self.padding), vec![node]) + } + + pub(crate) fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let mut children = layout.children(); + let padded = children.next().unwrap(); + + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + + let control_status = if let Some(controls) = &mut self.controls { + let controls_layout = children.next().unwrap(); + + controls.as_widget_mut().on_event( + &mut tree.children[1], + event.clone(), + controls_layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } else { + event::Status::Ignored + }; + + let title_status = self.content.as_widget_mut().on_event( + &mut tree.children[0], + event, + title_layout, + cursor_position, + renderer, + clipboard, + shell, + ); + + control_status.merge(title_status) + } + + pub(crate) fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + 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_position, + viewport, + renderer, + ); + + if let Some(controls) = &self.controls { + let controls_layout = children.next().unwrap(); + + controls + .as_widget() + .mouse_interaction( + &tree.children[1], + controls_layout, + cursor_position, + viewport, + renderer, + ) + .max(title_interaction) + } else { + title_interaction + } + } + + pub(crate) fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option<overlay::Element<'b, Message, Renderer>> { + 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() + .overlay(title_state, title_layout, renderer) + .or_else(move || { + controls.as_ref().and_then(|controls| { + let controls_layout = children.next()?; + + controls.as_widget().overlay( + controls_state, + controls_layout, + renderer, + ) + }) + }) + } +} |