diff options
Diffstat (limited to 'native/src/widget')
42 files changed, 0 insertions, 12594 deletions
| diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs deleted file mode 100644 index 3f1b6b6c..00000000 --- a/native/src/widget/action.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::widget::operation::{ -    self, Focusable, Operation, Scrollable, TextInput, -}; -use crate::widget::Id; - -use iced_futures::MaybeSend; - -use std::any::Any; -use std::rc::Rc; - -/// An operation to be performed on the widget tree. -#[allow(missing_debug_implementations)] -pub struct Action<T>(Box<dyn Operation<T>>); - -impl<T> Action<T> { -    /// Creates a new [`Action`] with the given [`Operation`]. -    pub fn new(operation: impl Operation<T> + 'static) -> Self { -        Self(Box::new(operation)) -    } - -    /// Maps the output of an [`Action`] using the given function. -    pub fn map<A>( -        self, -        f: impl Fn(T) -> A + 'static + MaybeSend + Sync, -    ) -> Action<A> -    where -        T: 'static, -        A: 'static, -    { -        Action(Box::new(Map { -            operation: self.0, -            f: Rc::new(f), -        })) -    } - -    /// Consumes the [`Action`] and returns the internal [`Operation`]. -    pub fn into_operation(self) -> Box<dyn Operation<T>> { -        self.0 -    } -} - -#[allow(missing_debug_implementations)] -struct Map<A, B> { -    operation: Box<dyn Operation<A>>, -    f: Rc<dyn Fn(A) -> B>, -} - -impl<A, B> Operation<B> for Map<A, B> -where -    A: 'static, -    B: 'static, -{ -    fn container( -        &mut self, -        id: Option<&Id>, -        operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), -    ) { -        struct MapRef<'a, A> { -            operation: &'a mut dyn Operation<A>, -        } - -        impl<'a, A, B> Operation<B> for MapRef<'a, A> { -            fn container( -                &mut self, -                id: Option<&Id>, -                operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), -            ) { -                let Self { operation, .. } = self; - -                operation.container(id, &mut |operation| { -                    operate_on_children(&mut MapRef { operation }); -                }); -            } - -            fn scrollable( -                &mut self, -                state: &mut dyn Scrollable, -                id: Option<&Id>, -            ) { -                self.operation.scrollable(state, id); -            } - -            fn focusable( -                &mut self, -                state: &mut dyn Focusable, -                id: Option<&Id>, -            ) { -                self.operation.focusable(state, id); -            } - -            fn text_input( -                &mut self, -                state: &mut dyn TextInput, -                id: Option<&Id>, -            ) { -                self.operation.text_input(state, id); -            } - -            fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { -                self.operation.custom(state, id); -            } -        } - -        let Self { operation, .. } = self; - -        MapRef { -            operation: operation.as_mut(), -        } -        .container(id, operate_on_children); -    } - -    fn focusable( -        &mut self, -        state: &mut dyn operation::Focusable, -        id: Option<&Id>, -    ) { -        self.operation.focusable(state, id); -    } - -    fn scrollable( -        &mut self, -        state: &mut dyn operation::Scrollable, -        id: Option<&Id>, -    ) { -        self.operation.scrollable(state, id); -    } - -    fn text_input( -        &mut self, -        state: &mut dyn operation::TextInput, -        id: Option<&Id>, -    ) { -        self.operation.text_input(state, id); -    } - -    fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { -        self.operation.custom(state, id); -    } - -    fn finish(&self) -> operation::Outcome<B> { -        match self.operation.finish() { -            operation::Outcome::None => operation::Outcome::None, -            operation::Outcome::Some(output) => { -                operation::Outcome::Some((self.f)(output)) -            } -            operation::Outcome::Chain(next) => { -                operation::Outcome::Chain(Box::new(Map { -                    operation: next, -                    f: self.f.clone(), -                })) -            } -        } -    } -} diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs deleted file mode 100644 index 39387173..00000000 --- a/native/src/widget/button.rs +++ /dev/null @@ -1,455 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::widget::Operation; -use crate::{ -    Background, Clipboard, Color, Element, Layout, Length, Padding, Point, -    Rectangle, Shell, Vector, Widget, -}; - -pub use iced_style::button::{Appearance, StyleSheet}; - -/// A generic widget that produces a message when pressed. -/// -/// ``` -/// # type Button<'a, Message> = -/// #     iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; -/// # -/// #[derive(Clone)] -/// enum Message { -///     ButtonPressed, -/// } -/// -/// let button = Button::new("Press me!").on_press(Message::ButtonPressed); -/// ``` -/// -/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will -/// be disabled: -/// -/// ``` -/// # type Button<'a, Message> = -/// #     iced_native::widget::Button<'a, Message, iced_native::renderer::Null>; -/// # -/// #[derive(Clone)] -/// enum Message { -///     ButtonPressed, -/// } -/// -/// fn disabled_button<'a>() -> Button<'a, Message> { -///     Button::new("I'm disabled!") -/// } -/// -/// fn enabled_button<'a>() -> Button<'a, Message> { -///     disabled_button().on_press(Message::ButtonPressed) -/// } -/// ``` -#[allow(missing_debug_implementations)] -pub struct Button<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    content: Element<'a, Message, Renderer>, -    on_press: Option<Message>, -    width: Length, -    height: Length, -    padding: Padding, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Button<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// Creates a new [`Button`] with the given content. -    pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self { -        Button { -            content: content.into(), -            on_press: None, -            width: Length::Shrink, -            height: Length::Shrink, -            padding: Padding::new(5.0), -            style: <Renderer::Theme as StyleSheet>::Style::default(), -        } -    } - -    /// Sets the width of the [`Button`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Button`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the [`Padding`] of the [`Button`]. -    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { -        self.padding = padding.into(); -        self -    } - -    /// Sets the message that will be produced when the [`Button`] is pressed. -    /// -    /// Unless `on_press` is called, the [`Button`] will be disabled. -    pub fn on_press(mut self, msg: Message) -> Self { -        self.on_press = Some(msg); -        self -    } - -    /// Sets the style variant of this [`Button`]. -    pub fn style( -        mut self, -        style: <Renderer::Theme as StyleSheet>::Style, -    ) -> Self { -        self.style = style; -        self -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Button<'a, Message, Renderer> -where -    Message: 'a + Clone, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State>() -    } - -    fn state(&self) -> tree::State { -        tree::State::new(State::new()) -    } - -    fn children(&self) -> Vec<Tree> { -        vec![Tree::new(&self.content)] -    } - -    fn diff(&self, tree: &mut Tree) { -        tree.diff_children(std::slice::from_ref(&self.content)) -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        layout( -            renderer, -            limits, -            self.width, -            self.height, -            self.padding, -            |renderer, limits| { -                self.content.as_widget().layout(renderer, limits) -            }, -        ) -    } - -    fn operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn Operation<Message>, -    ) { -        operation.container(None, &mut |operation| { -            self.content.as_widget().operate( -                &mut tree.children[0], -                layout.children().next().unwrap(), -                renderer, -                operation, -            ); -        }); -    } - -    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 { -        if let event::Status::Captured = self.content.as_widget_mut().on_event( -            &mut tree.children[0], -            event.clone(), -            layout.children().next().unwrap(), -            cursor_position, -            renderer, -            clipboard, -            shell, -        ) { -            return event::Status::Captured; -        } - -        update( -            event, -            layout, -            cursor_position, -            shell, -            &self.on_press, -            || tree.state.downcast_mut::<State>(), -        ) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let bounds = layout.bounds(); -        let content_layout = layout.children().next().unwrap(); - -        let styling = draw( -            renderer, -            bounds, -            cursor_position, -            self.on_press.is_some(), -            theme, -            &self.style, -            || tree.state.downcast_ref::<State>(), -        ); - -        self.content.as_widget().draw( -            &tree.children[0], -            renderer, -            theme, -            &renderer::Style { -                text_color: styling.text_color, -            }, -            content_layout, -            cursor_position, -            &bounds, -        ); -    } - -    fn mouse_interaction( -        &self, -        _tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        mouse_interaction(layout, cursor_position, self.on_press.is_some()) -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        self.content.as_widget_mut().overlay( -            &mut tree.children[0], -            layout.children().next().unwrap(), -            renderer, -        ) -    } -} - -impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: Clone + 'a, -    Renderer: crate::Renderer + 'a, -    Renderer::Theme: StyleSheet, -{ -    fn from(button: Button<'a, Message, Renderer>) -> Self { -        Self::new(button) -    } -} - -/// The local state of a [`Button`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { -    is_pressed: bool, -} - -impl State { -    /// Creates a new [`State`]. -    pub fn new() -> State { -        State::default() -    } -} - -/// Processes the given [`Event`] and updates the [`State`] of a [`Button`] -/// accordingly. -pub fn update<'a, Message: Clone>( -    event: Event, -    layout: Layout<'_>, -    cursor_position: Point, -    shell: &mut Shell<'_, Message>, -    on_press: &Option<Message>, -    state: impl FnOnce() -> &'a mut State, -) -> event::Status { -    match event { -        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerPressed { .. }) => { -            if on_press.is_some() { -                let bounds = layout.bounds(); - -                if bounds.contains(cursor_position) { -                    let state = state(); - -                    state.is_pressed = true; - -                    return event::Status::Captured; -                } -            } -        } -        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerLifted { .. }) => { -            if let Some(on_press) = on_press.clone() { -                let state = state(); - -                if state.is_pressed { -                    state.is_pressed = false; - -                    let bounds = layout.bounds(); - -                    if bounds.contains(cursor_position) { -                        shell.publish(on_press); -                    } - -                    return event::Status::Captured; -                } -            } -        } -        Event::Touch(touch::Event::FingerLost { .. }) => { -            let state = state(); - -            state.is_pressed = false; -        } -        _ => {} -    } - -    event::Status::Ignored -} - -/// Draws a [`Button`]. -pub fn draw<'a, Renderer: crate::Renderer>( -    renderer: &mut Renderer, -    bounds: Rectangle, -    cursor_position: Point, -    is_enabled: bool, -    style_sheet: &dyn StyleSheet< -        Style = <Renderer::Theme as StyleSheet>::Style, -    >, -    style: &<Renderer::Theme as StyleSheet>::Style, -    state: impl FnOnce() -> &'a State, -) -> Appearance -where -    Renderer::Theme: StyleSheet, -{ -    let is_mouse_over = bounds.contains(cursor_position); - -    let styling = if !is_enabled { -        style_sheet.disabled(style) -    } else if is_mouse_over { -        let state = state(); - -        if state.is_pressed { -            style_sheet.pressed(style) -        } else { -            style_sheet.hovered(style) -        } -    } else { -        style_sheet.active(style) -    }; - -    if styling.background.is_some() || styling.border_width > 0.0 { -        if styling.shadow_offset != Vector::default() { -            // TODO: Implement proper shadow support -            renderer.fill_quad( -                renderer::Quad { -                    bounds: Rectangle { -                        x: bounds.x + styling.shadow_offset.x, -                        y: bounds.y + styling.shadow_offset.y, -                        ..bounds -                    }, -                    border_radius: styling.border_radius.into(), -                    border_width: 0.0, -                    border_color: Color::TRANSPARENT, -                }, -                Background::Color([0.0, 0.0, 0.0, 0.5].into()), -            ); -        } - -        renderer.fill_quad( -            renderer::Quad { -                bounds, -                border_radius: styling.border_radius.into(), -                border_width: styling.border_width, -                border_color: styling.border_color, -            }, -            styling -                .background -                .unwrap_or(Background::Color(Color::TRANSPARENT)), -        ); -    } - -    styling -} - -/// Computes the layout of a [`Button`]. -pub fn layout<Renderer>( -    renderer: &Renderer, -    limits: &layout::Limits, -    width: Length, -    height: Length, -    padding: Padding, -    layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { -    let limits = limits.width(width).height(height); - -    let mut content = layout_content(renderer, &limits.pad(padding)); -    let padding = padding.fit(content.size(), limits.max()); -    let size = limits.pad(padding).resolve(content.size()).pad(padding); - -    content.move_to(Point::new(padding.left, padding.top)); - -    layout::Node::with_children(size, vec![content]) -} - -/// Returns the [`mouse::Interaction`] of a [`Button`]. -pub fn mouse_interaction( -    layout: Layout<'_>, -    cursor_position: Point, -    is_enabled: bool, -) -> mouse::Interaction { -    let is_mouse_over = layout.bounds().contains(cursor_position); - -    if is_mouse_over && is_enabled { -        mouse::Interaction::Pointer -    } else { -        mouse::Interaction::default() -    } -} diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs deleted file mode 100644 index cd8b9c6b..00000000 --- a/native/src/widget/checkbox.rs +++ /dev/null @@ -1,321 +0,0 @@ -//! Show toggle controls using checkboxes. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ -    Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, -    Shell, Widget, -}; - -pub use iced_style::checkbox::{Appearance, StyleSheet}; - -/// The icon in a [`Checkbox`]. -#[derive(Debug, Clone, PartialEq)] -pub struct Icon<Font> { -    /// Font that will be used to display the `code_point`, -    pub font: Font, -    /// The unicode code point that will be used as the icon. -    pub code_point: char, -    /// Font size of the content. -    pub size: Option<f32>, -} - -/// A box that can be checked. -/// -/// # Example -/// -/// ``` -/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>; -/// # -/// pub enum Message { -///     CheckboxToggled(bool), -/// } -/// -/// let is_checked = true; -/// -/// Checkbox::new("Toggle me!", is_checked, Message::CheckboxToggled); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Checkbox<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    is_checked: bool, -    on_toggle: Box<dyn Fn(bool) -> Message + 'a>, -    label: String, -    width: Length, -    size: f32, -    spacing: f32, -    text_size: Option<f32>, -    font: Option<Renderer::Font>, -    icon: Icon<Renderer::Font>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Checkbox<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    /// The default size of a [`Checkbox`]. -    const DEFAULT_SIZE: f32 = 20.0; - -    /// The default spacing of a [`Checkbox`]. -    const DEFAULT_SPACING: f32 = 15.0; - -    /// Creates a new [`Checkbox`]. -    /// -    /// It expects: -    ///   * a boolean describing whether the [`Checkbox`] is checked or not -    ///   * the label of the [`Checkbox`] -    ///   * a function that will be called when the [`Checkbox`] is toggled. It -    ///     will receive the new state of the [`Checkbox`] and must produce a -    ///     `Message`. -    pub fn new<F>(label: impl Into<String>, is_checked: bool, f: F) -> Self -    where -        F: 'a + Fn(bool) -> Message, -    { -        Checkbox { -            is_checked, -            on_toggle: Box::new(f), -            label: label.into(), -            width: Length::Shrink, -            size: Self::DEFAULT_SIZE, -            spacing: Self::DEFAULT_SPACING, -            text_size: None, -            font: None, -            icon: Icon { -                font: Renderer::ICON_FONT, -                code_point: Renderer::CHECKMARK_ICON, -                size: None, -            }, -            style: Default::default(), -        } -    } - -    /// Sets the size of the [`Checkbox`]. -    pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = size.into().0; -        self -    } - -    /// Sets the width of the [`Checkbox`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the spacing between the [`Checkbox`] and the text. -    pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self { -        self.spacing = spacing.into().0; -        self -    } - -    /// Sets the text size of the [`Checkbox`]. -    pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { -        self.text_size = Some(text_size.into().0); -        self -    } - -    /// Sets the [`Font`] of the text of the [`Checkbox`]. -    /// -    /// [`Font`]: crate::text::Renderer::Font -    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { -        self.font = Some(font.into()); -        self -    } - -    /// Sets the [`Icon`] of the [`Checkbox`]. -    pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self { -        self.icon = icon; -        self -    } - -    /// Sets the style of the [`Checkbox`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Checkbox<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        Row::<(), Renderer>::new() -            .width(self.width) -            .spacing(self.spacing) -            .align_items(Alignment::Center) -            .push(Row::new().width(self.size).height(self.size)) -            .push( -                Text::new(&self.label) -                    .font(self.font.unwrap_or_else(|| renderer.default_font())) -                    .width(self.width) -                    .size( -                        self.text_size -                            .unwrap_or_else(|| renderer.default_size()), -                    ), -            ) -            .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 { -        match event { -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerPressed { .. }) => { -                let mouse_over = layout.bounds().contains(cursor_position); - -                if mouse_over { -                    shell.publish((self.on_toggle)(!self.is_checked)); - -                    return event::Status::Captured; -                } -            } -            _ => {} -        } - -        event::Status::Ignored -    } - -    fn mouse_interaction( -        &self, -        _tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        if layout.bounds().contains(cursor_position) { -            mouse::Interaction::Pointer -        } else { -            mouse::Interaction::default() -        } -    } - -    fn draw( -        &self, -        _tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let bounds = layout.bounds(); -        let is_mouse_over = bounds.contains(cursor_position); - -        let mut children = layout.children(); - -        let custom_style = if is_mouse_over { -            theme.hovered(&self.style, self.is_checked) -        } else { -            theme.active(&self.style, self.is_checked) -        }; - -        { -            let layout = children.next().unwrap(); -            let bounds = layout.bounds(); - -            renderer.fill_quad( -                renderer::Quad { -                    bounds, -                    border_radius: custom_style.border_radius.into(), -                    border_width: custom_style.border_width, -                    border_color: custom_style.border_color, -                }, -                custom_style.background, -            ); - -            let Icon { -                font, -                code_point, -                size, -            } = &self.icon; -            let size = size.unwrap_or(bounds.height * 0.7); - -            if self.is_checked { -                renderer.fill_text(text::Text { -                    content: &code_point.to_string(), -                    font: *font, -                    size, -                    bounds: Rectangle { -                        x: bounds.center_x(), -                        y: bounds.center_y(), -                        ..bounds -                    }, -                    color: custom_style.icon_color, -                    horizontal_alignment: alignment::Horizontal::Center, -                    vertical_alignment: alignment::Vertical::Center, -                }); -            } -        } - -        { -            let label_layout = children.next().unwrap(); - -            widget::text::draw( -                renderer, -                style, -                label_layout, -                &self.label, -                self.text_size, -                self.font, -                widget::text::Appearance { -                    color: custom_style.text_color, -                }, -                alignment::Horizontal::Left, -                alignment::Vertical::Center, -            ); -        } -    } -} - -impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    fn from( -        checkbox: Checkbox<'a, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(checkbox) -    } -} diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs deleted file mode 100644 index ebe579d5..00000000 --- a/native/src/widget/column.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! Distribute content vertically. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ -    Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point, -    Rectangle, Shell, Widget, -}; - -/// A container that distributes its contents vertically. -#[allow(missing_debug_implementations)] -pub struct Column<'a, Message, Renderer> { -    spacing: f32, -    padding: Padding, -    width: Length, -    height: Length, -    max_width: f32, -    align_items: Alignment, -    children: Vec<Element<'a, Message, Renderer>>, -} - -impl<'a, Message, Renderer> Column<'a, Message, Renderer> { -    /// Creates an empty [`Column`]. -    pub fn new() -> Self { -        Self::with_children(Vec::new()) -    } - -    /// Creates a [`Column`] with the given elements. -    pub fn with_children( -        children: Vec<Element<'a, Message, Renderer>>, -    ) -> Self { -        Column { -            spacing: 0.0, -            padding: Padding::ZERO, -            width: Length::Shrink, -            height: Length::Shrink, -            max_width: f32::INFINITY, -            align_items: Alignment::Start, -            children, -        } -    } - -    /// Sets the vertical spacing _between_ elements. -    /// -    /// Custom margins per element do not exist in iced. You should use this -    /// method instead! While less flexible, it helps you keep spacing between -    /// elements consistent. -    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self { -        self.spacing = amount.into().0; -        self -    } - -    /// Sets the [`Padding`] of the [`Column`]. -    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { -        self.padding = padding.into(); -        self -    } - -    /// Sets the width of the [`Column`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Column`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the maximum width of the [`Column`]. -    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self { -        self.max_width = max_width.into().0; -        self -    } - -    /// Sets the horizontal alignment of the contents of the [`Column`] . -    pub fn align_items(mut self, align: Alignment) -> Self { -        self.align_items = align; -        self -    } - -    /// Adds an element to the [`Column`]. -    pub fn push( -        mut self, -        child: impl Into<Element<'a, Message, Renderer>>, -    ) -> Self { -        self.children.push(child.into()); -        self -    } -} - -impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { -    fn default() -> Self { -        Self::new() -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Column<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -{ -    fn children(&self) -> Vec<Tree> { -        self.children.iter().map(Tree::new).collect() -    } - -    fn diff(&self, tree: &mut Tree) { -        tree.diff_children(&self.children); -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits -            .max_width(self.max_width) -            .width(self.width) -            .height(self.height); - -        layout::flex::resolve( -            layout::flex::Axis::Vertical, -            renderer, -            &limits, -            self.padding, -            self.spacing, -            self.align_items, -            &self.children, -        ) -    } - -    fn operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn Operation<Message>, -    ) { -        operation.container(None, &mut |operation| { -            self.children -                .iter() -                .zip(&mut tree.children) -                .zip(layout.children()) -                .for_each(|((child, state), layout)| { -                    child -                        .as_widget() -                        .operate(state, layout, renderer, operation); -                }) -        }); -    } - -    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 { -        self.children -            .iter_mut() -            .zip(&mut tree.children) -            .zip(layout.children()) -            .map(|((child, state), layout)| { -                child.as_widget_mut().on_event( -                    state, -                    event.clone(), -                    layout, -                    cursor_position, -                    renderer, -                    clipboard, -                    shell, -                ) -            }) -            .fold(event::Status::Ignored, event::Status::merge) -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -        renderer: &Renderer, -    ) -> mouse::Interaction { -        self.children -            .iter() -            .zip(&tree.children) -            .zip(layout.children()) -            .map(|((child, state), layout)| { -                child.as_widget().mouse_interaction( -                    state, -                    layout, -                    cursor_position, -                    viewport, -                    renderer, -                ) -            }) -            .max() -            .unwrap_or_default() -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) { -        for ((child, state), layout) in self -            .children -            .iter() -            .zip(&tree.children) -            .zip(layout.children()) -        { -            child.as_widget().draw( -                state, -                renderer, -                theme, -                style, -                layout, -                cursor_position, -                viewport, -            ); -        } -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        overlay::from_children(&mut self.children, tree, layout, renderer) -    } -} - -impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: crate::Renderer + 'a, -{ -    fn from(column: Column<'a, Message, Renderer>) -> Self { -        Self::new(column) -    } -} diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs deleted file mode 100644 index b77bf50d..00000000 --- a/native/src/widget/container.rs +++ /dev/null @@ -1,368 +0,0 @@ -//! Decorate content and apply alignment. -use crate::alignment::{self, Alignment}; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{self, Operation, Tree}; -use crate::{ -    Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, -    Point, Rectangle, Shell, Widget, -}; - -pub use iced_style::container::{Appearance, StyleSheet}; - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    id: Option<Id>, -    padding: Padding, -    width: Length, -    height: Length, -    max_width: f32, -    max_height: f32, -    horizontal_alignment: alignment::Horizontal, -    vertical_alignment: alignment::Vertical, -    style: <Renderer::Theme as StyleSheet>::Style, -    content: Element<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Container<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// Creates an empty [`Container`]. -    pub fn new<T>(content: T) -> Self -    where -        T: Into<Element<'a, Message, Renderer>>, -    { -        Container { -            id: None, -            padding: Padding::ZERO, -            width: Length::Shrink, -            height: Length::Shrink, -            max_width: f32::INFINITY, -            max_height: f32::INFINITY, -            horizontal_alignment: alignment::Horizontal::Left, -            vertical_alignment: alignment::Vertical::Top, -            style: Default::default(), -            content: content.into(), -        } -    } - -    /// Sets the [`Id`] of the [`Container`]. -    pub fn id(mut self, id: Id) -> Self { -        self.id = Some(id); -        self -    } - -    /// Sets the [`Padding`] of the [`Container`]. -    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { -        self.padding = padding.into(); -        self -    } - -    /// Sets the width of the [`Container`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Container`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the maximum width of the [`Container`]. -    pub fn max_width(mut self, max_width: impl Into<Pixels>) -> Self { -        self.max_width = max_width.into().0; -        self -    } - -    /// Sets the maximum height of the [`Container`]. -    pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self { -        self.max_height = max_height.into().0; -        self -    } - -    /// Sets the content alignment for the horizontal axis of the [`Container`]. -    pub fn align_x(mut self, alignment: alignment::Horizontal) -> Self { -        self.horizontal_alignment = alignment; -        self -    } - -    /// Sets the content alignment for the vertical axis of the [`Container`]. -    pub fn align_y(mut self, alignment: alignment::Vertical) -> Self { -        self.vertical_alignment = alignment; -        self -    } - -    /// Centers the contents in the horizontal axis of the [`Container`]. -    pub fn center_x(mut self) -> Self { -        self.horizontal_alignment = alignment::Horizontal::Center; -        self -    } - -    /// Centers the contents in the vertical axis of the [`Container`]. -    pub fn center_y(mut self) -> Self { -        self.vertical_alignment = alignment::Vertical::Center; -        self -    } - -    /// Sets the style of the [`Container`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Container<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn children(&self) -> Vec<Tree> { -        vec![Tree::new(&self.content)] -    } - -    fn diff(&self, tree: &mut Tree) { -        tree.diff_children(std::slice::from_ref(&self.content)) -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        layout( -            renderer, -            limits, -            self.width, -            self.height, -            self.max_width, -            self.max_height, -            self.padding, -            self.horizontal_alignment, -            self.vertical_alignment, -            |renderer, limits| { -                self.content.as_widget().layout(renderer, limits) -            }, -        ) -    } - -    fn operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn Operation<Message>, -    ) { -        operation.container( -            self.id.as_ref().map(|id| &id.0), -            &mut |operation| { -                self.content.as_widget().operate( -                    &mut tree.children[0], -                    layout.children().next().unwrap(), -                    renderer, -                    operation, -                ); -            }, -        ); -    } - -    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 { -        self.content.as_widget_mut().on_event( -            &mut tree.children[0], -            event, -            layout.children().next().unwrap(), -            cursor_position, -            renderer, -            clipboard, -            shell, -        ) -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -        renderer: &Renderer, -    ) -> mouse::Interaction { -        self.content.as_widget().mouse_interaction( -            &tree.children[0], -            layout.children().next().unwrap(), -            cursor_position, -            viewport, -            renderer, -        ) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        renderer_style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) { -        let style = theme.appearance(&self.style); - -        draw_background(renderer, &style, layout.bounds()); - -        self.content.as_widget().draw( -            &tree.children[0], -            renderer, -            theme, -            &renderer::Style { -                text_color: style -                    .text_color -                    .unwrap_or(renderer_style.text_color), -            }, -            layout.children().next().unwrap(), -            cursor_position, -            viewport, -        ); -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        self.content.as_widget_mut().overlay( -            &mut tree.children[0], -            layout.children().next().unwrap(), -            renderer, -        ) -    } -} - -impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn from( -        column: Container<'a, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(column) -    } -} - -/// Computes the layout of a [`Container`]. -pub fn layout<Renderer>( -    renderer: &Renderer, -    limits: &layout::Limits, -    width: Length, -    height: Length, -    max_width: f32, -    max_height: f32, -    padding: Padding, -    horizontal_alignment: alignment::Horizontal, -    vertical_alignment: alignment::Vertical, -    layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { -    let limits = limits -        .loose() -        .max_width(max_width) -        .max_height(max_height) -        .width(width) -        .height(height); - -    let mut content = layout_content(renderer, &limits.pad(padding).loose()); -    let padding = padding.fit(content.size(), limits.max()); -    let size = limits.pad(padding).resolve(content.size()); - -    content.move_to(Point::new(padding.left, padding.top)); -    content.align( -        Alignment::from(horizontal_alignment), -        Alignment::from(vertical_alignment), -        size, -    ); - -    layout::Node::with_children(size.pad(padding), vec![content]) -} - -/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`. -pub fn draw_background<Renderer>( -    renderer: &mut Renderer, -    appearance: &Appearance, -    bounds: Rectangle, -) where -    Renderer: crate::Renderer, -{ -    if appearance.background.is_some() || appearance.border_width > 0.0 { -        renderer.fill_quad( -            renderer::Quad { -                bounds, -                border_radius: appearance.border_radius.into(), -                border_width: appearance.border_width, -                border_color: appearance.border_color, -            }, -            appearance -                .background -                .unwrap_or(Background::Color(Color::TRANSPARENT)), -        ); -    } -} - -/// The identifier of a [`Container`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { -    /// Creates a custom [`Id`]. -    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self { -        Self(widget::Id::new(id)) -    } - -    /// Creates a unique [`Id`]. -    /// -    /// This function produces a different [`Id`] every time it is called. -    pub fn unique() -> Self { -        Self(widget::Id::unique()) -    } -} - -impl From<Id> for widget::Id { -    fn from(id: Id) -> Self { -        id.0 -    } -} diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs deleted file mode 100644 index d13eca75..00000000 --- a/native/src/widget/helpers.rs +++ /dev/null @@ -1,317 +0,0 @@ -//! Helper functions to create pure widgets. -use crate::overlay; -use crate::widget; -use crate::{Element, Length, Pixels}; - -use std::borrow::Cow; -use std::ops::RangeInclusive; - -/// Creates a [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -#[macro_export] -macro_rules! column { -    () => ( -        $crate::widget::Column::new() -    ); -    ($($x:expr),+ $(,)?) => ( -        $crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+]) -    ); -} - -/// Creates a [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -#[macro_export] -macro_rules! row { -    () => ( -        $crate::widget::Row::new() -    ); -    ($($x:expr),+ $(,)?) => ( -        $crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+]) -    ); -} - -/// Creates a new [`Container`] with the provided content. -/// -/// [`Container`]: widget::Container -pub fn container<'a, Message, Renderer>( -    content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Container<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: widget::container::StyleSheet, -{ -    widget::Container::new(content) -} - -/// Creates a new [`Column`] with the given children. -/// -/// [`Column`]: widget::Column -pub fn column<Message, Renderer>( -    children: Vec<Element<'_, Message, Renderer>>, -) -> widget::Column<'_, Message, Renderer> { -    widget::Column::with_children(children) -} - -/// Creates a new [`Row`] with the given children. -/// -/// [`Row`]: widget::Row -pub fn row<Message, Renderer>( -    children: Vec<Element<'_, Message, Renderer>>, -) -> widget::Row<'_, Message, Renderer> { -    widget::Row::with_children(children) -} - -/// Creates a new [`Scrollable`] with the provided content. -/// -/// [`Scrollable`]: widget::Scrollable -pub fn scrollable<'a, Message, Renderer>( -    content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Scrollable<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: widget::scrollable::StyleSheet, -{ -    widget::Scrollable::new(content) -} - -/// Creates a new [`Button`] with the provided content. -/// -/// [`Button`]: widget::Button -pub fn button<'a, Message, Renderer>( -    content: impl Into<Element<'a, Message, Renderer>>, -) -> widget::Button<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: widget::button::StyleSheet, -    <Renderer::Theme as widget::button::StyleSheet>::Style: Default, -{ -    widget::Button::new(content) -} - -/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`]. -/// -/// [`Tooltip`]: widget::Tooltip -/// [`tooltip::Position`]: widget::tooltip::Position -pub fn tooltip<'a, Message, Renderer>( -    content: impl Into<Element<'a, Message, Renderer>>, -    tooltip: impl ToString, -    position: widget::tooltip::Position, -) -> widget::Tooltip<'a, Message, Renderer> -where -    Renderer: crate::text::Renderer, -    Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet, -{ -    widget::Tooltip::new(content, tooltip.to_string(), position) -} - -/// Creates a new [`Text`] widget with the provided content. -/// -/// [`Text`]: widget::Text -pub fn text<'a, Renderer>(text: impl ToString) -> widget::Text<'a, Renderer> -where -    Renderer: crate::text::Renderer, -    Renderer::Theme: widget::text::StyleSheet, -{ -    widget::Text::new(text.to_string()) -} - -/// Creates a new [`Checkbox`]. -/// -/// [`Checkbox`]: widget::Checkbox -pub fn checkbox<'a, Message, Renderer>( -    label: impl Into<String>, -    is_checked: bool, -    f: impl Fn(bool) -> Message + 'a, -) -> widget::Checkbox<'a, Message, Renderer> -where -    Renderer: crate::text::Renderer, -    Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet, -{ -    widget::Checkbox::new(label, is_checked, f) -} - -/// Creates a new [`Radio`]. -/// -/// [`Radio`]: widget::Radio -pub fn radio<Message, Renderer, V>( -    label: impl Into<String>, -    value: V, -    selected: Option<V>, -    on_click: impl FnOnce(V) -> Message, -) -> widget::Radio<Message, Renderer> -where -    Message: Clone, -    Renderer: crate::text::Renderer, -    Renderer::Theme: widget::radio::StyleSheet, -    V: Copy + Eq, -{ -    widget::Radio::new(value, label, selected, on_click) -} - -/// Creates a new [`Toggler`]. -/// -/// [`Toggler`]: widget::Toggler -pub fn toggler<'a, Message, Renderer>( -    label: impl Into<Option<String>>, -    is_checked: bool, -    f: impl Fn(bool) -> Message + 'a, -) -> widget::Toggler<'a, Message, Renderer> -where -    Renderer: crate::text::Renderer, -    Renderer::Theme: widget::toggler::StyleSheet, -{ -    widget::Toggler::new(label, is_checked, f) -} - -/// Creates a new [`TextInput`]. -/// -/// [`TextInput`]: widget::TextInput -pub fn text_input<'a, Message, Renderer>( -    placeholder: &str, -    value: &str, -    on_change: impl Fn(String) -> Message + 'a, -) -> widget::TextInput<'a, Message, Renderer> -where -    Message: Clone, -    Renderer: crate::text::Renderer, -    Renderer::Theme: widget::text_input::StyleSheet, -{ -    widget::TextInput::new(placeholder, value, on_change) -} - -/// Creates a new [`Slider`]. -/// -/// [`Slider`]: widget::Slider -pub fn slider<'a, T, Message, Renderer>( -    range: std::ops::RangeInclusive<T>, -    value: T, -    on_change: impl Fn(T) -> Message + 'a, -) -> widget::Slider<'a, T, Message, Renderer> -where -    T: Copy + From<u8> + std::cmp::PartialOrd, -    Message: Clone, -    Renderer: crate::Renderer, -    Renderer::Theme: widget::slider::StyleSheet, -{ -    widget::Slider::new(range, value, on_change) -} - -/// Creates a new [`VerticalSlider`]. -/// -/// [`VerticalSlider`]: widget::VerticalSlider -pub fn vertical_slider<'a, T, Message, Renderer>( -    range: std::ops::RangeInclusive<T>, -    value: T, -    on_change: impl Fn(T) -> Message + 'a, -) -> widget::VerticalSlider<'a, T, Message, Renderer> -where -    T: Copy + From<u8> + std::cmp::PartialOrd, -    Message: Clone, -    Renderer: crate::Renderer, -    Renderer::Theme: widget::slider::StyleSheet, -{ -    widget::VerticalSlider::new(range, value, on_change) -} - -/// Creates a new [`PickList`]. -/// -/// [`PickList`]: widget::PickList -pub fn pick_list<'a, Message, Renderer, T>( -    options: impl Into<Cow<'a, [T]>>, -    selected: Option<T>, -    on_selected: impl Fn(T) -> Message + 'a, -) -> widget::PickList<'a, T, Message, Renderer> -where -    T: ToString + Eq + 'static, -    [T]: ToOwned<Owned = Vec<T>>, -    Renderer: crate::text::Renderer, -    Renderer::Theme: widget::pick_list::StyleSheet -        + widget::scrollable::StyleSheet -        + overlay::menu::StyleSheet -        + widget::container::StyleSheet, -    <Renderer::Theme as overlay::menu::StyleSheet>::Style: -        From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>, -{ -    widget::PickList::new(options, selected, on_selected) -} - -/// Creates a new [`Image`]. -/// -/// [`Image`]: widget::Image -pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> { -    widget::Image::new(handle.into()) -} - -/// Creates a new horizontal [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn horizontal_space(width: impl Into<Length>) -> widget::Space { -    widget::Space::with_width(width) -} - -/// Creates a new vertical [`Space`] with the given [`Length`]. -/// -/// [`Space`]: widget::Space -pub fn vertical_space(height: impl Into<Length>) -> widget::Space { -    widget::Space::with_height(height) -} - -/// Creates a horizontal [`Rule`] with the given height. -/// -/// [`Rule`]: widget::Rule -pub fn horizontal_rule<Renderer>( -    height: impl Into<Pixels>, -) -> widget::Rule<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: widget::rule::StyleSheet, -{ -    widget::Rule::horizontal(height) -} - -/// Creates a vertical [`Rule`] with the given width. -/// -/// [`Rule`]: widget::Rule -pub fn vertical_rule<Renderer>( -    width: impl Into<Pixels>, -) -> widget::Rule<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: widget::rule::StyleSheet, -{ -    widget::Rule::vertical(width) -} - -/// Creates a new [`ProgressBar`]. -/// -/// It expects: -///   * an inclusive range of possible values, and -///   * the current value of the [`ProgressBar`]. -/// -/// [`ProgressBar`]: widget::ProgressBar -pub fn progress_bar<Renderer>( -    range: RangeInclusive<f32>, -    value: f32, -) -> widget::ProgressBar<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: widget::progress_bar::StyleSheet, -{ -    widget::ProgressBar::new(range, value) -} - -/// Creates a new [`Svg`] widget from the given [`Handle`]. -/// -/// [`Svg`]: widget::Svg -/// [`Handle`]: widget::svg::Handle -pub fn svg<Renderer>( -    handle: impl Into<widget::svg::Handle>, -) -> widget::Svg<Renderer> -where -    Renderer: crate::svg::Renderer, -    Renderer::Theme: widget::svg::StyleSheet, -{ -    widget::Svg::new(handle) -} diff --git a/native/src/widget/id.rs b/native/src/widget/id.rs deleted file mode 100644 index 4b8fedf1..00000000 --- a/native/src/widget/id.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::borrow; -use std::sync::atomic::{self, AtomicUsize}; - -static NEXT_ID: AtomicUsize = AtomicUsize::new(0); - -/// The identifier of a generic widget. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(Internal); - -impl Id { -    /// Creates a custom [`Id`]. -    pub fn new(id: impl Into<borrow::Cow<'static, str>>) -> Self { -        Self(Internal::Custom(id.into())) -    } - -    /// Creates a unique [`Id`]. -    /// -    /// This function produces a different [`Id`] every time it is called. -    pub fn unique() -> Self { -        let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); - -        Self(Internal::Unique(id)) -    } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Internal { -    Unique(usize), -    Custom(borrow::Cow<'static, str>), -} - -#[cfg(test)] -mod tests { -    use super::Id; - -    #[test] -    fn unique_generates_different_ids() { -        let a = Id::unique(); -        let b = Id::unique(); - -        assert_ne!(a, b); -    } -} diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs deleted file mode 100644 index 73257a74..00000000 --- a/native/src/widget/image.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Display images in your user interface. -pub mod viewer; -pub use viewer::Viewer; - -use crate::image; -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ -    ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, -}; - -use std::hash::Hash; - -/// Creates a new [`Viewer`] with the given image `Handle`. -pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> { -    Viewer::new(handle) -} - -/// A frame that displays an image while keeping aspect ratio. -/// -/// # Example -/// -/// ``` -/// # use iced_native::widget::Image; -/// # use iced_native::image; -/// # -/// let image = Image::<image::Handle>::new("resources/ferris.png"); -/// ``` -/// -/// <img src="https://github.com/iced-rs/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300"> -#[derive(Debug)] -pub struct Image<Handle> { -    handle: Handle, -    width: Length, -    height: Length, -    content_fit: ContentFit, -} - -impl<Handle> Image<Handle> { -    /// Creates a new [`Image`] with the given path. -    pub fn new<T: Into<Handle>>(handle: T) -> Self { -        Image { -            handle: handle.into(), -            width: Length::Shrink, -            height: Length::Shrink, -            content_fit: ContentFit::Contain, -        } -    } - -    /// Sets the width of the [`Image`] boundaries. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Image`] boundaries. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the [`ContentFit`] of the [`Image`]. -    /// -    /// Defaults to [`ContentFit::Contain`] -    pub fn content_fit(self, content_fit: ContentFit) -> Self { -        Self { -            content_fit, -            ..self -        } -    } -} - -/// Computes the layout of an [`Image`]. -pub fn layout<Renderer, Handle>( -    renderer: &Renderer, -    limits: &layout::Limits, -    handle: &Handle, -    width: Length, -    height: Length, -    content_fit: ContentFit, -) -> layout::Node -where -    Renderer: image::Renderer<Handle = Handle>, -{ -    // The raw w/h of the underlying image -    let image_size = { -        let Size { width, height } = renderer.dimensions(handle); - -        Size::new(width as f32, height as f32) -    }; - -    // The size to be available to the widget prior to `Shrink`ing -    let raw_size = limits.width(width).height(height).resolve(image_size); - -    // The uncropped size of the image when fit to the bounds above -    let full_size = content_fit.fit(image_size, raw_size); - -    // Shrink the widget to fit the resized image, if requested -    let final_size = Size { -        width: match width { -            Length::Shrink => f32::min(raw_size.width, full_size.width), -            _ => raw_size.width, -        }, -        height: match height { -            Length::Shrink => f32::min(raw_size.height, full_size.height), -            _ => raw_size.height, -        }, -    }; - -    layout::Node::new(final_size) -} - -/// Draws an [`Image`] -pub fn draw<Renderer, Handle>( -    renderer: &mut Renderer, -    layout: Layout<'_>, -    handle: &Handle, -    content_fit: ContentFit, -) where -    Renderer: image::Renderer<Handle = Handle>, -    Handle: Clone + Hash, -{ -    let Size { width, height } = renderer.dimensions(handle); -    let image_size = Size::new(width as f32, height as f32); - -    let bounds = layout.bounds(); -    let adjusted_fit = content_fit.fit(image_size, bounds.size()); - -    let render = |renderer: &mut Renderer| { -        let offset = Vector::new( -            (bounds.width - adjusted_fit.width).max(0.0) / 2.0, -            (bounds.height - adjusted_fit.height).max(0.0) / 2.0, -        ); - -        let drawing_bounds = Rectangle { -            width: adjusted_fit.width, -            height: adjusted_fit.height, -            ..bounds -        }; - -        renderer.draw(handle.clone(), drawing_bounds + offset) -    }; - -    if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height -    { -        renderer.with_layer(bounds, render); -    } else { -        render(renderer) -    } -} - -impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle> -where -    Renderer: image::Renderer<Handle = Handle>, -    Handle: Clone + Hash, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        layout( -            renderer, -            limits, -            &self.handle, -            self.width, -            self.height, -            self.content_fit, -        ) -    } - -    fn draw( -        &self, -        _state: &Tree, -        renderer: &mut Renderer, -        _theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        _cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        draw(renderer, layout, &self.handle, self.content_fit) -    } -} - -impl<'a, Message, Renderer, Handle> From<Image<Handle>> -    for Element<'a, Message, Renderer> -where -    Renderer: image::Renderer<Handle = Handle>, -    Handle: Clone + Hash + 'a, -{ -    fn from(image: Image<Handle>) -> Element<'a, Message, Renderer> { -        Element::new(image) -    } -} diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs deleted file mode 100644 index 1f8d5d7a..00000000 --- a/native/src/widget/image/viewer.rs +++ /dev/null @@ -1,428 +0,0 @@ -//! Zoom and pan on an image. -use crate::event::{self, Event}; -use crate::image; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::widget::tree::{self, Tree}; -use crate::{ -    Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, -    Vector, Widget, -}; - -use std::hash::Hash; - -/// A frame that displays an image with the ability to zoom in/out and pan. -#[allow(missing_debug_implementations)] -pub struct Viewer<Handle> { -    padding: f32, -    width: Length, -    height: Length, -    min_scale: f32, -    max_scale: f32, -    scale_step: f32, -    handle: Handle, -} - -impl<Handle> Viewer<Handle> { -    /// Creates a new [`Viewer`] with the given [`State`]. -    pub fn new(handle: Handle) -> Self { -        Viewer { -            padding: 0.0, -            width: Length::Shrink, -            height: Length::Shrink, -            min_scale: 0.25, -            max_scale: 10.0, -            scale_step: 0.10, -            handle, -        } -    } - -    /// Sets the padding of the [`Viewer`]. -    pub fn padding(mut self, padding: impl Into<Pixels>) -> Self { -        self.padding = padding.into().0; -        self -    } - -    /// Sets the width of the [`Viewer`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Viewer`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the max scale applied to the image of the [`Viewer`]. -    /// -    /// Default is `10.0` -    pub fn max_scale(mut self, max_scale: f32) -> Self { -        self.max_scale = max_scale; -        self -    } - -    /// Sets the min scale applied to the image of the [`Viewer`]. -    /// -    /// Default is `0.25` -    pub fn min_scale(mut self, min_scale: f32) -> Self { -        self.min_scale = min_scale; -        self -    } - -    /// Sets the percentage the image of the [`Viewer`] will be scaled by -    /// when zoomed in / out. -    /// -    /// Default is `0.10` -    pub fn scale_step(mut self, scale_step: f32) -> Self { -        self.scale_step = scale_step; -        self -    } -} - -impl<Message, Renderer, Handle> Widget<Message, Renderer> for Viewer<Handle> -where -    Renderer: image::Renderer<Handle = Handle>, -    Handle: Clone + Hash, -{ -    fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State>() -    } - -    fn state(&self) -> tree::State { -        tree::State::new(State::new()) -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let Size { width, height } = renderer.dimensions(&self.handle); - -        let mut size = limits -            .width(self.width) -            .height(self.height) -            .resolve(Size::new(width as f32, height as f32)); - -        let expansion_size = if height > width { -            self.width -        } else { -            self.height -        }; - -        // Only calculate viewport sizes if the images are constrained to a limited space. -        // If they are Fill|Portion let them expand within their alotted space. -        match expansion_size { -            Length::Shrink | Length::Fixed(_) => { -                let aspect_ratio = width as f32 / height as f32; -                let viewport_aspect_ratio = size.width / size.height; -                if viewport_aspect_ratio > aspect_ratio { -                    size.width = width as f32 * size.height / height as f32; -                } else { -                    size.height = height as f32 * size.width / width as f32; -                } -            } -            Length::Fill | Length::FillPortion(_) => {} -        } - -        layout::Node::new(size) -    } - -    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 bounds = layout.bounds(); -        let is_mouse_over = bounds.contains(cursor_position); - -        match event { -            Event::Mouse(mouse::Event::WheelScrolled { delta }) -                if is_mouse_over => -            { -                match delta { -                    mouse::ScrollDelta::Lines { y, .. } -                    | mouse::ScrollDelta::Pixels { y, .. } => { -                        let state = tree.state.downcast_mut::<State>(); -                        let previous_scale = state.scale; - -                        if y < 0.0 && previous_scale > self.min_scale -                            || y > 0.0 && previous_scale < self.max_scale -                        { -                            state.scale = (if y > 0.0 { -                                state.scale * (1.0 + self.scale_step) -                            } else { -                                state.scale / (1.0 + self.scale_step) -                            }) -                            .clamp(self.min_scale, self.max_scale); - -                            let image_size = image_size( -                                renderer, -                                &self.handle, -                                state, -                                bounds.size(), -                            ); - -                            let factor = state.scale / previous_scale - 1.0; - -                            let cursor_to_center = -                                cursor_position - bounds.center(); - -                            let adjustment = cursor_to_center * factor -                                + state.current_offset * factor; - -                            state.current_offset = Vector::new( -                                if image_size.width > bounds.width { -                                    state.current_offset.x + adjustment.x -                                } else { -                                    0.0 -                                }, -                                if image_size.height > bounds.height { -                                    state.current_offset.y + adjustment.y -                                } else { -                                    0.0 -                                }, -                            ); -                        } -                    } -                } - -                event::Status::Captured -            } -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -                if is_mouse_over => -            { -                let state = tree.state.downcast_mut::<State>(); - -                state.cursor_grabbed_at = Some(cursor_position); -                state.starting_offset = state.current_offset; - -                event::Status::Captured -            } -            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { -                let state = tree.state.downcast_mut::<State>(); - -                if state.cursor_grabbed_at.is_some() { -                    state.cursor_grabbed_at = None; - -                    event::Status::Captured -                } else { -                    event::Status::Ignored -                } -            } -            Event::Mouse(mouse::Event::CursorMoved { position }) => { -                let state = tree.state.downcast_mut::<State>(); - -                if let Some(origin) = state.cursor_grabbed_at { -                    let image_size = image_size( -                        renderer, -                        &self.handle, -                        state, -                        bounds.size(), -                    ); - -                    let hidden_width = (image_size.width - bounds.width / 2.0) -                        .max(0.0) -                        .round(); - -                    let hidden_height = (image_size.height -                        - bounds.height / 2.0) -                        .max(0.0) -                        .round(); - -                    let delta = position - origin; - -                    let x = if bounds.width < image_size.width { -                        (state.starting_offset.x - delta.x) -                            .clamp(-hidden_width, hidden_width) -                    } else { -                        0.0 -                    }; - -                    let y = if bounds.height < image_size.height { -                        (state.starting_offset.y - delta.y) -                            .clamp(-hidden_height, hidden_height) -                    } else { -                        0.0 -                    }; - -                    state.current_offset = Vector::new(x, y); - -                    event::Status::Captured -                } else { -                    event::Status::Ignored -                } -            } -            _ => event::Status::Ignored, -        } -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        let state = tree.state.downcast_ref::<State>(); -        let bounds = layout.bounds(); -        let is_mouse_over = bounds.contains(cursor_position); - -        if state.is_cursor_grabbed() { -            mouse::Interaction::Grabbing -        } else if is_mouse_over { -            mouse::Interaction::Grab -        } else { -            mouse::Interaction::Idle -        } -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        _theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        _cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let state = tree.state.downcast_ref::<State>(); -        let bounds = layout.bounds(); - -        let image_size = -            image_size(renderer, &self.handle, state, bounds.size()); - -        let translation = { -            let image_top_left = Vector::new( -                bounds.width / 2.0 - image_size.width / 2.0, -                bounds.height / 2.0 - image_size.height / 2.0, -            ); - -            image_top_left - state.offset(bounds, image_size) -        }; - -        renderer.with_layer(bounds, |renderer| { -            renderer.with_translation(translation, |renderer| { -                image::Renderer::draw( -                    renderer, -                    self.handle.clone(), -                    Rectangle { -                        x: bounds.x, -                        y: bounds.y, -                        ..Rectangle::with_size(image_size) -                    }, -                ) -            }); -        }); -    } -} - -/// The local state of a [`Viewer`]. -#[derive(Debug, Clone, Copy)] -pub struct State { -    scale: f32, -    starting_offset: Vector, -    current_offset: Vector, -    cursor_grabbed_at: Option<Point>, -} - -impl Default for State { -    fn default() -> Self { -        Self { -            scale: 1.0, -            starting_offset: Vector::default(), -            current_offset: Vector::default(), -            cursor_grabbed_at: None, -        } -    } -} - -impl State { -    /// Creates a new [`State`]. -    pub fn new() -> Self { -        State::default() -    } - -    /// Returns the current offset of the [`State`], given the bounds -    /// of the [`Viewer`] and its image. -    fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector { -        let hidden_width = -            (image_size.width - bounds.width / 2.0).max(0.0).round(); - -        let hidden_height = -            (image_size.height - bounds.height / 2.0).max(0.0).round(); - -        Vector::new( -            self.current_offset.x.clamp(-hidden_width, hidden_width), -            self.current_offset.y.clamp(-hidden_height, hidden_height), -        ) -    } - -    /// Returns if the cursor is currently grabbed by the [`Viewer`]. -    pub fn is_cursor_grabbed(&self) -> bool { -        self.cursor_grabbed_at.is_some() -    } -} - -impl<'a, Message, Renderer, Handle> From<Viewer<Handle>> -    for Element<'a, Message, Renderer> -where -    Renderer: 'a + image::Renderer<Handle = Handle>, -    Message: 'a, -    Handle: Clone + Hash + 'a, -{ -    fn from(viewer: Viewer<Handle>) -> Element<'a, Message, Renderer> { -        Element::new(viewer) -    } -} - -/// Returns the bounds of the underlying image, given the bounds of -/// the [`Viewer`]. Scaling will be applied and original aspect ratio -/// will be respected. -pub fn image_size<Renderer>( -    renderer: &Renderer, -    handle: &<Renderer as image::Renderer>::Handle, -    state: &State, -    bounds: Size, -) -> Size -where -    Renderer: image::Renderer, -{ -    let Size { width, height } = renderer.dimensions(handle); - -    let (width, height) = { -        let dimensions = (width as f32, height as f32); - -        let width_ratio = bounds.width / dimensions.0; -        let height_ratio = bounds.height / dimensions.1; - -        let ratio = width_ratio.min(height_ratio); -        let scale = state.scale; - -        if ratio < 1.0 { -            (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale) -        } else { -            (dimensions.0 * scale, dimensions.1 * scale) -        } -    }; - -    Size::new(width, height) -} diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs deleted file mode 100644 index 53688a21..00000000 --- a/native/src/widget/operation.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Query or update internal widget state. -pub mod focusable; -pub mod scrollable; -pub mod text_input; - -pub use focusable::Focusable; -pub use scrollable::Scrollable; -pub use text_input::TextInput; - -use crate::widget::Id; - -use std::any::Any; -use std::fmt; - -/// A piece of logic that can traverse the widget tree of an application in -/// order to query or update some widget state. -pub trait Operation<T> { -    /// Operates on a widget that contains other widgets. -    /// -    /// The `operate_on_children` function can be called to return control to -    /// the widget tree and keep traversing it. -    fn container( -        &mut self, -        id: Option<&Id>, -        operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -    ); - -    /// Operates on a widget that can be focused. -    fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} - -    /// Operates on a widget that can be scrolled. -    fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} - -    /// Operates on a widget that has text input. -    fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} - -    /// Operates on a custom widget with some state. -    fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} - -    /// Finishes the [`Operation`] and returns its [`Outcome`]. -    fn finish(&self) -> Outcome<T> { -        Outcome::None -    } -} - -/// The result of an [`Operation`]. -pub enum Outcome<T> { -    /// The [`Operation`] produced no result. -    None, - -    /// The [`Operation`] produced some result. -    Some(T), - -    /// The [`Operation`] needs to be followed by another [`Operation`]. -    Chain(Box<dyn Operation<T>>), -} - -impl<T> fmt::Debug for Outcome<T> -where -    T: fmt::Debug, -{ -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        match self { -            Self::None => write!(f, "Outcome::None"), -            Self::Some(output) => write!(f, "Outcome::Some({output:?})"), -            Self::Chain(_) => write!(f, "Outcome::Chain(...)"), -        } -    } -} - -/// Produces an [`Operation`] that applies the given [`Operation`] to the -/// children of a container with the given [`Id`]. -pub fn scoped<T: 'static>( -    target: Id, -    operation: impl Operation<T> + 'static, -) -> impl Operation<T> { -    struct ScopedOperation<Message> { -        target: Id, -        operation: Box<dyn Operation<Message>>, -    } - -    impl<Message: 'static> Operation<Message> for ScopedOperation<Message> { -        fn container( -            &mut self, -            id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>), -        ) { -            if id == Some(&self.target) { -                operate_on_children(self.operation.as_mut()); -            } else { -                operate_on_children(self); -            } -        } - -        fn finish(&self) -> Outcome<Message> { -            match self.operation.finish() { -                Outcome::Chain(next) => { -                    Outcome::Chain(Box::new(ScopedOperation { -                        target: self.target.clone(), -                        operation: next, -                    })) -                } -                outcome => outcome, -            } -        } -    } - -    ScopedOperation { -        target, -        operation: Box::new(operation), -    } -} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs deleted file mode 100644 index 312e4894..00000000 --- a/native/src/widget/operation/focusable.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Operate on widgets that can be focused. -use crate::widget::operation::{Operation, Outcome}; -use crate::widget::Id; - -/// The internal state of a widget that can be focused. -pub trait Focusable { -    /// Returns whether the widget is focused or not. -    fn is_focused(&self) -> bool; - -    /// Focuses the widget. -    fn focus(&mut self); - -    /// Unfocuses the widget. -    fn unfocus(&mut self); -} - -/// A summary of the focusable widgets present on a widget tree. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct Count { -    /// The index of the current focused widget, if any. -    pub focused: Option<usize>, - -    /// The total amount of focusable widgets. -    pub total: usize, -} - -/// Produces an [`Operation`] that focuses the widget with the given [`Id`]. -pub fn focus<T>(target: Id) -> impl Operation<T> { -    struct Focus { -        target: Id, -    } - -    impl<T> Operation<T> for Focus { -        fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { -            match id { -                Some(id) if id == &self.target => { -                    state.focus(); -                } -                _ => { -                    state.unfocus(); -                } -            } -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } -    } - -    Focus { target } -} - -/// Produces an [`Operation`] that generates a [`Count`] and chains it with the -/// provided function to build a new [`Operation`]. -pub fn count<T, O>(f: fn(Count) -> O) -> impl Operation<T> -where -    O: Operation<T> + 'static, -{ -    struct CountFocusable<O> { -        count: Count, -        next: fn(Count) -> O, -    } - -    impl<T, O> Operation<T> for CountFocusable<O> -    where -        O: Operation<T> + 'static, -    { -        fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { -            if state.is_focused() { -                self.count.focused = Some(self.count.total); -            } - -            self.count.total += 1; -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } - -        fn finish(&self) -> Outcome<T> { -            Outcome::Chain(Box::new((self.next)(self.count))) -        } -    } - -    CountFocusable { -        count: Count::default(), -        next: f, -    } -} - -/// Produces an [`Operation`] that searches for the current focused widget, and -/// - if found, focuses the previous focusable widget. -/// - if not found, focuses the last focusable widget. -pub fn focus_previous<T>() -> impl Operation<T> { -    struct FocusPrevious { -        count: Count, -        current: usize, -    } - -    impl<T> Operation<T> for FocusPrevious { -        fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { -            if self.count.total == 0 { -                return; -            } - -            match self.count.focused { -                None if self.current == self.count.total - 1 => state.focus(), -                Some(0) if self.current == 0 => state.unfocus(), -                Some(0) => {} -                Some(focused) if focused == self.current => state.unfocus(), -                Some(focused) if focused - 1 == self.current => state.focus(), -                _ => {} -            } - -            self.current += 1; -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } -    } - -    count(|count| FocusPrevious { count, current: 0 }) -} - -/// Produces an [`Operation`] that searches for the current focused widget, and -/// - if found, focuses the next focusable widget. -/// - if not found, focuses the first focusable widget. -pub fn focus_next<T>() -> impl Operation<T> { -    struct FocusNext { -        count: Count, -        current: usize, -    } - -    impl<T> Operation<T> for FocusNext { -        fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { -            match self.count.focused { -                None if self.current == 0 => state.focus(), -                Some(focused) if focused == self.current => state.unfocus(), -                Some(focused) if focused + 1 == self.current => state.focus(), -                _ => {} -            } - -            self.current += 1; -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } -    } - -    count(|count| FocusNext { count, current: 0 }) -} - -/// Produces an [`Operation`] that searches for the current focused widget -/// and stores its ID. This ignores widgets that do not have an ID. -pub fn find_focused() -> impl Operation<Id> { -    struct FindFocused { -        focused: Option<Id>, -    } - -    impl Operation<Id> for FindFocused { -        fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { -            if state.is_focused() && id.is_some() { -                self.focused = id.cloned(); -            } -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>), -        ) { -            operate_on_children(self) -        } - -        fn finish(&self) -> Outcome<Id> { -            if let Some(id) = &self.focused { -                Outcome::Some(id.clone()) -            } else { -                Outcome::None -            } -        } -    } - -    FindFocused { focused: None } -} diff --git a/native/src/widget/operation/scrollable.rs b/native/src/widget/operation/scrollable.rs deleted file mode 100644 index 3b20631f..00000000 --- a/native/src/widget/operation/scrollable.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Operate on widgets that can be scrolled. -use crate::widget::{Id, Operation}; - -/// The internal state of a widget that can be scrolled. -pub trait Scrollable { -    /// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis. -    fn snap_to(&mut self, offset: RelativeOffset); -} - -/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to -/// the provided `percentage`. -pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> { -    struct SnapTo { -        target: Id, -        offset: RelativeOffset, -    } - -    impl<T> Operation<T> for SnapTo { -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } - -        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { -            if Some(&self.target) == id { -                state.snap_to(self.offset); -            } -        } -    } - -    SnapTo { target, offset } -} - -/// The amount of offset in each direction of a [`Scrollable`]. -/// -/// A value of `0.0` means start, while `1.0` means end. -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct RelativeOffset { -    /// The amount of horizontal offset -    pub x: f32, -    /// The amount of vertical offset -    pub y: f32, -} - -impl RelativeOffset { -    /// A relative offset that points to the top-left of a [`Scrollable`]. -    pub const START: Self = Self { x: 0.0, y: 0.0 }; - -    /// A relative offset that points to the bottom-right of a [`Scrollable`]. -    pub const END: Self = Self { x: 1.0, y: 1.0 }; -} diff --git a/native/src/widget/operation/text_input.rs b/native/src/widget/operation/text_input.rs deleted file mode 100644 index 4c773e99..00000000 --- a/native/src/widget/operation/text_input.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Operate on widgets that have text input. -use crate::widget::operation::Operation; -use crate::widget::Id; - -/// The internal state of a widget that has text input. -pub trait TextInput { -    /// Moves the cursor of the text input to the front of the input text. -    fn move_cursor_to_front(&mut self); -    /// Moves the cursor of the text input to the end of the input text. -    fn move_cursor_to_end(&mut self); -    /// Moves the cursor of the text input to an arbitrary location. -    fn move_cursor_to(&mut self, position: usize); -    /// Selects all the content of the text input. -    fn select_all(&mut self); -} - -/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the -/// front. -pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> { -    struct MoveCursor { -        target: Id, -    } - -    impl<T> Operation<T> for MoveCursor { -        fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { -            match id { -                Some(id) if id == &self.target => { -                    state.move_cursor_to_front(); -                } -                _ => {} -            } -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } -    } - -    MoveCursor { target } -} - -/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the -/// end. -pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> { -    struct MoveCursor { -        target: Id, -    } - -    impl<T> Operation<T> for MoveCursor { -        fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { -            match id { -                Some(id) if id == &self.target => { -                    state.move_cursor_to_end(); -                } -                _ => {} -            } -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } -    } - -    MoveCursor { target } -} - -/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the -/// provided position. -pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> { -    struct MoveCursor { -        target: Id, -        position: usize, -    } - -    impl<T> Operation<T> for MoveCursor { -        fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { -            match id { -                Some(id) if id == &self.target => { -                    state.move_cursor_to(self.position); -                } -                _ => {} -            } -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } -    } - -    MoveCursor { target, position } -} - -/// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`]. -pub fn select_all<T>(target: Id) -> impl Operation<T> { -    struct MoveCursor { -        target: Id, -    } - -    impl<T> Operation<T> for MoveCursor { -        fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { -            match id { -                Some(id) if id == &self.target => { -                    state.select_all(); -                } -                _ => {} -            } -        } - -        fn container( -            &mut self, -            _id: Option<&Id>, -            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), -        ) { -            operate_on_children(self) -        } -    } - -    MoveCursor { target } -} diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs deleted file mode 100644 index bcb17ebd..00000000 --- a/native/src/widget/pane_grid.rs +++ /dev/null @@ -1,991 +0,0 @@ -//! 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.8/examples/pane_grid -mod axis; -mod configuration; -mod content; -mod direction; -mod draggable; -mod node; -mod pane; -mod split; -mod title_bar; - -pub mod state; - -pub use axis::Axis; -pub use configuration::Configuration; -pub use content::Content; -pub use direction::Direction; -pub use draggable::Draggable; -pub use node::Node; -pub use pane::Pane; -pub use split::Split; -pub use state::State; -pub use title_bar::TitleBar; - -pub use iced_style::pane_grid::{Line, StyleSheet}; - -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay::{self, Group}; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::container; -use crate::widget::tree::{self, Tree}; -use crate::{ -    Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, -    Size, Vector, Widget, -}; - -/// 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_native::widget::{pane_grid, text}; -/// # -/// # type PaneGrid<'a, Message> = -/// #     iced_native::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, is_maximized| { -///         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> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet + container::StyleSheet, -{ -    contents: Contents<'a, Content<'a, Message, Renderer>>, -    width: Length, -    height: Length, -    spacing: f32, -    on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>, -    on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, -    on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet + container::StyleSheet, -{ -    /// Creates a [`PaneGrid`] with the given [`State`] and view function. -    /// -    /// The view function will be called to display each [`Pane`] present in the -    /// [`State`]. [`bool`] is set if the pane is maximized. -    pub fn new<T>( -        state: &'a State<T>, -        view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>, -    ) -> Self { -        let contents = if let Some((pane, pane_state)) = -            state.maximized.and_then(|pane| { -                state.panes.get(&pane).map(|pane_state| (pane, pane_state)) -            }) { -            Contents::Maximized( -                pane, -                view(pane, pane_state, true), -                Node::Pane(pane), -            ) -        } else { -            Contents::All( -                state -                    .panes -                    .iter() -                    .map(|(pane, pane_state)| { -                        (*pane, view(*pane, pane_state, false)) -                    }) -                    .collect(), -                &state.internal, -            ) -        }; - -        Self { -            contents, -            width: Length::Fill, -            height: Length::Fill, -            spacing: 0.0, -            on_click: None, -            on_drag: None, -            on_resize: None, -            style: Default::default(), -        } -    } - -    /// Sets the width of the [`PaneGrid`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`PaneGrid`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the spacing _between_ the panes of the [`PaneGrid`]. -    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self { -        self.spacing = amount.into().0; -        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: impl Into<Pixels>, f: F) -> Self -    where -        F: 'a + Fn(ResizeEvent) -> Message, -    { -        self.on_resize = Some((leeway.into().0, Box::new(f))); -        self -    } - -    /// Sets the style of the [`PaneGrid`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } - -    fn drag_enabled(&self) -> bool { -        (!self.contents.is_maximized()) -            .then(|| self.on_drag.is_some()) -            .unwrap_or_default() -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for PaneGrid<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet + container::StyleSheet, -{ -    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.contents -            .iter() -            .map(|(_, content)| content.state()) -            .collect() -    } - -    fn diff(&self, tree: &mut Tree) { -        match &self.contents { -            Contents::All(contents, _) => tree.diff_children_custom( -                contents, -                |state, (_, content)| content.diff(state), -                |(_, content)| content.state(), -            ), -            Contents::Maximized(_, content, _) => tree.diff_children_custom( -                &[content], -                |state, content| 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 { -        layout( -            renderer, -            limits, -            self.contents.layout(), -            self.width, -            self.height, -            self.spacing, -            self.contents.iter(), -            |content, renderer, limits| content.layout(renderer, limits), -        ) -    } - -    fn operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn widget::Operation<Message>, -    ) { -        operation.container(None, &mut |operation| { -            self.contents -                .iter() -                .zip(&mut tree.children) -                .zip(layout.children()) -                .for_each(|(((_pane, content), state), layout)| { -                    content.operate(state, layout, renderer, operation); -                }) -        }); -    } - -    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 on_drag = if self.drag_enabled() { -            &self.on_drag -        } else { -            &None -        }; - -        let event_status = update( -            action, -            self.contents.layout(), -            &event, -            layout, -            cursor_position, -            shell, -            self.spacing, -            self.contents.iter(), -            &self.on_click, -            on_drag, -            &self.on_resize, -        ); - -        let picked_pane = action.picked_pane().map(|(pane, _)| pane); - -        self.contents -            .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 { -        mouse_interaction( -            tree.state.downcast_ref(), -            self.contents.layout(), -            layout, -            cursor_position, -            self.spacing, -            self.on_resize.as_ref().map(|(leeway, _)| *leeway), -        ) -        .unwrap_or_else(|| { -            self.contents -                .iter() -                .zip(&tree.children) -                .zip(layout.children()) -                .map(|(((_pane, content), tree), layout)| { -                    content.mouse_interaction( -                        tree, -                        layout, -                        cursor_position, -                        viewport, -                        renderer, -                        self.drag_enabled(), -                    ) -                }) -                .max() -                .unwrap_or_default() -        }) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) { -        draw( -            tree.state.downcast_ref(), -            self.contents.layout(), -            layout, -            cursor_position, -            renderer, -            theme, -            style, -            viewport, -            self.spacing, -            self.on_resize.as_ref().map(|(leeway, _)| *leeway), -            &self.style, -            self.contents -                .iter() -                .zip(&tree.children) -                .map(|((pane, content), tree)| (pane, (content, tree))), -            |(content, tree), -             renderer, -             style, -             layout, -             cursor_position, -             rectangle| { -                content.draw( -                    tree, -                    renderer, -                    theme, -                    style, -                    layout, -                    cursor_position, -                    rectangle, -                ); -            }, -        ) -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'_, Message, Renderer>> { -        let children = self -            .contents -            .iter_mut() -            .zip(&mut tree.children) -            .zip(layout.children()) -            .filter_map(|(((_, content), state), layout)| { -                content.overlay(state, layout, renderer) -            }) -            .collect::<Vec<_>>(); - -        (!children.is_empty()).then(|| Group::with_children(children).overlay()) -    } -} - -impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet + container::StyleSheet, -{ -    fn from( -        pane_grid: PaneGrid<'a, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(pane_grid) -    } -} - -/// Calculates the [`Layout`] of a [`PaneGrid`]. -pub fn layout<Renderer, T>( -    renderer: &Renderer, -    limits: &layout::Limits, -    node: &Node, -    width: Length, -    height: Length, -    spacing: f32, -    contents: impl Iterator<Item = (Pane, T)>, -    layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { -    let limits = limits.width(width).height(height); -    let size = limits.resolve(Size::ZERO); - -    let regions = node.pane_regions(spacing, size); -    let children = contents -        .filter_map(|(pane, content)| { -            let region = regions.get(&pane)?; -            let size = Size::new(region.width, region.height); - -            let mut node = layout_content( -                content, -                renderer, -                &layout::Limits::new(size, size), -            ); - -            node.move_to(Point::new(region.x, region.y)); - -            Some(node) -        }) -        .collect(); - -    layout::Node::with_children(size, children) -} - -/// Processes an [`Event`] and updates the [`state`] of a [`PaneGrid`] -/// accordingly. -pub fn update<'a, Message, T: Draggable>( -    action: &mut state::Action, -    node: &Node, -    event: &Event, -    layout: Layout<'_>, -    cursor_position: Point, -    shell: &mut Shell<'_, Message>, -    spacing: f32, -    contents: impl Iterator<Item = (Pane, T)>, -    on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, -    on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, -    on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, -) -> event::Status { -    let mut event_status = event::Status::Ignored; - -    match event { -        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerPressed { .. }) => { -            let bounds = layout.bounds(); - -            if bounds.contains(cursor_position) { -                event_status = event::Status::Captured; - -                match on_resize { -                    Some((leeway, _)) => { -                        let relative_cursor = Point::new( -                            cursor_position.x - bounds.x, -                            cursor_position.y - bounds.y, -                        ); - -                        let splits = node.split_regions( -                            spacing, -                            Size::new(bounds.width, bounds.height), -                        ); - -                        let clicked_split = hovered_split( -                            splits.iter(), -                            spacing + leeway, -                            relative_cursor, -                        ); - -                        if let Some((split, axis, _)) = clicked_split { -                            if action.picked_pane().is_none() { -                                *action = -                                    state::Action::Resizing { split, axis }; -                            } -                        } else { -                            click_pane( -                                action, -                                layout, -                                cursor_position, -                                shell, -                                contents, -                                on_click, -                                on_drag, -                            ); -                        } -                    } -                    None => { -                        click_pane( -                            action, -                            layout, -                            cursor_position, -                            shell, -                            contents, -                            on_click, -                            on_drag, -                        ); -                    } -                } -            } -        } -        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerLifted { .. }) -        | Event::Touch(touch::Event::FingerLost { .. }) => { -            if let Some((pane, _)) = action.picked_pane() { -                if let Some(on_drag) = on_drag { -                    let mut dropped_region = contents -                        .zip(layout.children()) -                        .filter(|(_, layout)| { -                            layout.bounds().contains(cursor_position) -                        }); - -                    let event = match dropped_region.next() { -                        Some(((target, _), _)) if pane != target => { -                            DragEvent::Dropped { pane, target } -                        } -                        _ => DragEvent::Canceled { pane }, -                    }; - -                    shell.publish(on_drag(event)); -                } - -                *action = state::Action::Idle; - -                event_status = event::Status::Captured; -            } else if action.picked_split().is_some() { -                *action = state::Action::Idle; - -                event_status = event::Status::Captured; -            } -        } -        Event::Mouse(mouse::Event::CursorMoved { .. }) -        | Event::Touch(touch::Event::FingerMoved { .. }) => { -            if let Some((_, on_resize)) = on_resize { -                if let Some((split, _)) = action.picked_split() { -                    let bounds = layout.bounds(); - -                    let splits = node.split_regions( -                        spacing, -                        Size::new(bounds.width, bounds.height), -                    ); - -                    if let Some((axis, rectangle, _)) = splits.get(&split) { -                        let ratio = match axis { -                            Axis::Horizontal => { -                                let position = -                                    cursor_position.y - bounds.y - rectangle.y; - -                                (position / rectangle.height).clamp(0.1, 0.9) -                            } -                            Axis::Vertical => { -                                let position = -                                    cursor_position.x - bounds.x - rectangle.x; - -                                (position / rectangle.width).clamp(0.1, 0.9) -                            } -                        }; - -                        shell.publish(on_resize(ResizeEvent { split, ratio })); - -                        event_status = event::Status::Captured; -                    } -                } -            } -        } -        _ => {} -    } - -    event_status -} - -fn click_pane<'a, Message, T>( -    action: &mut state::Action, -    layout: Layout<'_>, -    cursor_position: Point, -    shell: &mut Shell<'_, Message>, -    contents: impl Iterator<Item = (Pane, T)>, -    on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, -    on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, -) where -    T: Draggable, -{ -    let mut clicked_region = contents -        .zip(layout.children()) -        .filter(|(_, layout)| layout.bounds().contains(cursor_position)); - -    if let Some(((pane, content), layout)) = clicked_region.next() { -        if let Some(on_click) = &on_click { -            shell.publish(on_click(pane)); -        } - -        if let Some(on_drag) = &on_drag { -            if content.can_be_dragged_at(layout, cursor_position) { -                let pane_position = layout.position(); - -                let origin = cursor_position -                    - Vector::new(pane_position.x, pane_position.y); - -                *action = state::Action::Dragging { pane, origin }; - -                shell.publish(on_drag(DragEvent::Picked { pane })); -            } -        } -    } -} - -/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. -pub fn mouse_interaction( -    action: &state::Action, -    node: &Node, -    layout: Layout<'_>, -    cursor_position: Point, -    spacing: f32, -    resize_leeway: Option<f32>, -) -> Option<mouse::Interaction> { -    if action.picked_pane().is_some() { -        return Some(mouse::Interaction::Grabbing); -    } - -    let resize_axis = -        action.picked_split().map(|(_, axis)| axis).or_else(|| { -            resize_leeway.and_then(|leeway| { -                let bounds = layout.bounds(); - -                let splits = node.split_regions(spacing, bounds.size()); - -                let relative_cursor = Point::new( -                    cursor_position.x - bounds.x, -                    cursor_position.y - bounds.y, -                ); - -                hovered_split(splits.iter(), spacing + leeway, relative_cursor) -                    .map(|(_, axis, _)| axis) -            }) -        }); - -    if let Some(resize_axis) = resize_axis { -        return Some(match resize_axis { -            Axis::Horizontal => mouse::Interaction::ResizingVertically, -            Axis::Vertical => mouse::Interaction::ResizingHorizontally, -        }); -    } - -    None -} - -/// Draws a [`PaneGrid`]. -pub fn draw<Renderer, T>( -    action: &state::Action, -    node: &Node, -    layout: Layout<'_>, -    cursor_position: Point, -    renderer: &mut Renderer, -    theme: &Renderer::Theme, -    default_style: &renderer::Style, -    viewport: &Rectangle, -    spacing: f32, -    resize_leeway: Option<f32>, -    style: &<Renderer::Theme as StyleSheet>::Style, -    contents: impl Iterator<Item = (Pane, T)>, -    draw_pane: impl Fn( -        T, -        &mut Renderer, -        &renderer::Style, -        Layout<'_>, -        Point, -        &Rectangle, -    ), -) where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    let picked_pane = action.picked_pane(); - -    let picked_split = action -        .picked_split() -        .and_then(|(split, axis)| { -            let bounds = layout.bounds(); - -            let splits = node.split_regions(spacing, bounds.size()); - -            let (_axis, region, ratio) = splits.get(&split)?; - -            let region = axis.split_line_bounds(*region, *ratio, spacing); - -            Some((axis, region + Vector::new(bounds.x, bounds.y), true)) -        }) -        .or_else(|| match resize_leeway { -            Some(leeway) => { -                let bounds = layout.bounds(); - -                let relative_cursor = Point::new( -                    cursor_position.x - bounds.x, -                    cursor_position.y - bounds.y, -                ); - -                let splits = node.split_regions(spacing, bounds.size()); - -                let (_split, axis, region) = hovered_split( -                    splits.iter(), -                    spacing + leeway, -                    relative_cursor, -                )?; - -                Some((axis, region + Vector::new(bounds.x, bounds.y), false)) -            } -            None => None, -        }); - -    let pane_cursor_position = if picked_pane.is_some() { -        // TODO: Remove once cursor availability is encoded in the type -        // system -        Point::new(-1.0, -1.0) -    } else { -        cursor_position -    }; - -    let mut render_picked_pane = None; - -    for ((id, pane), layout) in contents.zip(layout.children()) { -        match picked_pane { -            Some((dragging, origin)) if id == dragging => { -                render_picked_pane = Some((pane, origin, layout)); -            } -            _ => { -                draw_pane( -                    pane, -                    renderer, -                    default_style, -                    layout, -                    pane_cursor_position, -                    viewport, -                ); -            } -        } -    } - -    // Render picked pane last -    if let Some((pane, origin, layout)) = render_picked_pane { -        let bounds = layout.bounds(); - -        renderer.with_translation( -            cursor_position -                - Point::new(bounds.x + origin.x, bounds.y + origin.y), -            |renderer| { -                renderer.with_layer(bounds, |renderer| { -                    draw_pane( -                        pane, -                        renderer, -                        default_style, -                        layout, -                        pane_cursor_position, -                        viewport, -                    ); -                }); -            }, -        ); -    }; - -    if let Some((axis, split_region, is_picked)) = picked_split { -        let highlight = if is_picked { -            theme.picked_split(style) -        } else { -            theme.hovered_split(style) -        }; - -        if let Some(highlight) = highlight { -            renderer.fill_quad( -                renderer::Quad { -                    bounds: match axis { -                        Axis::Horizontal => Rectangle { -                            x: split_region.x, -                            y: (split_region.y -                                + (split_region.height - highlight.width) -                                    / 2.0) -                                .round(), -                            width: split_region.width, -                            height: highlight.width, -                        }, -                        Axis::Vertical => Rectangle { -                            x: (split_region.x -                                + (split_region.width - highlight.width) / 2.0) -                                .round(), -                            y: split_region.y, -                            width: highlight.width, -                            height: split_region.height, -                        }, -                    }, -                    border_radius: 0.0.into(), -                    border_width: 0.0, -                    border_color: Color::TRANSPARENT, -                }, -                highlight.color, -            ); -        } -    } -} - -/// An event produced during a drag and drop interaction of a [`PaneGrid`]. -#[derive(Debug, Clone, Copy)] -pub enum DragEvent { -    /// A [`Pane`] was picked for dragging. -    Picked { -        /// The picked [`Pane`]. -        pane: Pane, -    }, - -    /// A [`Pane`] was dropped on top of another [`Pane`]. -    Dropped { -        /// The picked [`Pane`]. -        pane: Pane, - -        /// The [`Pane`] where the picked one was dropped on. -        target: Pane, -    }, - -    /// A [`Pane`] was picked and then dropped outside of other [`Pane`] -    /// boundaries. -    Canceled { -        /// The picked [`Pane`]. -        pane: Pane, -    }, -} - -/// An event produced during a resize interaction of a [`PaneGrid`]. -#[derive(Debug, Clone, Copy)] -pub struct ResizeEvent { -    /// The [`Split`] that is being dragged for resizing. -    pub split: Split, - -    /// The new ratio of the [`Split`]. -    /// -    /// The ratio is a value in [0, 1], representing the exact position of a -    /// [`Split`] between two panes. -    pub ratio: f32, -} - -/* - * Helpers - */ -fn hovered_split<'a>( -    splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>, -    spacing: f32, -    cursor_position: Point, -) -> Option<(Split, Axis, Rectangle)> { -    splits -        .filter_map(|(split, (axis, region, ratio))| { -            let bounds = axis.split_line_bounds(*region, *ratio, spacing); - -            if bounds.contains(cursor_position) { -                Some((*split, *axis, bounds)) -            } else { -                None -            } -        }) -        .next() -} - -/// The visible contents of the [`PaneGrid`] -#[derive(Debug)] -pub enum Contents<'a, T> { -    /// All panes are visible -    All(Vec<(Pane, T)>, &'a state::Internal), -    /// A maximized pane is visible -    Maximized(Pane, T, Node), -} - -impl<'a, T> Contents<'a, T> { -    /// Returns the layout [`Node`] of the [`Contents`] -    pub fn layout(&self) -> &Node { -        match self { -            Contents::All(_, state) => state.layout(), -            Contents::Maximized(_, _, layout) => layout, -        } -    } - -    /// Returns an iterator over the values of the [`Contents`] -    pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> { -        match self { -            Contents::All(contents, _) => Box::new( -                contents.iter().map(|(pane, content)| (*pane, content)), -            ), -            Contents::Maximized(pane, content, _) => { -                Box::new(std::iter::once((*pane, content))) -            } -        } -    } - -    fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> { -        match self { -            Contents::All(contents, _) => Box::new( -                contents.iter_mut().map(|(pane, content)| (*pane, content)), -            ), -            Contents::Maximized(pane, content, _) => { -                Box::new(std::iter::once((*pane, content))) -            } -        } -    } - -    fn is_maximized(&self) -> bool { -        matches!(self, Self::Maximized(..)) -    } -} diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs deleted file mode 100644 index 02bde064..00000000 --- a/native/src/widget/pane_grid/axis.rs +++ /dev/null @@ -1,241 +0,0 @@ -use crate::Rectangle; - -/// A fixed reference line for the measurement of coordinates. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Axis { -    /// The horizontal axis: — -    Horizontal, -    /// The vertical axis: | -    Vertical, -} - -impl Axis { -    /// Splits the provided [`Rectangle`] on the current [`Axis`] with the -    /// given `ratio` and `spacing`. -    pub fn split( -        &self, -        rectangle: &Rectangle, -        ratio: f32, -        spacing: f32, -    ) -> (Rectangle, Rectangle) { -        match self { -            Axis::Horizontal => { -                let height_top = -                    (rectangle.height * ratio - spacing / 2.0).round(); -                let height_bottom = rectangle.height - height_top - spacing; - -                ( -                    Rectangle { -                        height: height_top, -                        ..*rectangle -                    }, -                    Rectangle { -                        y: rectangle.y + height_top + spacing, -                        height: height_bottom, -                        ..*rectangle -                    }, -                ) -            } -            Axis::Vertical => { -                let width_left = -                    (rectangle.width * ratio - spacing / 2.0).round(); -                let width_right = rectangle.width - width_left - spacing; - -                ( -                    Rectangle { -                        width: width_left, -                        ..*rectangle -                    }, -                    Rectangle { -                        x: rectangle.x + width_left + spacing, -                        width: width_right, -                        ..*rectangle -                    }, -                ) -            } -        } -    } - -    /// Calculates the bounds of the split line in a [`Rectangle`] region. -    pub fn split_line_bounds( -        &self, -        rectangle: Rectangle, -        ratio: f32, -        spacing: f32, -    ) -> Rectangle { -        match self { -            Axis::Horizontal => Rectangle { -                x: rectangle.x, -                y: (rectangle.y + rectangle.height * ratio - spacing / 2.0) -                    .round(), -                width: rectangle.width, -                height: spacing, -            }, -            Axis::Vertical => Rectangle { -                x: (rectangle.x + rectangle.width * ratio - spacing / 2.0) -                    .round(), -                y: rectangle.y, -                width: spacing, -                height: rectangle.height, -            }, -        } -    } -} - -#[cfg(test)] -mod tests { -    use super::*; - -    enum Case { -        Horizontal { -            overall_height: f32, -            spacing: f32, -            top_height: f32, -            bottom_y: f32, -            bottom_height: f32, -        }, -        Vertical { -            overall_width: f32, -            spacing: f32, -            left_width: f32, -            right_x: f32, -            right_width: f32, -        }, -    } - -    #[test] -    fn split() { -        let cases = vec![ -            // Even height, even spacing -            Case::Horizontal { -                overall_height: 10.0, -                spacing: 2.0, -                top_height: 4.0, -                bottom_y: 6.0, -                bottom_height: 4.0, -            }, -            // Odd height, even spacing -            Case::Horizontal { -                overall_height: 9.0, -                spacing: 2.0, -                top_height: 4.0, -                bottom_y: 6.0, -                bottom_height: 3.0, -            }, -            // Even height, odd spacing -            Case::Horizontal { -                overall_height: 10.0, -                spacing: 1.0, -                top_height: 5.0, -                bottom_y: 6.0, -                bottom_height: 4.0, -            }, -            // Odd height, odd spacing -            Case::Horizontal { -                overall_height: 9.0, -                spacing: 1.0, -                top_height: 4.0, -                bottom_y: 5.0, -                bottom_height: 4.0, -            }, -            // Even width, even spacing -            Case::Vertical { -                overall_width: 10.0, -                spacing: 2.0, -                left_width: 4.0, -                right_x: 6.0, -                right_width: 4.0, -            }, -            // Odd width, even spacing -            Case::Vertical { -                overall_width: 9.0, -                spacing: 2.0, -                left_width: 4.0, -                right_x: 6.0, -                right_width: 3.0, -            }, -            // Even width, odd spacing -            Case::Vertical { -                overall_width: 10.0, -                spacing: 1.0, -                left_width: 5.0, -                right_x: 6.0, -                right_width: 4.0, -            }, -            // Odd width, odd spacing -            Case::Vertical { -                overall_width: 9.0, -                spacing: 1.0, -                left_width: 4.0, -                right_x: 5.0, -                right_width: 4.0, -            }, -        ]; -        for case in cases { -            match case { -                Case::Horizontal { -                    overall_height, -                    spacing, -                    top_height, -                    bottom_y, -                    bottom_height, -                } => { -                    let a = Axis::Horizontal; -                    let r = Rectangle { -                        x: 0.0, -                        y: 0.0, -                        width: 10.0, -                        height: overall_height, -                    }; -                    let (top, bottom) = a.split(&r, 0.5, spacing); -                    assert_eq!( -                        top, -                        Rectangle { -                            height: top_height, -                            ..r -                        } -                    ); -                    assert_eq!( -                        bottom, -                        Rectangle { -                            y: bottom_y, -                            height: bottom_height, -                            ..r -                        } -                    ); -                } -                Case::Vertical { -                    overall_width, -                    spacing, -                    left_width, -                    right_x, -                    right_width, -                } => { -                    let a = Axis::Vertical; -                    let r = Rectangle { -                        x: 0.0, -                        y: 0.0, -                        width: overall_width, -                        height: 10.0, -                    }; -                    let (left, right) = a.split(&r, 0.5, spacing); -                    assert_eq!( -                        left, -                        Rectangle { -                            width: left_width, -                            ..r -                        } -                    ); -                    assert_eq!( -                        right, -                        Rectangle { -                            x: right_x, -                            width: right_width, -                            ..r -                        } -                    ); -                } -            } -        } -    } -} diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs deleted file mode 100644 index 7d68fb46..00000000 --- a/native/src/widget/pane_grid/configuration.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::widget::pane_grid::Axis; - -/// The arrangement of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[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 [`Configuration`] of the split. -        a: Box<Configuration<T>>, - -        /// The right/bottom [`Configuration`] of the split. -        b: Box<Configuration<T>>, -    }, -    /// A [`Pane`]. -    /// -    /// [`Pane`]: crate::widget::pane_grid::Pane -    Pane(T), -} diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs deleted file mode 100644 index c9b0df07..00000000 --- a/native/src/widget/pane_grid/content.rs +++ /dev/null @@ -1,373 +0,0 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::pane_grid::{Draggable, TitleBar}; -use crate::widget::{self, Tree}; -use crate::{Clipboard, Element, 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> -where -    Renderer: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    title_bar: Option<TitleBar<'a, Message, Renderer>>, -    body: Element<'a, Message, Renderer>, -    style: <Renderer::Theme as container::StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    /// 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: 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: impl Into<<Renderer::Theme as container::StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    pub(super) 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(super) 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::Renderer -    pub fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) { -        use container::StyleSheet; - -        let bounds = layout.bounds(); - -        { -            let style = theme.appearance(&self.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); - -            self.body.as_widget().draw( -                &tree.children[0], -                renderer, -                theme, -                style, -                body_layout, -                cursor_position, -                viewport, -            ); - -            title_bar.draw( -                &tree.children[1], -                renderer, -                theme, -                style, -                title_bar_layout, -                cursor_position, -                viewport, -                show_controls, -            ); -        } else { -            self.body.as_widget().draw( -                &tree.children[0], -                renderer, -                theme, -                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 operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn widget::Operation<Message>, -    ) { -        let body_layout = if let Some(title_bar) = &self.title_bar { -            let mut children = layout.children(); - -            title_bar.operate( -                &mut tree.children[1], -                children.next().unwrap(), -                renderer, -                operation, -            ); - -            children.next().unwrap() -        } else { -            layout -        }; - -        self.body.as_widget().operate( -            &mut tree.children[0], -            body_layout, -            renderer, -            operation, -        ); -    } - -    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, -        drag_enabled: bool, -    ) -> 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 && drag_enabled { -                    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 mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        if let Some(title_bar) = self.title_bar.as_mut() { -            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_mut().overlay( -                    body_state, -                    children.next()?, -                    renderer, -                ), -            } -        } else { -            self.body.as_widget_mut().overlay( -                &mut tree.children[0], -                layout, -                renderer, -            ) -        } -    } -} - -impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    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: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    fn from(element: T) -> Self { -        Self::new(element) -    } -} diff --git a/native/src/widget/pane_grid/direction.rs b/native/src/widget/pane_grid/direction.rs deleted file mode 100644 index b31a8737..00000000 --- a/native/src/widget/pane_grid/direction.rs +++ /dev/null @@ -1,12 +0,0 @@ -/// A four cardinal direction. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { -    /// ↑ -    Up, -    /// ↓ -    Down, -    /// ← -    Left, -    /// → -    Right, -} diff --git a/native/src/widget/pane_grid/draggable.rs b/native/src/widget/pane_grid/draggable.rs deleted file mode 100644 index 6044871d..00000000 --- a/native/src/widget/pane_grid/draggable.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::{Layout, Point}; - -/// A pane that can be dragged. -pub trait Draggable { -    /// Returns whether the [`Draggable`] with the given [`Layout`] can be picked -    /// at the provided cursor position. -    fn can_be_dragged_at( -        &self, -        layout: Layout<'_>, -        cursor_position: Point, -    ) -> bool; -} diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs deleted file mode 100644 index cc304b96..00000000 --- a/native/src/widget/pane_grid/node.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::widget::pane_grid::{Axis, Pane, Split}; -use crate::{Rectangle, Size}; - -use std::collections::BTreeMap; - -/// A layout node of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone)] -pub enum Node { -    /// The region of this [`Node`] is split into two. -    Split { -        /// The [`Split`] of this [`Node`]. -        id: Split, - -        /// The direction of the split. -        axis: Axis, - -        /// The ratio of the split in [0.0, 1.0]. -        ratio: f32, - -        /// The left/top [`Node`] of the split. -        a: Box<Node>, - -        /// The right/bottom [`Node`] of the split. -        b: Box<Node>, -    }, -    /// The region of this [`Node`] is taken by a [`Pane`]. -    Pane(Pane), -} - -impl Node { -    /// Returns an iterator over each [`Split`] in this [`Node`]. -    pub fn splits(&self) -> impl Iterator<Item = &Split> { -        let mut unvisited_nodes = vec![self]; - -        std::iter::from_fn(move || { -            while let Some(node) = unvisited_nodes.pop() { -                if let Node::Split { id, a, b, .. } = node { -                    unvisited_nodes.push(a); -                    unvisited_nodes.push(b); - -                    return Some(id); -                } -            } - -            None -        }) -    } - -    /// Returns the rectangular region for each [`Pane`] in the [`Node`] given -    /// the spacing between panes and the total available space. -    pub fn pane_regions( -        &self, -        spacing: f32, -        size: Size, -    ) -> BTreeMap<Pane, Rectangle> { -        let mut regions = BTreeMap::new(); - -        self.compute_regions( -            spacing, -            &Rectangle { -                x: 0.0, -                y: 0.0, -                width: size.width, -                height: size.height, -            }, -            &mut regions, -        ); - -        regions -    } - -    /// Returns the axis, rectangular region, and ratio for each [`Split`] in -    /// the [`Node`] given the spacing between panes and the total available -    /// space. -    pub fn split_regions( -        &self, -        spacing: f32, -        size: Size, -    ) -> BTreeMap<Split, (Axis, Rectangle, f32)> { -        let mut splits = BTreeMap::new(); - -        self.compute_splits( -            spacing, -            &Rectangle { -                x: 0.0, -                y: 0.0, -                width: size.width, -                height: size.height, -            }, -            &mut splits, -        ); - -        splits -    } - -    pub(crate) fn find(&mut self, pane: &Pane) -> Option<&mut Node> { -        match self { -            Node::Split { a, b, .. } => { -                a.find(pane).or_else(move || b.find(pane)) -            } -            Node::Pane(p) => { -                if p == pane { -                    Some(self) -                } else { -                    None -                } -            } -        } -    } - -    pub(crate) fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) { -        *self = Node::Split { -            id, -            axis, -            ratio: 0.5, -            a: Box::new(self.clone()), -            b: Box::new(Node::Pane(new_pane)), -        }; -    } - -    pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) { -        if let Node::Split { a, b, .. } = self { -            a.update(f); -            b.update(f); -        } - -        f(self); -    } - -    pub(crate) fn resize(&mut self, split: &Split, percentage: f32) -> bool { -        match self { -            Node::Split { -                id, ratio, a, b, .. -            } => { -                if id == split { -                    *ratio = percentage; - -                    true -                } else if a.resize(split, percentage) { -                    true -                } else { -                    b.resize(split, percentage) -                } -            } -            Node::Pane(_) => false, -        } -    } - -    pub(crate) fn remove(&mut self, pane: &Pane) -> Option<Pane> { -        match self { -            Node::Split { a, b, .. } => { -                if a.pane() == Some(*pane) { -                    *self = *b.clone(); -                    Some(self.first_pane()) -                } else if b.pane() == Some(*pane) { -                    *self = *a.clone(); -                    Some(self.first_pane()) -                } else { -                    a.remove(pane).or_else(|| b.remove(pane)) -                } -            } -            Node::Pane(_) => None, -        } -    } - -    fn pane(&self) -> Option<Pane> { -        match self { -            Node::Split { .. } => None, -            Node::Pane(pane) => Some(*pane), -        } -    } - -    fn first_pane(&self) -> Pane { -        match self { -            Node::Split { a, .. } => a.first_pane(), -            Node::Pane(pane) => *pane, -        } -    } - -    fn compute_regions( -        &self, -        spacing: f32, -        current: &Rectangle, -        regions: &mut BTreeMap<Pane, Rectangle>, -    ) { -        match self { -            Node::Split { -                axis, ratio, a, b, .. -            } => { -                let (region_a, region_b) = axis.split(current, *ratio, spacing); - -                a.compute_regions(spacing, ®ion_a, regions); -                b.compute_regions(spacing, ®ion_b, regions); -            } -            Node::Pane(pane) => { -                let _ = regions.insert(*pane, *current); -            } -        } -    } - -    fn compute_splits( -        &self, -        spacing: f32, -        current: &Rectangle, -        splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>, -    ) { -        match self { -            Node::Split { -                axis, -                ratio, -                a, -                b, -                id, -            } => { -                let (region_a, region_b) = axis.split(current, *ratio, spacing); - -                let _ = splits.insert(*id, (*axis, *current, *ratio)); - -                a.compute_splits(spacing, ®ion_a, splits); -                b.compute_splits(spacing, ®ion_b, splits); -            } -            Node::Pane(_) => {} -        } -    } -} - -impl std::hash::Hash for Node { -    fn hash<H: std::hash::Hasher>(&self, state: &mut H) { -        match self { -            Node::Split { -                id, -                axis, -                ratio, -                a, -                b, -            } => { -                id.hash(state); -                axis.hash(state); -                ((ratio * 100_000.0) as u32).hash(state); -                a.hash(state); -                b.hash(state); -            } -            Node::Pane(pane) => { -                pane.hash(state); -            } -        } -    } -} diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs deleted file mode 100644 index d6fbab83..00000000 --- a/native/src/widget/pane_grid/pane.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// A rectangular region in a [`PaneGrid`] used to display widgets. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Pane(pub(super) usize); diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs deleted file mode 100644 index 8132272a..00000000 --- a/native/src/widget/pane_grid/split.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// A divider that splits a region in a [`PaneGrid`] into two different panes. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Split(pub(super) usize); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs deleted file mode 100644 index c4ae0a0e..00000000 --- a/native/src/widget/pane_grid/state.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! The state of a [`PaneGrid`]. -//! -//! [`PaneGrid`]: crate::widget::PaneGrid -use crate::widget::pane_grid::{ -    Axis, Configuration, Direction, Node, Pane, Split, -}; -use crate::{Point, Size}; - -use std::collections::HashMap; - -/// The state of a [`PaneGrid`]. -/// -/// It keeps track of the state of each [`Pane`] and the position of each -/// [`Split`]. -/// -/// The [`State`] needs to own any mutable contents a [`Pane`] may need. This is -/// why this struct is generic over the type `T`. Values of this type are -/// provided to the view function of [`PaneGrid::new`] for displaying each -/// [`Pane`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -/// [`PaneGrid::new`]: crate::widget::PaneGrid::new -#[derive(Debug, Clone)] -pub struct State<T> { -    /// The panes of the [`PaneGrid`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    pub panes: HashMap<Pane, T>, - -    /// The internal state of the [`PaneGrid`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    pub internal: Internal, - -    /// The maximized [`Pane`] of the [`PaneGrid`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    pub(super) maximized: Option<Pane>, -} - -impl<T> State<T> { -    /// Creates a new [`State`], initializing the first pane with the provided -    /// state. -    /// -    /// Alongside the [`State`], it returns the first [`Pane`] identifier. -    pub fn new(first_pane_state: T) -> (Self, Pane) { -        ( -            Self::with_configuration(Configuration::Pane(first_pane_state)), -            Pane(0), -        ) -    } - -    /// Creates a new [`State`] with the given [`Configuration`]. -    pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self { -        let mut panes = HashMap::new(); - -        let internal = -            Internal::from_configuration(&mut panes, config.into(), 0); - -        State { -            panes, -            internal, -            maximized: None, -        } -    } - -    /// Returns the total amount of panes in the [`State`]. -    pub fn len(&self) -> usize { -        self.panes.len() -    } - -    /// Returns `true` if the amount of panes in the [`State`] is 0. -    pub fn is_empty(&self) -> bool { -        self.len() == 0 -    } - -    /// Returns the internal state of the given [`Pane`], if it exists. -    pub fn get(&self, pane: &Pane) -> Option<&T> { -        self.panes.get(pane) -    } - -    /// Returns the internal state of the given [`Pane`] with mutability, if it -    /// exists. -    pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { -        self.panes.get_mut(pane) -    } - -    /// Returns an iterator over all the panes of the [`State`], alongside its -    /// internal state. -    pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> { -        self.panes.iter() -    } - -    /// Returns a mutable iterator over all the panes of the [`State`], -    /// alongside its internal state. -    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut T)> { -        self.panes.iter_mut() -    } - -    /// Returns the layout of the [`State`]. -    pub fn layout(&self) -> &Node { -        &self.internal.layout -    } - -    /// Returns the adjacent [`Pane`] of another [`Pane`] in the given -    /// direction, if there is one. -    pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> { -        let regions = self -            .internal -            .layout -            .pane_regions(0.0, Size::new(4096.0, 4096.0)); - -        let current_region = regions.get(pane)?; - -        let target = match direction { -            Direction::Left => { -                Point::new(current_region.x - 1.0, current_region.y + 1.0) -            } -            Direction::Right => Point::new( -                current_region.x + current_region.width + 1.0, -                current_region.y + 1.0, -            ), -            Direction::Up => { -                Point::new(current_region.x + 1.0, current_region.y - 1.0) -            } -            Direction::Down => Point::new( -                current_region.x + 1.0, -                current_region.y + current_region.height + 1.0, -            ), -        }; - -        let mut colliding_regions = -            regions.iter().filter(|(_, region)| region.contains(target)); - -        let (pane, _) = colliding_regions.next()?; - -        Some(*pane) -    } - -    /// Splits the given [`Pane`] into two in the given [`Axis`] and -    /// initializing the new [`Pane`] with the provided internal state. -    pub fn split( -        &mut self, -        axis: Axis, -        pane: &Pane, -        state: T, -    ) -> Option<(Pane, Split)> { -        let node = self.internal.layout.find(pane)?; - -        let new_pane = { -            self.internal.last_id = self.internal.last_id.checked_add(1)?; - -            Pane(self.internal.last_id) -        }; - -        let new_split = { -            self.internal.last_id = self.internal.last_id.checked_add(1)?; - -            Split(self.internal.last_id) -        }; - -        node.split(new_split, axis, new_pane); - -        let _ = self.panes.insert(new_pane, state); -        let _ = self.maximized.take(); - -        Some((new_pane, new_split)) -    } - -    /// Swaps the position of the provided panes in the [`State`]. -    /// -    /// If you want to swap panes on drag and drop in your [`PaneGrid`], you -    /// will need to call this method when handling a [`DragEvent`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    /// [`DragEvent`]: crate::widget::pane_grid::DragEvent -    pub fn swap(&mut self, a: &Pane, b: &Pane) { -        self.internal.layout.update(&|node| match node { -            Node::Split { .. } => {} -            Node::Pane(pane) => { -                if pane == a { -                    *node = Node::Pane(*b); -                } else if pane == b { -                    *node = Node::Pane(*a); -                } -            } -        }); -    } - -    /// Resizes two panes by setting the position of the provided [`Split`]. -    /// -    /// The ratio is a value in [0, 1], representing the exact position of a -    /// [`Split`] between two panes. -    /// -    /// If you want to enable resize interactions in your [`PaneGrid`], you will -    /// need to call this method when handling a [`ResizeEvent`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    /// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent -    pub fn resize(&mut self, split: &Split, ratio: f32) { -        let _ = self.internal.layout.resize(split, ratio); -    } - -    /// Closes the given [`Pane`] and returns its internal state and its closest -    /// sibling, if it exists. -    pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { -        if self.maximized == Some(*pane) { -            let _ = self.maximized.take(); -        } - -        if let Some(sibling) = self.internal.layout.remove(pane) { -            self.panes.remove(pane).map(|state| (state, sibling)) -        } else { -            None -        } -    } - -    /// Maximize the given [`Pane`]. Only this pane will be rendered by the -    /// [`PaneGrid`] until [`Self::restore()`] is called. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    pub fn maximize(&mut self, pane: &Pane) { -        self.maximized = Some(*pane); -    } - -    /// Restore the currently maximized [`Pane`] to it's normal size. All panes -    /// will be rendered by the [`PaneGrid`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    pub fn restore(&mut self) { -        let _ = self.maximized.take(); -    } - -    /// Returns the maximized [`Pane`] of the [`PaneGrid`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    pub fn maximized(&self) -> Option<Pane> { -        self.maximized -    } -} - -/// The internal state of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone)] -pub struct Internal { -    layout: Node, -    last_id: usize, -} - -impl Internal { -    /// Initializes the [`Internal`] state of a [`PaneGrid`] from a -    /// [`Configuration`]. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    pub fn from_configuration<T>( -        panes: &mut HashMap<Pane, T>, -        content: Configuration<T>, -        next_id: usize, -    ) -> Self { -        let (layout, last_id) = match content { -            Configuration::Split { axis, ratio, a, b } => { -                let Internal { -                    layout: a, -                    last_id: next_id, -                    .. -                } = Self::from_configuration(panes, *a, next_id); - -                let Internal { -                    layout: b, -                    last_id: next_id, -                    .. -                } = Self::from_configuration(panes, *b, next_id); - -                ( -                    Node::Split { -                        id: Split(next_id), -                        axis, -                        ratio, -                        a: Box::new(a), -                        b: Box::new(b), -                    }, -                    next_id + 1, -                ) -            } -            Configuration::Pane(state) => { -                let id = Pane(next_id); -                let _ = panes.insert(id, state); - -                (Node::Pane(id), next_id + 1) -            } -        }; - -        Self { layout, last_id } -    } -} - -/// The current action of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Action { -    /// The [`PaneGrid`] is idle. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    Idle, -    /// A [`Pane`] in the [`PaneGrid`] is being dragged. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    Dragging { -        /// The [`Pane`] being dragged. -        pane: Pane, -        /// The starting [`Point`] of the drag interaction. -        origin: Point, -    }, -    /// A [`Split`] in the [`PaneGrid`] is being dragged. -    /// -    /// [`PaneGrid`]: crate::widget::PaneGrid -    Resizing { -        /// The [`Split`] being dragged. -        split: Split, -        /// The [`Axis`] of the [`Split`]. -        axis: Axis, -    }, -} - -impl Action { -    /// Returns the current [`Pane`] that is being dragged, if any. -    pub fn picked_pane(&self) -> Option<(Pane, Point)> { -        match *self { -            Action::Dragging { pane, origin, .. } => Some((pane, origin)), -            _ => None, -        } -    } - -    /// Returns the current [`Split`] that is being dragged, if any. -    pub fn picked_split(&self) -> Option<(Split, Axis)> { -        match *self { -            Action::Resizing { split, axis, .. } => Some((split, axis)), -            _ => None, -        } -    } -} - -impl Internal { -    /// The layout [`Node`] of the [`Internal`] state -    pub fn layout(&self) -> &Node { -        &self.layout -    } -} diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs deleted file mode 100644 index 107078ef..00000000 --- a/native/src/widget/pane_grid/title_bar.rs +++ /dev/null @@ -1,432 +0,0 @@ -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::container; -use crate::widget::{self, Tree}; -use crate::{ -    Clipboard, Element, 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> -where -    Renderer: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    content: Element<'a, Message, Renderer>, -    controls: Option<Element<'a, Message, Renderer>>, -    padding: Padding, -    always_show_controls: bool, -    style: <Renderer::Theme as container::StyleSheet>::Style, -} - -impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    /// 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: 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<<Renderer::Theme as container::StyleSheet>::Style>, -    ) -> Self { -        self.style = 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: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    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::Renderer -    pub fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        inherited_style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -        show_controls: bool, -    ) { -        use container::StyleSheet; - -        let bounds = layout.bounds(); -        let style = theme.appearance(&self.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(); -        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_position, -                    viewport, -                ); -            } -        } - -        if show_title { -            self.content.as_widget().draw( -                &tree.children[0], -                renderer, -                theme, -                &inherited_style, -                title_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(); - -                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, -        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, self.padding.top)); - -        layout::Node::with_children(node.size().pad(self.padding), vec![node]) -    } - -    pub(crate) fn operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn widget::Operation<Message>, -    ) { -        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_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 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_position, -                renderer, -                clipboard, -                shell, -            ) -        } 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_position, -                renderer, -                clipboard, -                shell, -            ) -        } else { -            event::Status::Ignored -        }; - -        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(); -            let controls_interaction = controls.as_widget().mouse_interaction( -                &tree.children[1], -                controls_layout, -                cursor_position, -                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, -    ) -> 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_mut() -            .overlay(title_state, title_layout, renderer) -            .or_else(move || { -                controls.as_mut().and_then(|controls| { -                    let controls_layout = children.next()?; - -                    controls.as_widget_mut().overlay( -                        controls_state, -                        controls_layout, -                        renderer, -                    ) -                }) -            }) -    } -} diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs deleted file mode 100644 index 8ff82f3b..00000000 --- a/native/src/widget/pick_list.rs +++ /dev/null @@ -1,657 +0,0 @@ -//! Display a dropdown list of selectable values. -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::overlay::menu::{self, Menu}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::touch; -use crate::widget::container; -use crate::widget::scrollable; -use crate::widget::tree::{self, Tree}; -use crate::{ -    Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, -    Shell, Size, Widget, -}; -use std::borrow::Cow; - -pub use iced_style::pick_list::{Appearance, StyleSheet}; - -/// A widget for selecting a single value from a list of options. -#[allow(missing_debug_implementations)] -pub struct PickList<'a, T, Message, Renderer> -where -    [T]: ToOwned<Owned = Vec<T>>, -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    on_selected: Box<dyn Fn(T) -> Message + 'a>, -    options: Cow<'a, [T]>, -    placeholder: Option<String>, -    selected: Option<T>, -    width: Length, -    padding: Padding, -    text_size: Option<f32>, -    font: Option<Renderer::Font>, -    handle: Handle<Renderer::Font>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer> -where -    T: ToString + Eq, -    [T]: ToOwned<Owned = Vec<T>>, -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet -        + scrollable::StyleSheet -        + menu::StyleSheet -        + container::StyleSheet, -    <Renderer::Theme as menu::StyleSheet>::Style: -        From<<Renderer::Theme as StyleSheet>::Style>, -{ -    /// The default padding of a [`PickList`]. -    pub const DEFAULT_PADDING: Padding = Padding::new(5.0); - -    /// Creates a new [`PickList`] with the given list of options, the current -    /// selected value, and the message to produce when an option is selected. -    pub fn new( -        options: impl Into<Cow<'a, [T]>>, -        selected: Option<T>, -        on_selected: impl Fn(T) -> Message + 'a, -    ) -> Self { -        Self { -            on_selected: Box::new(on_selected), -            options: options.into(), -            placeholder: None, -            selected, -            width: Length::Shrink, -            padding: Self::DEFAULT_PADDING, -            text_size: None, -            font: None, -            handle: Default::default(), -            style: Default::default(), -        } -    } - -    /// Sets the placeholder of the [`PickList`]. -    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self { -        self.placeholder = Some(placeholder.into()); -        self -    } - -    /// Sets the width of the [`PickList`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the [`Padding`] of the [`PickList`]. -    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { -        self.padding = padding.into(); -        self -    } - -    /// Sets the text size of the [`PickList`]. -    pub fn text_size(mut self, size: impl Into<Pixels>) -> Self { -        self.text_size = Some(size.into().0); -        self -    } - -    /// Sets the font of the [`PickList`]. -    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { -        self.font = Some(font.into()); -        self -    } - -    /// Sets the [`Handle`] of the [`PickList`]. -    pub fn handle(mut self, handle: Handle<Renderer::Font>) -> Self { -        self.handle = handle; -        self -    } - -    /// Sets the style of the [`PickList`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer> -    for PickList<'a, T, Message, Renderer> -where -    T: Clone + ToString + Eq + 'static, -    [T]: ToOwned<Owned = Vec<T>>, -    Message: 'a, -    Renderer: text::Renderer + 'a, -    Renderer::Theme: StyleSheet -        + scrollable::StyleSheet -        + menu::StyleSheet -        + container::StyleSheet, -    <Renderer::Theme as menu::StyleSheet>::Style: -        From<<Renderer::Theme as StyleSheet>::Style>, -{ -    fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State<T>>() -    } - -    fn state(&self) -> tree::State { -        tree::State::new(State::<T>::new()) -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        layout( -            renderer, -            limits, -            self.width, -            self.padding, -            self.text_size, -            self.font, -            self.placeholder.as_deref(), -            &self.options, -        ) -    } - -    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 { -        update( -            event, -            layout, -            cursor_position, -            shell, -            self.on_selected.as_ref(), -            self.selected.as_ref(), -            &self.options, -            || tree.state.downcast_mut::<State<T>>(), -        ) -    } - -    fn mouse_interaction( -        &self, -        _tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        mouse_interaction(layout, cursor_position) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let font = self.font.unwrap_or_else(|| renderer.default_font()); -        draw( -            renderer, -            theme, -            layout, -            cursor_position, -            self.padding, -            self.text_size, -            font, -            self.placeholder.as_deref(), -            self.selected.as_ref(), -            &self.handle, -            &self.style, -            || tree.state.downcast_ref::<State<T>>(), -        ) -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        let state = tree.state.downcast_mut::<State<T>>(); - -        overlay( -            layout, -            state, -            self.padding, -            self.text_size, -            self.font.unwrap_or_else(|| renderer.default_font()), -            &self.options, -            self.style.clone(), -        ) -    } -} - -impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    T: Clone + ToString + Eq + 'static, -    [T]: ToOwned<Owned = Vec<T>>, -    Message: 'a, -    Renderer: text::Renderer + 'a, -    Renderer::Theme: StyleSheet -        + scrollable::StyleSheet -        + menu::StyleSheet -        + container::StyleSheet, -    <Renderer::Theme as menu::StyleSheet>::Style: -        From<<Renderer::Theme as StyleSheet>::Style>, -{ -    fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self { -        Self::new(pick_list) -    } -} - -/// The local state of a [`PickList`]. -#[derive(Debug)] -pub struct State<T> { -    menu: menu::State, -    keyboard_modifiers: keyboard::Modifiers, -    is_open: bool, -    hovered_option: Option<usize>, -    last_selection: Option<T>, -} - -impl<T> State<T> { -    /// Creates a new [`State`] for a [`PickList`]. -    pub fn new() -> Self { -        Self { -            menu: menu::State::default(), -            keyboard_modifiers: keyboard::Modifiers::default(), -            is_open: bool::default(), -            hovered_option: Option::default(), -            last_selection: Option::default(), -        } -    } -} - -impl<T> Default for State<T> { -    fn default() -> Self { -        Self::new() -    } -} - -/// The handle to the right side of the [`PickList`]. -#[derive(Debug, Clone, PartialEq)] -pub enum Handle<Font> { -    /// Displays an arrow icon (▼). -    /// -    /// This is the default. -    Arrow { -        /// Font size of the content. -        size: Option<f32>, -    }, -    /// A custom static handle. -    Static(Icon<Font>), -    /// A custom dynamic handle. -    Dynamic { -        /// The [`Icon`] used when [`PickList`] is closed. -        closed: Icon<Font>, -        /// The [`Icon`] used when [`PickList`] is open. -        open: Icon<Font>, -    }, -    /// No handle will be shown. -    None, -} - -impl<Font> Default for Handle<Font> { -    fn default() -> Self { -        Self::Arrow { size: None } -    } -} - -/// The icon of a [`Handle`]. -#[derive(Debug, Clone, PartialEq)] -pub struct Icon<Font> { -    /// Font that will be used to display the `code_point`, -    pub font: Font, -    /// The unicode code point that will be used as the icon. -    pub code_point: char, -    /// Font size of the content. -    pub size: Option<f32>, -} - -/// Computes the layout of a [`PickList`]. -pub fn layout<Renderer, T>( -    renderer: &Renderer, -    limits: &layout::Limits, -    width: Length, -    padding: Padding, -    text_size: Option<f32>, -    font: Option<Renderer::Font>, -    placeholder: Option<&str>, -    options: &[T], -) -> layout::Node -where -    Renderer: text::Renderer, -    T: ToString, -{ -    use std::f32; - -    let limits = limits.width(width).height(Length::Shrink).pad(padding); -    let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - -    let max_width = match width { -        Length::Shrink => { -            let measure = |label: &str| -> f32 { -                let (width, _) = renderer.measure( -                    label, -                    text_size, -                    font.unwrap_or_else(|| renderer.default_font()), -                    Size::new(f32::INFINITY, f32::INFINITY), -                ); - -                width.round() -            }; - -            let labels = options.iter().map(ToString::to_string); - -            let labels_width = labels -                .map(|label| measure(&label)) -                .fold(100.0, |candidate, current| current.max(candidate)); - -            let placeholder_width = placeholder.map(measure).unwrap_or(100.0); - -            labels_width.max(placeholder_width) -        } -        _ => 0.0, -    }; - -    let size = { -        let intrinsic = -            Size::new(max_width + text_size + padding.left, text_size * 1.2); - -        limits.resolve(intrinsic).pad(padding) -    }; - -    layout::Node::new(size) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`PickList`] -/// accordingly. -pub fn update<'a, T, Message>( -    event: Event, -    layout: Layout<'_>, -    cursor_position: Point, -    shell: &mut Shell<'_, Message>, -    on_selected: &dyn Fn(T) -> Message, -    selected: Option<&T>, -    options: &[T], -    state: impl FnOnce() -> &'a mut State<T>, -) -> event::Status -where -    T: PartialEq + Clone + 'a, -{ -    match event { -        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerPressed { .. }) => { -            let state = state(); - -            let event_status = if state.is_open { -                // Event wasn't processed by overlay, so cursor was clicked either outside it's -                // bounds or on the drop-down, either way we close the overlay. -                state.is_open = false; - -                event::Status::Captured -            } else if layout.bounds().contains(cursor_position) { -                state.is_open = true; -                state.hovered_option = -                    options.iter().position(|option| Some(option) == selected); - -                event::Status::Captured -            } else { -                event::Status::Ignored -            }; - -            if let Some(last_selection) = state.last_selection.take() { -                shell.publish((on_selected)(last_selection)); - -                state.is_open = false; - -                event::Status::Captured -            } else { -                event_status -            } -        } -        Event::Mouse(mouse::Event::WheelScrolled { -            delta: mouse::ScrollDelta::Lines { y, .. }, -        }) => { -            let state = state(); - -            if state.keyboard_modifiers.command() -                && layout.bounds().contains(cursor_position) -                && !state.is_open -            { -                fn find_next<'a, T: PartialEq>( -                    selected: &'a T, -                    mut options: impl Iterator<Item = &'a T>, -                ) -> Option<&'a T> { -                    let _ = options.find(|&option| option == selected); - -                    options.next() -                } - -                let next_option = if y < 0.0 { -                    if let Some(selected) = selected { -                        find_next(selected, options.iter()) -                    } else { -                        options.first() -                    } -                } else if y > 0.0 { -                    if let Some(selected) = selected { -                        find_next(selected, options.iter().rev()) -                    } else { -                        options.last() -                    } -                } else { -                    None -                }; - -                if let Some(next_option) = next_option { -                    shell.publish((on_selected)(next_option.clone())); -                } - -                event::Status::Captured -            } else { -                event::Status::Ignored -            } -        } -        Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { -            let state = state(); - -            state.keyboard_modifiers = modifiers; - -            event::Status::Ignored -        } -        _ => event::Status::Ignored, -    } -} - -/// Returns the current [`mouse::Interaction`] of a [`PickList`]. -pub fn mouse_interaction( -    layout: Layout<'_>, -    cursor_position: Point, -) -> mouse::Interaction { -    let bounds = layout.bounds(); -    let is_mouse_over = bounds.contains(cursor_position); - -    if is_mouse_over { -        mouse::Interaction::Pointer -    } else { -        mouse::Interaction::default() -    } -} - -/// Returns the current overlay of a [`PickList`]. -pub fn overlay<'a, T, Message, Renderer>( -    layout: Layout<'_>, -    state: &'a mut State<T>, -    padding: Padding, -    text_size: Option<f32>, -    font: Renderer::Font, -    options: &'a [T], -    style: <Renderer::Theme as StyleSheet>::Style, -) -> Option<overlay::Element<'a, Message, Renderer>> -where -    T: Clone + ToString, -    Message: 'a, -    Renderer: text::Renderer + 'a, -    Renderer::Theme: StyleSheet -        + scrollable::StyleSheet -        + menu::StyleSheet -        + container::StyleSheet, -    <Renderer::Theme as menu::StyleSheet>::Style: -        From<<Renderer::Theme as StyleSheet>::Style>, -{ -    if state.is_open { -        let bounds = layout.bounds(); - -        let mut menu = Menu::new( -            &mut state.menu, -            options, -            &mut state.hovered_option, -            &mut state.last_selection, -        ) -        .width(bounds.width) -        .padding(padding) -        .font(font) -        .style(style); - -        if let Some(text_size) = text_size { -            menu = menu.text_size(text_size); -        } - -        Some(menu.overlay(layout.position(), bounds.height)) -    } else { -        None -    } -} - -/// Draws a [`PickList`]. -pub fn draw<'a, T, Renderer>( -    renderer: &mut Renderer, -    theme: &Renderer::Theme, -    layout: Layout<'_>, -    cursor_position: Point, -    padding: Padding, -    text_size: Option<f32>, -    font: Renderer::Font, -    placeholder: Option<&str>, -    selected: Option<&T>, -    handle: &Handle<Renderer::Font>, -    style: &<Renderer::Theme as StyleSheet>::Style, -    state: impl FnOnce() -> &'a State<T>, -) where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -    T: ToString + 'a, -{ -    let bounds = layout.bounds(); -    let is_mouse_over = bounds.contains(cursor_position); -    let is_selected = selected.is_some(); - -    let style = if is_mouse_over { -        theme.hovered(style) -    } else { -        theme.active(style) -    }; - -    renderer.fill_quad( -        renderer::Quad { -            bounds, -            border_color: style.border_color, -            border_width: style.border_width, -            border_radius: style.border_radius.into(), -        }, -        style.background, -    ); - -    let handle = match handle { -        Handle::Arrow { size } => { -            Some((Renderer::ICON_FONT, Renderer::ARROW_DOWN_ICON, *size)) -        } -        Handle::Static(Icon { -            font, -            code_point, -            size, -        }) => Some((*font, *code_point, *size)), -        Handle::Dynamic { open, closed } => { -            if state().is_open { -                Some((open.font, open.code_point, open.size)) -            } else { -                Some((closed.font, closed.code_point, closed.size)) -            } -        } -        Handle::None => None, -    }; - -    if let Some((font, code_point, size)) = handle { -        let size = size.unwrap_or_else(|| renderer.default_size()); - -        renderer.fill_text(Text { -            content: &code_point.to_string(), -            size, -            font, -            color: style.handle_color, -            bounds: Rectangle { -                x: bounds.x + bounds.width - padding.horizontal(), -                y: bounds.center_y(), -                height: size * 1.2, -                ..bounds -            }, -            horizontal_alignment: alignment::Horizontal::Right, -            vertical_alignment: alignment::Vertical::Center, -        }); -    } - -    let label = selected.map(ToString::to_string); - -    if let Some(label) = label.as_deref().or(placeholder) { -        let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - -        renderer.fill_text(Text { -            content: label, -            size: text_size, -            font, -            color: if is_selected { -                style.text_color -            } else { -                style.placeholder_color -            }, -            bounds: Rectangle { -                x: bounds.x + padding.left, -                y: bounds.center_y(), -                width: bounds.width - padding.horizontal(), -                height: text_size * 1.2, -            }, -            horizontal_alignment: alignment::Horizontal::Left, -            vertical_alignment: alignment::Vertical::Center, -        }); -    } -} diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs deleted file mode 100644 index dd46fa76..00000000 --- a/native/src/widget/progress_bar.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Provide progress feedback to your users. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget}; - -use std::ops::RangeInclusive; - -pub use iced_style::progress_bar::{Appearance, StyleSheet}; - -/// A bar that displays progress. -/// -/// # Example -/// ``` -/// # type ProgressBar = iced_native::widget::ProgressBar<iced_native::renderer::Null>; -/// let value = 50.0; -/// -/// ProgressBar::new(0.0..=100.0, value); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct ProgressBar<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    range: RangeInclusive<f32>, -    value: f32, -    width: Length, -    height: Option<Length>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Renderer> ProgressBar<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// The default height of a [`ProgressBar`]. -    pub const DEFAULT_HEIGHT: f32 = 30.0; - -    /// Creates a new [`ProgressBar`]. -    /// -    /// It expects: -    ///   * an inclusive range of possible values -    ///   * the current value of the [`ProgressBar`] -    pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { -        ProgressBar { -            value: value.clamp(*range.start(), *range.end()), -            range, -            width: Length::Fill, -            height: None, -            style: Default::default(), -        } -    } - -    /// Sets the width of the [`ProgressBar`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`ProgressBar`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = Some(height.into()); -        self -    } - -    /// Sets the style of the [`ProgressBar`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)) -    } - -    fn layout( -        &self, -        _renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits -            .width(self.width) -            .height(self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT))); - -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) -    } - -    fn draw( -        &self, -        _state: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        _cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let bounds = layout.bounds(); -        let (range_start, range_end) = self.range.clone().into_inner(); - -        let active_progress_width = if range_start >= range_end { -            0.0 -        } else { -            bounds.width * (self.value - range_start) -                / (range_end - range_start) -        }; - -        let style = theme.appearance(&self.style); - -        renderer.fill_quad( -            renderer::Quad { -                bounds: Rectangle { ..bounds }, -                border_radius: style.border_radius.into(), -                border_width: 0.0, -                border_color: Color::TRANSPARENT, -            }, -            style.background, -        ); - -        if active_progress_width > 0.0 { -            renderer.fill_quad( -                renderer::Quad { -                    bounds: Rectangle { -                        width: active_progress_width, -                        ..bounds -                    }, -                    border_radius: style.border_radius.into(), -                    border_width: 0.0, -                    border_color: Color::TRANSPARENT, -                }, -                style.bar, -            ); -        } -    } -} - -impl<'a, Message, Renderer> From<ProgressBar<Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn from( -        progress_bar: ProgressBar<Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(progress_bar) -    } -} diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs deleted file mode 100644 index 5f60eaef..00000000 --- a/native/src/widget/radio.rs +++ /dev/null @@ -1,299 +0,0 @@ -//! Create choices using radio buttons. -use crate::alignment; -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::touch; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ -    Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point, -    Rectangle, Shell, Widget, -}; - -pub use iced_style::radio::{Appearance, StyleSheet}; - -/// A circular button representing a choice. -/// -/// # Example -/// ``` -/// # type Radio<Message> = -/// #     iced_native::widget::Radio<Message, iced_native::renderer::Null>; -/// # -/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// pub enum Choice { -///     A, -///     B, -/// } -/// -/// #[derive(Debug, Clone, Copy)] -/// pub enum Message { -///     RadioSelected(Choice), -/// } -/// -/// let selected_choice = Some(Choice::A); -/// -/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); -/// -/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Radio<Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    is_selected: bool, -    on_click: Message, -    label: String, -    width: Length, -    size: f32, -    spacing: f32, -    text_size: Option<f32>, -    font: Option<Renderer::Font>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Message, Renderer> Radio<Message, Renderer> -where -    Message: Clone, -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// The default size of a [`Radio`] button. -    pub const DEFAULT_SIZE: f32 = 28.0; - -    /// The default spacing of a [`Radio`] button. -    pub const DEFAULT_SPACING: f32 = 15.0; - -    /// Creates a new [`Radio`] button. -    /// -    /// It expects: -    ///   * the value related to the [`Radio`] button -    ///   * the label of the [`Radio`] button -    ///   * the current selected value -    ///   * a function that will be called when the [`Radio`] is selected. It -    ///   receives the value of the radio and must produce a `Message`. -    pub fn new<F, V>( -        value: V, -        label: impl Into<String>, -        selected: Option<V>, -        f: F, -    ) -> Self -    where -        V: Eq + Copy, -        F: FnOnce(V) -> Message, -    { -        Radio { -            is_selected: Some(value) == selected, -            on_click: f(value), -            label: label.into(), -            width: Length::Shrink, -            size: Self::DEFAULT_SIZE, -            spacing: Self::DEFAULT_SPACING, //15 -            text_size: None, -            font: None, -            style: Default::default(), -        } -    } - -    /// Sets the size of the [`Radio`] button. -    pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = size.into().0; -        self -    } - -    /// Sets the width of the [`Radio`] button. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the spacing between the [`Radio`] button and the text. -    pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self { -        self.spacing = spacing.into().0; -        self -    } - -    /// Sets the text size of the [`Radio`] button. -    pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { -        self.text_size = Some(text_size.into().0); -        self -    } - -    /// Sets the text font of the [`Radio`] button. -    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { -        self.font = Some(font.into()); -        self -    } - -    /// Sets the style of the [`Radio`] button. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer> -where -    Message: Clone, -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        Row::<(), Renderer>::new() -            .width(self.width) -            .spacing(self.spacing) -            .align_items(Alignment::Center) -            .push(Row::new().width(self.size).height(self.size)) -            .push(Text::new(&self.label).width(self.width).size( -                self.text_size.unwrap_or_else(|| renderer.default_size()), -            )) -            .layout(renderer, limits) -    } - -    fn on_event( -        &mut self, -        _state: &mut Tree, -        event: Event, -        layout: Layout<'_>, -        cursor_position: Point, -        _renderer: &Renderer, -        _clipboard: &mut dyn Clipboard, -        shell: &mut Shell<'_, Message>, -    ) -> event::Status { -        match event { -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerPressed { .. }) => { -                if layout.bounds().contains(cursor_position) { -                    shell.publish(self.on_click.clone()); - -                    return event::Status::Captured; -                } -            } -            _ => {} -        } - -        event::Status::Ignored -    } - -    fn mouse_interaction( -        &self, -        _state: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        if layout.bounds().contains(cursor_position) { -            mouse::Interaction::Pointer -        } else { -            mouse::Interaction::default() -        } -    } - -    fn draw( -        &self, -        _state: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let bounds = layout.bounds(); -        let is_mouse_over = bounds.contains(cursor_position); - -        let mut children = layout.children(); - -        let custom_style = if is_mouse_over { -            theme.hovered(&self.style, self.is_selected) -        } else { -            theme.active(&self.style, self.is_selected) -        }; - -        { -            let layout = children.next().unwrap(); -            let bounds = layout.bounds(); - -            let size = bounds.width; -            let dot_size = size / 2.0; - -            renderer.fill_quad( -                renderer::Quad { -                    bounds, -                    border_radius: (size / 2.0).into(), -                    border_width: custom_style.border_width, -                    border_color: custom_style.border_color, -                }, -                custom_style.background, -            ); - -            if self.is_selected { -                renderer.fill_quad( -                    renderer::Quad { -                        bounds: Rectangle { -                            x: bounds.x + dot_size / 2.0, -                            y: bounds.y + dot_size / 2.0, -                            width: bounds.width - dot_size, -                            height: bounds.height - dot_size, -                        }, -                        border_radius: (dot_size / 2.0).into(), -                        border_width: 0.0, -                        border_color: Color::TRANSPARENT, -                    }, -                    custom_style.dot_color, -                ); -            } -        } - -        { -            let label_layout = children.next().unwrap(); - -            widget::text::draw( -                renderer, -                style, -                label_layout, -                &self.label, -                self.text_size, -                self.font, -                widget::text::Appearance { -                    color: custom_style.text_color, -                }, -                alignment::Horizontal::Left, -                alignment::Vertical::Center, -            ); -        } -    } -} - -impl<'a, Message, Renderer> From<Radio<Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a + Clone, -    Renderer: 'a + text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> { -        Element::new(radio) -    } -} diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs deleted file mode 100644 index 286c1c2d..00000000 --- a/native/src/widget/row.rs +++ /dev/null @@ -1,253 +0,0 @@ -//! Distribute content horizontally. -use crate::event::{self, Event}; -use crate::layout::{self, Layout}; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::widget::{Operation, Tree}; -use crate::{ -    Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, -    Shell, Widget, -}; - -/// A container that distributes its contents horizontally. -#[allow(missing_debug_implementations)] -pub struct Row<'a, Message, Renderer> { -    spacing: f32, -    padding: Padding, -    width: Length, -    height: Length, -    align_items: Alignment, -    children: Vec<Element<'a, Message, Renderer>>, -} - -impl<'a, Message, Renderer> Row<'a, Message, Renderer> { -    /// Creates an empty [`Row`]. -    pub fn new() -> Self { -        Self::with_children(Vec::new()) -    } - -    /// Creates a [`Row`] with the given elements. -    pub fn with_children( -        children: Vec<Element<'a, Message, Renderer>>, -    ) -> Self { -        Row { -            spacing: 0.0, -            padding: Padding::ZERO, -            width: Length::Shrink, -            height: Length::Shrink, -            align_items: Alignment::Start, -            children, -        } -    } - -    /// Sets the horizontal spacing _between_ elements. -    /// -    /// Custom margins per element do not exist in iced. You should use this -    /// method instead! While less flexible, it helps you keep spacing between -    /// elements consistent. -    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self { -        self.spacing = amount.into().0; -        self -    } - -    /// Sets the [`Padding`] of the [`Row`]. -    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { -        self.padding = padding.into(); -        self -    } - -    /// Sets the width of the [`Row`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Row`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the vertical alignment of the contents of the [`Row`] . -    pub fn align_items(mut self, align: Alignment) -> Self { -        self.align_items = align; -        self -    } - -    /// Adds an [`Element`] to the [`Row`]. -    pub fn push( -        mut self, -        child: impl Into<Element<'a, Message, Renderer>>, -    ) -> Self { -        self.children.push(child.into()); -        self -    } -} - -impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { -    fn default() -> Self { -        Self::new() -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Row<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -{ -    fn children(&self) -> Vec<Tree> { -        self.children.iter().map(Tree::new).collect() -    } - -    fn diff(&self, tree: &mut Tree) { -        tree.diff_children(&self.children) -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        layout::flex::resolve( -            layout::flex::Axis::Horizontal, -            renderer, -            &limits, -            self.padding, -            self.spacing, -            self.align_items, -            &self.children, -        ) -    } - -    fn operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn Operation<Message>, -    ) { -        operation.container(None, &mut |operation| { -            self.children -                .iter() -                .zip(&mut tree.children) -                .zip(layout.children()) -                .for_each(|((child, state), layout)| { -                    child -                        .as_widget() -                        .operate(state, layout, renderer, operation); -                }) -        }); -    } - -    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 { -        self.children -            .iter_mut() -            .zip(&mut tree.children) -            .zip(layout.children()) -            .map(|((child, state), layout)| { -                child.as_widget_mut().on_event( -                    state, -                    event.clone(), -                    layout, -                    cursor_position, -                    renderer, -                    clipboard, -                    shell, -                ) -            }) -            .fold(event::Status::Ignored, event::Status::merge) -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -        renderer: &Renderer, -    ) -> mouse::Interaction { -        self.children -            .iter() -            .zip(&tree.children) -            .zip(layout.children()) -            .map(|((child, state), layout)| { -                child.as_widget().mouse_interaction( -                    state, -                    layout, -                    cursor_position, -                    viewport, -                    renderer, -                ) -            }) -            .max() -            .unwrap_or_default() -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) { -        for ((child, state), layout) in self -            .children -            .iter() -            .zip(&tree.children) -            .zip(layout.children()) -        { -            child.as_widget().draw( -                state, -                renderer, -                theme, -                style, -                layout, -                cursor_position, -                viewport, -            ); -        } -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        overlay::from_children(&mut self.children, tree, layout, renderer) -    } -} - -impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: crate::Renderer + 'a, -{ -    fn from(row: Row<'a, Message, Renderer>) -> Self { -        Self::new(row) -    } -} diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs deleted file mode 100644 index 1ab6a0d3..00000000 --- a/native/src/widget/rule.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Display a horizontal or vertical rule for dividing content. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{ -    Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget, -}; - -pub use iced_style::rule::{Appearance, FillMode, StyleSheet}; - -/// Display a horizontal or vertical rule for dividing content. -#[allow(missing_debug_implementations)] -pub struct Rule<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    width: Length, -    height: Length, -    is_horizontal: bool, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Renderer> Rule<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// Creates a horizontal [`Rule`] with the given height. -    pub fn horizontal(height: impl Into<Pixels>) -> Self { -        Rule { -            width: Length::Fill, -            height: Length::Fixed(height.into().0), -            is_horizontal: true, -            style: Default::default(), -        } -    } - -    /// Creates a vertical [`Rule`] with the given width. -    pub fn vertical(width: impl Into<Pixels>) -> Self { -        Rule { -            width: Length::Fixed(width.into().0), -            height: Length::Fill, -            is_horizontal: false, -            style: Default::default(), -        } -    } - -    /// Sets the style of the [`Rule`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        _renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        layout::Node::new(limits.resolve(Size::ZERO)) -    } - -    fn draw( -        &self, -        _state: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        _cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let bounds = layout.bounds(); -        let style = theme.appearance(&self.style); - -        let bounds = if self.is_horizontal { -            let line_y = (bounds.y + (bounds.height / 2.0) -                - (style.width as f32 / 2.0)) -                .round(); - -            let (offset, line_width) = style.fill_mode.fill(bounds.width); -            let line_x = bounds.x + offset; - -            Rectangle { -                x: line_x, -                y: line_y, -                width: line_width, -                height: style.width as f32, -            } -        } else { -            let line_x = (bounds.x + (bounds.width / 2.0) -                - (style.width as f32 / 2.0)) -                .round(); - -            let (offset, line_height) = style.fill_mode.fill(bounds.height); -            let line_y = bounds.y + offset; - -            Rectangle { -                x: line_x, -                y: line_y, -                width: style.width as f32, -                height: line_height, -            } -        }; - -        renderer.fill_quad( -            renderer::Quad { -                bounds, -                border_radius: style.radius.into(), -                border_width: 0.0, -                border_color: Color::TRANSPARENT, -            }, -            style.color, -        ); -    } -} - -impl<'a, Message, Renderer> From<Rule<Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> { -        Element::new(rule) -    } -} diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs deleted file mode 100644 index c1df8c39..00000000 --- a/native/src/widget/scrollable.rs +++ /dev/null @@ -1,1327 +0,0 @@ -//! Navigate an endless amount of content with a scrollbar. -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse; -use crate::overlay; -use crate::renderer; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::{ -    Background, Clipboard, Color, Command, Element, Layout, Length, Pixels, -    Point, Rectangle, Shell, Size, Vector, Widget, -}; - -pub use iced_style::scrollable::StyleSheet; -pub use operation::scrollable::RelativeOffset; - -pub mod style { -    //! The styles of a [`Scrollable`]. -    //! -    //! [`Scrollable`]: crate::widget::Scrollable -    pub use iced_style::scrollable::{Scrollbar, Scroller}; -} - -/// A widget that can vertically display an infinite amount of content with a -/// scrollbar. -#[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    id: Option<Id>, -    height: Length, -    vertical: Properties, -    horizontal: Option<Properties>, -    content: Element<'a, Message, Renderer>, -    on_scroll: Option<Box<dyn Fn(RelativeOffset) -> Message + 'a>>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// Creates a new [`Scrollable`]. -    pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self { -        Scrollable { -            id: None, -            height: Length::Shrink, -            vertical: Properties::default(), -            horizontal: None, -            content: content.into(), -            on_scroll: None, -            style: Default::default(), -        } -    } - -    /// Sets the [`Id`] of the [`Scrollable`]. -    pub fn id(mut self, id: Id) -> Self { -        self.id = Some(id); -        self -    } - -    /// Sets the height of the [`Scrollable`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Configures the vertical scrollbar of the [`Scrollable`] . -    pub fn vertical_scroll(mut self, properties: Properties) -> Self { -        self.vertical = properties; -        self -    } - -    /// Configures the horizontal scrollbar of the [`Scrollable`] . -    pub fn horizontal_scroll(mut self, properties: Properties) -> Self { -        self.horizontal = Some(properties); -        self -    } - -    /// Sets a function to call when the [`Scrollable`] is scrolled. -    /// -    /// The function takes the new relative x & y offset of the [`Scrollable`] -    /// (e.g. `0` means beginning, while `1` means end). -    pub fn on_scroll( -        mut self, -        f: impl Fn(RelativeOffset) -> Message + 'a, -    ) -> Self { -        self.on_scroll = Some(Box::new(f)); -        self -    } - -    /// Sets the style of the [`Scrollable`] . -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -/// Properties of a scrollbar within a [`Scrollable`]. -#[derive(Debug)] -pub struct Properties { -    width: f32, -    margin: f32, -    scroller_width: f32, -} - -impl Default for Properties { -    fn default() -> Self { -        Self { -            width: 10.0, -            margin: 0.0, -            scroller_width: 10.0, -        } -    } -} - -impl Properties { -    /// Creates new [`Properties`] for use in a [`Scrollable`]. -    pub fn new() -> Self { -        Self::default() -    } - -    /// Sets the scrollbar width of the [`Scrollable`] . -    /// Silently enforces a minimum width of 1. -    pub fn width(mut self, width: impl Into<Pixels>) -> Self { -        self.width = width.into().0.max(1.0); -        self -    } - -    /// Sets the scrollbar margin of the [`Scrollable`] . -    pub fn margin(mut self, margin: impl Into<Pixels>) -> Self { -        self.margin = margin.into().0; -        self -    } - -    /// Sets the scroller width of the [`Scrollable`] . -    /// Silently enforces a minimum width of 1. -    pub fn scroller_width(mut self, scroller_width: impl Into<Pixels>) -> Self { -        self.scroller_width = scroller_width.into().0.max(1.0); -        self -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Scrollable<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State>() -    } - -    fn state(&self) -> tree::State { -        tree::State::new(State::new()) -    } - -    fn children(&self) -> Vec<Tree> { -        vec![Tree::new(&self.content)] -    } - -    fn diff(&self, tree: &mut Tree) { -        tree.diff_children(std::slice::from_ref(&self.content)) -    } - -    fn width(&self) -> Length { -        self.content.as_widget().width() -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        layout( -            renderer, -            limits, -            Widget::<Message, Renderer>::width(self), -            self.height, -            self.horizontal.is_some(), -            |renderer, limits| { -                self.content.as_widget().layout(renderer, limits) -            }, -        ) -    } - -    fn operate( -        &self, -        tree: &mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -        operation: &mut dyn Operation<Message>, -    ) { -        let state = tree.state.downcast_mut::<State>(); - -        operation.scrollable(state, self.id.as_ref().map(|id| &id.0)); - -        operation.container( -            self.id.as_ref().map(|id| &id.0), -            &mut |operation| { -                self.content.as_widget().operate( -                    &mut tree.children[0], -                    layout.children().next().unwrap(), -                    renderer, -                    operation, -                ); -            }, -        ); -    } - -    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 { -        update( -            tree.state.downcast_mut::<State>(), -            event, -            layout, -            cursor_position, -            clipboard, -            shell, -            &self.vertical, -            self.horizontal.as_ref(), -            &self.on_scroll, -            |event, layout, cursor_position, clipboard, shell| { -                self.content.as_widget_mut().on_event( -                    &mut tree.children[0], -                    event, -                    layout, -                    cursor_position, -                    renderer, -                    clipboard, -                    shell, -                ) -            }, -        ) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        draw( -            tree.state.downcast_ref::<State>(), -            renderer, -            theme, -            layout, -            cursor_position, -            &self.vertical, -            self.horizontal.as_ref(), -            &self.style, -            |renderer, layout, cursor_position, viewport| { -                self.content.as_widget().draw( -                    &tree.children[0], -                    renderer, -                    theme, -                    style, -                    layout, -                    cursor_position, -                    viewport, -                ) -            }, -        ) -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        renderer: &Renderer, -    ) -> mouse::Interaction { -        mouse_interaction( -            tree.state.downcast_ref::<State>(), -            layout, -            cursor_position, -            &self.vertical, -            self.horizontal.as_ref(), -            |layout, cursor_position, viewport| { -                self.content.as_widget().mouse_interaction( -                    &tree.children[0], -                    layout, -                    cursor_position, -                    viewport, -                    renderer, -                ) -            }, -        ) -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        self.content -            .as_widget_mut() -            .overlay( -                &mut tree.children[0], -                layout.children().next().unwrap(), -                renderer, -            ) -            .map(|overlay| { -                let bounds = layout.bounds(); -                let content_layout = layout.children().next().unwrap(); -                let content_bounds = content_layout.bounds(); -                let offset = tree -                    .state -                    .downcast_ref::<State>() -                    .offset(bounds, content_bounds); - -                overlay.translate(Vector::new(-offset.x, -offset.y)) -            }) -    } -} - -impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn from( -        text_input: Scrollable<'a, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(text_input) -    } -} - -/// The identifier of a [`Scrollable`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { -    /// Creates a custom [`Id`]. -    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self { -        Self(widget::Id::new(id)) -    } - -    /// Creates a unique [`Id`]. -    /// -    /// This function produces a different [`Id`] every time it is called. -    pub fn unique() -> Self { -        Self(widget::Id::unique()) -    } -} - -impl From<Id> for widget::Id { -    fn from(id: Id) -> Self { -        id.0 -    } -} - -/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`] -/// to the provided `percentage` along the x & y axis. -pub fn snap_to<Message: 'static>( -    id: Id, -    offset: RelativeOffset, -) -> Command<Message> { -    Command::widget(operation::scrollable::snap_to(id.0, offset)) -} - -/// Computes the layout of a [`Scrollable`]. -pub fn layout<Renderer>( -    renderer: &Renderer, -    limits: &layout::Limits, -    width: Length, -    height: Length, -    horizontal_enabled: bool, -    layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -) -> layout::Node { -    let limits = limits -        .max_height(f32::INFINITY) -        .max_width(if horizontal_enabled { -            f32::INFINITY -        } else { -            limits.max().width -        }) -        .width(width) -        .height(height); - -    let child_limits = layout::Limits::new( -        Size::new(limits.min().width, 0.0), -        Size::new( -            if horizontal_enabled { -                f32::INFINITY -            } else { -                limits.max().width -            }, -            f32::MAX, -        ), -    ); - -    let content = layout_content(renderer, &child_limits); -    let size = limits.resolve(content.size()); - -    layout::Node::with_children(size, vec![content]) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`] -/// accordingly. -pub fn update<Message>( -    state: &mut State, -    event: Event, -    layout: Layout<'_>, -    cursor_position: Point, -    clipboard: &mut dyn Clipboard, -    shell: &mut Shell<'_, Message>, -    vertical: &Properties, -    horizontal: Option<&Properties>, -    on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>, -    update_content: impl FnOnce( -        Event, -        Layout<'_>, -        Point, -        &mut dyn Clipboard, -        &mut Shell<'_, Message>, -    ) -> event::Status, -) -> event::Status { -    let bounds = layout.bounds(); -    let mouse_over_scrollable = bounds.contains(cursor_position); - -    let content = layout.children().next().unwrap(); -    let content_bounds = content.bounds(); - -    let scrollbars = -        Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); - -    let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = -        scrollbars.is_mouse_over(cursor_position); - -    let event_status = { -        let cursor_position = if mouse_over_scrollable -            && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar) -        { -            cursor_position + state.offset(bounds, content_bounds) -        } else { -            // TODO: Make `cursor_position` an `Option<Point>` so we can encode -            // cursor availability. -            // This will probably happen naturally once we add multi-window -            // support. -            Point::new(-1.0, -1.0) -        }; - -        update_content( -            event.clone(), -            content, -            cursor_position, -            clipboard, -            shell, -        ) -    }; - -    if let event::Status::Captured = event_status { -        return event::Status::Captured; -    } - -    if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = event -    { -        state.keyboard_modifiers = modifiers; - -        return event::Status::Ignored; -    } - -    if mouse_over_scrollable { -        match event { -            Event::Mouse(mouse::Event::WheelScrolled { delta }) => { -                let delta = match delta { -                    mouse::ScrollDelta::Lines { x, y } => { -                        // TODO: Configurable speed/friction (?) -                        let movement = if state.keyboard_modifiers.shift() { -                            Vector::new(y, x) -                        } else { -                            Vector::new(x, y) -                        }; - -                        movement * 60.0 -                    } -                    mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y), -                }; - -                state.scroll(delta, bounds, content_bounds); - -                notify_on_scroll( -                    state, -                    on_scroll, -                    bounds, -                    content_bounds, -                    shell, -                ); - -                return event::Status::Captured; -            } -            Event::Touch(event) -                if state.scroll_area_touched_at.is_some() -                    || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar => -            { -                match event { -                    touch::Event::FingerPressed { .. } => { -                        state.scroll_area_touched_at = Some(cursor_position); -                    } -                    touch::Event::FingerMoved { .. } => { -                        if let Some(scroll_box_touched_at) = -                            state.scroll_area_touched_at -                        { -                            let delta = Vector::new( -                                cursor_position.x - scroll_box_touched_at.x, -                                cursor_position.y - scroll_box_touched_at.y, -                            ); - -                            state.scroll(delta, bounds, content_bounds); - -                            state.scroll_area_touched_at = -                                Some(cursor_position); - -                            notify_on_scroll( -                                state, -                                on_scroll, -                                bounds, -                                content_bounds, -                                shell, -                            ); -                        } -                    } -                    touch::Event::FingerLifted { .. } -                    | touch::Event::FingerLost { .. } => { -                        state.scroll_area_touched_at = None; -                    } -                } - -                return event::Status::Captured; -            } -            _ => {} -        } -    } - -    if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { -        match event { -            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerLifted { .. }) -            | Event::Touch(touch::Event::FingerLost { .. }) => { -                state.y_scroller_grabbed_at = None; - -                return event::Status::Captured; -            } -            Event::Mouse(mouse::Event::CursorMoved { .. }) -            | Event::Touch(touch::Event::FingerMoved { .. }) => { -                if let Some(scrollbar) = scrollbars.y { -                    state.scroll_y_to( -                        scrollbar.scroll_percentage_y( -                            scroller_grabbed_at, -                            cursor_position, -                        ), -                        bounds, -                        content_bounds, -                    ); - -                    notify_on_scroll( -                        state, -                        on_scroll, -                        bounds, -                        content_bounds, -                        shell, -                    ); - -                    return event::Status::Captured; -                } -            } -            _ => {} -        } -    } else if mouse_over_y_scrollbar { -        match event { -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerPressed { .. }) => { -                if let (Some(scroller_grabbed_at), Some(scrollbar)) = -                    (scrollbars.grab_y_scroller(cursor_position), scrollbars.y) -                { -                    state.scroll_y_to( -                        scrollbar.scroll_percentage_y( -                            scroller_grabbed_at, -                            cursor_position, -                        ), -                        bounds, -                        content_bounds, -                    ); - -                    state.y_scroller_grabbed_at = Some(scroller_grabbed_at); - -                    notify_on_scroll( -                        state, -                        on_scroll, -                        bounds, -                        content_bounds, -                        shell, -                    ); -                } - -                return event::Status::Captured; -            } -            _ => {} -        } -    } - -    if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { -        match event { -            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerLifted { .. }) -            | Event::Touch(touch::Event::FingerLost { .. }) => { -                state.x_scroller_grabbed_at = None; - -                return event::Status::Captured; -            } -            Event::Mouse(mouse::Event::CursorMoved { .. }) -            | Event::Touch(touch::Event::FingerMoved { .. }) => { -                if let Some(scrollbar) = scrollbars.x { -                    state.scroll_x_to( -                        scrollbar.scroll_percentage_x( -                            scroller_grabbed_at, -                            cursor_position, -                        ), -                        bounds, -                        content_bounds, -                    ); - -                    notify_on_scroll( -                        state, -                        on_scroll, -                        bounds, -                        content_bounds, -                        shell, -                    ); -                } - -                return event::Status::Captured; -            } -            _ => {} -        } -    } else if mouse_over_x_scrollbar { -        match event { -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerPressed { .. }) => { -                if let (Some(scroller_grabbed_at), Some(scrollbar)) = -                    (scrollbars.grab_x_scroller(cursor_position), scrollbars.x) -                { -                    state.scroll_x_to( -                        scrollbar.scroll_percentage_x( -                            scroller_grabbed_at, -                            cursor_position, -                        ), -                        bounds, -                        content_bounds, -                    ); - -                    state.x_scroller_grabbed_at = Some(scroller_grabbed_at); - -                    notify_on_scroll( -                        state, -                        on_scroll, -                        bounds, -                        content_bounds, -                        shell, -                    ); - -                    return event::Status::Captured; -                } -            } -            _ => {} -        } -    } - -    event::Status::Ignored -} - -/// Computes the current [`mouse::Interaction`] of a [`Scrollable`]. -pub fn mouse_interaction( -    state: &State, -    layout: Layout<'_>, -    cursor_position: Point, -    vertical: &Properties, -    horizontal: Option<&Properties>, -    content_interaction: impl FnOnce( -        Layout<'_>, -        Point, -        &Rectangle, -    ) -> mouse::Interaction, -) -> mouse::Interaction { -    let bounds = layout.bounds(); -    let mouse_over_scrollable = bounds.contains(cursor_position); - -    let content_layout = layout.children().next().unwrap(); -    let content_bounds = content_layout.bounds(); - -    let scrollbars = -        Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); - -    let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = -        scrollbars.is_mouse_over(cursor_position); - -    if (mouse_over_x_scrollbar || mouse_over_y_scrollbar) -        || state.scrollers_grabbed() -    { -        mouse::Interaction::Idle -    } else { -        let offset = state.offset(bounds, content_bounds); - -        let cursor_position = if mouse_over_scrollable -            && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar) -        { -            cursor_position + offset -        } else { -            Point::new(-1.0, -1.0) -        }; - -        content_interaction( -            content_layout, -            cursor_position, -            &Rectangle { -                y: bounds.y + offset.y, -                x: bounds.x + offset.x, -                ..bounds -            }, -        ) -    } -} - -/// Draws a [`Scrollable`]. -pub fn draw<Renderer>( -    state: &State, -    renderer: &mut Renderer, -    theme: &Renderer::Theme, -    layout: Layout<'_>, -    cursor_position: Point, -    vertical: &Properties, -    horizontal: Option<&Properties>, -    style: &<Renderer::Theme as StyleSheet>::Style, -    draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle), -) where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    let bounds = layout.bounds(); -    let content_layout = layout.children().next().unwrap(); -    let content_bounds = content_layout.bounds(); - -    let scrollbars = -        Scrollbars::new(state, vertical, horizontal, bounds, content_bounds); - -    let mouse_over_scrollable = bounds.contains(cursor_position); -    let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) = -        scrollbars.is_mouse_over(cursor_position); - -    let offset = state.offset(bounds, content_bounds); - -    let cursor_position = if mouse_over_scrollable -        && !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) -    { -        cursor_position + offset -    } else { -        Point::new(-1.0, -1.0) -    }; - -    // Draw inner content -    if scrollbars.active() { -        renderer.with_layer(bounds, |renderer| { -            renderer.with_translation( -                Vector::new(-offset.x, -offset.y), -                |renderer| { -                    draw_content( -                        renderer, -                        content_layout, -                        cursor_position, -                        &Rectangle { -                            y: bounds.y + offset.y, -                            x: bounds.x + offset.x, -                            ..bounds -                        }, -                    ); -                }, -            ); -        }); - -        let draw_scrollbar = -            |renderer: &mut Renderer, -             style: style::Scrollbar, -             scrollbar: &Scrollbar| { -                //track -                if style.background.is_some() -                    || (style.border_color != Color::TRANSPARENT -                        && style.border_width > 0.0) -                { -                    renderer.fill_quad( -                        renderer::Quad { -                            bounds: scrollbar.bounds, -                            border_radius: style.border_radius.into(), -                            border_width: style.border_width, -                            border_color: style.border_color, -                        }, -                        style -                            .background -                            .unwrap_or(Background::Color(Color::TRANSPARENT)), -                    ); -                } - -                //thumb -                if style.scroller.color != Color::TRANSPARENT -                    || (style.scroller.border_color != Color::TRANSPARENT -                        && style.scroller.border_width > 0.0) -                { -                    renderer.fill_quad( -                        renderer::Quad { -                            bounds: scrollbar.scroller.bounds, -                            border_radius: style.scroller.border_radius.into(), -                            border_width: style.scroller.border_width, -                            border_color: style.scroller.border_color, -                        }, -                        style.scroller.color, -                    ); -                } -            }; - -        renderer.with_layer( -            Rectangle { -                width: bounds.width + 2.0, -                height: bounds.height + 2.0, -                ..bounds -            }, -            |renderer| { -                //draw y scrollbar -                if let Some(scrollbar) = scrollbars.y { -                    let style = if state.y_scroller_grabbed_at.is_some() { -                        theme.dragging(style) -                    } else if mouse_over_y_scrollbar { -                        theme.hovered(style) -                    } else { -                        theme.active(style) -                    }; - -                    draw_scrollbar(renderer, style, &scrollbar); -                } - -                //draw x scrollbar -                if let Some(scrollbar) = scrollbars.x { -                    let style = if state.x_scroller_grabbed_at.is_some() { -                        theme.dragging_horizontal(style) -                    } else if mouse_over_x_scrollbar { -                        theme.hovered_horizontal(style) -                    } else { -                        theme.active_horizontal(style) -                    }; - -                    draw_scrollbar(renderer, style, &scrollbar); -                } -            }, -        ); -    } else { -        draw_content( -            renderer, -            content_layout, -            cursor_position, -            &Rectangle { -                x: bounds.x + offset.x, -                y: bounds.y + offset.y, -                ..bounds -            }, -        ); -    } -} - -fn notify_on_scroll<Message>( -    state: &State, -    on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>, -    bounds: Rectangle, -    content_bounds: Rectangle, -    shell: &mut Shell<'_, Message>, -) { -    if let Some(on_scroll) = on_scroll { -        if content_bounds.width <= bounds.width -            && content_bounds.height <= bounds.height -        { -            return; -        } - -        let x = state.offset_x.absolute(bounds.width, content_bounds.width) -            / (content_bounds.width - bounds.width); - -        let y = state -            .offset_y -            .absolute(bounds.height, content_bounds.height) -            / (content_bounds.height - bounds.height); - -        shell.publish(on_scroll(RelativeOffset { x, y })) -    } -} - -/// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Copy)] -pub struct State { -    scroll_area_touched_at: Option<Point>, -    offset_y: Offset, -    y_scroller_grabbed_at: Option<f32>, -    offset_x: Offset, -    x_scroller_grabbed_at: Option<f32>, -    keyboard_modifiers: keyboard::Modifiers, -} - -impl Default for State { -    fn default() -> Self { -        Self { -            scroll_area_touched_at: None, -            offset_y: Offset::Absolute(0.0), -            y_scroller_grabbed_at: None, -            offset_x: Offset::Absolute(0.0), -            x_scroller_grabbed_at: None, -            keyboard_modifiers: keyboard::Modifiers::default(), -        } -    } -} - -impl operation::Scrollable for State { -    fn snap_to(&mut self, offset: RelativeOffset) { -        State::snap_to(self, offset); -    } -} - -#[derive(Debug, Clone, Copy)] -enum Offset { -    Absolute(f32), -    Relative(f32), -} - -impl Offset { -    fn absolute(self, window: f32, content: f32) -> f32 { -        match self { -            Offset::Absolute(absolute) => { -                absolute.min((content - window).max(0.0)) -            } -            Offset::Relative(percentage) => { -                ((content - window) * percentage).max(0.0) -            } -        } -    } -} - -impl State { -    /// Creates a new [`State`] with the scrollbar(s) at the beginning. -    pub fn new() -> Self { -        State::default() -    } - -    /// Apply a scrolling offset to the current [`State`], given the bounds of -    /// the [`Scrollable`] and its contents. -    pub fn scroll( -        &mut self, -        delta: Vector<f32>, -        bounds: Rectangle, -        content_bounds: Rectangle, -    ) { -        if bounds.height < content_bounds.height { -            self.offset_y = Offset::Absolute( -                (self.offset_y.absolute(bounds.height, content_bounds.height) -                    - delta.y) -                    .clamp(0.0, content_bounds.height - bounds.height), -            ) -        } - -        if bounds.width < content_bounds.width { -            self.offset_x = Offset::Absolute( -                (self.offset_x.absolute(bounds.width, content_bounds.width) -                    - delta.x) -                    .clamp(0.0, content_bounds.width - bounds.width), -            ); -        } -    } - -    /// Scrolls the [`Scrollable`] to a relative amount along the y axis. -    /// -    /// `0` represents scrollbar at the beginning, while `1` represents scrollbar at -    /// the end. -    pub fn scroll_y_to( -        &mut self, -        percentage: f32, -        bounds: Rectangle, -        content_bounds: Rectangle, -    ) { -        self.offset_y = Offset::Relative(percentage.clamp(0.0, 1.0)); -        self.unsnap(bounds, content_bounds); -    } - -    /// Scrolls the [`Scrollable`] to a relative amount along the x axis. -    /// -    /// `0` represents scrollbar at the beginning, while `1` represents scrollbar at -    /// the end. -    pub fn scroll_x_to( -        &mut self, -        percentage: f32, -        bounds: Rectangle, -        content_bounds: Rectangle, -    ) { -        self.offset_x = Offset::Relative(percentage.clamp(0.0, 1.0)); -        self.unsnap(bounds, content_bounds); -    } - -    /// Snaps the scroll position to a [`RelativeOffset`]. -    pub fn snap_to(&mut self, offset: RelativeOffset) { -        self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0)); -        self.offset_y = Offset::Relative(offset.y.clamp(0.0, 1.0)); -    } - -    /// Unsnaps the current scroll position, if snapped, given the bounds of the -    /// [`Scrollable`] and its contents. -    pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) { -        self.offset_x = Offset::Absolute( -            self.offset_x.absolute(bounds.width, content_bounds.width), -        ); -        self.offset_y = Offset::Absolute( -            self.offset_y.absolute(bounds.height, content_bounds.height), -        ); -    } - -    /// Returns the scrolling offset of the [`State`], given the bounds of the -    /// [`Scrollable`] and its contents. -    pub fn offset( -        &self, -        bounds: Rectangle, -        content_bounds: Rectangle, -    ) -> Vector { -        Vector::new( -            self.offset_x.absolute(bounds.width, content_bounds.width), -            self.offset_y.absolute(bounds.height, content_bounds.height), -        ) -    } - -    /// Returns whether any scroller is currently grabbed or not. -    pub fn scrollers_grabbed(&self) -> bool { -        self.x_scroller_grabbed_at.is_some() -            || self.y_scroller_grabbed_at.is_some() -    } -} - -#[derive(Debug)] -/// State of both [`Scrollbar`]s. -struct Scrollbars { -    y: Option<Scrollbar>, -    x: Option<Scrollbar>, -} - -impl Scrollbars { -    /// Create y and/or x scrollbar(s) if content is overflowing the [`Scrollable`] bounds. -    fn new( -        state: &State, -        vertical: &Properties, -        horizontal: Option<&Properties>, -        bounds: Rectangle, -        content_bounds: Rectangle, -    ) -> Self { -        let offset = state.offset(bounds, content_bounds); - -        let show_scrollbar_x = horizontal.and_then(|h| { -            if content_bounds.width > bounds.width { -                Some(h) -            } else { -                None -            } -        }); - -        let y_scrollbar = if content_bounds.height > bounds.height { -            let Properties { -                width, -                margin, -                scroller_width, -            } = *vertical; - -            // Adjust the height of the vertical scrollbar if the horizontal scrollbar -            // is present -            let x_scrollbar_height = show_scrollbar_x -                .map_or(0.0, |h| h.width.max(h.scroller_width) + h.margin); - -            let total_scrollbar_width = -                width.max(scroller_width) + 2.0 * margin; - -            // Total bounds of the scrollbar + margin + scroller width -            let total_scrollbar_bounds = Rectangle { -                x: bounds.x + bounds.width - total_scrollbar_width, -                y: bounds.y, -                width: total_scrollbar_width, -                height: (bounds.height - x_scrollbar_height).max(0.0), -            }; - -            // Bounds of just the scrollbar -            let scrollbar_bounds = Rectangle { -                x: bounds.x + bounds.width -                    - total_scrollbar_width / 2.0 -                    - width / 2.0, -                y: bounds.y, -                width, -                height: (bounds.height - x_scrollbar_height).max(0.0), -            }; - -            let ratio = bounds.height / content_bounds.height; -            // min height for easier grabbing with super tall content -            let scroller_height = (bounds.height * ratio).max(2.0); -            let scroller_offset = offset.y * ratio; - -            let scroller_bounds = Rectangle { -                x: bounds.x + bounds.width -                    - total_scrollbar_width / 2.0 -                    - scroller_width / 2.0, -                y: (scrollbar_bounds.y + scroller_offset - x_scrollbar_height) -                    .max(0.0), -                width: scroller_width, -                height: scroller_height, -            }; - -            Some(Scrollbar { -                total_bounds: total_scrollbar_bounds, -                bounds: scrollbar_bounds, -                scroller: Scroller { -                    bounds: scroller_bounds, -                }, -            }) -        } else { -            None -        }; - -        let x_scrollbar = if let Some(horizontal) = show_scrollbar_x { -            let Properties { -                width, -                margin, -                scroller_width, -            } = *horizontal; - -            // Need to adjust the width of the horizontal scrollbar if the vertical scrollbar -            // is present -            let scrollbar_y_width = y_scrollbar.map_or(0.0, |_| { -                vertical.width.max(vertical.scroller_width) + vertical.margin -            }); - -            let total_scrollbar_height = -                width.max(scroller_width) + 2.0 * margin; - -            // Total bounds of the scrollbar + margin + scroller width -            let total_scrollbar_bounds = Rectangle { -                x: bounds.x, -                y: bounds.y + bounds.height - total_scrollbar_height, -                width: (bounds.width - scrollbar_y_width).max(0.0), -                height: total_scrollbar_height, -            }; - -            // Bounds of just the scrollbar -            let scrollbar_bounds = Rectangle { -                x: bounds.x, -                y: bounds.y + bounds.height -                    - total_scrollbar_height / 2.0 -                    - width / 2.0, -                width: (bounds.width - scrollbar_y_width).max(0.0), -                height: width, -            }; - -            let ratio = bounds.width / content_bounds.width; -            // min width for easier grabbing with extra wide content -            let scroller_length = (bounds.width * ratio).max(2.0); -            let scroller_offset = offset.x * ratio; - -            let scroller_bounds = Rectangle { -                x: (scrollbar_bounds.x + scroller_offset - scrollbar_y_width) -                    .max(0.0), -                y: bounds.y + bounds.height -                    - total_scrollbar_height / 2.0 -                    - scroller_width / 2.0, -                width: scroller_length, -                height: scroller_width, -            }; - -            Some(Scrollbar { -                total_bounds: total_scrollbar_bounds, -                bounds: scrollbar_bounds, -                scroller: Scroller { -                    bounds: scroller_bounds, -                }, -            }) -        } else { -            None -        }; - -        Self { -            y: y_scrollbar, -            x: x_scrollbar, -        } -    } - -    fn is_mouse_over(&self, cursor_position: Point) -> (bool, bool) { -        ( -            self.y -                .as_ref() -                .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) -                .unwrap_or(false), -            self.x -                .as_ref() -                .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) -                .unwrap_or(false), -        ) -    } - -    fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> { -        self.y.and_then(|scrollbar| { -            if scrollbar.total_bounds.contains(cursor_position) { -                Some(if scrollbar.scroller.bounds.contains(cursor_position) { -                    (cursor_position.y - scrollbar.scroller.bounds.y) -                        / scrollbar.scroller.bounds.height -                } else { -                    0.5 -                }) -            } else { -                None -            } -        }) -    } - -    fn grab_x_scroller(&self, cursor_position: Point) -> Option<f32> { -        self.x.and_then(|scrollbar| { -            if scrollbar.total_bounds.contains(cursor_position) { -                Some(if scrollbar.scroller.bounds.contains(cursor_position) { -                    (cursor_position.x - scrollbar.scroller.bounds.x) -                        / scrollbar.scroller.bounds.width -                } else { -                    0.5 -                }) -            } else { -                None -            } -        }) -    } - -    fn active(&self) -> bool { -        self.y.is_some() || self.x.is_some() -    } -} - -/// The scrollbar of a [`Scrollable`]. -#[derive(Debug, Copy, Clone)] -struct Scrollbar { -    /// The total bounds of the [`Scrollbar`], including the scrollbar, the scroller, -    /// and the scrollbar margin. -    total_bounds: Rectangle, - -    /// The bounds of just the [`Scrollbar`]. -    bounds: Rectangle, - -    /// The state of this scrollbar's [`Scroller`]. -    scroller: Scroller, -} - -impl Scrollbar { -    /// Returns whether the mouse is over the scrollbar or not. -    fn is_mouse_over(&self, cursor_position: Point) -> bool { -        self.total_bounds.contains(cursor_position) -    } - -    /// Returns the y-axis scrolled percentage from the cursor position. -    fn scroll_percentage_y( -        &self, -        grabbed_at: f32, -        cursor_position: Point, -    ) -> f32 { -        if cursor_position.x < 0.0 && cursor_position.y < 0.0 { -            // cursor position is unavailable! Set to either end or beginning of scrollbar depending -            // on where the thumb currently is in the track -            (self.scroller.bounds.y / self.total_bounds.height).round() -        } else { -            (cursor_position.y -                - self.bounds.y -                - self.scroller.bounds.height * grabbed_at) -                / (self.bounds.height - self.scroller.bounds.height) -        } -    } - -    /// Returns the x-axis scrolled percentage from the cursor position. -    fn scroll_percentage_x( -        &self, -        grabbed_at: f32, -        cursor_position: Point, -    ) -> f32 { -        if cursor_position.x < 0.0 && cursor_position.y < 0.0 { -            (self.scroller.bounds.x / self.total_bounds.width).round() -        } else { -            (cursor_position.x -                - self.bounds.x -                - self.scroller.bounds.width * grabbed_at) -                / (self.bounds.width - self.scroller.bounds.width) -        } -    } -} - -/// The handle of a [`Scrollbar`]. -#[derive(Debug, Clone, Copy)] -struct Scroller { -    /// The bounds of the [`Scroller`]. -    bounds: Rectangle, -} diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs deleted file mode 100644 index d3715b1c..00000000 --- a/native/src/widget/slider.rs +++ /dev/null @@ -1,473 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. -use crate::event::{self, Event}; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::touch; -use crate::widget::tree::{self, Tree}; -use crate::{ -    Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, -    Rectangle, Shell, Size, Widget, -}; - -use std::ops::RangeInclusive; - -pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// The [`Slider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_native::widget::slider; -/// # use iced_native::renderer::Null; -/// # -/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>; -/// # -/// #[derive(Clone)] -/// pub enum Message { -///     SliderChanged(f32), -/// } -/// -/// let value = 50.0; -/// -/// Slider::new(0.0..=100.0, value, Message::SliderChanged); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    range: RangeInclusive<T>, -    step: T, -    value: T, -    on_change: Box<dyn Fn(T) -> Message + 'a>, -    on_release: Option<Message>, -    width: Length, -    height: f32, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> -where -    T: Copy + From<u8> + std::cmp::PartialOrd, -    Message: Clone, -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// The default height of a [`Slider`]. -    pub const DEFAULT_HEIGHT: f32 = 22.0; - -    /// Creates a new [`Slider`]. -    /// -    /// It expects: -    ///   * an inclusive range of possible values -    ///   * the current value of the [`Slider`] -    ///   * a function that will be called when the [`Slider`] is dragged. -    ///   It receives the new value of the [`Slider`] and must produce a -    ///   `Message`. -    pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self -    where -        F: 'a + Fn(T) -> Message, -    { -        let value = if value >= *range.start() { -            value -        } else { -            *range.start() -        }; - -        let value = if value <= *range.end() { -            value -        } else { -            *range.end() -        }; - -        Slider { -            value, -            range, -            step: T::from(1), -            on_change: Box::new(on_change), -            on_release: None, -            width: Length::Fill, -            height: Self::DEFAULT_HEIGHT, -            style: Default::default(), -        } -    } - -    /// Sets the release message of the [`Slider`]. -    /// This is called when the mouse is released from the slider. -    /// -    /// Typically, the user's interaction with the slider is finished when this message is produced. -    /// This is useful if you need to spawn a long-running task from the slider's result, where -    /// the default on_change message could create too many events. -    pub fn on_release(mut self, on_release: Message) -> Self { -        self.on_release = Some(on_release); -        self -    } - -    /// Sets the width of the [`Slider`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Slider`]. -    pub fn height(mut self, height: impl Into<Pixels>) -> Self { -        self.height = height.into().0; -        self -    } - -    /// Sets the style of the [`Slider`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } - -    /// Sets the step size of the [`Slider`]. -    pub fn step(mut self, step: T) -> Self { -        self.step = step; -        self -    } -} - -impl<'a, T, Message, Renderer> Widget<Message, Renderer> -    for Slider<'a, T, Message, Renderer> -where -    T: Copy + Into<f64> + num_traits::FromPrimitive, -    Message: Clone, -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State>() -    } - -    fn state(&self) -> tree::State { -        tree::State::new(State::new()) -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink -    } - -    fn layout( -        &self, -        _renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) -    } - -    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 { -        update( -            event, -            layout, -            cursor_position, -            shell, -            tree.state.downcast_mut::<State>(), -            &mut self.value, -            &self.range, -            self.step, -            self.on_change.as_ref(), -            &self.on_release, -        ) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        draw( -            renderer, -            layout, -            cursor_position, -            tree.state.downcast_ref::<State>(), -            self.value, -            &self.range, -            theme, -            &self.style, -        ) -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        mouse_interaction( -            layout, -            cursor_position, -            tree.state.downcast_ref::<State>(), -        ) -    } -} - -impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, -    Message: 'a + Clone, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn from( -        slider: Slider<'a, T, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(slider) -    } -} - -/// Processes an [`Event`] and updates the [`State`] of a [`Slider`] -/// accordingly. -pub fn update<Message, T>( -    event: Event, -    layout: Layout<'_>, -    cursor_position: Point, -    shell: &mut Shell<'_, Message>, -    state: &mut State, -    value: &mut T, -    range: &RangeInclusive<T>, -    step: T, -    on_change: &dyn Fn(T) -> Message, -    on_release: &Option<Message>, -) -> event::Status -where -    T: Copy + Into<f64> + num_traits::FromPrimitive, -    Message: Clone, -{ -    let is_dragging = state.is_dragging; - -    let mut change = || { -        let bounds = layout.bounds(); -        let new_value = if cursor_position.x <= bounds.x { -            *range.start() -        } else if cursor_position.x >= bounds.x + bounds.width { -            *range.end() -        } else { -            let step = step.into(); -            let start = (*range.start()).into(); -            let end = (*range.end()).into(); - -            let percent = f64::from(cursor_position.x - bounds.x) -                / f64::from(bounds.width); - -            let steps = (percent * (end - start) / step).round(); -            let value = steps * step + start; - -            if let Some(value) = T::from_f64(value) { -                value -            } else { -                return; -            } -        }; - -        if ((*value).into() - new_value.into()).abs() > f64::EPSILON { -            shell.publish((on_change)(new_value)); - -            *value = new_value; -        } -    }; - -    match event { -        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerPressed { .. }) => { -            if layout.bounds().contains(cursor_position) { -                change(); -                state.is_dragging = true; - -                return event::Status::Captured; -            } -        } -        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerLifted { .. }) -        | Event::Touch(touch::Event::FingerLost { .. }) => { -            if is_dragging { -                if let Some(on_release) = on_release.clone() { -                    shell.publish(on_release); -                } -                state.is_dragging = false; - -                return event::Status::Captured; -            } -        } -        Event::Mouse(mouse::Event::CursorMoved { .. }) -        | Event::Touch(touch::Event::FingerMoved { .. }) => { -            if is_dragging { -                change(); - -                return event::Status::Captured; -            } -        } -        _ => {} -    } - -    event::Status::Ignored -} - -/// Draws a [`Slider`]. -pub fn draw<T, R>( -    renderer: &mut R, -    layout: Layout<'_>, -    cursor_position: Point, -    state: &State, -    value: T, -    range: &RangeInclusive<T>, -    style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, -    style: &<R::Theme as StyleSheet>::Style, -) where -    T: Into<f64> + Copy, -    R: crate::Renderer, -    R::Theme: StyleSheet, -{ -    let bounds = layout.bounds(); -    let is_mouse_over = bounds.contains(cursor_position); - -    let style = if state.is_dragging { -        style_sheet.dragging(style) -    } else if is_mouse_over { -        style_sheet.hovered(style) -    } else { -        style_sheet.active(style) -    }; - -    let rail_y = bounds.y + (bounds.height / 2.0).round(); - -    renderer.fill_quad( -        renderer::Quad { -            bounds: Rectangle { -                x: bounds.x, -                y: rail_y - 1.0, -                width: bounds.width, -                height: 2.0, -            }, -            border_radius: 0.0.into(), -            border_width: 0.0, -            border_color: Color::TRANSPARENT, -        }, -        style.rail_colors.0, -    ); - -    renderer.fill_quad( -        renderer::Quad { -            bounds: Rectangle { -                x: bounds.x, -                y: rail_y + 1.0, -                width: bounds.width, -                height: 2.0, -            }, -            border_radius: 0.0.into(), -            border_width: 0.0, -            border_color: Color::TRANSPARENT, -        }, -        Background::Color(style.rail_colors.1), -    ); - -    let (handle_width, handle_height, handle_border_radius) = match style -        .handle -        .shape -    { -        HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius), -        HandleShape::Rectangle { -            width, -            border_radius, -        } => (f32::from(width), bounds.height, border_radius), -    }; - -    let value = value.into() as f32; -    let (range_start, range_end) = { -        let (start, end) = range.clone().into_inner(); - -        (start.into() as f32, end.into() as f32) -    }; - -    let handle_offset = if range_start >= range_end { -        0.0 -    } else { -        (bounds.width - handle_width) * (value - range_start) -            / (range_end - range_start) -    }; - -    renderer.fill_quad( -        renderer::Quad { -            bounds: Rectangle { -                x: bounds.x + handle_offset.round(), -                y: rail_y - handle_height / 2.0, -                width: handle_width, -                height: handle_height, -            }, -            border_radius: handle_border_radius.into(), -            border_width: style.handle.border_width, -            border_color: style.handle.border_color, -        }, -        style.handle.color, -    ); -} - -/// Computes the current [`mouse::Interaction`] of a [`Slider`]. -pub fn mouse_interaction( -    layout: Layout<'_>, -    cursor_position: Point, -    state: &State, -) -> mouse::Interaction { -    let bounds = layout.bounds(); -    let is_mouse_over = bounds.contains(cursor_position); - -    if state.is_dragging { -        mouse::Interaction::Grabbing -    } else if is_mouse_over { -        mouse::Interaction::Grab -    } else { -        mouse::Interaction::default() -    } -} - -/// The local state of a [`Slider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { -    is_dragging: bool, -} - -impl State { -    /// Creates a new [`State`]. -    pub fn new() -> State { -        State::default() -    } -} diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs deleted file mode 100644 index a6fc977e..00000000 --- a/native/src/widget/space.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Distribute content vertically. -use crate::layout; -use crate::renderer; -use crate::widget::Tree; -use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget}; - -/// An amount of empty space. -/// -/// It can be useful if you want to fill some space with nothing. -#[derive(Debug)] -pub struct Space { -    width: Length, -    height: Length, -} - -impl Space { -    /// Creates an amount of empty [`Space`] with the given width and height. -    pub fn new(width: impl Into<Length>, height: impl Into<Length>) -> Self { -        Space { -            width: width.into(), -            height: height.into(), -        } -    } - -    /// Creates an amount of horizontal [`Space`]. -    pub fn with_width(width: impl Into<Length>) -> Self { -        Space { -            width: width.into(), -            height: Length::Shrink, -        } -    } - -    /// Creates an amount of vertical [`Space`]. -    pub fn with_height(height: impl Into<Length>) -> Self { -        Space { -            width: Length::Shrink, -            height: height.into(), -        } -    } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Space -where -    Renderer: crate::Renderer, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        _renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        layout::Node::new(limits.resolve(Size::ZERO)) -    } - -    fn draw( -        &self, -        _state: &Tree, -        _renderer: &mut Renderer, -        _theme: &Renderer::Theme, -        _style: &renderer::Style, -        _layout: Layout<'_>, -        _cursor_position: Point, -        _viewport: &Rectangle, -    ) { -    } -} - -impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -    Message: 'a, -{ -    fn from(space: Space) -> Element<'a, Message, Renderer> { -        Element::new(space) -    } -} diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs deleted file mode 100644 index f5ed0a6c..00000000 --- a/native/src/widget/svg.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Display vector graphics in your application. -use crate::layout; -use crate::renderer; -use crate::svg; -use crate::widget::Tree; -use crate::{ -    ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, -}; - -use std::path::PathBuf; - -pub use iced_style::svg::{Appearance, StyleSheet}; -pub use svg::Handle; - -/// A vector graphics image. -/// -/// An [`Svg`] image resizes smoothly without losing any quality. -/// -/// [`Svg`] images can have a considerable rendering cost when resized, -/// specially when they are complex. -#[allow(missing_debug_implementations)] -pub struct Svg<Renderer> -where -    Renderer: svg::Renderer, -    Renderer::Theme: StyleSheet, -{ -    handle: Handle, -    width: Length, -    height: Length, -    content_fit: ContentFit, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<Renderer> Svg<Renderer> -where -    Renderer: svg::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// Creates a new [`Svg`] from the given [`Handle`]. -    pub fn new(handle: impl Into<Handle>) -> Self { -        Svg { -            handle: handle.into(), -            width: Length::Fill, -            height: Length::Shrink, -            content_fit: ContentFit::Contain, -            style: Default::default(), -        } -    } - -    /// Creates a new [`Svg`] that will display the contents of the file at the -    /// provided path. -    #[must_use] -    pub fn from_path(path: impl Into<PathBuf>) -> Self { -        Self::new(Handle::from_path(path)) -    } - -    /// Sets the width of the [`Svg`]. -    #[must_use] -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Svg`]. -    #[must_use] -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the [`ContentFit`] of the [`Svg`]. -    /// -    /// Defaults to [`ContentFit::Contain`] -    #[must_use] -    pub fn content_fit(self, content_fit: ContentFit) -> Self { -        Self { -            content_fit, -            ..self -        } -    } - -    /// Sets the style variant of this [`Svg`]. -    #[must_use] -    pub fn style( -        mut self, -        style: <Renderer::Theme as StyleSheet>::Style, -    ) -> Self { -        self.style = style; -        self -    } -} - -impl<Message, Renderer> Widget<Message, Renderer> for Svg<Renderer> -where -    Renderer: svg::Renderer, -    Renderer::Theme: iced_style::svg::StyleSheet, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        // The raw w/h of the underlying image -        let Size { width, height } = renderer.dimensions(&self.handle); -        let image_size = Size::new(width as f32, height as f32); - -        // The size to be available to the widget prior to `Shrink`ing -        let raw_size = limits -            .width(self.width) -            .height(self.height) -            .resolve(image_size); - -        // The uncropped size of the image when fit to the bounds above -        let full_size = self.content_fit.fit(image_size, raw_size); - -        // Shrink the widget to fit the resized image, if requested -        let final_size = Size { -            width: match self.width { -                Length::Shrink => f32::min(raw_size.width, full_size.width), -                _ => raw_size.width, -            }, -            height: match self.height { -                Length::Shrink => f32::min(raw_size.height, full_size.height), -                _ => raw_size.height, -            }, -        }; - -        layout::Node::new(final_size) -    } - -    fn draw( -        &self, -        _state: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        _cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        let Size { width, height } = renderer.dimensions(&self.handle); -        let image_size = Size::new(width as f32, height as f32); - -        let bounds = layout.bounds(); -        let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); - -        let render = |renderer: &mut Renderer| { -            let offset = Vector::new( -                (bounds.width - adjusted_fit.width).max(0.0) / 2.0, -                (bounds.height - adjusted_fit.height).max(0.0) / 2.0, -            ); - -            let drawing_bounds = Rectangle { -                width: adjusted_fit.width, -                height: adjusted_fit.height, -                ..bounds -            }; - -            let appearance = theme.appearance(&self.style); - -            renderer.draw( -                self.handle.clone(), -                appearance.color, -                drawing_bounds + offset, -            ); -        }; - -        if adjusted_fit.width > bounds.width -            || adjusted_fit.height > bounds.height -        { -            renderer.with_layer(bounds, render); -        } else { -            render(renderer); -        } -    } -} - -impl<'a, Message, Renderer> From<Svg<Renderer>> -    for Element<'a, Message, Renderer> -where -    Renderer: svg::Renderer + 'a, -    Renderer::Theme: iced_style::svg::StyleSheet, -{ -    fn from(icon: Svg<Renderer>) -> Element<'a, Message, Renderer> { -        Element::new(icon) -    } -} diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs deleted file mode 100644 index aede754a..00000000 --- a/native/src/widget/text.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! Write some text for your users to read. -use crate::alignment; -use crate::layout; -use crate::renderer; -use crate::text; -use crate::widget::Tree; -use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget}; - -use std::borrow::Cow; - -pub use iced_style::text::{Appearance, StyleSheet}; - -/// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_native::Color; -/// # -/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; -/// # -/// Text::new("I <3 iced!") -///     .size(40) -///     .style(Color::from([0.0, 0.0, 1.0])); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Text<'a, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    content: Cow<'a, str>, -    size: Option<f32>, -    width: Length, -    height: Length, -    horizontal_alignment: alignment::Horizontal, -    vertical_alignment: alignment::Vertical, -    font: Option<Renderer::Font>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Renderer> Text<'a, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// Create a new fragment of [`Text`] with the given contents. -    pub fn new(content: impl Into<Cow<'a, str>>) -> Self { -        Text { -            content: content.into(), -            size: None, -            font: None, -            width: Length::Shrink, -            height: Length::Shrink, -            horizontal_alignment: alignment::Horizontal::Left, -            vertical_alignment: alignment::Vertical::Top, -            style: Default::default(), -        } -    } - -    /// Sets the size of the [`Text`]. -    pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = Some(size.into().0); -        self -    } - -    /// Sets the [`Font`] of the [`Text`]. -    /// -    /// [`Font`]: crate::text::Renderer::Font -    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { -        self.font = Some(font.into()); -        self -    } - -    /// Sets the style of the [`Text`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } - -    /// Sets the width of the [`Text`] boundaries. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the height of the [`Text`] boundaries. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the [`alignment::Horizontal`] of the [`Text`]. -    pub fn horizontal_alignment( -        mut self, -        alignment: alignment::Horizontal, -    ) -> Self { -        self.horizontal_alignment = alignment; -        self -    } - -    /// Sets the [`alignment::Vertical`] of the [`Text`]. -    pub fn vertical_alignment( -        mut self, -        alignment: alignment::Vertical, -    ) -> Self { -        self.vertical_alignment = alignment; -        self -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        let size = self.size.unwrap_or_else(|| renderer.default_size()); - -        let bounds = limits.max(); - -        let (width, height) = renderer.measure( -            &self.content, -            size, -            self.font.unwrap_or_else(|| renderer.default_font()), -            bounds, -        ); - -        let size = limits.resolve(Size::new(width, height)); - -        layout::Node::new(size) -    } - -    fn draw( -        &self, -        _state: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        _cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        draw( -            renderer, -            style, -            layout, -            &self.content, -            self.size, -            self.font, -            theme.appearance(self.style), -            self.horizontal_alignment, -            self.vertical_alignment, -        ); -    } -} - -/// Draws text using the same logic as the [`Text`] widget. -/// -/// Specifically: -/// -/// * If no `size` is provided, the default text size of the `Renderer` will be -///   used. -/// * If no `color` is provided, the [`renderer::Style::text_color`] will be -///   used. -/// * The alignment attributes do not affect the position of the bounds of the -///   [`Layout`]. -pub fn draw<Renderer>( -    renderer: &mut Renderer, -    style: &renderer::Style, -    layout: Layout<'_>, -    content: &str, -    size: Option<f32>, -    font: Option<Renderer::Font>, -    appearance: Appearance, -    horizontal_alignment: alignment::Horizontal, -    vertical_alignment: alignment::Vertical, -) where -    Renderer: text::Renderer, -{ -    let bounds = layout.bounds(); - -    let x = match horizontal_alignment { -        alignment::Horizontal::Left => bounds.x, -        alignment::Horizontal::Center => bounds.center_x(), -        alignment::Horizontal::Right => bounds.x + bounds.width, -    }; - -    let y = match vertical_alignment { -        alignment::Vertical::Top => bounds.y, -        alignment::Vertical::Center => bounds.center_y(), -        alignment::Vertical::Bottom => bounds.y + bounds.height, -    }; - -    renderer.fill_text(crate::text::Text { -        content, -        size: size.unwrap_or_else(|| renderer.default_size()), -        bounds: Rectangle { x, y, ..bounds }, -        color: appearance.color.unwrap_or(style.text_color), -        font: font.unwrap_or_else(|| renderer.default_font()), -        horizontal_alignment, -        vertical_alignment, -    }); -} - -impl<'a, Message, Renderer> From<Text<'a, Renderer>> -    for Element<'a, Message, Renderer> -where -    Renderer: text::Renderer + 'a, -    Renderer::Theme: StyleSheet, -{ -    fn from(text: Text<'a, Renderer>) -> Element<'a, Message, Renderer> { -        Element::new(text) -    } -} - -impl<'a, Renderer> Clone for Text<'a, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn clone(&self) -> Self { -        Self { -            content: self.content.clone(), -            size: self.size, -            width: self.width, -            height: self.height, -            horizontal_alignment: self.horizontal_alignment, -            vertical_alignment: self.vertical_alignment, -            font: self.font, -            style: self.style, -        } -    } -} - -impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer> -where -    Renderer: text::Renderer + 'a, -    Renderer::Theme: StyleSheet, -{ -    fn from(contents: &'a str) -> Self { -        Text::new(contents).into() -    } -} diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs deleted file mode 100644 index 65a9bd3b..00000000 --- a/native/src/widget/text_input.rs +++ /dev/null @@ -1,1218 +0,0 @@ -//! Display fields that can be filled with text. -//! -//! A [`TextInput`] has some local [`State`]. -mod editor; -mod value; - -pub mod cursor; - -pub use cursor::Cursor; -pub use value::Value; - -use editor::Editor; - -use crate::alignment; -use crate::event::{self, Event}; -use crate::keyboard; -use crate::layout; -use crate::mouse::{self, click}; -use crate::renderer; -use crate::text::{self, Text}; -use crate::time::{Duration, Instant}; -use crate::touch; -use crate::widget; -use crate::widget::operation::{self, Operation}; -use crate::widget::tree::{self, Tree}; -use crate::window; -use crate::{ -    Clipboard, Color, Command, Element, Layout, Length, Padding, Pixels, Point, -    Rectangle, Shell, Size, Vector, Widget, -}; - -pub use iced_style::text_input::{Appearance, StyleSheet}; - -/// A field that can be filled with text. -/// -/// # Example -/// ``` -/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>; -/// #[derive(Debug, Clone)] -/// enum Message { -///     TextInputChanged(String), -/// } -/// -/// let value = "Some text"; -/// -/// let input = TextInput::new( -///     "This is the placeholder...", -///     value, -///     Message::TextInputChanged, -/// ) -/// .padding(10); -/// ``` -///  -#[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    id: Option<Id>, -    placeholder: String, -    value: Value, -    is_secure: bool, -    font: Option<Renderer::Font>, -    width: Length, -    padding: Padding, -    size: Option<f32>, -    on_change: Box<dyn Fn(String) -> Message + 'a>, -    on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>, -    on_submit: Option<Message>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> TextInput<'a, Message, Renderer> -where -    Message: Clone, -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// Creates a new [`TextInput`]. -    /// -    /// It expects: -    /// - a placeholder, -    /// - the current value, and -    /// - a function that produces a message when the [`TextInput`] changes. -    pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self -    where -        F: 'a + Fn(String) -> Message, -    { -        TextInput { -            id: None, -            placeholder: String::from(placeholder), -            value: Value::new(value), -            is_secure: false, -            font: None, -            width: Length::Fill, -            padding: Padding::new(5.0), -            size: None, -            on_change: Box::new(on_change), -            on_paste: None, -            on_submit: None, -            style: Default::default(), -        } -    } - -    /// Sets the [`Id`] of the [`TextInput`]. -    pub fn id(mut self, id: Id) -> Self { -        self.id = Some(id); -        self -    } - -    /// Converts the [`TextInput`] into a secure password input. -    pub fn password(mut self) -> Self { -        self.is_secure = true; -        self -    } - -    /// Sets the message that should be produced when some text is pasted into -    /// the [`TextInput`]. -    pub fn on_paste( -        mut self, -        on_paste: impl Fn(String) -> Message + 'a, -    ) -> Self { -        self.on_paste = Some(Box::new(on_paste)); -        self -    } - -    /// Sets the [`Font`] of the [`TextInput`]. -    /// -    /// [`Font`]: text::Renderer::Font -    pub fn font(mut self, font: Renderer::Font) -> Self { -        self.font = Some(font); -        self -    } -    /// Sets the width of the [`TextInput`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the [`Padding`] of the [`TextInput`]. -    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { -        self.padding = padding.into(); -        self -    } - -    /// Sets the text size of the [`TextInput`]. -    pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = Some(size.into().0); -        self -    } - -    /// Sets the message that should be produced when the [`TextInput`] is -    /// focused and the enter key is pressed. -    pub fn on_submit(mut self, message: Message) -> Self { -        self.on_submit = Some(message); -        self -    } - -    /// Sets the style of the [`TextInput`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } - -    /// Draws the [`TextInput`] with the given [`Renderer`], overriding its -    /// [`Value`] if provided. -    /// -    /// [`Renderer`]: text::Renderer -    pub fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        layout: Layout<'_>, -        cursor_position: Point, -        value: Option<&Value>, -    ) { -        draw( -            renderer, -            theme, -            layout, -            cursor_position, -            tree.state.downcast_ref::<State>(), -            value.unwrap_or(&self.value), -            &self.placeholder, -            self.size, -            self.font, -            self.is_secure, -            &self.style, -        ) -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for TextInput<'a, Message, Renderer> -where -    Message: Clone, -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State>() -    } - -    fn state(&self) -> tree::State { -        tree::State::new(State::new()) -    } - -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        layout(renderer, limits, self.width, self.padding, self.size) -    } - -    fn operate( -        &self, -        tree: &mut Tree, -        _layout: Layout<'_>, -        _renderer: &Renderer, -        operation: &mut dyn Operation<Message>, -    ) { -        let state = tree.state.downcast_mut::<State>(); - -        operation.focusable(state, self.id.as_ref().map(|id| &id.0)); -        operation.text_input(state, self.id.as_ref().map(|id| &id.0)); -    } - -    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 { -        update( -            event, -            layout, -            cursor_position, -            renderer, -            clipboard, -            shell, -            &mut self.value, -            self.size, -            self.font, -            self.is_secure, -            self.on_change.as_ref(), -            self.on_paste.as_deref(), -            &self.on_submit, -            || tree.state.downcast_mut::<State>(), -        ) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        draw( -            renderer, -            theme, -            layout, -            cursor_position, -            tree.state.downcast_ref::<State>(), -            &self.value, -            &self.placeholder, -            self.size, -            self.font, -            self.is_secure, -            &self.style, -        ) -    } - -    fn mouse_interaction( -        &self, -        _state: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        mouse_interaction(layout, cursor_position) -    } -} - -impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a + Clone, -    Renderer: 'a + text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn from( -        text_input: TextInput<'a, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(text_input) -    } -} - -/// The identifier of a [`TextInput`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Id(widget::Id); - -impl Id { -    /// Creates a custom [`Id`]. -    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self { -        Self(widget::Id::new(id)) -    } - -    /// Creates a unique [`Id`]. -    /// -    /// This function produces a different [`Id`] every time it is called. -    pub fn unique() -> Self { -        Self(widget::Id::unique()) -    } -} - -impl From<Id> for widget::Id { -    fn from(id: Id) -> Self { -        id.0 -    } -} - -/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. -pub fn focus<Message: 'static>(id: Id) -> Command<Message> { -    Command::widget(operation::focusable::focus(id.0)) -} - -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the -/// end. -pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> { -    Command::widget(operation::text_input::move_cursor_to_end(id.0)) -} - -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the -/// front. -pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> { -    Command::widget(operation::text_input::move_cursor_to_front(id.0)) -} - -/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the -/// provided position. -pub fn move_cursor_to<Message: 'static>( -    id: Id, -    position: usize, -) -> Command<Message> { -    Command::widget(operation::text_input::move_cursor_to(id.0, position)) -} - -/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`]. -pub fn select_all<Message: 'static>(id: Id) -> Command<Message> { -    Command::widget(operation::text_input::select_all(id.0)) -} - -/// Computes the layout of a [`TextInput`]. -pub fn layout<Renderer>( -    renderer: &Renderer, -    limits: &layout::Limits, -    width: Length, -    padding: Padding, -    size: Option<f32>, -) -> layout::Node -where -    Renderer: text::Renderer, -{ -    let text_size = size.unwrap_or_else(|| renderer.default_size()); -    let padding = padding.fit(Size::ZERO, limits.max()); -    let limits = limits.width(width).pad(padding).height(text_size * 1.2); - -    let mut text = layout::Node::new(limits.resolve(Size::ZERO)); -    text.move_to(Point::new(padding.left, padding.top)); - -    layout::Node::with_children(text.size().pad(padding), vec![text]) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`] -/// accordingly. -pub fn update<'a, Message, Renderer>( -    event: Event, -    layout: Layout<'_>, -    cursor_position: Point, -    renderer: &Renderer, -    clipboard: &mut dyn Clipboard, -    shell: &mut Shell<'_, Message>, -    value: &mut Value, -    size: Option<f32>, -    font: Option<Renderer::Font>, -    is_secure: bool, -    on_change: &dyn Fn(String) -> Message, -    on_paste: Option<&dyn Fn(String) -> Message>, -    on_submit: &Option<Message>, -    state: impl FnOnce() -> &'a mut State, -) -> event::Status -where -    Message: Clone, -    Renderer: text::Renderer, -{ -    match event { -        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerPressed { .. }) => { -            let state = state(); -            let is_clicked = layout.bounds().contains(cursor_position); - -            state.is_focused = if is_clicked { -                state.is_focused.or_else(|| { -                    let now = Instant::now(); - -                    Some(Focus { -                        updated_at: now, -                        now, -                    }) -                }) -            } else { -                None -            }; - -            if is_clicked { -                let text_layout = layout.children().next().unwrap(); -                let target = cursor_position.x - text_layout.bounds().x; - -                let click = -                    mouse::Click::new(cursor_position, state.last_click); - -                match click.kind() { -                    click::Kind::Single => { -                        let position = if target > 0.0 { -                            let value = if is_secure { -                                value.secure() -                            } else { -                                value.clone() -                            }; - -                            find_cursor_position( -                                renderer, -                                text_layout.bounds(), -                                font, -                                size, -                                &value, -                                state, -                                target, -                            ) -                        } else { -                            None -                        } -                        .unwrap_or(0); - -                        if state.keyboard_modifiers.shift() { -                            state.cursor.select_range( -                                state.cursor.start(value), -                                position, -                            ); -                        } else { -                            state.cursor.move_to(position); -                        } -                        state.is_dragging = true; -                    } -                    click::Kind::Double => { -                        if is_secure { -                            state.cursor.select_all(value); -                        } else { -                            let position = find_cursor_position( -                                renderer, -                                text_layout.bounds(), -                                font, -                                size, -                                value, -                                state, -                                target, -                            ) -                            .unwrap_or(0); - -                            state.cursor.select_range( -                                value.previous_start_of_word(position), -                                value.next_end_of_word(position), -                            ); -                        } - -                        state.is_dragging = false; -                    } -                    click::Kind::Triple => { -                        state.cursor.select_all(value); -                        state.is_dragging = false; -                    } -                } - -                state.last_click = Some(click); - -                return event::Status::Captured; -            } -        } -        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerLifted { .. }) -        | Event::Touch(touch::Event::FingerLost { .. }) => { -            state().is_dragging = false; -        } -        Event::Mouse(mouse::Event::CursorMoved { position }) -        | Event::Touch(touch::Event::FingerMoved { position, .. }) => { -            let state = state(); - -            if state.is_dragging { -                let text_layout = layout.children().next().unwrap(); -                let target = position.x - text_layout.bounds().x; - -                let value = if is_secure { -                    value.secure() -                } else { -                    value.clone() -                }; - -                let position = find_cursor_position( -                    renderer, -                    text_layout.bounds(), -                    font, -                    size, -                    &value, -                    state, -                    target, -                ) -                .unwrap_or(0); - -                state -                    .cursor -                    .select_range(state.cursor.start(&value), position); - -                return event::Status::Captured; -            } -        } -        Event::Keyboard(keyboard::Event::CharacterReceived(c)) => { -            let state = state(); - -            if let Some(focus) = &mut state.is_focused { -                if state.is_pasting.is_none() -                    && !state.keyboard_modifiers.command() -                    && !c.is_control() -                { -                    let mut editor = Editor::new(value, &mut state.cursor); - -                    editor.insert(c); - -                    let message = (on_change)(editor.contents()); -                    shell.publish(message); - -                    focus.updated_at = Instant::now(); - -                    return event::Status::Captured; -                } -            } -        } -        Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { -            let state = state(); - -            if let Some(focus) = &mut state.is_focused { -                let modifiers = state.keyboard_modifiers; -                focus.updated_at = Instant::now(); - -                match key_code { -                    keyboard::KeyCode::Enter -                    | keyboard::KeyCode::NumpadEnter => { -                        if let Some(on_submit) = on_submit.clone() { -                            shell.publish(on_submit); -                        } -                    } -                    keyboard::KeyCode::Backspace => { -                        if platform::is_jump_modifier_pressed(modifiers) -                            && state.cursor.selection(value).is_none() -                        { -                            if is_secure { -                                let cursor_pos = state.cursor.end(value); -                                state.cursor.select_range(0, cursor_pos); -                            } else { -                                state.cursor.select_left_by_words(value); -                            } -                        } - -                        let mut editor = Editor::new(value, &mut state.cursor); -                        editor.backspace(); - -                        let message = (on_change)(editor.contents()); -                        shell.publish(message); -                    } -                    keyboard::KeyCode::Delete => { -                        if platform::is_jump_modifier_pressed(modifiers) -                            && state.cursor.selection(value).is_none() -                        { -                            if is_secure { -                                let cursor_pos = state.cursor.end(value); -                                state -                                    .cursor -                                    .select_range(cursor_pos, value.len()); -                            } else { -                                state.cursor.select_right_by_words(value); -                            } -                        } - -                        let mut editor = Editor::new(value, &mut state.cursor); -                        editor.delete(); - -                        let message = (on_change)(editor.contents()); -                        shell.publish(message); -                    } -                    keyboard::KeyCode::Left => { -                        if platform::is_jump_modifier_pressed(modifiers) -                            && !is_secure -                        { -                            if modifiers.shift() { -                                state.cursor.select_left_by_words(value); -                            } else { -                                state.cursor.move_left_by_words(value); -                            } -                        } else if modifiers.shift() { -                            state.cursor.select_left(value) -                        } else { -                            state.cursor.move_left(value); -                        } -                    } -                    keyboard::KeyCode::Right => { -                        if platform::is_jump_modifier_pressed(modifiers) -                            && !is_secure -                        { -                            if modifiers.shift() { -                                state.cursor.select_right_by_words(value); -                            } else { -                                state.cursor.move_right_by_words(value); -                            } -                        } else if modifiers.shift() { -                            state.cursor.select_right(value) -                        } else { -                            state.cursor.move_right(value); -                        } -                    } -                    keyboard::KeyCode::Home => { -                        if modifiers.shift() { -                            state -                                .cursor -                                .select_range(state.cursor.start(value), 0); -                        } else { -                            state.cursor.move_to(0); -                        } -                    } -                    keyboard::KeyCode::End => { -                        if modifiers.shift() { -                            state.cursor.select_range( -                                state.cursor.start(value), -                                value.len(), -                            ); -                        } else { -                            state.cursor.move_to(value.len()); -                        } -                    } -                    keyboard::KeyCode::C -                        if state.keyboard_modifiers.command() => -                    { -                        if let Some((start, end)) = -                            state.cursor.selection(value) -                        { -                            clipboard -                                .write(value.select(start, end).to_string()); -                        } -                    } -                    keyboard::KeyCode::X -                        if state.keyboard_modifiers.command() => -                    { -                        if let Some((start, end)) = -                            state.cursor.selection(value) -                        { -                            clipboard -                                .write(value.select(start, end).to_string()); -                        } - -                        let mut editor = Editor::new(value, &mut state.cursor); -                        editor.delete(); - -                        let message = (on_change)(editor.contents()); -                        shell.publish(message); -                    } -                    keyboard::KeyCode::V => { -                        if state.keyboard_modifiers.command() { -                            let content = match state.is_pasting.take() { -                                Some(content) => content, -                                None => { -                                    let content: String = clipboard -                                        .read() -                                        .unwrap_or_default() -                                        .chars() -                                        .filter(|c| !c.is_control()) -                                        .collect(); - -                                    Value::new(&content) -                                } -                            }; - -                            let mut editor = -                                Editor::new(value, &mut state.cursor); - -                            editor.paste(content.clone()); - -                            let message = if let Some(paste) = &on_paste { -                                (paste)(editor.contents()) -                            } else { -                                (on_change)(editor.contents()) -                            }; -                            shell.publish(message); - -                            state.is_pasting = Some(content); -                        } else { -                            state.is_pasting = None; -                        } -                    } -                    keyboard::KeyCode::A -                        if state.keyboard_modifiers.command() => -                    { -                        state.cursor.select_all(value); -                    } -                    keyboard::KeyCode::Escape => { -                        state.is_focused = None; -                        state.is_dragging = false; -                        state.is_pasting = None; - -                        state.keyboard_modifiers = -                            keyboard::Modifiers::default(); -                    } -                    keyboard::KeyCode::Tab -                    | keyboard::KeyCode::Up -                    | keyboard::KeyCode::Down => { -                        return event::Status::Ignored; -                    } -                    _ => {} -                } - -                return event::Status::Captured; -            } -        } -        Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => { -            let state = state(); - -            if state.is_focused.is_some() { -                match key_code { -                    keyboard::KeyCode::V => { -                        state.is_pasting = None; -                    } -                    keyboard::KeyCode::Tab -                    | keyboard::KeyCode::Up -                    | keyboard::KeyCode::Down => { -                        return event::Status::Ignored; -                    } -                    _ => {} -                } - -                return event::Status::Captured; -            } else { -                state.is_pasting = None; -            } -        } -        Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { -            let state = state(); - -            state.keyboard_modifiers = modifiers; -        } -        Event::Window(window::Event::RedrawRequested(now)) => { -            let state = state(); - -            if let Some(focus) = &mut state.is_focused { -                focus.now = now; - -                let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS -                    - (now - focus.updated_at).as_millis() -                        % CURSOR_BLINK_INTERVAL_MILLIS; - -                shell.request_redraw(window::RedrawRequest::At( -                    now + Duration::from_millis(millis_until_redraw as u64), -                )); -            } -        } -        _ => {} -    } - -    event::Status::Ignored -} - -/// Draws the [`TextInput`] with the given [`Renderer`], overriding its -/// [`Value`] if provided. -/// -/// [`Renderer`]: text::Renderer -pub fn draw<Renderer>( -    renderer: &mut Renderer, -    theme: &Renderer::Theme, -    layout: Layout<'_>, -    cursor_position: Point, -    state: &State, -    value: &Value, -    placeholder: &str, -    size: Option<f32>, -    font: Option<Renderer::Font>, -    is_secure: bool, -    style: &<Renderer::Theme as StyleSheet>::Style, -) where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    let secure_value = is_secure.then(|| value.secure()); -    let value = secure_value.as_ref().unwrap_or(value); - -    let bounds = layout.bounds(); -    let text_bounds = layout.children().next().unwrap().bounds(); - -    let is_mouse_over = bounds.contains(cursor_position); - -    let appearance = if state.is_focused() { -        theme.focused(style) -    } else if is_mouse_over { -        theme.hovered(style) -    } else { -        theme.active(style) -    }; - -    renderer.fill_quad( -        renderer::Quad { -            bounds, -            border_radius: appearance.border_radius.into(), -            border_width: appearance.border_width, -            border_color: appearance.border_color, -        }, -        appearance.background, -    ); - -    let text = value.to_string(); -    let font = font.unwrap_or_else(|| renderer.default_font()); -    let size = size.unwrap_or_else(|| renderer.default_size()); - -    let (cursor, offset) = if let Some(focus) = &state.is_focused { -        match state.cursor.state(value) { -            cursor::State::Index(position) => { -                let (text_value_width, offset) = -                    measure_cursor_and_scroll_offset( -                        renderer, -                        text_bounds, -                        value, -                        size, -                        position, -                        font, -                    ); - -                let is_cursor_visible = ((focus.now - focus.updated_at) -                    .as_millis() -                    / CURSOR_BLINK_INTERVAL_MILLIS) -                    % 2 -                    == 0; - -                let cursor = if is_cursor_visible { -                    Some(( -                        renderer::Quad { -                            bounds: Rectangle { -                                x: text_bounds.x + text_value_width, -                                y: text_bounds.y, -                                width: 1.0, -                                height: text_bounds.height, -                            }, -                            border_radius: 0.0.into(), -                            border_width: 0.0, -                            border_color: Color::TRANSPARENT, -                        }, -                        theme.value_color(style), -                    )) -                } else { -                    None -                }; - -                (cursor, offset) -            } -            cursor::State::Selection { start, end } => { -                let left = start.min(end); -                let right = end.max(start); - -                let (left_position, left_offset) = -                    measure_cursor_and_scroll_offset( -                        renderer, -                        text_bounds, -                        value, -                        size, -                        left, -                        font, -                    ); - -                let (right_position, right_offset) = -                    measure_cursor_and_scroll_offset( -                        renderer, -                        text_bounds, -                        value, -                        size, -                        right, -                        font, -                    ); - -                let width = right_position - left_position; - -                ( -                    Some(( -                        renderer::Quad { -                            bounds: Rectangle { -                                x: text_bounds.x + left_position, -                                y: text_bounds.y, -                                width, -                                height: text_bounds.height, -                            }, -                            border_radius: 0.0.into(), -                            border_width: 0.0, -                            border_color: Color::TRANSPARENT, -                        }, -                        theme.selection_color(style), -                    )), -                    if end == right { -                        right_offset -                    } else { -                        left_offset -                    }, -                ) -            } -        } -    } else { -        (None, 0.0) -    }; - -    let text_width = renderer.measure_width( -        if text.is_empty() { placeholder } else { &text }, -        size, -        font, -    ); - -    let render = |renderer: &mut Renderer| { -        if let Some((cursor, color)) = cursor { -            renderer.fill_quad(cursor, color); -        } - -        renderer.fill_text(Text { -            content: if text.is_empty() { placeholder } else { &text }, -            color: if text.is_empty() { -                theme.placeholder_color(style) -            } else { -                theme.value_color(style) -            }, -            font, -            bounds: Rectangle { -                y: text_bounds.center_y(), -                width: f32::INFINITY, -                ..text_bounds -            }, -            size, -            horizontal_alignment: alignment::Horizontal::Left, -            vertical_alignment: alignment::Vertical::Center, -        }); -    }; - -    if text_width > text_bounds.width { -        renderer.with_layer(text_bounds, |renderer| { -            renderer.with_translation(Vector::new(-offset, 0.0), render) -        }); -    } else { -        render(renderer); -    } -} - -/// Computes the current [`mouse::Interaction`] of the [`TextInput`]. -pub fn mouse_interaction( -    layout: Layout<'_>, -    cursor_position: Point, -) -> mouse::Interaction { -    if layout.bounds().contains(cursor_position) { -        mouse::Interaction::Text -    } else { -        mouse::Interaction::default() -    } -} - -/// The state of a [`TextInput`]. -#[derive(Debug, Default, Clone)] -pub struct State { -    is_focused: Option<Focus>, -    is_dragging: bool, -    is_pasting: Option<Value>, -    last_click: Option<mouse::Click>, -    cursor: Cursor, -    keyboard_modifiers: keyboard::Modifiers, -    // TODO: Add stateful horizontal scrolling offset -} - -#[derive(Debug, Clone, Copy)] -struct Focus { -    updated_at: Instant, -    now: Instant, -} - -impl State { -    /// Creates a new [`State`], representing an unfocused [`TextInput`]. -    pub fn new() -> Self { -        Self::default() -    } - -    /// Creates a new [`State`], representing a focused [`TextInput`]. -    pub fn focused() -> Self { -        Self { -            is_focused: None, -            is_dragging: false, -            is_pasting: None, -            last_click: None, -            cursor: Cursor::default(), -            keyboard_modifiers: keyboard::Modifiers::default(), -        } -    } - -    /// Returns whether the [`TextInput`] is currently focused or not. -    pub fn is_focused(&self) -> bool { -        self.is_focused.is_some() -    } - -    /// Returns the [`Cursor`] of the [`TextInput`]. -    pub fn cursor(&self) -> Cursor { -        self.cursor -    } - -    /// Focuses the [`TextInput`]. -    pub fn focus(&mut self) { -        let now = Instant::now(); - -        self.is_focused = Some(Focus { -            updated_at: now, -            now, -        }); - -        self.move_cursor_to_end(); -    } - -    /// Unfocuses the [`TextInput`]. -    pub fn unfocus(&mut self) { -        self.is_focused = None; -    } - -    /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. -    pub fn move_cursor_to_front(&mut self) { -        self.cursor.move_to(0); -    } - -    /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text. -    pub fn move_cursor_to_end(&mut self) { -        self.cursor.move_to(usize::MAX); -    } - -    /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location. -    pub fn move_cursor_to(&mut self, position: usize) { -        self.cursor.move_to(position); -    } - -    /// Selects all the content of the [`TextInput`]. -    pub fn select_all(&mut self) { -        self.cursor.select_range(0, usize::MAX); -    } -} - -impl operation::Focusable for State { -    fn is_focused(&self) -> bool { -        State::is_focused(self) -    } - -    fn focus(&mut self) { -        State::focus(self) -    } - -    fn unfocus(&mut self) { -        State::unfocus(self) -    } -} - -impl operation::TextInput for State { -    fn move_cursor_to_front(&mut self) { -        State::move_cursor_to_front(self) -    } - -    fn move_cursor_to_end(&mut self) { -        State::move_cursor_to_end(self) -    } - -    fn move_cursor_to(&mut self, position: usize) { -        State::move_cursor_to(self, position) -    } - -    fn select_all(&mut self) { -        State::select_all(self) -    } -} - -mod platform { -    use crate::keyboard; - -    pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { -        if cfg!(target_os = "macos") { -            modifiers.alt() -        } else { -            modifiers.control() -        } -    } -} - -fn offset<Renderer>( -    renderer: &Renderer, -    text_bounds: Rectangle, -    font: Renderer::Font, -    size: f32, -    value: &Value, -    state: &State, -) -> f32 -where -    Renderer: text::Renderer, -{ -    if state.is_focused() { -        let cursor = state.cursor(); - -        let focus_position = match cursor.state(value) { -            cursor::State::Index(i) => i, -            cursor::State::Selection { end, .. } => end, -        }; - -        let (_, offset) = measure_cursor_and_scroll_offset( -            renderer, -            text_bounds, -            value, -            size, -            focus_position, -            font, -        ); - -        offset -    } else { -        0.0 -    } -} - -fn measure_cursor_and_scroll_offset<Renderer>( -    renderer: &Renderer, -    text_bounds: Rectangle, -    value: &Value, -    size: f32, -    cursor_index: usize, -    font: Renderer::Font, -) -> (f32, f32) -where -    Renderer: text::Renderer, -{ -    let text_before_cursor = value.until(cursor_index).to_string(); - -    let text_value_width = -        renderer.measure_width(&text_before_cursor, size, font); - -    let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); - -    (text_value_width, offset) -} - -/// Computes the position of the text cursor at the given X coordinate of -/// a [`TextInput`]. -fn find_cursor_position<Renderer>( -    renderer: &Renderer, -    text_bounds: Rectangle, -    font: Option<Renderer::Font>, -    size: Option<f32>, -    value: &Value, -    state: &State, -    x: f32, -) -> Option<usize> -where -    Renderer: text::Renderer, -{ -    let font = font.unwrap_or_else(|| renderer.default_font()); -    let size = size.unwrap_or_else(|| renderer.default_size()); - -    let offset = offset(renderer, text_bounds, font, size, value, state); -    let value = value.to_string(); - -    let char_offset = renderer -        .hit_test( -            &value, -            size, -            font, -            Size::INFINITY, -            Point::new(x + offset, text_bounds.height / 2.0), -            true, -        ) -        .map(text::Hit::cursor)?; - -    Some( -        unicode_segmentation::UnicodeSegmentation::graphemes( -            &value[..char_offset], -            true, -        ) -        .count(), -    ) -} - -const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs deleted file mode 100644 index 4f3b159b..00000000 --- a/native/src/widget/text_input/cursor.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Track the cursor of a text input. -use crate::widget::text_input::Value; - -/// The cursor of a text input. -#[derive(Debug, Copy, Clone)] -pub struct Cursor { -    state: State, -} - -/// The state of a [`Cursor`]. -#[derive(Debug, Copy, Clone)] -pub enum State { -    /// Cursor without a selection -    Index(usize), - -    /// Cursor selecting a range of text -    Selection { -        /// The start of the selection -        start: usize, -        /// The end of the selection -        end: usize, -    }, -} - -impl Default for Cursor { -    fn default() -> Self { -        Cursor { -            state: State::Index(0), -        } -    } -} - -impl Cursor { -    /// Returns the [`State`] of the [`Cursor`]. -    pub fn state(&self, value: &Value) -> State { -        match self.state { -            State::Index(index) => State::Index(index.min(value.len())), -            State::Selection { start, end } => { -                let start = start.min(value.len()); -                let end = end.min(value.len()); - -                if start == end { -                    State::Index(start) -                } else { -                    State::Selection { start, end } -                } -            } -        } -    } - -    /// Returns the current selection of the [`Cursor`] for the given [`Value`]. -    /// -    /// `start` is guaranteed to be <= than `end`. -    pub fn selection(&self, value: &Value) -> Option<(usize, usize)> { -        match self.state(value) { -            State::Selection { start, end } => { -                Some((start.min(end), start.max(end))) -            } -            _ => None, -        } -    } - -    pub(crate) fn move_to(&mut self, position: usize) { -        self.state = State::Index(position); -    } - -    pub(crate) fn move_right(&mut self, value: &Value) { -        self.move_right_by_amount(value, 1) -    } - -    pub(crate) fn move_right_by_words(&mut self, value: &Value) { -        self.move_to(value.next_end_of_word(self.right(value))) -    } - -    pub(crate) fn move_right_by_amount( -        &mut self, -        value: &Value, -        amount: usize, -    ) { -        match self.state(value) { -            State::Index(index) => { -                self.move_to(index.saturating_add(amount).min(value.len())) -            } -            State::Selection { start, end } => self.move_to(end.max(start)), -        } -    } - -    pub(crate) fn move_left(&mut self, value: &Value) { -        match self.state(value) { -            State::Index(index) if index > 0 => self.move_to(index - 1), -            State::Selection { start, end } => self.move_to(start.min(end)), -            _ => self.move_to(0), -        } -    } - -    pub(crate) fn move_left_by_words(&mut self, value: &Value) { -        self.move_to(value.previous_start_of_word(self.left(value))); -    } - -    pub(crate) fn select_range(&mut self, start: usize, end: usize) { -        if start == end { -            self.state = State::Index(start); -        } else { -            self.state = State::Selection { start, end }; -        } -    } - -    pub(crate) fn select_left(&mut self, value: &Value) { -        match self.state(value) { -            State::Index(index) if index > 0 => { -                self.select_range(index, index - 1) -            } -            State::Selection { start, end } if end > 0 => { -                self.select_range(start, end - 1) -            } -            _ => {} -        } -    } - -    pub(crate) fn select_right(&mut self, value: &Value) { -        match self.state(value) { -            State::Index(index) if index < value.len() => { -                self.select_range(index, index + 1) -            } -            State::Selection { start, end } if end < value.len() => { -                self.select_range(start, end + 1) -            } -            _ => {} -        } -    } - -    pub(crate) fn select_left_by_words(&mut self, value: &Value) { -        match self.state(value) { -            State::Index(index) => { -                self.select_range(index, value.previous_start_of_word(index)) -            } -            State::Selection { start, end } => { -                self.select_range(start, value.previous_start_of_word(end)) -            } -        } -    } - -    pub(crate) fn select_right_by_words(&mut self, value: &Value) { -        match self.state(value) { -            State::Index(index) => { -                self.select_range(index, value.next_end_of_word(index)) -            } -            State::Selection { start, end } => { -                self.select_range(start, value.next_end_of_word(end)) -            } -        } -    } - -    pub(crate) fn select_all(&mut self, value: &Value) { -        self.select_range(0, value.len()); -    } - -    pub(crate) fn start(&self, value: &Value) -> usize { -        let start = match self.state { -            State::Index(index) => index, -            State::Selection { start, .. } => start, -        }; - -        start.min(value.len()) -    } - -    pub(crate) fn end(&self, value: &Value) -> usize { -        let end = match self.state { -            State::Index(index) => index, -            State::Selection { end, .. } => end, -        }; - -        end.min(value.len()) -    } - -    fn left(&self, value: &Value) -> usize { -        match self.state(value) { -            State::Index(index) => index, -            State::Selection { start, end } => start.min(end), -        } -    } - -    fn right(&self, value: &Value) -> usize { -        match self.state(value) { -            State::Index(index) => index, -            State::Selection { start, end } => start.max(end), -        } -    } -} diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs deleted file mode 100644 index d53fa8d9..00000000 --- a/native/src/widget/text_input/editor.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::widget::text_input::{Cursor, Value}; - -pub struct Editor<'a> { -    value: &'a mut Value, -    cursor: &'a mut Cursor, -} - -impl<'a> Editor<'a> { -    pub fn new(value: &'a mut Value, cursor: &'a mut Cursor) -> Editor<'a> { -        Editor { value, cursor } -    } - -    pub fn contents(&self) -> String { -        self.value.to_string() -    } - -    pub fn insert(&mut self, character: char) { -        if let Some((left, right)) = self.cursor.selection(self.value) { -            self.cursor.move_left(self.value); -            self.value.remove_many(left, right); -        } - -        self.value.insert(self.cursor.end(self.value), character); -        self.cursor.move_right(self.value); -    } - -    pub fn paste(&mut self, content: Value) { -        let length = content.len(); -        if let Some((left, right)) = self.cursor.selection(self.value) { -            self.cursor.move_left(self.value); -            self.value.remove_many(left, right); -        } - -        self.value.insert_many(self.cursor.end(self.value), content); - -        self.cursor.move_right_by_amount(self.value, length); -    } - -    pub fn backspace(&mut self) { -        match self.cursor.selection(self.value) { -            Some((start, end)) => { -                self.cursor.move_left(self.value); -                self.value.remove_many(start, end); -            } -            None => { -                let start = self.cursor.start(self.value); - -                if start > 0 { -                    self.cursor.move_left(self.value); -                    self.value.remove(start - 1); -                } -            } -        } -    } - -    pub fn delete(&mut self) { -        match self.cursor.selection(self.value) { -            Some(_) => { -                self.backspace(); -            } -            None => { -                let end = self.cursor.end(self.value); - -                if end < self.value.len() { -                    self.value.remove(end); -                } -            } -        } -    } -} diff --git a/native/src/widget/text_input/value.rs b/native/src/widget/text_input/value.rs deleted file mode 100644 index cf4da562..00000000 --- a/native/src/widget/text_input/value.rs +++ /dev/null @@ -1,133 +0,0 @@ -use unicode_segmentation::UnicodeSegmentation; - -/// The value of a [`TextInput`]. -/// -/// [`TextInput`]: crate::widget::TextInput -// TODO: Reduce allocations, cache results (?) -#[derive(Debug, Clone)] -pub struct Value { -    graphemes: Vec<String>, -} - -impl Value { -    /// Creates a new [`Value`] from a string slice. -    pub fn new(string: &str) -> Self { -        let graphemes = UnicodeSegmentation::graphemes(string, true) -            .map(String::from) -            .collect(); - -        Self { graphemes } -    } - -    /// Returns whether the [`Value`] is empty or not. -    /// -    /// A [`Value`] is empty when it contains no graphemes. -    pub fn is_empty(&self) -> bool { -        self.len() == 0 -    } - -    /// Returns the total amount of graphemes in the [`Value`]. -    pub fn len(&self) -> usize { -        self.graphemes.len() -    } - -    /// Returns the position of the previous start of a word from the given -    /// grapheme `index`. -    pub fn previous_start_of_word(&self, index: usize) -> usize { -        let previous_string = -            &self.graphemes[..index.min(self.graphemes.len())].concat(); - -        UnicodeSegmentation::split_word_bound_indices(previous_string as &str) -            .filter(|(_, word)| !word.trim_start().is_empty()) -            .next_back() -            .map(|(i, previous_word)| { -                index -                    - UnicodeSegmentation::graphemes(previous_word, true) -                        .count() -                    - UnicodeSegmentation::graphemes( -                        &previous_string[i + previous_word.len()..] as &str, -                        true, -                    ) -                    .count() -            }) -            .unwrap_or(0) -    } - -    /// Returns the position of the next end of a word from the given grapheme -    /// `index`. -    pub fn next_end_of_word(&self, index: usize) -> usize { -        let next_string = &self.graphemes[index..].concat(); - -        UnicodeSegmentation::split_word_bound_indices(next_string as &str) -            .find(|(_, word)| !word.trim_start().is_empty()) -            .map(|(i, next_word)| { -                index -                    + UnicodeSegmentation::graphemes(next_word, true).count() -                    + UnicodeSegmentation::graphemes( -                        &next_string[..i] as &str, -                        true, -                    ) -                    .count() -            }) -            .unwrap_or(self.len()) -    } - -    /// Returns a new [`Value`] containing the graphemes from `start` until the -    /// given `end`. -    pub fn select(&self, start: usize, end: usize) -> Self { -        let graphemes = -            self.graphemes[start.min(self.len())..end.min(self.len())].to_vec(); - -        Self { graphemes } -    } - -    /// Returns a new [`Value`] containing the graphemes until the given -    /// `index`. -    pub fn until(&self, index: usize) -> Self { -        let graphemes = self.graphemes[..index.min(self.len())].to_vec(); - -        Self { graphemes } -    } - -    /// Converts the [`Value`] into a `String`. -    pub fn to_string(&self) -> String { -        self.graphemes.concat() -    } - -    /// Inserts a new `char` at the given grapheme `index`. -    pub fn insert(&mut self, index: usize, c: char) { -        self.graphemes.insert(index, c.to_string()); - -        self.graphemes = -            UnicodeSegmentation::graphemes(&self.to_string() as &str, true) -                .map(String::from) -                .collect(); -    } - -    /// Inserts a bunch of graphemes at the given grapheme `index`. -    pub fn insert_many(&mut self, index: usize, mut value: Value) { -        let _ = self -            .graphemes -            .splice(index..index, value.graphemes.drain(..)); -    } - -    /// Removes the grapheme at the given `index`. -    pub fn remove(&mut self, index: usize) { -        let _ = self.graphemes.remove(index); -    } - -    /// Removes the graphemes from `start` to `end`. -    pub fn remove_many(&mut self, start: usize, end: usize) { -        let _ = self.graphemes.splice(start..end, std::iter::empty()); -    } - -    /// Returns a new [`Value`] with all its graphemes replaced with the -    /// dot ('•') character. -    pub fn secure(&self) -> Self { -        Self { -            graphemes: std::iter::repeat(String::from("•")) -                .take(self.graphemes.len()) -                .collect(), -        } -    } -} diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs deleted file mode 100644 index d9c80ebe..00000000 --- a/native/src/widget/toggler.rs +++ /dev/null @@ -1,324 +0,0 @@ -//! Show toggle controls using togglers. -use crate::alignment; -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget::{self, Row, Text, Tree}; -use crate::{ -    Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Point, -    Rectangle, Shell, Widget, -}; - -pub use iced_style::toggler::{Appearance, StyleSheet}; - -/// A toggler widget. -/// -/// # Example -/// -/// ``` -/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>; -/// # -/// pub enum Message { -///     TogglerToggled(bool), -/// } -/// -/// let is_toggled = true; -/// -/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); -/// ``` -#[allow(missing_debug_implementations)] -pub struct Toggler<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    is_toggled: bool, -    on_toggle: Box<dyn Fn(bool) -> Message + 'a>, -    label: Option<String>, -    width: Length, -    size: f32, -    text_size: Option<f32>, -    text_alignment: alignment::Horizontal, -    spacing: f32, -    font: Option<Renderer::Font>, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Toggler<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// The default size of a [`Toggler`]. -    pub const DEFAULT_SIZE: f32 = 20.0; - -    /// Creates a new [`Toggler`]. -    /// -    /// It expects: -    ///   * a boolean describing whether the [`Toggler`] is checked or not -    ///   * An optional label for the [`Toggler`] -    ///   * a function that will be called when the [`Toggler`] is toggled. It -    ///     will receive the new state of the [`Toggler`] and must produce a -    ///     `Message`. -    pub fn new<F>( -        label: impl Into<Option<String>>, -        is_toggled: bool, -        f: F, -    ) -> Self -    where -        F: 'a + Fn(bool) -> Message, -    { -        Toggler { -            is_toggled, -            on_toggle: Box::new(f), -            label: label.into(), -            width: Length::Fill, -            size: Self::DEFAULT_SIZE, -            text_size: None, -            text_alignment: alignment::Horizontal::Left, -            spacing: 0.0, -            font: None, -            style: Default::default(), -        } -    } - -    /// Sets the size of the [`Toggler`]. -    pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = size.into().0; -        self -    } - -    /// Sets the width of the [`Toggler`]. -    pub fn width(mut self, width: impl Into<Length>) -> Self { -        self.width = width.into(); -        self -    } - -    /// Sets the text size o the [`Toggler`]. -    pub fn text_size(mut self, text_size: impl Into<Pixels>) -> Self { -        self.text_size = Some(text_size.into().0); -        self -    } - -    /// Sets the horizontal alignment of the text of the [`Toggler`] -    pub fn text_alignment(mut self, alignment: alignment::Horizontal) -> Self { -        self.text_alignment = alignment; -        self -    } - -    /// Sets the spacing between the [`Toggler`] and the text. -    pub fn spacing(mut self, spacing: impl Into<Pixels>) -> Self { -        self.spacing = spacing.into().0; -        self -    } - -    /// Sets the [`Font`] of the text of the [`Toggler`] -    /// -    /// [`Font`]: crate::text::Renderer::Font -    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { -        self.font = Some(font.into()); -        self -    } - -    /// Sets the style of the [`Toggler`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Toggler<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let mut row = Row::<(), Renderer>::new() -            .width(self.width) -            .spacing(self.spacing) -            .align_items(Alignment::Center); - -        if let Some(label) = &self.label { -            row = row.push( -                Text::new(label) -                    .horizontal_alignment(self.text_alignment) -                    .font(self.font.unwrap_or_else(|| renderer.default_font())) -                    .width(self.width) -                    .size( -                        self.text_size -                            .unwrap_or_else(|| renderer.default_size()), -                    ), -            ); -        } - -        row = row.push(Row::new().width(2.0 * self.size).height(self.size)); - -        row.layout(renderer, limits) -    } - -    fn on_event( -        &mut self, -        _state: &mut Tree, -        event: Event, -        layout: Layout<'_>, -        cursor_position: Point, -        _renderer: &Renderer, -        _clipboard: &mut dyn Clipboard, -        shell: &mut Shell<'_, Message>, -    ) -> event::Status { -        match event { -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { -                let mouse_over = layout.bounds().contains(cursor_position); - -                if mouse_over { -                    shell.publish((self.on_toggle)(!self.is_toggled)); - -                    event::Status::Captured -                } else { -                    event::Status::Ignored -                } -            } -            _ => event::Status::Ignored, -        } -    } - -    fn mouse_interaction( -        &self, -        _state: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        if layout.bounds().contains(cursor_position) { -            mouse::Interaction::Pointer -        } else { -            mouse::Interaction::default() -        } -    } - -    fn draw( -        &self, -        _state: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        /// Makes sure that the border radius of the toggler looks good at every size. -        const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0; - -        /// The space ratio between the background Quad and the Toggler bounds, and -        /// between the background Quad and foreground Quad. -        const SPACE_RATIO: f32 = 0.05; - -        let mut children = layout.children(); - -        if let Some(label) = &self.label { -            let label_layout = children.next().unwrap(); - -            crate::widget::text::draw( -                renderer, -                style, -                label_layout, -                label, -                self.text_size, -                self.font, -                Default::default(), -                self.text_alignment, -                alignment::Vertical::Center, -            ); -        } - -        let toggler_layout = children.next().unwrap(); -        let bounds = toggler_layout.bounds(); - -        let is_mouse_over = bounds.contains(cursor_position); - -        let style = if is_mouse_over { -            theme.hovered(&self.style, self.is_toggled) -        } else { -            theme.active(&self.style, self.is_toggled) -        }; - -        let border_radius = bounds.height / BORDER_RADIUS_RATIO; -        let space = SPACE_RATIO * bounds.height; - -        let toggler_background_bounds = Rectangle { -            x: bounds.x + space, -            y: bounds.y + space, -            width: bounds.width - (2.0 * space), -            height: bounds.height - (2.0 * space), -        }; - -        renderer.fill_quad( -            renderer::Quad { -                bounds: toggler_background_bounds, -                border_radius: border_radius.into(), -                border_width: 1.0, -                border_color: style -                    .background_border -                    .unwrap_or(style.background), -            }, -            style.background, -        ); - -        let toggler_foreground_bounds = Rectangle { -            x: bounds.x -                + if self.is_toggled { -                    bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) -                } else { -                    2.0 * space -                }, -            y: bounds.y + (2.0 * space), -            width: bounds.height - (4.0 * space), -            height: bounds.height - (4.0 * space), -        }; - -        renderer.fill_quad( -            renderer::Quad { -                bounds: toggler_foreground_bounds, -                border_radius: border_radius.into(), -                border_width: 1.0, -                border_color: style -                    .foreground_border -                    .unwrap_or(style.foreground), -            }, -            style.foreground, -        ); -    } -} - -impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + text::Renderer, -    Renderer::Theme: StyleSheet + widget::text::StyleSheet, -{ -    fn from( -        toggler: Toggler<'a, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(toggler) -    } -} diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs deleted file mode 100644 index 2a24c055..00000000 --- a/native/src/widget/tooltip.rs +++ /dev/null @@ -1,387 +0,0 @@ -//! Display a widget over another. -use crate::event; -use crate::layout; -use crate::mouse; -use crate::renderer; -use crate::text; -use crate::widget; -use crate::widget::container; -use crate::widget::overlay; -use crate::widget::{Text, Tree}; -use crate::{ -    Clipboard, Element, Event, Layout, Length, Padding, Pixels, Point, -    Rectangle, Shell, Size, Vector, Widget, -}; - -use std::borrow::Cow; - -/// An element to display a widget over another. -#[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: text::Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ -    content: Element<'a, Message, Renderer>, -    tooltip: Text<'a, Renderer>, -    position: Position, -    gap: f32, -    padding: f32, -    snap_within_viewport: bool, -    style: <Renderer::Theme as container::StyleSheet>::Style, -} - -impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ -    /// The default padding of a [`Tooltip`] drawn by this renderer. -    const DEFAULT_PADDING: f32 = 5.0; - -    /// Creates a new [`Tooltip`]. -    /// -    /// [`Tooltip`]: struct.Tooltip.html -    pub fn new( -        content: impl Into<Element<'a, Message, Renderer>>, -        tooltip: impl Into<Cow<'a, str>>, -        position: Position, -    ) -> Self { -        Tooltip { -            content: content.into(), -            tooltip: Text::new(tooltip), -            position, -            gap: 0.0, -            padding: Self::DEFAULT_PADDING, -            snap_within_viewport: true, -            style: Default::default(), -        } -    } - -    /// Sets the size of the text of the [`Tooltip`]. -    pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.tooltip = self.tooltip.size(size); -        self -    } - -    /// Sets the font of the [`Tooltip`]. -    /// -    /// [`Font`]: Renderer::Font -    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { -        self.tooltip = self.tooltip.font(font); -        self -    } - -    /// Sets the gap between the content and its [`Tooltip`]. -    pub fn gap(mut self, gap: impl Into<Pixels>) -> Self { -        self.gap = gap.into().0; -        self -    } - -    /// Sets the padding of the [`Tooltip`]. -    pub fn padding(mut self, padding: impl Into<Pixels>) -> Self { -        self.padding = padding.into().0; -        self -    } - -    /// Sets whether the [`Tooltip`] is snapped within the viewport. -    pub fn snap_within_viewport(mut self, snap: bool) -> Self { -        self.snap_within_viewport = snap; -        self -    } - -    /// Sets the style of the [`Tooltip`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as container::StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } -} - -impl<'a, Message, Renderer> Widget<Message, Renderer> -    for Tooltip<'a, Message, Renderer> -where -    Renderer: text::Renderer, -    Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ -    fn children(&self) -> Vec<Tree> { -        vec![Tree::new(&self.content)] -    } - -    fn diff(&self, tree: &mut Tree) { -        tree.diff_children(std::slice::from_ref(&self.content)) -    } - -    fn width(&self) -> Length { -        self.content.as_widget().width() -    } - -    fn height(&self) -> Length { -        self.content.as_widget().height() -    } - -    fn layout( -        &self, -        renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        self.content.as_widget().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 { -        self.content.as_widget_mut().on_event( -            &mut tree.children[0], -            event, -            layout, -            cursor_position, -            renderer, -            clipboard, -            shell, -        ) -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -        renderer: &Renderer, -    ) -> mouse::Interaction { -        self.content.as_widget().mouse_interaction( -            &tree.children[0], -            layout, -            cursor_position, -            viewport, -            renderer, -        ) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        inherited_style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) { -        self.content.as_widget().draw( -            &tree.children[0], -            renderer, -            theme, -            inherited_style, -            layout, -            cursor_position, -            viewport, -        ); - -        let tooltip = &self.tooltip; - -        draw( -            renderer, -            theme, -            inherited_style, -            layout, -            cursor_position, -            viewport, -            self.position, -            self.gap, -            self.padding, -            self.snap_within_viewport, -            &self.style, -            |renderer, limits| { -                Widget::<(), Renderer>::layout(tooltip, renderer, limits) -            }, -            |renderer, defaults, layout, cursor_position, viewport| { -                Widget::<(), Renderer>::draw( -                    tooltip, -                    &Tree::empty(), -                    renderer, -                    theme, -                    defaults, -                    layout, -                    cursor_position, -                    viewport, -                ); -            }, -        ); -    } - -    fn overlay<'b>( -        &'b mut self, -        tree: &'b mut Tree, -        layout: Layout<'_>, -        renderer: &Renderer, -    ) -> Option<overlay::Element<'b, Message, Renderer>> { -        self.content.as_widget_mut().overlay( -            &mut tree.children[0], -            layout, -            renderer, -        ) -    } -} - -impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    Message: 'a, -    Renderer: 'a + text::Renderer, -    Renderer::Theme: container::StyleSheet + widget::text::StyleSheet, -{ -    fn from( -        tooltip: Tooltip<'a, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(tooltip) -    } -} - -/// The position of the tooltip. Defaults to following the cursor. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Position { -    /// The tooltip will follow the cursor. -    FollowCursor, -    /// The tooltip will appear on the top of the widget. -    Top, -    /// The tooltip will appear on the bottom of the widget. -    Bottom, -    /// The tooltip will appear on the left of the widget. -    Left, -    /// The tooltip will appear on the right of the widget. -    Right, -} - -/// Draws a [`Tooltip`]. -pub fn draw<Renderer>( -    renderer: &mut Renderer, -    theme: &Renderer::Theme, -    inherited_style: &renderer::Style, -    layout: Layout<'_>, -    cursor_position: Point, -    viewport: &Rectangle, -    position: Position, -    gap: f32, -    padding: f32, -    snap_within_viewport: bool, -    style: &<Renderer::Theme as container::StyleSheet>::Style, -    layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, -    draw_text: impl FnOnce( -        &mut Renderer, -        &renderer::Style, -        Layout<'_>, -        Point, -        &Rectangle, -    ), -) where -    Renderer: crate::Renderer, -    Renderer::Theme: container::StyleSheet, -{ -    use container::StyleSheet; - -    let bounds = layout.bounds(); - -    if bounds.contains(cursor_position) { -        let style = theme.appearance(style); - -        let defaults = renderer::Style { -            text_color: style.text_color.unwrap_or(inherited_style.text_color), -        }; - -        let text_layout = layout_text( -            renderer, -            &layout::Limits::new( -                Size::ZERO, -                snap_within_viewport -                    .then(|| viewport.size()) -                    .unwrap_or(Size::INFINITY), -            ) -            .pad(Padding::new(padding)), -        ); - -        let text_bounds = text_layout.bounds(); -        let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; -        let y_center = bounds.y + (bounds.height - text_bounds.height) / 2.0; - -        let mut tooltip_bounds = { -            let offset = match position { -                Position::Top => Vector::new( -                    x_center, -                    bounds.y - text_bounds.height - gap - padding, -                ), -                Position::Bottom => Vector::new( -                    x_center, -                    bounds.y + bounds.height + gap + padding, -                ), -                Position::Left => Vector::new( -                    bounds.x - text_bounds.width - gap - padding, -                    y_center, -                ), -                Position::Right => Vector::new( -                    bounds.x + bounds.width + gap + padding, -                    y_center, -                ), -                Position::FollowCursor => Vector::new( -                    cursor_position.x, -                    cursor_position.y - text_bounds.height, -                ), -            }; - -            Rectangle { -                x: offset.x - padding, -                y: offset.y - padding, -                width: text_bounds.width + padding * 2.0, -                height: text_bounds.height + padding * 2.0, -            } -        }; - -        if snap_within_viewport { -            if tooltip_bounds.x < viewport.x { -                tooltip_bounds.x = viewport.x; -            } else if viewport.x + viewport.width -                < tooltip_bounds.x + tooltip_bounds.width -            { -                tooltip_bounds.x = -                    viewport.x + viewport.width - tooltip_bounds.width; -            } - -            if tooltip_bounds.y < viewport.y { -                tooltip_bounds.y = viewport.y; -            } else if viewport.y + viewport.height -                < tooltip_bounds.y + tooltip_bounds.height -            { -                tooltip_bounds.y = -                    viewport.y + viewport.height - tooltip_bounds.height; -            } -        } - -        renderer.with_layer(Rectangle::with_size(Size::INFINITY), |renderer| { -            container::draw_background(renderer, &style, tooltip_bounds); - -            draw_text( -                renderer, -                &defaults, -                Layout::with_offset( -                    Vector::new( -                        tooltip_bounds.x + padding, -                        tooltip_bounds.y + padding, -                    ), -                    &text_layout, -                ), -                cursor_position, -                viewport, -            ) -        }); -    } -} diff --git a/native/src/widget/tree.rs b/native/src/widget/tree.rs deleted file mode 100644 index 0af40c33..00000000 --- a/native/src/widget/tree.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! Store internal widget state in a state tree to ensure continuity. -use crate::Widget; - -use std::any::{self, Any}; -use std::borrow::Borrow; -use std::fmt; - -/// A persistent state widget tree. -/// -/// A [`Tree`] is normally associated with a specific widget in the widget tree. -#[derive(Debug)] -pub struct Tree { -    /// The tag of the [`Tree`]. -    pub tag: Tag, - -    /// The [`State`] of the [`Tree`]. -    pub state: State, - -    /// The children of the root widget of the [`Tree`]. -    pub children: Vec<Tree>, -} - -impl Tree { -    /// Creates an empty, stateless [`Tree`] with no children. -    pub fn empty() -> Self { -        Self { -            tag: Tag::stateless(), -            state: State::None, -            children: Vec::new(), -        } -    } - -    /// Creates a new [`Tree`] for the provided [`Widget`]. -    pub fn new<'a, Message, Renderer>( -        widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>, -    ) -> Self -    where -        Renderer: crate::Renderer, -    { -        let widget = widget.borrow(); - -        Self { -            tag: widget.tag(), -            state: widget.state(), -            children: widget.children(), -        } -    } - -    /// Reconciliates the current tree with the provided [`Widget`]. -    /// -    /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the -    /// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). -    /// -    /// Otherwise, the whole [`Tree`] is recreated. -    /// -    /// [`Widget::diff`]: crate::Widget::diff -    pub fn diff<'a, Message, Renderer>( -        &mut self, -        new: impl Borrow<dyn Widget<Message, Renderer> + 'a>, -    ) where -        Renderer: crate::Renderer, -    { -        if self.tag == new.borrow().tag() { -            new.borrow().diff(self) -        } else { -            *self = Self::new(new); -        } -    } - -    /// Reconciliates the children of the tree with the provided list of widgets. -    pub fn diff_children<'a, Message, Renderer>( -        &mut self, -        new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>], -    ) where -        Renderer: crate::Renderer, -    { -        self.diff_children_custom( -            new_children, -            |tree, widget| tree.diff(widget.borrow()), -            |widget| Self::new(widget.borrow()), -        ) -    } - -    /// Reconciliates the children of the tree with the provided list of widgets using custom -    /// logic both for diffing and creating new widget state. -    pub fn diff_children_custom<T>( -        &mut self, -        new_children: &[T], -        diff: impl Fn(&mut Tree, &T), -        new_state: impl Fn(&T) -> Self, -    ) { -        if self.children.len() > new_children.len() { -            self.children.truncate(new_children.len()); -        } - -        for (child_state, new) in -            self.children.iter_mut().zip(new_children.iter()) -        { -            diff(child_state, new); -        } - -        if self.children.len() < new_children.len() { -            self.children.extend( -                new_children[self.children.len()..].iter().map(new_state), -            ); -        } -    } -} - -/// The identifier of some widget state. -#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub struct Tag(any::TypeId); - -impl Tag { -    /// Creates a [`Tag`] for a state of type `T`. -    pub fn of<T>() -> Self -    where -        T: 'static, -    { -        Self(any::TypeId::of::<T>()) -    } - -    /// Creates a [`Tag`] for a stateless widget. -    pub fn stateless() -> Self { -        Self::of::<()>() -    } -} - -/// The internal [`State`] of a widget. -pub enum State { -    /// No meaningful internal state. -    None, - -    /// Some meaningful internal state. -    Some(Box<dyn Any>), -} - -impl State { -    /// Creates a new [`State`]. -    pub fn new<T>(state: T) -> Self -    where -        T: 'static, -    { -        State::Some(Box::new(state)) -    } - -    /// Downcasts the [`State`] to `T` and returns a reference to it. -    /// -    /// # Panics -    /// This method will panic if the downcast fails or the [`State`] is [`State::None`]. -    pub fn downcast_ref<T>(&self) -> &T -    where -        T: 'static, -    { -        match self { -            State::None => panic!("Downcast on stateless state"), -            State::Some(state) => { -                state.downcast_ref().expect("Downcast widget state") -            } -        } -    } - -    /// Downcasts the [`State`] to `T` and returns a mutable reference to it. -    /// -    /// # Panics -    /// This method will panic if the downcast fails or the [`State`] is [`State::None`]. -    pub fn downcast_mut<T>(&mut self) -> &mut T -    where -        T: 'static, -    { -        match self { -            State::None => panic!("Downcast on stateless state"), -            State::Some(state) => { -                state.downcast_mut().expect("Downcast widget state") -            } -        } -    } -} - -impl fmt::Debug for State { -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        match self { -            Self::None => write!(f, "State::None"), -            Self::Some(_) => write!(f, "State::Some"), -        } -    } -} diff --git a/native/src/widget/vertical_slider.rs b/native/src/widget/vertical_slider.rs deleted file mode 100644 index f1687e38..00000000 --- a/native/src/widget/vertical_slider.rs +++ /dev/null @@ -1,468 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`VerticalSlider`] has some local [`State`]. -use std::ops::RangeInclusive; - -pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet}; - -use crate::event::{self, Event}; -use crate::widget::tree::{self, Tree}; -use crate::{ -    layout, mouse, renderer, touch, Background, Clipboard, Color, Element, -    Layout, Length, Pixels, Point, Rectangle, Shell, Size, Widget, -}; - -/// An vertical bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`VerticalSlider`] will try to fill the vertical space of its container. -/// -/// The [`VerticalSlider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_native::widget::vertical_slider; -/// # use iced_native::renderer::Null; -/// # -/// # type VerticalSlider<'a, T, Message> = vertical_slider::VerticalSlider<'a, T, Message, Null>; -/// # -/// #[derive(Clone)] -/// pub enum Message { -///     SliderChanged(f32), -/// } -/// -/// let value = 50.0; -/// -/// VerticalSlider::new(0.0..=100.0, value, Message::SliderChanged); -/// ``` -#[allow(missing_debug_implementations)] -pub struct VerticalSlider<'a, T, Message, Renderer> -where -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    range: RangeInclusive<T>, -    step: T, -    value: T, -    on_change: Box<dyn Fn(T) -> Message + 'a>, -    on_release: Option<Message>, -    width: f32, -    height: Length, -    style: <Renderer::Theme as StyleSheet>::Style, -} - -impl<'a, T, Message, Renderer> VerticalSlider<'a, T, Message, Renderer> -where -    T: Copy + From<u8> + std::cmp::PartialOrd, -    Message: Clone, -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    /// The default width of a [`VerticalSlider`]. -    pub const DEFAULT_WIDTH: f32 = 22.0; - -    /// Creates a new [`VerticalSlider`]. -    /// -    /// It expects: -    ///   * an inclusive range of possible values -    ///   * the current value of the [`VerticalSlider`] -    ///   * a function that will be called when the [`VerticalSlider`] is dragged. -    ///   It receives the new value of the [`VerticalSlider`] and must produce a -    ///   `Message`. -    pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self -    where -        F: 'a + Fn(T) -> Message, -    { -        let value = if value >= *range.start() { -            value -        } else { -            *range.start() -        }; - -        let value = if value <= *range.end() { -            value -        } else { -            *range.end() -        }; - -        VerticalSlider { -            value, -            range, -            step: T::from(1), -            on_change: Box::new(on_change), -            on_release: None, -            width: Self::DEFAULT_WIDTH, -            height: Length::Fill, -            style: Default::default(), -        } -    } - -    /// Sets the release message of the [`VerticalSlider`]. -    /// This is called when the mouse is released from the slider. -    /// -    /// Typically, the user's interaction with the slider is finished when this message is produced. -    /// This is useful if you need to spawn a long-running task from the slider's result, where -    /// the default on_change message could create too many events. -    pub fn on_release(mut self, on_release: Message) -> Self { -        self.on_release = Some(on_release); -        self -    } - -    /// Sets the width of the [`VerticalSlider`]. -    pub fn width(mut self, width: impl Into<Pixels>) -> Self { -        self.width = width.into().0; -        self -    } - -    /// Sets the height of the [`VerticalSlider`]. -    pub fn height(mut self, height: impl Into<Length>) -> Self { -        self.height = height.into(); -        self -    } - -    /// Sets the style of the [`VerticalSlider`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer::Theme as StyleSheet>::Style>, -    ) -> Self { -        self.style = style.into(); -        self -    } - -    /// Sets the step size of the [`VerticalSlider`]. -    pub fn step(mut self, step: T) -> Self { -        self.step = step; -        self -    } -} - -impl<'a, T, Message, Renderer> Widget<Message, Renderer> -    for VerticalSlider<'a, T, Message, Renderer> -where -    T: Copy + Into<f64> + num_traits::FromPrimitive, -    Message: Clone, -    Renderer: crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn tag(&self) -> tree::Tag { -        tree::Tag::of::<State>() -    } - -    fn state(&self) -> tree::State { -        tree::State::new(State::new()) -    } - -    fn width(&self) -> Length { -        Length::Shrink -    } - -    fn height(&self) -> Length { -        self.height -    } - -    fn layout( -        &self, -        _renderer: &Renderer, -        limits: &layout::Limits, -    ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) -    } - -    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 { -        update( -            event, -            layout, -            cursor_position, -            shell, -            tree.state.downcast_mut::<State>(), -            &mut self.value, -            &self.range, -            self.step, -            self.on_change.as_ref(), -            &self.on_release, -        ) -    } - -    fn draw( -        &self, -        tree: &Tree, -        renderer: &mut Renderer, -        theme: &Renderer::Theme, -        _style: &renderer::Style, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -    ) { -        draw( -            renderer, -            layout, -            cursor_position, -            tree.state.downcast_ref::<State>(), -            self.value, -            &self.range, -            theme, -            &self.style, -        ) -    } - -    fn mouse_interaction( -        &self, -        tree: &Tree, -        layout: Layout<'_>, -        cursor_position: Point, -        _viewport: &Rectangle, -        _renderer: &Renderer, -    ) -> mouse::Interaction { -        mouse_interaction( -            layout, -            cursor_position, -            tree.state.downcast_ref::<State>(), -        ) -    } -} - -impl<'a, T, Message, Renderer> From<VerticalSlider<'a, T, Message, Renderer>> -    for Element<'a, Message, Renderer> -where -    T: 'a + Copy + Into<f64> + num_traits::FromPrimitive, -    Message: 'a + Clone, -    Renderer: 'a + crate::Renderer, -    Renderer::Theme: StyleSheet, -{ -    fn from( -        slider: VerticalSlider<'a, T, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { -        Element::new(slider) -    } -} - -/// Processes an [`Event`] and updates the [`State`] of a [`VerticalSlider`] -/// accordingly. -pub fn update<Message, T>( -    event: Event, -    layout: Layout<'_>, -    cursor_position: Point, -    shell: &mut Shell<'_, Message>, -    state: &mut State, -    value: &mut T, -    range: &RangeInclusive<T>, -    step: T, -    on_change: &dyn Fn(T) -> Message, -    on_release: &Option<Message>, -) -> event::Status -where -    T: Copy + Into<f64> + num_traits::FromPrimitive, -    Message: Clone, -{ -    let is_dragging = state.is_dragging; - -    let mut change = || { -        let bounds = layout.bounds(); -        let new_value = if cursor_position.y >= bounds.y + bounds.height { -            *range.start() -        } else if cursor_position.y <= bounds.y { -            *range.end() -        } else { -            let step = step.into(); -            let start = (*range.start()).into(); -            let end = (*range.end()).into(); - -            let percent = 1.0 -                - f64::from(cursor_position.y - bounds.y) -                    / f64::from(bounds.height); - -            let steps = (percent * (end - start) / step).round(); -            let value = steps * step + start; - -            if let Some(value) = T::from_f64(value) { -                value -            } else { -                return; -            } -        }; - -        if ((*value).into() - new_value.into()).abs() > f64::EPSILON { -            shell.publish((on_change)(new_value)); - -            *value = new_value; -        } -    }; - -    match event { -        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerPressed { .. }) => { -            if layout.bounds().contains(cursor_position) { -                change(); -                state.is_dragging = true; - -                return event::Status::Captured; -            } -        } -        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerLifted { .. }) -        | Event::Touch(touch::Event::FingerLost { .. }) => { -            if is_dragging { -                if let Some(on_release) = on_release.clone() { -                    shell.publish(on_release); -                } -                state.is_dragging = false; - -                return event::Status::Captured; -            } -        } -        Event::Mouse(mouse::Event::CursorMoved { .. }) -        | Event::Touch(touch::Event::FingerMoved { .. }) => { -            if is_dragging { -                change(); - -                return event::Status::Captured; -            } -        } -        _ => {} -    } - -    event::Status::Ignored -} - -/// Draws a [`VerticalSlider`]. -pub fn draw<T, R>( -    renderer: &mut R, -    layout: Layout<'_>, -    cursor_position: Point, -    state: &State, -    value: T, -    range: &RangeInclusive<T>, -    style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, -    style: &<R::Theme as StyleSheet>::Style, -) where -    T: Into<f64> + Copy, -    R: crate::Renderer, -    R::Theme: StyleSheet, -{ -    let bounds = layout.bounds(); -    let is_mouse_over = bounds.contains(cursor_position); - -    let style = if state.is_dragging { -        style_sheet.dragging(style) -    } else if is_mouse_over { -        style_sheet.hovered(style) -    } else { -        style_sheet.active(style) -    }; - -    let rail_x = bounds.x + (bounds.width / 2.0).round(); - -    renderer.fill_quad( -        renderer::Quad { -            bounds: Rectangle { -                x: rail_x - 1.0, -                y: bounds.y, -                width: 2.0, -                height: bounds.height, -            }, -            border_radius: 0.0.into(), -            border_width: 0.0, -            border_color: Color::TRANSPARENT, -        }, -        style.rail_colors.0, -    ); - -    renderer.fill_quad( -        renderer::Quad { -            bounds: Rectangle { -                x: rail_x + 1.0, -                y: bounds.y, -                width: 2.0, -                height: bounds.height, -            }, -            border_radius: 0.0.into(), -            border_width: 0.0, -            border_color: Color::TRANSPARENT, -        }, -        Background::Color(style.rail_colors.1), -    ); - -    let (handle_width, handle_height, handle_border_radius) = match style -        .handle -        .shape -    { -        HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius), -        HandleShape::Rectangle { -            width, -            border_radius, -        } => (f32::from(width), bounds.width, border_radius), -    }; - -    let value = value.into() as f32; -    let (range_start, range_end) = { -        let (start, end) = range.clone().into_inner(); - -        (start.into() as f32, end.into() as f32) -    }; - -    let handle_offset = if range_start >= range_end { -        0.0 -    } else { -        (bounds.height - handle_width) * (value - range_end) -            / (range_start - range_end) -    }; - -    renderer.fill_quad( -        renderer::Quad { -            bounds: Rectangle { -                x: rail_x - (handle_height / 2.0), -                y: bounds.y + handle_offset.round(), -                width: handle_height, -                height: handle_width, -            }, -            border_radius: handle_border_radius.into(), -            border_width: style.handle.border_width, -            border_color: style.handle.border_color, -        }, -        style.handle.color, -    ); -} - -/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`]. -pub fn mouse_interaction( -    layout: Layout<'_>, -    cursor_position: Point, -    state: &State, -) -> mouse::Interaction { -    let bounds = layout.bounds(); -    let is_mouse_over = bounds.contains(cursor_position); - -    if state.is_dragging { -        mouse::Interaction::Grabbing -    } else if is_mouse_over { -        mouse::Interaction::Grab -    } else { -        mouse::Interaction::default() -    } -} - -/// The local state of a [`VerticalSlider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { -    is_dragging: bool, -} - -impl State { -    /// Creates a new [`State`]. -    pub fn new() -> State { -        State::default() -    } -} | 
