diff options
| author | 2022-03-10 19:25:57 +0700 | |
|---|---|---|
| committer | 2022-03-14 17:44:25 +0700 | |
| commit | 6dd187ff0822230f084e43636b1aabeb1baf06f6 (patch) | |
| tree | 27426980b2882025a3c812ba2401d3bf8f6dca8d /pure/src | |
| parent | 9f27969d14232355ad628431fb67aa07e42e768f (diff) | |
| download | iced-6dd187ff0822230f084e43636b1aabeb1baf06f6.tar.gz iced-6dd187ff0822230f084e43636b1aabeb1baf06f6.tar.bz2 iced-6dd187ff0822230f084e43636b1aabeb1baf06f6.zip | |
Implement `pure` version of `PaneGrid` widget
Diffstat (limited to '')
| -rw-r--r-- | pure/src/widget.rs | 2 | ||||
| -rw-r--r-- | pure/src/widget/pane_grid.rs | 399 | ||||
| -rw-r--r-- | pure/src/widget/pane_grid/content.rs | 331 | ||||
| -rw-r--r-- | pure/src/widget/pane_grid/title_bar.rs | 355 | ||||
| -rw-r--r-- | pure/src/widget/tree.rs | 17 | 
5 files changed, 1102 insertions, 2 deletions
| diff --git a/pure/src/widget.rs b/pure/src/widget.rs index 8f2cf920..564f0583 100644 --- a/pure/src/widget.rs +++ b/pure/src/widget.rs @@ -1,4 +1,5 @@  pub mod image; +pub mod pane_grid;  pub mod progress_bar;  pub mod rule;  pub mod tree; @@ -24,6 +25,7 @@ pub use column::Column;  pub use container::Container;  pub use element::Element;  pub use image::Image; +pub use pane_grid::PaneGrid;  pub use pick_list::PickList;  pub use progress_bar::ProgressBar;  pub use radio::Radio; diff --git a/pure/src/widget/pane_grid.rs b/pure/src/widget/pane_grid.rs new file mode 100644 index 00000000..717c9ceb --- /dev/null +++ b/pure/src/widget/pane_grid.rs @@ -0,0 +1,399 @@ +//! Let your users split regions of your application and organize layout dynamically. +//! +//! [](https://gfycat.com/mixedflatjellyfish) +//! +//! # Example +//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, +//! drag and drop, and hotkey support. +//! +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.3/examples/pane_grid +mod content; +mod title_bar; + +pub use content::Content; +pub use title_bar::TitleBar; + +pub use iced_native::widget::pane_grid::{ +    Axis, Configuration, Direction, DragEvent, Node, Pane, ResizeEvent, Split, +    State, +}; + +use crate::overlay; +use crate::widget::tree::{self, Tree}; +use crate::{Element, Widget}; + +use iced_native::event::{self, Event}; +use iced_native::layout; +use iced_native::mouse; +use iced_native::renderer; +use iced_native::widget::pane_grid; +use iced_native::widget::pane_grid::state; +use iced_native::{Clipboard, Layout, Length, Point, Rectangle, Shell}; + +pub use iced_style::pane_grid::{Line, StyleSheet}; + +/// A collection of panes distributed using either vertical or horizontal splits +/// to completely fill the space available. +/// +/// [](https://gfycat.com/frailfreshairedaleterrier) +/// +/// This distribution of space is common in tiling window managers (like +/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even +/// [`tmux`](https://github.com/tmux/tmux)). +/// +/// A [`PaneGrid`] supports: +/// +/// * Vertical and horizontal splits +/// * Tracking of the last active pane +/// * Mouse-based resizing +/// * Drag and drop to reorganize panes +/// * Hotkey support +/// * Configurable modifier keys +/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.) +/// +/// ## Example +/// +/// ``` +/// # use iced_pure::widget::{pane_grid, text}; +/// # +/// # type PaneGrid<'a, Message> = +/// #     iced_pure::widget::PaneGrid<'a, Message, iced_native::renderer::Null>; +/// # +/// enum PaneState { +///     SomePane, +///     AnotherKindOfPane, +/// } +/// +/// enum Message { +///     PaneDragged(pane_grid::DragEvent), +///     PaneResized(pane_grid::ResizeEvent), +/// } +/// +/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); +/// +/// let pane_grid = +///     PaneGrid::new(&state, |pane, state| { +///         pane_grid::Content::new(match state { +///             PaneState::SomePane => text("This is some pane"), +///             PaneState::AnotherKindOfPane => text("This is another kind of pane"), +///         }) +///     }) +///     .on_drag(Message::PaneDragged) +///     .on_resize(10, Message::PaneResized); +/// ``` +#[allow(missing_debug_implementations)] +pub struct PaneGrid<'a, Message, Renderer> { +    state: &'a state::Internal, +    elements: Vec<(Pane, Content<'a, Message, Renderer>)>, +    width: Length, +    height: Length, +    spacing: u16, +    on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>, +    on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, +    on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, +    style_sheet: Box<dyn StyleSheet + 'a>, +} + +impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> +where +    Renderer: iced_native::Renderer, +{ +    /// Creates a [`PaneGrid`] with the given [`State`] and view function. +    /// +    /// The view function will be called to display each [`Pane`] present in the +    /// [`State`]. +    pub fn new<T>( +        state: &'a State<T>, +        view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>, +    ) -> Self { +        let elements = { +            state +                .panes +                .iter() +                .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) +                .collect() +        }; + +        Self { +            elements, +            state: &state.internal, +            width: Length::Fill, +            height: Length::Fill, +            spacing: 0, +            on_click: None, +            on_drag: None, +            on_resize: None, +            style_sheet: Default::default(), +        } +    } + +    /// Sets the width of the [`PaneGrid`]. +    pub fn width(mut self, width: Length) -> Self { +        self.width = width; +        self +    } + +    /// Sets the height of the [`PaneGrid`]. +    pub fn height(mut self, height: Length) -> Self { +        self.height = height; +        self +    } + +    /// Sets the spacing _between_ the panes of the [`PaneGrid`]. +    pub fn spacing(mut self, units: u16) -> Self { +        self.spacing = units; +        self +    } + +    /// Sets the message that will be produced when a [`Pane`] of the +    /// [`PaneGrid`] is clicked. +    pub fn on_click<F>(mut self, f: F) -> Self +    where +        F: 'a + Fn(Pane) -> Message, +    { +        self.on_click = Some(Box::new(f)); +        self +    } + +    /// Enables the drag and drop interactions of the [`PaneGrid`], which will +    /// use the provided function to produce messages. +    pub fn on_drag<F>(mut self, f: F) -> Self +    where +        F: 'a + Fn(DragEvent) -> Message, +    { +        self.on_drag = Some(Box::new(f)); +        self +    } + +    /// Enables the resize interactions of the [`PaneGrid`], which will +    /// use the provided function to produce messages. +    /// +    /// The `leeway` describes the amount of space around a split that can be +    /// used to grab it. +    /// +    /// The grabbable area of a split will have a length of `spacing + leeway`, +    /// properly centered. In other words, a length of +    /// `(spacing + leeway) / 2.0` on either side of the split line. +    pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self +    where +        F: 'a + Fn(ResizeEvent) -> Message, +    { +        self.on_resize = Some((leeway, Box::new(f))); +        self +    } + +    /// Sets the style of the [`PaneGrid`]. +    pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { +        self.style_sheet = style.into(); +        self +    } +} + +impl<'a, Message, Renderer> Widget<Message, Renderer> +    for PaneGrid<'a, Message, Renderer> +where +    Renderer: iced_native::Renderer, +{ +    fn tag(&self) -> tree::Tag { +        tree::Tag::of::<state::Action>() +    } + +    fn state(&self) -> tree::State { +        tree::State::new(state::Action::Idle) +    } + +    fn children(&self) -> Vec<Tree> { +        self.elements +            .iter() +            .map(|(_, content)| content.state()) +            .collect() +    } + +    fn diff(&self, tree: &mut Tree) { +        tree.diff_children_custom( +            &self.elements, +            |(_, content), state| content.diff(state), +            |(_, content)| content.state(), +        ) +    } + +    fn width(&self) -> Length { +        self.width +    } + +    fn height(&self) -> Length { +        self.height +    } + +    fn layout( +        &self, +        renderer: &Renderer, +        limits: &layout::Limits, +    ) -> layout::Node { +        pane_grid::layout( +            renderer, +            limits, +            self.state, +            self.width, +            self.height, +            self.spacing, +            self.elements.iter().map(|(pane, content)| (*pane, content)), +            |element, renderer, limits| element.layout(renderer, limits), +        ) +    } + +    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 action = tree.state.downcast_mut::<state::Action>(); + +        let event_status = pane_grid::update( +            action, +            self.state, +            &event, +            layout, +            cursor_position, +            shell, +            self.spacing, +            self.elements.iter().map(|(pane, content)| (*pane, content)), +            &self.on_click, +            &self.on_drag, +            &self.on_resize, +        ); + +        let picked_pane = action.picked_pane().map(|(pane, _)| pane); + +        self.elements +            .iter_mut() +            .zip(&mut tree.children) +            .zip(layout.children()) +            .map(|(((pane, content), tree), layout)| { +                let is_picked = picked_pane == Some(*pane); + +                content.on_event( +                    tree, +                    event.clone(), +                    layout, +                    cursor_position, +                    renderer, +                    clipboard, +                    shell, +                    is_picked, +                ) +            }) +            .fold(event_status, event::Status::merge) +    } + +    fn mouse_interaction( +        &self, +        tree: &Tree, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +        renderer: &Renderer, +    ) -> mouse::Interaction { +        pane_grid::mouse_interaction( +            tree.state.downcast_ref(), +            self.state, +            layout, +            cursor_position, +            self.spacing, +            self.on_resize.as_ref().map(|(leeway, _)| *leeway), +        ) +        .unwrap_or_else(|| { +            self.elements +                .iter() +                .zip(&tree.children) +                .zip(layout.children()) +                .map(|(((_pane, content), tree), layout)| { +                    content.mouse_interaction( +                        tree, +                        layout, +                        cursor_position, +                        viewport, +                        renderer, +                    ) +                }) +                .max() +                .unwrap_or_default() +        }) +    } + +    fn draw( +        &self, +        tree: &Tree, +        renderer: &mut Renderer, +        style: &renderer::Style, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) { +        pane_grid::draw( +            tree.state.downcast_ref(), +            self.state, +            layout, +            cursor_position, +            renderer, +            style, +            viewport, +            self.spacing, +            self.on_resize.as_ref().map(|(leeway, _)| *leeway), +            self.style_sheet.as_ref(), +            self.elements +                .iter() +                .zip(&tree.children) +                .map(|((pane, content), tree)| (*pane, (content, tree))), +            |(content, tree), +             renderer, +             style, +             layout, +             cursor_position, +             rectangle| { +                content.draw( +                    tree, +                    renderer, +                    style, +                    layout, +                    cursor_position, +                    rectangle, +                ); +            }, +        ) +    } + +    fn overlay<'b>( +        &'b self, +        tree: &'b mut Tree, +        layout: Layout<'_>, +        renderer: &Renderer, +    ) -> Option<overlay::Element<'_, Message, Renderer>> { +        self.elements +            .iter() +            .zip(&mut tree.children) +            .zip(layout.children()) +            .filter_map(|(((_, pane), tree), layout)| { +                pane.overlay(tree, layout, renderer) +            }) +            .next() +    } +} + +impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>> +    for Element<'a, Message, Renderer> +where +    Renderer: 'a + iced_native::Renderer, +    Message: 'a, +{ +    fn from( +        pane_grid: PaneGrid<'a, Message, Renderer>, +    ) -> Element<'a, Message, Renderer> { +        Element::new(pane_grid) +    } +} 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, +                    ) +                }) +            }) +    } +} diff --git a/pure/src/widget/tree.rs b/pure/src/widget/tree.rs index 33f5693a..3fcf0922 100644 --- a/pure/src/widget/tree.rs +++ b/pure/src/widget/tree.rs @@ -42,6 +42,19 @@ impl Tree {          &mut self,          new_children: &[Element<'_, Message, Renderer>],      ) { +        self.diff_children_custom( +            new_children, +            |new, child_state| child_state.diff(new), +            Self::new, +        ) +    } + +    pub fn diff_children_custom<T>( +        &mut self, +        new_children: &[T], +        diff: impl Fn(&T, &mut Tree), +        new_state: impl Fn(&T) -> Self, +    ) {          if self.children.len() > new_children.len() {              self.children.truncate(new_children.len());          } @@ -49,12 +62,12 @@ impl Tree {          for (child_state, new) in              self.children.iter_mut().zip(new_children.iter())          { -            child_state.diff(new); +            diff(new, child_state);          }          if self.children.len() < new_children.len() {              self.children.extend( -                new_children[self.children.len()..].iter().map(Self::new), +                new_children[self.children.len()..].iter().map(new_state),              );          }      } | 
