diff options
Diffstat (limited to 'native/src/widget')
26 files changed, 1916 insertions, 1443 deletions
| diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index c469a0e5..1d785f35 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -5,20 +5,24 @@ use crate::event::{self, Event};  use crate::layout;  use crate::mouse;  use crate::overlay; +use crate::renderer;  use crate::touch;  use crate::{ -    Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, -    Widget, +    Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding, +    Point, Rectangle, Vector, Widget,  }; +  use std::hash::Hash; +pub use iced_style::button::{Style, StyleSheet}; +  /// A generic widget that produces a message when pressed.  ///  /// ``` -/// # use iced_native::{button, Text}; +/// # use iced_native::widget::{button, Text};  /// #  /// # type Button<'a, Message> = -/// #     iced_native::Button<'a, Message, iced_native::renderer::Null>; +/// #     iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;  /// #  /// #[derive(Clone)]  /// enum Message { @@ -34,10 +38,10 @@ use std::hash::Hash;  /// be disabled:  ///  /// ``` -/// # use iced_native::{button, Text}; +/// # use iced_native::widget::{button, Text};  /// #  /// # type Button<'a, Message> = -/// #     iced_native::Button<'a, Message, iced_native::renderer::Null>; +/// #     iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;  /// #  /// #[derive(Clone)]  /// enum Message { @@ -53,7 +57,7 @@ use std::hash::Hash;  /// }  /// ```  #[allow(missing_debug_implementations)] -pub struct Button<'a, Message, Renderer: self::Renderer> { +pub struct Button<'a, Message, Renderer> {      state: &'a mut State,      content: Element<'a, Message, Renderer>,      on_press: Option<Message>, @@ -62,13 +66,13 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {      min_width: u32,      min_height: u32,      padding: Padding, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  }  impl<'a, Message, Renderer> Button<'a, Message, Renderer>  where      Message: Clone, -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      /// Creates a new [`Button`] with some local [`State`] and the given      /// content. @@ -84,8 +88,8 @@ where              height: Length::Shrink,              min_width: 0,              min_height: 0, -            padding: Renderer::DEFAULT_PADDING, -            style: Renderer::Style::default(), +            padding: Padding::new(5), +            style_sheet: Default::default(),          }      } @@ -127,8 +131,11 @@ where      }      /// Sets the style of the [`Button`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      }  } @@ -150,7 +157,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>      for Button<'a, Message, Renderer>  where      Message: Clone, -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -241,24 +248,88 @@ where          event::Status::Ignored      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> mouse::Interaction { +        let is_mouse_over = layout.bounds().contains(cursor_position); +        let is_disabled = self.on_press.is_none(); + +        if is_mouse_over && !is_disabled { +            mouse::Interaction::Pointer +        } else { +            mouse::Interaction::default() +        } +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw( -            defaults, -            layout.bounds(), +    ) { +        let bounds = layout.bounds(); +        let content_layout = layout.children().next().unwrap(); + +        let is_mouse_over = bounds.contains(cursor_position); +        let is_disabled = self.on_press.is_none(); + +        let styling = if is_disabled { +            self.style_sheet.disabled() +        } else if is_mouse_over { +            if self.state.is_pressed { +                self.style_sheet.pressed() +            } else { +                self.style_sheet.hovered() +            } +        } else { +            self.style_sheet.active() +        }; + +        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, +                        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, +                    border_width: styling.border_width, +                    border_color: styling.border_color, +                }, +                styling +                    .background +                    .unwrap_or(Background::Color(Color::TRANSPARENT)), +            ); +        } + +        self.content.draw( +            renderer, +            &renderer::Style { +                text_color: styling.text_color, +            }, +            content_layout,              cursor_position, -            self.on_press.is_none(), -            self.state.is_pressed, -            &self.style, -            &self.content, -            layout.children().next().unwrap(), -        ) +            &bounds, +        );      }      fn hash_layout(&self, state: &mut Hasher) { @@ -277,38 +348,11 @@ where      }  } -/// The renderer of a [`Button`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Button`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer + Sized { -    /// The default padding of a [`Button`]. -    const DEFAULT_PADDING: Padding; - -    /// The style supported by this renderer. -    type Style: Default; - -    /// Draws a [`Button`]. -    fn draw<Message>( -        &mut self, -        defaults: &Self::Defaults, -        bounds: Rectangle, -        cursor_position: Point, -        is_disabled: bool, -        is_pressed: bool, -        style: &Self::Style, -        content: &Element<'_, Message, Self>, -        content_layout: Layout<'_>, -    ) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where      Message: 'a + Clone, -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,  {      fn from(          button: Button<'a, Message, Renderer>, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 8bdb6b78..0d4a43ec 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -1,24 +1,27 @@  //! Show toggle controls using checkboxes.  use std::hash::Hash; -use crate::alignment::{self, Alignment}; +use crate::alignment;  use crate::event::{self, Event};  use crate::layout;  use crate::mouse; -use crate::row; +use crate::renderer;  use crate::text;  use crate::touch; +use crate::widget::{self, Row, Text};  use crate::{ -    Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row, -    Text, Widget, +    Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point, +    Rectangle, Widget,  }; +pub use iced_style::checkbox::{Style, StyleSheet}; +  /// A box that can be checked.  ///  /// # Example  ///  /// ``` -/// # type Checkbox<Message> = iced_native::Checkbox<Message, iced_native::renderer::Null>; +/// # type Checkbox<'a, Message> = iced_native::widget::Checkbox<'a, Message, iced_native::renderer::Null>;  /// #  /// pub enum Message {  ///     CheckboxToggled(bool), @@ -31,7 +34,7 @@ use crate::{  ///  ///   #[allow(missing_debug_implementations)] -pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> { +pub struct Checkbox<'a, Message, Renderer: text::Renderer> {      is_checked: bool,      on_toggle: Box<dyn Fn(bool) -> Message>,      label: String, @@ -41,12 +44,16 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {      text_size: Option<u16>,      font: Renderer::Font,      text_color: Option<Color>, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  } -impl<Message, Renderer: self::Renderer + text::Renderer> -    Checkbox<Message, Renderer> -{ +impl<'a, Message, Renderer: text::Renderer> Checkbox<'a, Message, Renderer> { +    /// The default size of a [`Checkbox`]. +    const DEFAULT_SIZE: u16 = 20; + +    /// The default spacing of a [`Checkbox`]. +    const DEFAULT_SPACING: u16 = 15; +      /// Creates a new [`Checkbox`].      ///      /// It expects: @@ -64,12 +71,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>              on_toggle: Box::new(f),              label: label.into(),              width: Length::Shrink, -            size: <Renderer as self::Renderer>::DEFAULT_SIZE, -            spacing: Renderer::DEFAULT_SPACING, +            size: Self::DEFAULT_SIZE, +            spacing: Self::DEFAULT_SPACING,              text_size: None,              font: Renderer::Font::default(),              text_color: None, -            style: Renderer::Style::default(), +            style_sheet: Default::default(),          }      } @@ -112,16 +119,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>      }      /// Sets the style of the [`Checkbox`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      }  } -impl<Message, Renderer> Widget<Message, Renderer> -    for Checkbox<Message, Renderer> +impl<'a, Message, Renderer> Widget<Message, Renderer> +    for Checkbox<'a, Message, Renderer>  where -    Renderer: self::Renderer + text::Renderer + row::Renderer, +    Renderer: text::Renderer,  {      fn width(&self) -> Length {          self.width @@ -180,43 +190,84 @@ where          event::Status::Ignored      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> mouse::Interaction { +        if layout.bounds().contains(cursor_position) { +            mouse::Interaction::Pointer +        } else { +            mouse::Interaction::default() +        } +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { +    ) {          let bounds = layout.bounds(); +        let is_mouse_over = bounds.contains(cursor_position); +          let mut children = layout.children(); -        let checkbox_layout = children.next().unwrap(); -        let label_layout = children.next().unwrap(); -        let checkbox_bounds = checkbox_layout.bounds(); - -        let label = text::Renderer::draw( -            renderer, -            defaults, -            label_layout.bounds(), -            &self.label, -            self.text_size.unwrap_or(renderer.default_size()), -            self.font, -            self.text_color, -            alignment::Horizontal::Left, -            alignment::Vertical::Center, -        ); +        { +            let layout = children.next().unwrap(); +            let bounds = layout.bounds(); -        let is_mouse_over = bounds.contains(cursor_position); +            let style = if is_mouse_over { +                self.style_sheet.hovered(self.is_checked) +            } else { +                self.style_sheet.active(self.is_checked) +            }; + +            renderer.fill_quad( +                renderer::Quad { +                    bounds, +                    border_radius: style.border_radius, +                    border_width: style.border_width, +                    border_color: style.border_color, +                }, +                style.background, +            ); -        self::Renderer::draw( -            renderer, -            checkbox_bounds, -            self.is_checked, -            is_mouse_over, -            label, -            &self.style, -        ) +            if self.is_checked { +                renderer.fill_text(text::Text { +                    content: &Renderer::CHECKMARK_ICON.to_string(), +                    font: Renderer::ICON_FONT, +                    size: bounds.height * 0.7, +                    bounds: Rectangle { +                        x: bounds.center_x(), +                        y: bounds.center_y(), +                        ..bounds +                    }, +                    color: style.checkmark_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.font, +                self.text_size, +                self.text_color, +                alignment::Horizontal::Left, +                alignment::Vertical::Center, +            ); +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -227,47 +278,14 @@ where      }  } -/// The renderer of a [`Checkbox`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Checkbox`] in your user interface. -/// -/// [renderer]: crate::Renderer -pub trait Renderer: crate::Renderer { -    /// The style supported by this renderer. -    type Style: Default; - -    /// The default size of a [`Checkbox`]. -    const DEFAULT_SIZE: u16; - -    /// The default spacing of a [`Checkbox`]. -    const DEFAULT_SPACING: u16; - -    /// Draws a [`Checkbox`]. -    /// -    /// It receives: -    ///   * the bounds of the [`Checkbox`] -    ///   * whether the [`Checkbox`] is selected or not -    ///   * whether the mouse is over the [`Checkbox`] or not -    ///   * the drawn label of the [`Checkbox`] -    fn draw( -        &mut self, -        bounds: Rectangle, -        is_checked: bool, -        is_mouse_over: bool, -        label: Self::Output, -        style: &Self::Style, -    ) -> Self::Output; -} - -impl<'a, Message, Renderer> From<Checkbox<Message, Renderer>> +impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer + text::Renderer + row::Renderer, +    Renderer: 'a + text::Renderer,      Message: 'a,  {      fn from( -        checkbox: Checkbox<Message, Renderer>, +        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 index 30cf0781..0d4d6fa7 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -3,7 +3,9 @@ use std::hash::Hash;  use crate::event::{self, Event};  use crate::layout; +use crate::mouse;  use crate::overlay; +use crate::renderer;  use crate::{      Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,      Rectangle, Widget, @@ -105,7 +107,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Column<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -162,21 +164,37 @@ where              .fold(event::Status::Ignored, event::Status::merge)      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) -> mouse::Interaction { +        self.children +            .iter() +            .zip(layout.children()) +            .map(|(child, layout)| { +                child.widget.mouse_interaction( +                    layout, +                    cursor_position, +                    viewport, +                ) +            }) +            .max() +            .unwrap_or_default() +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw( -            defaults, -            &self.children, -            layout, -            cursor_position, -            viewport, -        ) +    ) { +        for (child, layout) in self.children.iter().zip(layout.children()) { +            child.draw(renderer, style, layout, cursor_position, viewport); +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -208,33 +226,10 @@ where      }  } -/// The renderer of a [`Column`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Column`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer + Sized { -    /// Draws a [`Column`]. -    /// -    /// It receives: -    /// - the children of the [`Column`] -    /// - the [`Layout`] of the [`Column`] and its children -    /// - the cursor position -    fn draw<Message>( -        &mut self, -        defaults: &Self::Defaults, -        content: &[Element<'_, Message, Self>], -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,      Message: 'a,  {      fn from( diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 0e86ab62..596af7fd 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -4,19 +4,23 @@ use std::hash::Hash;  use crate::alignment::{self, Alignment};  use crate::event::{self, Event};  use crate::layout; +use crate::mouse;  use crate::overlay; +use crate::renderer;  use crate::{ -    Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, -    Widget, +    Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding, +    Point, Rectangle, Widget,  };  use std::u32; +pub use iced_style::container::{Style, StyleSheet}; +  /// An element decorating some content.  ///  /// It is normally used for alignment purposes.  #[allow(missing_debug_implementations)] -pub struct Container<'a, Message, Renderer: self::Renderer> { +pub struct Container<'a, Message, Renderer> {      padding: Padding,      width: Length,      height: Length, @@ -24,13 +28,13 @@ pub struct Container<'a, Message, Renderer: self::Renderer> {      max_height: u32,      horizontal_alignment: alignment::Horizontal,      vertical_alignment: alignment::Vertical, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,      content: Element<'a, Message, Renderer>,  }  impl<'a, Message, Renderer> Container<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      /// Creates an empty [`Container`].      pub fn new<T>(content: T) -> Self @@ -45,7 +49,7 @@ where              max_height: u32::MAX,              horizontal_alignment: alignment::Horizontal::Left,              vertical_alignment: alignment::Vertical::Top, -            style: Renderer::Style::default(), +            style_sheet: Default::default(),              content: content.into(),          }      } @@ -105,8 +109,11 @@ where      }      /// Sets the style of the [`Container`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      }  } @@ -114,7 +121,7 @@ where  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Container<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -172,23 +179,42 @@ where          )      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) -> mouse::Interaction { +        self.content.widget.mouse_interaction( +            layout.children().next().unwrap(), +            cursor_position, +            viewport, +        ) +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        renderer_style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw( -            defaults, -            layout.bounds(), +    ) { +        let style = self.style_sheet.style(); + +        draw_background(renderer, &style, layout.bounds()); + +        self.content.draw( +            renderer, +            &renderer::Style { +                text_color: style +                    .text_color +                    .unwrap_or(renderer_style.text_color), +            }, +            layout.children().next().unwrap(),              cursor_position,              viewport, -            &self.style, -            &self.content, -            layout.children().next().unwrap(), -        ) +        );      }      fn hash_layout(&self, state: &mut Hasher) { @@ -212,33 +238,33 @@ where      }  } -/// The renderer of a [`Container`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Container`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { -    /// The style supported by this renderer. -    type Style: Default; - -    /// Draws a [`Container`]. -    fn draw<Message>( -        &mut self, -        defaults: &Self::Defaults, -        bounds: Rectangle, -        cursor_position: Point, -        viewport: &Rectangle, -        style: &Self::Style, -        content: &Element<'_, Message, Self>, -        content_layout: Layout<'_>, -    ) -> Self::Output; +/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`. +pub fn draw_background<Renderer>( +    renderer: &mut Renderer, +    style: &Style, +    bounds: Rectangle, +) where +    Renderer: crate::Renderer, +{ +    if style.background.is_some() || style.border_width > 0.0 { +        renderer.fill_quad( +            renderer::Quad { +                bounds, +                border_radius: style.border_radius, +                border_width: style.border_width, +                border_color: style.border_color, +            }, +            style +                .background +                .unwrap_or(Background::Color(Color::TRANSPARENT)), +        ); +    }  }  impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,      Message: 'a,  {      fn from( diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 4d8e0a3f..66e95265 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -2,21 +2,19 @@  pub mod viewer;  pub use viewer::Viewer; +use crate::image::{self, Handle};  use crate::layout; +use crate::renderer;  use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; -use std::{ -    hash::{Hash, Hasher as _}, -    path::PathBuf, -    sync::Arc, -}; +use std::hash::Hash;  /// A frame that displays an image while keeping aspect ratio.  ///  /// # Example  ///  /// ``` -/// # use iced_native::Image; +/// # use iced_native::widget::Image;  /// #  /// let image = Image::new("resources/ferris.png");  /// ``` @@ -54,7 +52,7 @@ impl Image {  impl<Message, Renderer> Widget<Message, Renderer> for Image  where -    Renderer: self::Renderer, +    Renderer: image::Renderer,  {      fn width(&self) -> Length {          self.width @@ -92,12 +90,12 @@ where      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          _cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw(self.handle.clone(), layout) +    ) { +        renderer.draw(self.handle.clone(), layout.bounds());      }      fn hash_layout(&self, state: &mut Hasher) { @@ -110,129 +108,9 @@ where      }  } -/// An [`Image`] handle. -#[derive(Debug, Clone)] -pub struct Handle { -    id: u64, -    data: Arc<Data>, -} - -impl Handle { -    /// Creates an image [`Handle`] pointing to the image of the given path. -    /// -    /// Makes an educated guess about the image format by examining the data in the file. -    pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { -        Self::from_data(Data::Path(path.into())) -    } - -    /// Creates an image [`Handle`] containing the image pixels directly. This -    /// function expects the input data to be provided as a `Vec<u8>` of BGRA -    /// pixels. -    /// -    /// This is useful if you have already decoded your image. -    pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle { -        Self::from_data(Data::Pixels { -            width, -            height, -            pixels, -        }) -    } - -    /// Creates an image [`Handle`] containing the image data directly. -    /// -    /// Makes an educated guess about the image format by examining the given data. -    /// -    /// This is useful if you already have your image loaded in-memory, maybe -    /// because you downloaded or generated it procedurally. -    pub fn from_memory(bytes: Vec<u8>) -> Handle { -        Self::from_data(Data::Bytes(bytes)) -    } - -    fn from_data(data: Data) -> Handle { -        let mut hasher = Hasher::default(); -        data.hash(&mut hasher); - -        Handle { -            id: hasher.finish(), -            data: Arc::new(data), -        } -    } - -    /// Returns the unique identifier of the [`Handle`]. -    pub fn id(&self) -> u64 { -        self.id -    } - -    /// Returns a reference to the image [`Data`]. -    pub fn data(&self) -> &Data { -        &self.data -    } -} - -impl<T> From<T> for Handle -where -    T: Into<PathBuf>, -{ -    fn from(path: T) -> Handle { -        Handle::from_path(path.into()) -    } -} - -impl Hash for Handle { -    fn hash<H: std::hash::Hasher>(&self, state: &mut H) { -        self.id.hash(state); -    } -} - -/// The data of an [`Image`]. -#[derive(Clone, Hash)] -pub enum Data { -    /// File data -    Path(PathBuf), - -    /// In-memory data -    Bytes(Vec<u8>), - -    /// Decoded image pixels in BGRA format. -    Pixels { -        /// The width of the image. -        width: u32, -        /// The height of the image. -        height: u32, -        /// The pixels. -        pixels: Vec<u8>, -    }, -} - -impl std::fmt::Debug for Data { -    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -        match self { -            Data::Path(path) => write!(f, "Path({:?})", path), -            Data::Bytes(_) => write!(f, "Bytes(...)"), -            Data::Pixels { width, height, .. } => { -                write!(f, "Pixels({} * {})", width, height) -            } -        } -    } -} - -/// The renderer of an [`Image`]. -/// -/// Your [renderer] will need to implement this trait before being able to use -/// an [`Image`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { -    /// Returns the dimensions of an [`Image`] located on the given path. -    fn dimensions(&self, handle: &Handle) -> (u32, u32); - -    /// Draws an [`Image`]. -    fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: image::Renderer,  {      fn from(image: Image) -> Element<'a, Message, Renderer> {          Element::new(image) diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 405daf00..95e5c6e4 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -3,6 +3,7 @@ use crate::event::{self, Event};  use crate::image;  use crate::layout;  use crate::mouse; +use crate::renderer;  use crate::{      Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,      Widget, @@ -88,7 +89,7 @@ impl<'a> Viewer<'a> {      /// will be respected.      fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size      where -        Renderer: self::Renderer + image::Renderer, +        Renderer: image::Renderer,      {          let (width, height) = renderer.dimensions(&self.handle); @@ -115,7 +116,7 @@ impl<'a> Viewer<'a> {  impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>  where -    Renderer: self::Renderer + image::Renderer, +    Renderer: image::Renderer,  {      fn width(&self) -> Length {          self.width @@ -280,14 +281,32 @@ where          }      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> mouse::Interaction { +        let bounds = layout.bounds(); +        let is_mouse_over = bounds.contains(cursor_position); + +        if self.state.is_cursor_grabbed() { +            mouse::Interaction::Grabbing +        } else if is_mouse_over { +            mouse::Interaction::Grab +        } else { +            mouse::Interaction::Idle +        } +    } +      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        _cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { +    ) {          let bounds = layout.bounds();          let image_size = self.image_size(renderer, bounds.size()); @@ -301,17 +320,19 @@ where              image_top_left - self.state.offset(bounds, image_size)          }; -        let is_mouse_over = bounds.contains(cursor_position); - -        self::Renderer::draw( -            renderer, -            &self.state, -            bounds, -            image_size, -            translation, -            self.handle.clone(), -            is_mouse_over, -        ) +        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) +                    }, +                ) +            }); +        });      }      fn hash_layout(&self, state: &mut Hasher) { @@ -373,38 +394,9 @@ impl State {      }  } -/// The renderer of an [`Viewer`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Viewer`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer + Sized { -    /// Draws the [`Viewer`]. -    /// -    /// It receives: -    /// - the [`State`] of the [`Viewer`] -    /// - the bounds of the [`Viewer`] widget -    /// - the [`Size`] of the scaled [`Viewer`] image -    /// - the translation of the clipped image -    /// - the [`Handle`] to the underlying image -    /// - whether the mouse is over the [`Viewer`] or not -    /// -    /// [`Handle`]: image::Handle -    fn draw( -        &mut self, -        state: &State, -        bounds: Rectangle, -        image_size: Size, -        translation: Vector, -        handle: image::Handle, -        is_mouse_over: bool, -    ) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer + image::Renderer, +    Renderer: 'a + image::Renderer,      Message: 'a,  {      fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 26a72409..20616ed4 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -27,18 +27,19 @@ pub use split::Split;  pub use state::State;  pub use title_bar::TitleBar; -use crate::container;  use crate::event::{self, Event};  use crate::layout;  use crate::mouse;  use crate::overlay; -use crate::row; +use crate::renderer;  use crate::touch;  use crate::{ -    Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, -    Widget, +    Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, +    Vector, Widget,  }; +pub use iced_style::pane_grid::{Line, StyleSheet}; +  /// A collection of panes distributed using either vertical or horizontal splits  /// to completely fill the space available.  /// @@ -61,10 +62,10 @@ use crate::{  /// ## Example  ///  /// ``` -/// # use iced_native::{pane_grid, Text}; +/// # use iced_native::widget::{pane_grid, Text};  /// #  /// # type PaneGrid<'a, Message> = -/// #     iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>; +/// #     iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;  /// #  /// enum PaneState {  ///     SomePane, @@ -89,7 +90,7 @@ use crate::{  ///     .on_resize(10, Message::PaneResized);  /// ```  #[allow(missing_debug_implementations)] -pub struct PaneGrid<'a, Message, Renderer: self::Renderer> { +pub struct PaneGrid<'a, Message, Renderer> {      state: &'a mut state::Internal,      elements: Vec<(Pane, Content<'a, Message, Renderer>)>,      width: Length, @@ -98,12 +99,12 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {      on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,      on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,      on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, -    style: <Renderer as self::Renderer>::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  }  impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      /// Creates a [`PaneGrid`] with the given [`State`] and view function.      /// @@ -130,7 +131,7 @@ where              on_click: None,              on_drag: None,              on_resize: None, -            style: Default::default(), +            style_sheet: Default::default(),          }      } @@ -190,18 +191,15 @@ where      }      /// Sets the style of the [`PaneGrid`]. -    pub fn style( -        mut self, -        style: impl Into<<Renderer as self::Renderer>::Style>, -    ) -> Self { -        self.style = style.into(); +    pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { +        self.style_sheet = style.into();          self      }  }  impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn click_pane(          &mut self, @@ -318,7 +316,7 @@ pub struct ResizeEvent {  impl<'a, Message, Renderer> Widget<Message, Renderer>      for PaneGrid<'a, Message, Renderer>  where -    Renderer: self::Renderer + container::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -473,14 +471,43 @@ where              .fold(event_status, event::Status::merge)      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) -> mouse::Interaction { +        if self.state.picked_pane().is_some() { +            return mouse::Interaction::Grab; +        } + +        if let Some((_, axis)) = self.state.picked_split() { +            return match axis { +                Axis::Horizontal => mouse::Interaction::ResizingHorizontally, +                Axis::Vertical => mouse::Interaction::ResizingVertically, +            }; +        } + +        self.elements +            .iter() +            .zip(layout.children()) +            .map(|((_pane, content), layout)| { +                content.mouse_interaction(layout, cursor_position, viewport) +            }) +            .max() +            .unwrap_or_default() +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          viewport: &Rectangle, -    ) -> Renderer::Output { +    ) { +        let picked_pane = self.state.picked_pane(); +          let picked_split = self              .state              .picked_split() @@ -529,17 +556,89 @@ where                  None => None,              }); -        self::Renderer::draw( -            renderer, -            defaults, -            &self.elements, -            self.state.picked_pane(), -            picked_split, -            layout, -            &self.style, -            cursor_position, -            viewport, -        ) +        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 +        }; + +        for ((id, pane), layout) in self.elements.iter().zip(layout.children()) +        { +            match picked_pane { +                Some((dragging, origin)) if *id == dragging => { +                    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| { +                                pane.draw( +                                    renderer, +                                    style, +                                    layout, +                                    pane_cursor_position, +                                    viewport, +                                ); +                            }); +                        }, +                    ); +                } +                _ => { +                    pane.draw( +                        renderer, +                        style, +                        layout, +                        pane_cursor_position, +                        viewport, +                    ); +                } +            } +        } + +        if let Some((axis, split_region, is_picked)) = picked_split { +            let highlight = if is_picked { +                self.style_sheet.picked_split() +            } else { +                self.style_sheet.hovered_split() +            }; + +            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, +                        border_width: 0.0, +                        border_color: Color::TRANSPARENT, +                    }, +                    highlight.color, +                ); +            } +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -569,78 +668,10 @@ where      }  } -/// The renderer of a [`PaneGrid`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`PaneGrid`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer + container::Renderer + Sized { -    /// The style supported by this renderer. -    type Style: Default; - -    /// Draws a [`PaneGrid`]. -    /// -    /// It receives: -    /// - the elements of the [`PaneGrid`] -    /// - the [`Pane`] that is currently being dragged -    /// - the [`Axis`] that is currently being resized -    /// - the [`Layout`] of the [`PaneGrid`] and its elements -    /// - the cursor position -    fn draw<Message>( -        &mut self, -        defaults: &Self::Defaults, -        content: &[(Pane, Content<'_, Message, Self>)], -        dragging: Option<(Pane, Point)>, -        resizing: Option<(Axis, Rectangle, bool)>, -        layout: Layout<'_>, -        style: &<Self as self::Renderer>::Style, -        cursor_position: Point, -        viewport: &Rectangle, -    ) -> Self::Output; - -    /// Draws a [`Pane`]. -    /// -    /// It receives: -    /// - the [`TitleBar`] of the [`Pane`], if any -    /// - the [`Content`] of the [`Pane`] -    /// - the [`Layout`] of the [`Pane`] and its elements -    /// - the cursor position -    fn draw_pane<Message>( -        &mut self, -        defaults: &Self::Defaults, -        bounds: Rectangle, -        style: &<Self as container::Renderer>::Style, -        title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>, -        body: (&Element<'_, Message, Self>, Layout<'_>), -        cursor_position: Point, -        viewport: &Rectangle, -    ) -> Self::Output; - -    /// Draws a [`TitleBar`]. -    /// -    /// It receives: -    /// - the bounds, style of the [`TitleBar`] -    /// - the style of the [`TitleBar`] -    /// - the content of the [`TitleBar`] with its layout -    /// - the controls of the [`TitleBar`] with their [`Layout`], if any -    /// - the cursor position -    fn draw_title_bar<Message>( -        &mut self, -        defaults: &Self::Defaults, -        bounds: Rectangle, -        style: &<Self as container::Renderer>::Style, -        content: (&Element<'_, Message, Self>, Layout<'_>), -        controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) -> Self::Output; -} -  impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer + row::Renderer, +    Renderer: 'a + crate::Renderer,      Message: 'a,  {      fn from( diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs index 4c43826e..4c52bad4 100644 --- a/native/src/widget/pane_grid/configuration.rs +++ b/native/src/widget/pane_grid/configuration.rs @@ -1,4 +1,4 @@ -use crate::pane_grid::Axis; +use crate::widget::pane_grid::Axis;  /// The arrangement of a [`PaneGrid`].  /// diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index bac9fdd4..c44506dd 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,30 +1,32 @@ -use crate::container;  use crate::event::{self, Event};  use crate::layout; +use crate::mouse;  use crate::overlay; -use crate::pane_grid::{self, TitleBar}; +use crate::renderer; +use crate::widget::container; +use crate::widget::pane_grid::TitleBar;  use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};  /// The content of a [`Pane`].  ///  /// [`Pane`]: crate::widget::pane_grid::Pane  #[allow(missing_debug_implementations)] -pub struct Content<'a, Message, Renderer: pane_grid::Renderer> { +pub struct Content<'a, Message, Renderer> {      title_bar: Option<TitleBar<'a, Message, Renderer>>,      body: Element<'a, Message, Renderer>, -    style: <Renderer as container::Renderer>::Style, +    style_sheet: Box<dyn container::StyleSheet + 'a>,  }  impl<'a, Message, Renderer> Content<'a, Message, Renderer>  where -    Renderer: pane_grid::Renderer, +    Renderer: crate::Renderer,  {      /// Creates a new [`Content`] with the provided body.      pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {          Self {              title_bar: None,              body: body.into(), -            style: Default::default(), +            style_sheet: Default::default(),          }      } @@ -40,16 +42,16 @@ where      /// Sets the style of the [`Content`].      pub fn style(          mut self, -        style: impl Into<<Renderer as container::Renderer>::Style>, +        style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,      ) -> Self { -        self.style = style.into(); +        self.style_sheet = style_sheet.into();          self      }  }  impl<'a, Message, Renderer> Content<'a, Message, Renderer>  where -    Renderer: pane_grid::Renderer, +    Renderer: crate::Renderer,  {      /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].      /// @@ -57,35 +59,45 @@ where      pub fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          viewport: &Rectangle, -    ) -> Renderer::Output { +    ) { +        let bounds = layout.bounds(); + +        { +            let style = self.style_sheet.style(); + +            container::draw_background(renderer, &style, bounds); +        } +          if let Some(title_bar) = &self.title_bar {              let mut children = layout.children();              let title_bar_layout = children.next().unwrap();              let body_layout = children.next().unwrap(); -            renderer.draw_pane( -                defaults, -                layout.bounds(), -                &self.style, -                Some((title_bar, title_bar_layout)), -                (&self.body, body_layout), +            let show_controls = bounds.contains(cursor_position); + +            title_bar.draw( +                renderer, +                style, +                title_bar_layout,                  cursor_position,                  viewport, -            ) -        } else { -            renderer.draw_pane( -                defaults, -                layout.bounds(), -                &self.style, -                None, -                (&self.body, layout), +                show_controls, +            ); + +            self.body.draw( +                renderer, +                style, +                body_layout,                  cursor_position,                  viewport, -            ) +            ); +        } else { +            self.body +                .draw(renderer, style, layout, cursor_position, viewport);          }      } @@ -186,6 +198,40 @@ where          event_status.merge(body_status)      } +    pub(crate) fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) -> mouse::Interaction { +        let (body_layout, title_bar_interaction) = +            if let Some(title_bar) = &self.title_bar { +                let mut children = layout.children(); +                let title_bar_layout = children.next().unwrap(); + +                let is_over_pick_area = title_bar +                    .is_over_pick_area(title_bar_layout, cursor_position); + +                if is_over_pick_area { +                    return mouse::Interaction::Grab; +                } + +                let mouse_interaction = title_bar.mouse_interaction( +                    title_bar_layout, +                    cursor_position, +                    viewport, +                ); + +                (children.next().unwrap(), mouse_interaction) +            } else { +                (layout, mouse::Interaction::default()) +            }; + +        self.body +            .mouse_interaction(body_layout, cursor_position, viewport) +            .max(title_bar_interaction) +    } +      pub(crate) fn hash_layout(&self, state: &mut Hasher) {          if let Some(title_bar) = &self.title_bar {              title_bar.hash_layout(state); @@ -215,7 +261,7 @@ where  impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>  where      T: Into<Element<'a, Message, Renderer>>, -    Renderer: pane_grid::Renderer + container::Renderer, +    Renderer: crate::Renderer,  {      fn from(element: T) -> Self {          Self::new(element) diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 84714e00..af6573a0 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -1,7 +1,5 @@ -use crate::{ -    pane_grid::{Axis, Pane, Split}, -    Rectangle, Size, -}; +use crate::widget::pane_grid::{Axis, Pane, Split}; +use crate::{Rectangle, Size};  use std::collections::BTreeMap; diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index fb96f89f..bcc724a8 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,7 +1,7 @@ -use crate::{ -    pane_grid::{Axis, Configuration, Direction, Node, Pane, Split}, -    Hasher, Point, Rectangle, Size, +use crate::widget::pane_grid::{ +    Axis, Configuration, Direction, Node, Pane, Split,  }; +use crate::{Hasher, Point, Rectangle, Size};  use std::collections::{BTreeMap, HashMap}; diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 070010f8..070cf404 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -1,8 +1,9 @@ -use crate::container;  use crate::event::{self, Event};  use crate::layout; +use crate::mouse;  use crate::overlay; -use crate::pane_grid; +use crate::renderer; +use crate::widget::container;  use crate::{      Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,  }; @@ -11,17 +12,17 @@ use crate::{  ///  /// [`Pane`]: crate::widget::pane_grid::Pane  #[allow(missing_debug_implementations)] -pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> { +pub struct TitleBar<'a, Message, Renderer> {      content: Element<'a, Message, Renderer>,      controls: Option<Element<'a, Message, Renderer>>,      padding: Padding,      always_show_controls: bool, -    style: <Renderer as container::Renderer>::Style, +    style_sheet: Box<dyn container::StyleSheet + 'a>,  }  impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>  where -    Renderer: pane_grid::Renderer, +    Renderer: crate::Renderer,  {      /// Creates a new [`TitleBar`] with the given content.      pub fn new<E>(content: E) -> Self @@ -33,7 +34,7 @@ where              controls: None,              padding: Padding::ZERO,              always_show_controls: false, -            style: Default::default(), +            style_sheet: Default::default(),          }      } @@ -55,9 +56,9 @@ where      /// Sets the style of the [`TitleBar`].      pub fn style(          mut self, -        style: impl Into<<Renderer as container::Renderer>::Style>, +        style: impl Into<Box<dyn container::StyleSheet + 'a>>,      ) -> Self { -        self.style = style.into(); +        self.style_sheet = style.into();          self      } @@ -77,7 +78,7 @@ where  impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>  where -    Renderer: pane_grid::Renderer, +    Renderer: crate::Renderer,  {      /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].      /// @@ -85,39 +86,47 @@ where      pub fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        inherited_style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          viewport: &Rectangle,          show_controls: bool, -    ) -> Renderer::Output { +    ) { +        let bounds = layout.bounds(); +        let style = self.style_sheet.style(); +        let inherited_style = renderer::Style { +            text_color: style.text_color.unwrap_or(inherited_style.text_color), +        }; + +        container::draw_background(renderer, &style, bounds); +          let mut children = layout.children();          let padded = children.next().unwrap();          let mut children = padded.children();          let title_layout = children.next().unwrap(); -        let controls = if let Some(controls) = &self.controls { +        self.content.draw( +            renderer, +            &inherited_style, +            title_layout, +            cursor_position, +            viewport, +        ); + +        if let Some(controls) = &self.controls {              let controls_layout = children.next().unwrap();              if show_controls || self.always_show_controls { -                Some((controls, controls_layout)) -            } else { -                None +                controls.draw( +                    renderer, +                    &inherited_style, +                    controls_layout, +                    cursor_position, +                    viewport, +                );              } -        } else { -            None -        }; - -        renderer.draw_title_bar( -            defaults, -            layout.bounds(), -            &self.style, -            (&self.content, title_layout), -            controls, -            cursor_position, -            viewport, -        ) +        }      }      /// Returns whether the mouse cursor is over the pick area of the @@ -244,6 +253,35 @@ where          control_status.merge(title_status)      } +    pub(crate) fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) -> 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.mouse_interaction( +            title_layout, +            cursor_position, +            viewport, +        ); + +        if let Some(controls) = &self.controls { +            let controls_layout = children.next().unwrap(); + +            controls +                .mouse_interaction(controls_layout, cursor_position, viewport) +                .max(title_interaction) +        } else { +            title_interaction +        } +    } +      pub(crate) fn overlay(          &mut self,          layout: Layout<'_>, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index d7792000..9d1a86ec 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -1,12 +1,13 @@  //! 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::scrollable; -use crate::text; +use crate::renderer; +use crate::text::{self, Text};  use crate::touch;  use crate::{      Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, @@ -14,9 +15,11 @@ use crate::{  };  use std::borrow::Cow; +pub use iced_style::pick_list::{Style, StyleSheet}; +  /// A widget for selecting a single value from a list of options.  #[allow(missing_debug_implementations)] -pub struct PickList<'a, T, Message, Renderer: self::Renderer> +pub struct PickList<'a, T, Message, Renderer: text::Renderer>  where      [T]: ToOwned<Owned = Vec<T>>,  { @@ -33,7 +36,7 @@ where      padding: Padding,      text_size: Option<u16>,      font: Renderer::Font, -    style: <Renderer as self::Renderer>::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  }  /// The local state of a [`PickList`]. @@ -58,12 +61,15 @@ impl<T> Default for State<T> {      }  } -impl<'a, T: 'a, Message, Renderer: self::Renderer> +impl<'a, T: 'a, Message, Renderer: text::Renderer>      PickList<'a, T, Message, Renderer>  where      T: ToString + Eq,      [T]: ToOwned<Owned = Vec<T>>,  { +    /// The default padding of a [`PickList`]. +    pub const DEFAULT_PADDING: Padding = Padding::new(5); +      /// Creates a new [`PickList`] with the given [`State`], a list of options,      /// the current selected value, and the message to produce when an option is      /// selected. @@ -93,9 +99,9 @@ where              selected,              width: Length::Shrink,              text_size: None, -            padding: Renderer::DEFAULT_PADDING, +            padding: Self::DEFAULT_PADDING,              font: Default::default(), -            style: Default::default(), +            style_sheet: Default::default(),          }      } @@ -132,9 +138,9 @@ where      /// Sets the style of the [`PickList`].      pub fn style(          mut self, -        style: impl Into<<Renderer as self::Renderer>::Style>, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,      ) -> Self { -        self.style = style.into(); +        self.style_sheet = style_sheet.into();          self      }  } @@ -145,7 +151,7 @@ where      T: Clone + ToString + Eq,      [T]: ToOwned<Owned = Vec<T>>,      Message: 'static, -    Renderer: self::Renderer + scrollable::Renderer + 'a, +    Renderer: text::Renderer + 'a,  {      fn width(&self) -> Length {          self.width @@ -320,25 +326,90 @@ where          }      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> 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() +        } +    } +      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        self::Renderer::draw( -            renderer, -            layout.bounds(), -            cursor_position, -            self.selected.as_ref().map(ToString::to_string), -            self.placeholder.as_ref().map(String::as_str), -            self.padding, -            self.text_size.unwrap_or(renderer.default_size()), -            self.font, -            &self.style, -        ) +    ) { +        let bounds = layout.bounds(); +        let is_mouse_over = bounds.contains(cursor_position); +        let is_selected = self.selected.is_some(); + +        let style = if is_mouse_over { +            self.style_sheet.hovered() +        } else { +            self.style_sheet.active() +        }; + +        renderer.fill_quad( +            renderer::Quad { +                bounds, +                border_color: style.border_color, +                border_width: style.border_width, +                border_radius: style.border_radius, +            }, +            style.background, +        ); + +        renderer.fill_text(Text { +            content: &Renderer::ARROW_DOWN_ICON.to_string(), +            font: Renderer::ICON_FONT, +            size: bounds.height * style.icon_size, +            bounds: Rectangle { +                x: bounds.x + bounds.width +                    - f32::from(self.padding.horizontal()), +                y: bounds.center_y(), +                ..bounds +            }, +            color: style.text_color, +            horizontal_alignment: alignment::Horizontal::Right, +            vertical_alignment: alignment::Vertical::Center, +        }); + +        if let Some(label) = self +            .selected +            .as_ref() +            .map(ToString::to_string) +            .as_ref() +            .or_else(|| self.placeholder.as_ref()) +        { +            renderer.fill_text(Text { +                content: label, +                size: f32::from( +                    self.text_size.unwrap_or(renderer.default_size()), +                ), +                font: self.font, +                color: is_selected +                    .then(|| style.text_color) +                    .unwrap_or(style.placeholder_color), +                bounds: Rectangle { +                    x: bounds.x + f32::from(self.padding.left), +                    y: bounds.center_y(), +                    ..bounds +                }, +                horizontal_alignment: alignment::Horizontal::Left, +                vertical_alignment: alignment::Vertical::Center, +            }) +        }      }      fn overlay( @@ -357,7 +428,7 @@ where              .width(bounds.width.round() as u16)              .padding(self.padding)              .font(self.font) -            .style(Renderer::menu_style(&self.style)); +            .style(self.style_sheet.menu());              if let Some(text_size) = self.text_size {                  menu = menu.text_size(text_size); @@ -370,44 +441,12 @@ where      }  } -/// The renderer of a [`PickList`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`PickList`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: text::Renderer + menu::Renderer { -    /// The default padding of a [`PickList`]. -    const DEFAULT_PADDING: Padding; - -    /// The [`PickList`] style supported by this renderer. -    type Style: Default; - -    /// Returns the style of the [`Menu`] of the [`PickList`]. -    fn menu_style( -        style: &<Self as Renderer>::Style, -    ) -> <Self as menu::Renderer>::Style; - -    /// Draws a [`PickList`]. -    fn draw( -        &mut self, -        bounds: Rectangle, -        cursor_position: Point, -        selected: Option<String>, -        placeholder: Option<&str>, -        padding: Padding, -        text_size: u16, -        font: Self::Font, -        style: &<Self as Renderer>::Style, -    ) -> Self::Output; -} -  impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>      for PickList<'a, T, Message, Renderer>  where      T: Clone + ToString + Eq,      [T]: ToOwned<Owned = Vec<T>>, -    Renderer: self::Renderer + 'a, +    Renderer: text::Renderer + 'a,      Message: 'static,  {      fn into(self) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index d294f198..69eb8c09 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -1,17 +1,19 @@  //! Provide progress feedback to your users. +use crate::layout; +use crate::renderer;  use crate::{ -    layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, +    Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,  };  use std::{hash::Hash, ops::RangeInclusive}; +pub use iced_style::progress_bar::{Style, StyleSheet}; +  /// A bar that displays progress.  ///  /// # Example  /// ``` -/// # use iced_native::renderer::Null; -/// # -/// # pub type ProgressBar = iced_native::ProgressBar<Null>; +/// # use iced_native::widget::ProgressBar;  /// let value = 50.0;  ///  /// ProgressBar::new(0.0..=100.0, value); @@ -19,15 +21,18 @@ use std::{hash::Hash, ops::RangeInclusive};  ///  ///   #[allow(missing_debug_implementations)] -pub struct ProgressBar<Renderer: self::Renderer> { +pub struct ProgressBar<'a> {      range: RangeInclusive<f32>,      value: f32,      width: Length,      height: Option<Length>, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  } -impl<Renderer: self::Renderer> ProgressBar<Renderer> { +impl<'a> ProgressBar<'a> { +    /// The default height of a [`ProgressBar`]. +    pub const DEFAULT_HEIGHT: u16 = 30; +      /// Creates a new [`ProgressBar`].      ///      /// It expects: @@ -39,7 +44,7 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {              range,              width: Length::Fill,              height: None, -            style: Renderer::Style::default(), +            style_sheet: Default::default(),          }      } @@ -56,23 +61,25 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {      }      /// Sets the style of the [`ProgressBar`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      }  } -impl<Message, Renderer> Widget<Message, Renderer> for ProgressBar<Renderer> +impl<'a, Message, Renderer> Widget<Message, Renderer> for ProgressBar<'a>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width      }      fn height(&self) -> Length { -        self.height -            .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)) +        self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT))      }      fn layout( @@ -80,10 +87,9 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height( -            self.height -                .unwrap_or(Length::Units(Renderer::DEFAULT_HEIGHT)), -        ); +        let limits = limits +            .width(self.width) +            .height(self.height.unwrap_or(Length::Units(Self::DEFAULT_HEIGHT)));          let size = limits.resolve(Size::ZERO); @@ -93,17 +99,47 @@ where      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          _cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw( -            layout.bounds(), -            self.range.clone(), -            self.value, -            &self.style, -        ) +    ) { +        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 = self.style_sheet.style(); + +        renderer.fill_quad( +            renderer::Quad { +                bounds: Rectangle { ..bounds }, +                border_radius: style.border_radius, +                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, +                    border_width: 0.0, +                    border_color: Color::TRANSPARENT, +                }, +                style.bar, +            ); +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -115,45 +151,13 @@ where      }  } -/// The renderer of a [`ProgressBar`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`ProgressBar`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { -    /// The style supported by this renderer. -    type Style: Default; - -    /// The default height of a [`ProgressBar`]. -    const DEFAULT_HEIGHT: u16; - -    /// Draws a [`ProgressBar`]. -    /// -    /// It receives: -    ///   * the bounds of the [`ProgressBar`] -    ///   * the range of values of the [`ProgressBar`] -    ///   * the current value of the [`ProgressBar`] -    ///   * maybe a specific background of the [`ProgressBar`] -    ///   * maybe a specific active color of the [`ProgressBar`] -    fn draw( -        &self, -        bounds: Rectangle, -        range: RangeInclusive<f32>, -        value: f32, -        style: &Self::Style, -    ) -> Self::Output; -} - -impl<'a, Message, Renderer> From<ProgressBar<Renderer>> +impl<'a, Message, Renderer> From<ProgressBar<'a>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,      Message: 'a,  { -    fn from( -        progress_bar: ProgressBar<Renderer>, -    ) -> Element<'a, Message, Renderer> { +    fn from(progress_bar: ProgressBar<'a>) -> Element<'a, Message, Renderer> {          Element::new(progress_bar)      }  } diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 513b2fce..86ad4c4e 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,24 +1,27 @@  //! Create choices using radio buttons.  use std::hash::Hash; -use crate::alignment::{self, Alignment}; +use crate::alignment;  use crate::event::{self, Event};  use crate::layout;  use crate::mouse; -use crate::row; +use crate::renderer;  use crate::text;  use crate::touch; +use crate::widget::{self, Row, Text};  use crate::{ -    Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Row, -    Text, Widget, +    Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point, +    Rectangle, Widget,  }; +pub use iced_style::radio::{Style, StyleSheet}; +  /// A circular button representing a choice.  ///  /// # Example  /// ``` -/// # type Radio<Message> = -/// #     iced_native::Radio<Message, iced_native::renderer::Null>; +/// # type Radio<'a, Message> = +/// #     iced_native::widget::Radio<'a, Message, iced_native::renderer::Null>;  /// #  /// #[derive(Debug, Clone, Copy, PartialEq, Eq)]  /// pub enum Choice { @@ -40,7 +43,7 @@ use crate::{  ///  ///   #[allow(missing_debug_implementations)] -pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> { +pub struct Radio<'a, Message, Renderer: text::Renderer> {      is_selected: bool,      on_click: Message,      label: String, @@ -50,14 +53,19 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {      text_size: Option<u16>,      text_color: Option<Color>,      font: Renderer::Font, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  } -impl<Message, Renderer: self::Renderer + text::Renderer> -    Radio<Message, Renderer> +impl<'a, Message, Renderer: text::Renderer> Radio<'a, Message, Renderer>  where      Message: Clone,  { +    /// The default size of a [`Radio`] button. +    pub const DEFAULT_SIZE: u16 = 28; + +    /// The default spacing of a [`Radio`] button. +    pub const DEFAULT_SPACING: u16 = 15; +      /// Creates a new [`Radio`] button.      ///      /// It expects: @@ -81,12 +89,12 @@ where              on_click: f(value),              label: label.into(),              width: Length::Shrink, -            size: <Renderer as self::Renderer>::DEFAULT_SIZE, -            spacing: Renderer::DEFAULT_SPACING, //15 +            size: Self::DEFAULT_SIZE, +            spacing: Self::DEFAULT_SPACING, //15              text_size: None,              text_color: None,              font: Default::default(), -            style: Renderer::Style::default(), +            style_sheet: Default::default(),          }      } @@ -127,16 +135,20 @@ where      }      /// Sets the style of the [`Radio`] button. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      }  } -impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer> +impl<'a, Message, Renderer> Widget<Message, Renderer> +    for Radio<'a, Message, Renderer>  where      Message: Clone, -    Renderer: self::Renderer + text::Renderer + row::Renderer, +    Renderer: text::Renderer,  {      fn width(&self) -> Length {          self.width @@ -192,43 +204,88 @@ where          event::Status::Ignored      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> mouse::Interaction { +        if layout.bounds().contains(cursor_position) { +            mouse::Interaction::Pointer +        } else { +            mouse::Interaction::default() +        } +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { +    ) {          let bounds = layout.bounds(); +        let is_mouse_over = bounds.contains(cursor_position); +          let mut children = layout.children(); -        let radio_layout = children.next().unwrap(); -        let label_layout = children.next().unwrap(); -        let radio_bounds = radio_layout.bounds(); - -        let label = text::Renderer::draw( -            renderer, -            defaults, -            label_layout.bounds(), -            &self.label, -            self.text_size.unwrap_or(renderer.default_size()), -            self.font, -            self.text_color, -            alignment::Horizontal::Left, -            alignment::Vertical::Center, -        ); +        { +            let layout = children.next().unwrap(); +            let bounds = layout.bounds(); -        let is_mouse_over = bounds.contains(cursor_position); +            let size = bounds.width; +            let dot_size = size / 2.0; + +            let style = if is_mouse_over { +                self.style_sheet.hovered() +            } else { +                self.style_sheet.active() +            }; + +            renderer.fill_quad( +                renderer::Quad { +                    bounds, +                    border_radius: size / 2.0, +                    border_width: style.border_width, +                    border_color: style.border_color, +                }, +                style.background, +            ); -        self::Renderer::draw( -            renderer, -            radio_bounds, -            self.is_selected, -            is_mouse_over, -            label, -            &self.style, -        ) +            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, +                        border_width: 0.0, +                        border_color: Color::TRANSPARENT, +                    }, +                    style.dot_color, +                ); +            } +        } + +        { +            let label_layout = children.next().unwrap(); + +            widget::text::draw( +                renderer, +                style, +                label_layout, +                &self.label, +                self.font, +                self.text_size, +                self.text_color, +                alignment::Horizontal::Left, +                alignment::Vertical::Center, +            ); +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -239,46 +296,15 @@ where      }  } -/// The renderer of a [`Radio`] button. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Radio`] button in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { -    /// The style supported by this renderer. -    type Style: Default; - -    /// The default size of a [`Radio`] button. -    const DEFAULT_SIZE: u16; - -    /// The default spacing of a [`Radio`] button. -    const DEFAULT_SPACING: u16; - -    /// Draws a [`Radio`] button. -    /// -    /// It receives: -    ///   * the bounds of the [`Radio`] -    ///   * whether the [`Radio`] is selected or not -    ///   * whether the mouse is over the [`Radio`] or not -    ///   * the drawn label of the [`Radio`] -    fn draw( -        &mut self, -        bounds: Rectangle, -        is_selected: bool, -        is_mouse_over: bool, -        label: Self::Output, -        style: &Self::Style, -    ) -> Self::Output; -} - -impl<'a, Message, Renderer> From<Radio<Message, Renderer>> +impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where      Message: 'a + Clone, -    Renderer: 'a + self::Renderer + row::Renderer + text::Renderer, +    Renderer: 'a + text::Renderer,  { -    fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> { +    fn from( +        radio: Radio<'a, Message, Renderer>, +    ) -> Element<'a, Message, Renderer> {          Element::new(radio)      }  } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 1923f213..6fe3284b 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -1,7 +1,9 @@  //! Distribute content horizontally.  use crate::event::{self, Event};  use crate::layout; +use crate::mouse;  use crate::overlay; +use crate::renderer;  use crate::{      Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point,      Rectangle, Widget, @@ -104,7 +106,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Row<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -161,21 +163,37 @@ where              .fold(event::Status::Ignored, event::Status::merge)      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) -> mouse::Interaction { +        self.children +            .iter() +            .zip(layout.children()) +            .map(|(child, layout)| { +                child.widget.mouse_interaction( +                    layout, +                    cursor_position, +                    viewport, +                ) +            }) +            .max() +            .unwrap_or_default() +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw( -            defaults, -            &self.children, -            layout, -            cursor_position, -            viewport, -        ) +    ) { +        for (child, layout) in self.children.iter().zip(layout.children()) { +            child.draw(renderer, style, layout, cursor_position, viewport); +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -207,33 +225,10 @@ where      }  } -/// The renderer of a [`Row`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Row`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer + Sized { -    /// Draws a [`Row`]. -    /// -    /// It receives: -    /// - the children of the [`Row`] -    /// - the [`Layout`] of the [`Row`] and its children -    /// - the cursor position -    fn draw<Message>( -        &mut self, -        defaults: &Self::Defaults, -        children: &[Element<'_, Message, Self>], -        layout: Layout<'_>, -        cursor_position: Point, -        viewport: &Rectangle, -    ) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,      Message: 'a,  {      fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs index 18c88658..7c8c5dbc 100644 --- a/native/src/widget/rule.rs +++ b/native/src/widget/rule.rs @@ -1,28 +1,31 @@  //! Display a horizontal or vertical rule for dividing content. +use crate::layout; +use crate::renderer; +use crate::{ +    Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, +};  use std::hash::Hash; -use crate::{ -    layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, -}; +pub use iced_style::rule::{FillMode, Style, StyleSheet};  /// Display a horizontal or vertical rule for dividing content. -#[derive(Debug, Copy, Clone)] -pub struct Rule<Renderer: self::Renderer> { +#[allow(missing_debug_implementations)] +pub struct Rule<'a> {      width: Length,      height: Length, -    style: Renderer::Style,      is_horizontal: bool, +    style_sheet: Box<dyn StyleSheet + 'a>,  } -impl<Renderer: self::Renderer> Rule<Renderer> { +impl<'a> Rule<'a> {      /// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.      pub fn horizontal(spacing: u16) -> Self {          Rule {              width: Length::Fill,              height: Length::from(Length::Units(spacing)), -            style: Renderer::Style::default(),              is_horizontal: true, +            style_sheet: Default::default(),          }      } @@ -31,21 +34,24 @@ impl<Renderer: self::Renderer> Rule<Renderer> {          Rule {              width: Length::from(Length::Units(spacing)),              height: Length::Fill, -            style: Renderer::Style::default(),              is_horizontal: false, +            style_sheet: Default::default(),          }      }      /// Sets the style of the [`Rule`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      }  } -impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer> +impl<'a, Message, Renderer> Widget<Message, Renderer> for Rule<'a>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -68,12 +74,53 @@ where      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          _cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw(layout.bounds(), &self.style, self.is_horizontal) +    ) { +        let bounds = layout.bounds(); +        let style = self.style_sheet.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, +                border_width: 0.0, +                border_color: Color::TRANSPARENT, +            }, +            style.color, +        );      }      fn hash_layout(&self, state: &mut Hasher) { @@ -85,32 +132,12 @@ where      }  } -/// The renderer of a [`Rule`]. -pub trait Renderer: crate::Renderer { -    /// The style supported by this renderer. -    type Style: Default; - -    /// Draws a [`Rule`]. -    /// -    /// It receives: -    ///   * the bounds of the [`Rule`] -    ///   * the style of the [`Rule`] -    ///   * whether the [`Rule`] is horizontal (true) or vertical (false) -    fn draw( -        &mut self, -        bounds: Rectangle, -        style: &Self::Style, -        is_horizontal: bool, -    ) -> Self::Output; -} - -impl<'a, Message, Renderer> From<Rule<Renderer>> -    for Element<'a, Message, Renderer> +impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,      Message: 'a,  { -    fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> { +    fn from(rule: Rule<'a>) -> Element<'a, Message, Renderer> {          Element::new(rule)      }  } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index a8e467d3..2bf2ea5e 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,21 +1,24 @@  //! Navigate an endless amount of content with a scrollbar. -use crate::column;  use crate::event::{self, Event};  use crate::layout;  use crate::mouse;  use crate::overlay; +use crate::renderer;  use crate::touch; +use crate::widget::Column;  use crate::{ -    Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding, -    Point, Rectangle, Size, Vector, Widget, +    Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length, +    Padding, Point, Rectangle, Size, Vector, Widget,  };  use std::{f32, hash::Hash, u32}; +pub use iced_style::scrollable::StyleSheet; +  /// A widget that can vertically display an infinite amount of content with a  /// scrollbar.  #[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer: self::Renderer> { +pub struct Scrollable<'a, Message, Renderer> {      state: &'a mut State,      height: Length,      max_height: u32, @@ -24,10 +27,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {      scroller_width: u16,      content: Column<'a, Message, Renderer>,      on_scroll: Option<Box<dyn Fn(f32) -> Message>>, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  } -impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { +impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {      /// Creates a new [`Scrollable`] with the given [`State`].      pub fn new(state: &'a mut State) -> Self {          Scrollable { @@ -39,7 +42,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {              scroller_width: 10,              content: Column::new(),              on_scroll: None, -            style: Renderer::Style::default(), +            style_sheet: Default::default(),          }      } @@ -120,8 +123,11 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {      }      /// Sets the style of the [`Scrollable`] . -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      } @@ -151,12 +157,63 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {              ));          }      } + +    fn scrollbar( +        &self, +        bounds: Rectangle, +        content_bounds: Rectangle, +    ) -> Option<Scrollbar> { +        let offset = self.state.offset(bounds, content_bounds); + +        if content_bounds.height > bounds.height { +            let outer_width = self.scrollbar_width.max(self.scroller_width) +                + 2 * self.scrollbar_margin; + +            let outer_bounds = Rectangle { +                x: bounds.x + bounds.width - outer_width as f32, +                y: bounds.y, +                width: outer_width as f32, +                height: bounds.height, +            }; + +            let scrollbar_bounds = Rectangle { +                x: bounds.x + bounds.width +                    - f32::from(outer_width / 2 + self.scrollbar_width / 2), +                y: bounds.y, +                width: self.scrollbar_width as f32, +                height: bounds.height, +            }; + +            let ratio = bounds.height / content_bounds.height; +            let scroller_height = bounds.height * ratio; +            let y_offset = offset as f32 * ratio; + +            let scroller_bounds = Rectangle { +                x: bounds.x + bounds.width +                    - f32::from(outer_width / 2 + self.scroller_width / 2), +                y: scrollbar_bounds.y + y_offset, +                width: self.scroller_width as f32, +                height: scroller_height, +            }; + +            Some(Scrollbar { +                outer_bounds, +                bounds: scrollbar_bounds, +                margin: self.scrollbar_margin, +                scroller: Scroller { +                    bounds: scroller_bounds, +                }, +            }) +        } else { +            None +        } +    }  }  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Scrollable<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          Widget::<Message, Renderer>::width(&self.content) @@ -202,15 +259,7 @@ where          let content = layout.children().next().unwrap();          let content_bounds = content.bounds(); -        let offset = self.state.offset(bounds, content_bounds); -        let scrollbar = renderer.scrollbar( -            bounds, -            content_bounds, -            offset, -            self.scrollbar_width, -            self.scrollbar_margin, -            self.scroller_width, -        ); +        let scrollbar = self.scrollbar(bounds, content_bounds);          let is_mouse_over_scrollbar = scrollbar              .as_ref()              .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) @@ -374,26 +423,16 @@ where          event::Status::Ignored      } -    fn draw( +    fn mouse_interaction(          &self, -        renderer: &mut Renderer, -        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { +    ) -> mouse::Interaction {          let bounds = layout.bounds();          let content_layout = layout.children().next().unwrap();          let content_bounds = content_layout.bounds(); -        let offset = self.state.offset(bounds, content_bounds); -        let scrollbar = renderer.scrollbar( -            bounds, -            content_bounds, -            offset, -            self.scrollbar_width, -            self.scrollbar_margin, -            self.scroller_width, -        ); +        let scrollbar = self.scrollbar(bounds, content_bounds);          let is_mouse_over = bounds.contains(cursor_position);          let is_mouse_over_scrollbar = scrollbar @@ -401,16 +440,18 @@ where              .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))              .unwrap_or(false); -        let content = { +        if is_mouse_over_scrollbar || self.state.is_scroller_grabbed() { +            mouse::Interaction::Idle +        } else { +            let offset = self.state.offset(bounds, content_bounds); +              let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {                  Point::new(cursor_position.x, cursor_position.y + offset as f32)              } else {                  Point::new(cursor_position.x, -1.0)              }; -            self.content.draw( -                renderer, -                defaults, +            self.content.mouse_interaction(                  content_layout,                  cursor_position,                  &Rectangle { @@ -418,20 +459,114 @@ where                      ..bounds                  },              ) +        } +    } + +    fn draw( +        &self, +        renderer: &mut Renderer, +        style: &renderer::Style, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) { +        let bounds = layout.bounds(); +        let content_layout = layout.children().next().unwrap(); +        let content_bounds = content_layout.bounds(); +        let offset = self.state.offset(bounds, content_bounds); +        let scrollbar = self.scrollbar(bounds, content_bounds); + +        let is_mouse_over = bounds.contains(cursor_position); +        let is_mouse_over_scrollbar = scrollbar +            .as_ref() +            .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) +            .unwrap_or(false); + +        let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { +            Point::new(cursor_position.x, cursor_position.y + offset as f32) +        } else { +            Point::new(cursor_position.x, -1.0)          }; -        self::Renderer::draw( -            renderer, -            &self.state, -            bounds, -            content_layout.bounds(), -            is_mouse_over, -            is_mouse_over_scrollbar, -            scrollbar, -            offset, -            &self.style, -            content, -        ) +        if let Some(scrollbar) = scrollbar { +            renderer.with_layer(bounds, |renderer| { +                renderer.with_translation( +                    Vector::new(0.0, -(offset as f32)), +                    |renderer| { +                        self.content.draw( +                            renderer, +                            style, +                            content_layout, +                            cursor_position, +                            &Rectangle { +                                y: bounds.y + offset as f32, +                                ..bounds +                            }, +                        ); +                    }, +                ); +            }); + +            let style = if self.state.is_scroller_grabbed() { +                self.style_sheet.dragging() +            } else if is_mouse_over_scrollbar { +                self.style_sheet.hovered() +            } else { +                self.style_sheet.active() +            }; + +            let is_scrollbar_visible = +                style.background.is_some() || style.border_width > 0.0; + +            renderer.with_layer( +                Rectangle { +                    width: bounds.width + 2.0, +                    height: bounds.height + 2.0, +                    ..bounds +                }, +                |renderer| { +                    if is_scrollbar_visible { +                        renderer.fill_quad( +                            renderer::Quad { +                                bounds: scrollbar.bounds, +                                border_radius: style.border_radius, +                                border_width: style.border_width, +                                border_color: style.border_color, +                            }, +                            style.background.unwrap_or(Background::Color( +                                Color::TRANSPARENT, +                            )), +                        ); +                    } + +                    if is_mouse_over +                        || self.state.is_scroller_grabbed() +                        || is_scrollbar_visible +                    { +                        renderer.fill_quad( +                            renderer::Quad { +                                bounds: scrollbar.scroller.bounds, +                                border_radius: style.scroller.border_radius, +                                border_width: style.scroller.border_width, +                                border_color: style.scroller.border_color, +                            }, +                            style.scroller.color, +                        ); +                    } +                }, +            ); +        } else { +            self.content.draw( +                renderer, +                style, +                content_layout, +                cursor_position, +                &Rectangle { +                    y: bounds.y + offset as f32, +                    ..bounds +                }, +            ); +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -577,19 +712,19 @@ impl State {  /// The scrollbar of a [`Scrollable`].  #[derive(Debug)] -pub struct Scrollbar { +struct Scrollbar {      /// The outer bounds of the scrollable, including the [`Scrollbar`] and      /// [`Scroller`]. -    pub outer_bounds: Rectangle, +    outer_bounds: Rectangle,      /// The bounds of the [`Scrollbar`]. -    pub bounds: Rectangle, +    bounds: Rectangle,      /// The margin within the [`Scrollbar`]. -    pub margin: u16, +    margin: u16,      /// The bounds of the [`Scroller`]. -    pub scroller: Scroller, +    scroller: Scroller,  }  impl Scrollbar { @@ -624,62 +759,15 @@ impl Scrollbar {  /// The handle of a [`Scrollbar`].  #[derive(Debug, Clone, Copy)] -pub struct Scroller { +struct Scroller {      /// The bounds of the [`Scroller`]. -    pub bounds: Rectangle, -} - -/// The renderer of a [`Scrollable`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Scrollable`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: column::Renderer + Sized { -    /// The style supported by this renderer. -    type Style: Default; - -    /// Returns the [`Scrollbar`] given the bounds and content bounds of a -    /// [`Scrollable`]. -    fn scrollbar( -        &self, -        bounds: Rectangle, -        content_bounds: Rectangle, -        offset: u32, -        scrollbar_width: u16, -        scrollbar_margin: u16, -        scroller_width: u16, -    ) -> Option<Scrollbar>; - -    /// Draws the [`Scrollable`]. -    /// -    /// It receives: -    /// - the [`State`] of the [`Scrollable`] -    /// - the bounds of the [`Scrollable`] widget -    /// - the bounds of the [`Scrollable`] content -    /// - whether the mouse is over the [`Scrollable`] or not -    /// - whether the mouse is over the [`Scrollbar`] or not -    /// - a optional [`Scrollbar`] to be rendered -    /// - the scrolling offset -    /// - the drawn content -    fn draw( -        &mut self, -        scrollable: &State, -        bounds: Rectangle, -        content_bounds: Rectangle, -        is_mouse_over: bool, -        is_mouse_over_scrollbar: bool, -        scrollbar: Option<Scrollbar>, -        offset: u32, -        style: &Self::Style, -        content: Self::Output, -    ) -> Self::Output; +    bounds: Rectangle,  }  impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,      Message: 'a,  {      fn from( diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 2a74d5a3..49bafab4 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -4,12 +4,17 @@  use crate::event::{self, Event};  use crate::layout;  use crate::mouse; +use crate::renderer;  use crate::touch;  use crate::{ -    Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, +    Background, Clipboard, Color, Element, Hasher, Layout, Length, Point, +    Rectangle, Size, Widget,  }; -use std::{hash::Hash, ops::RangeInclusive}; +use std::hash::Hash; +use std::ops::RangeInclusive; + +pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};  /// An horizontal bar and a handle that selects a single value from a range of  /// values. @@ -21,9 +26,8 @@ use std::{hash::Hash, ops::RangeInclusive};  ///  /// # Example  /// ``` -/// # use iced_native::{slider, renderer::Null}; +/// # use iced_native::widget::slider::{self, Slider};  /// # -/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;  /// #[derive(Clone)]  /// pub enum Message {  ///     SliderChanged(f32), @@ -37,7 +41,7 @@ use std::{hash::Hash, ops::RangeInclusive};  ///  ///   #[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message, Renderer: self::Renderer> { +pub struct Slider<'a, T, Message> {      state: &'a mut State,      range: RangeInclusive<T>,      step: T, @@ -46,15 +50,17 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {      on_release: Option<Message>,      width: Length,      height: u16, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  } -impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer> +impl<'a, T, Message> Slider<'a, T, Message>  where      T: Copy + From<u8> + std::cmp::PartialOrd,      Message: Clone, -    Renderer: self::Renderer,  { +    /// The default height of a [`Slider`]. +    pub const DEFAULT_HEIGHT: u16 = 22; +      /// Creates a new [`Slider`].      ///      /// It expects: @@ -93,8 +99,8 @@ where              on_change: Box::new(on_change),              on_release: None,              width: Length::Fill, -            height: Renderer::DEFAULT_HEIGHT, -            style: Renderer::Style::default(), +            height: Self::DEFAULT_HEIGHT, +            style_sheet: Default::default(),          }      } @@ -122,8 +128,11 @@ where      }      /// Sets the style of the [`Slider`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      } @@ -148,11 +157,11 @@ impl State {  }  impl<'a, T, Message, Renderer> Widget<Message, Renderer> -    for Slider<'a, T, Message, Renderer> +    for Slider<'a, T, Message>  where      T: Copy + Into<f64> + num_traits::FromPrimitive,      Message: Clone, -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -246,22 +255,113 @@ where      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        let start = *self.range.start(); -        let end = *self.range.end(); - -        renderer.draw( -            layout.bounds(), -            cursor_position, -            start.into() as f32..=end.into() as f32, -            self.value.into() as f32, -            self.state.is_dragging, -            &self.style, -        ) +    ) { +        let bounds = layout.bounds(); +        let is_mouse_over = bounds.contains(cursor_position); + +        let style = if self.state.is_dragging { +            self.style_sheet.dragging() +        } else if is_mouse_over { +            self.style_sheet.hovered() +        } else { +            self.style_sheet.active() +        }; + +        let rail_y = bounds.y + (bounds.height / 2.0).round(); + +        renderer.fill_quad( +            renderer::Quad { +                bounds: Rectangle { +                    x: bounds.x, +                    y: rail_y, +                    width: bounds.width, +                    height: 2.0, +                }, +                border_radius: 0.0, +                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 + 2.0, +                    width: bounds.width, +                    height: 2.0, +                }, +                border_radius: 0.0, +                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), f32::from(bounds.height), border_radius), +        }; + +        let value = self.value.into() as f32; +        let (range_start, range_end) = { +            let (start, end) = self.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, +                border_width: style.handle.border_width, +                border_color: style.handle.border_color, +            }, +            style.handle.color, +        ); +    } + +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> mouse::Interaction { +        let bounds = layout.bounds(); +        let is_mouse_over = bounds.contains(cursor_position); + +        if self.state.is_dragging { +            mouse::Interaction::Grabbing +        } else if is_mouse_over { +            mouse::Interaction::Grab +        } else { +            mouse::Interaction::default() +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -272,48 +372,14 @@ where      }  } -/// The renderer of a [`Slider`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Slider`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { -    /// The style supported by this renderer. -    type Style: Default; - -    /// The default height of a [`Slider`]. -    const DEFAULT_HEIGHT: u16; - -    /// Draws a [`Slider`]. -    /// -    /// It receives: -    ///   * the current cursor position -    ///   * the bounds of the [`Slider`] -    ///   * the local state of the [`Slider`] -    ///   * the range of values of the [`Slider`] -    ///   * the current value of the [`Slider`] -    fn draw( -        &mut self, -        bounds: Rectangle, -        cursor_position: Point, -        range: RangeInclusive<f32>, -        value: f32, -        is_dragging: bool, -        style: &Self::Style, -    ) -> Self::Output; -} - -impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>> +impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>>      for Element<'a, Message, Renderer>  where      T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,      Message: 'a + Clone, -    Renderer: 'a + self::Renderer, +    Renderer: 'a + crate::Renderer,  { -    fn from( -        slider: Slider<'a, T, Message, Renderer>, -    ) -> Element<'a, Message, Renderer> { +    fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message, Renderer> {          Element::new(slider)      }  } diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs index 6b34ece8..3373f3b7 100644 --- a/native/src/widget/space.rs +++ b/native/src/widget/space.rs @@ -1,9 +1,9 @@  //! Distribute content vertically. -use std::hash::Hash; +use crate::layout; +use crate::renderer; +use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; -use crate::{ -    layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, -}; +use std::hash::Hash;  /// An amount of empty space.  /// @@ -39,7 +39,7 @@ impl Space {  impl<Message, Renderer> Widget<Message, Renderer> for Space  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,  {      fn width(&self) -> Length {          self.width @@ -61,13 +61,12 @@ where      fn draw(          &self, -        renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, -        layout: Layout<'_>, +        _renderer: &mut Renderer, +        _style: &renderer::Style, +        _layout: Layout<'_>,          _cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw(layout.bounds()) +    ) {      }      fn hash_layout(&self, state: &mut Hasher) { @@ -78,17 +77,9 @@ where      }  } -/// The renderer of an amount of [`Space`]. -pub trait Renderer: crate::Renderer { -    /// Draws an amount of empty [`Space`]. -    /// -    /// You should most likely return an empty primitive here. -    fn draw(&mut self, bounds: Rectangle) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: crate::Renderer,      Message: 'a,  {      fn from(space: Space) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 9cd61918..f212dfcb 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -1,12 +1,11 @@  //! Display vector graphics in your application.  use crate::layout; +use crate::renderer; +use crate::svg::{self, Handle};  use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; -use std::{ -    hash::{Hash, Hasher as _}, -    path::PathBuf, -    sync::Arc, -}; +use std::hash::Hash; +use std::path::PathBuf;  /// A vector graphics image.  /// @@ -52,7 +51,7 @@ impl Svg {  impl<Message, Renderer> Widget<Message, Renderer> for Svg  where -    Renderer: self::Renderer, +    Renderer: svg::Renderer,  {      fn width(&self) -> Length {          self.width @@ -90,12 +89,12 @@ where      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          _cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw(self.handle.clone(), layout) +    ) { +        renderer.draw(self.handle.clone(), layout.bounds())      }      fn hash_layout(&self, state: &mut Hasher) { @@ -107,94 +106,9 @@ where      }  } -/// An [`Svg`] handle. -#[derive(Debug, Clone)] -pub struct Handle { -    id: u64, -    data: Arc<Data>, -} - -impl Handle { -    /// Creates an SVG [`Handle`] pointing to the vector image of the given -    /// path. -    pub fn from_path(path: impl Into<PathBuf>) -> Handle { -        Self::from_data(Data::Path(path.into())) -    } - -    /// Creates an SVG [`Handle`] from raw bytes containing either an SVG string -    /// or gzip compressed data. -    /// -    /// This is useful if you already have your SVG data in-memory, maybe -    /// because you downloaded or generated it procedurally. -    pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle { -        Self::from_data(Data::Bytes(bytes.into())) -    } - -    fn from_data(data: Data) -> Handle { -        let mut hasher = Hasher::default(); -        data.hash(&mut hasher); - -        Handle { -            id: hasher.finish(), -            data: Arc::new(data), -        } -    } - -    /// Returns the unique identifier of the [`Handle`]. -    pub fn id(&self) -> u64 { -        self.id -    } - -    /// Returns a reference to the SVG [`Data`]. -    pub fn data(&self) -> &Data { -        &self.data -    } -} - -impl Hash for Handle { -    fn hash<H: std::hash::Hasher>(&self, state: &mut H) { -        self.id.hash(state); -    } -} - -/// The data of an [`Svg`]. -#[derive(Clone, Hash)] -pub enum Data { -    /// File data -    Path(PathBuf), - -    /// In-memory data -    /// -    /// Can contain an SVG string or a gzip compressed data. -    Bytes(Vec<u8>), -} - -impl std::fmt::Debug for Data { -    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -        match self { -            Data::Path(path) => write!(f, "Path({:?})", path), -            Data::Bytes(_) => write!(f, "Bytes(...)"), -        } -    } -} - -/// The renderer of an [`Svg`]. -/// -/// Your [renderer] will need to implement this trait before being able to use -/// an [`Svg`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: crate::Renderer { -    /// Returns the default dimensions of an [`Svg`] for the given [`Handle`]. -    fn dimensions(&self, handle: &Handle) -> (u32, u32); - -    /// Draws an [`Svg`]. -    fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: svg::Renderer,  {      fn from(icon: Svg) -> Element<'a, Message, Renderer> {          Element::new(icon) diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 168d49c2..4dbc4a65 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -1,12 +1,12 @@  //! Write some text for your users to read.  use crate::alignment;  use crate::layout; +use crate::renderer; +use crate::text;  use crate::{      Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,  }; -pub use iced_core::text::Hit; -  use std::hash::Hash;  /// A paragraph of text. @@ -14,7 +14,7 @@ use std::hash::Hash;  /// # Example  ///  /// ``` -/// # type Text = iced_native::Text<iced_native::renderer::Null>; +/// # type Text = iced_native::widget::Text<iced_native::renderer::Null>;  /// #  /// Text::new("I <3 iced!")  ///     .color([0.0, 0.0, 1.0]) @@ -23,7 +23,7 @@ use std::hash::Hash;  ///  ///   #[derive(Debug)] -pub struct Text<Renderer: self::Renderer> { +pub struct Text<Renderer: text::Renderer> {      content: String,      size: Option<u16>,      color: Option<Color>, @@ -34,7 +34,7 @@ pub struct Text<Renderer: self::Renderer> {      vertical_alignment: alignment::Vertical,  } -impl<Renderer: self::Renderer> Text<Renderer> { +impl<Renderer: text::Renderer> Text<Renderer> {      /// Create a new fragment of [`Text`] with the given contents.      pub fn new<T: Into<String>>(label: T) -> Self {          Text { @@ -102,7 +102,7 @@ impl<Renderer: self::Renderer> Text<Renderer> {  impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>  where -    Renderer: self::Renderer, +    Renderer: text::Renderer,  {      fn width(&self) -> Length {          self.width @@ -134,21 +134,22 @@ where      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          _cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        renderer.draw( -            defaults, -            layout.bounds(), +    ) { +        draw( +            renderer, +            style, +            layout,              &self.content, -            self.size.unwrap_or(renderer.default_size()),              self.font, +            self.size,              self.color,              self.horizontal_alignment,              self.vertical_alignment, -        ) +        );      }      fn hash_layout(&self, state: &mut Hasher) { @@ -162,79 +163,65 @@ where      }  } -/// The renderer of a [`Text`] fragment. +/// Draws text using the same logic as the [`Text`] widget.  /// -/// Your [renderer] will need to implement this trait before being -/// able to use [`Text`] in your user interface. +/// Specifically:  /// -/// [renderer]: crate::Renderer -pub trait Renderer: crate::Renderer { -    /// The font type used for [`Text`]. -    type Font: Default + Copy; - -    /// Returns the default size of [`Text`]. -    fn default_size(&self) -> u16; - -    /// Measures the [`Text`] in the given bounds and returns the minimum -    /// boundaries that can fit the contents. -    fn measure( -        &self, -        content: &str, -        size: u16, -        font: Self::Font, -        bounds: Size, -    ) -> (f32, f32); - -    /// Tests whether the provided point is within the boundaries of [`Text`] -    /// laid out with the given parameters, returning information about -    /// the nearest character. -    /// -    /// If `nearest_only` is true, the hit test does not consider whether the -    /// the point is interior to any glyph bounds, returning only the character -    /// with the nearest centeroid. -    fn hit_test( -        &self, -        contents: &str, -        size: f32, -        font: Self::Font, -        bounds: Size, -        point: Point, -        nearest_only: bool, -    ) -> Option<Hit>; - -    /// Draws a [`Text`] fragment. -    /// -    /// It receives: -    ///   * the bounds of the [`Text`] -    ///   * the contents of the [`Text`] -    ///   * the size of the [`Text`] -    ///   * the color of the [`Text`] -    ///   * the [`HorizontalAlignment`] of the [`Text`] -    ///   * the [`VerticalAlignment`] of the [`Text`] -    fn draw( -        &mut self, -        defaults: &Self::Defaults, -        bounds: Rectangle, -        content: &str, -        size: u16, -        font: Self::Font, -        color: Option<Color>, -        horizontal_alignment: alignment::Horizontal, -        vertical_alignment: alignment::Vertical, -    ) -> Self::Output; +/// * 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, +    font: Renderer::Font, +    size: Option<u16>, +    color: Option<Color>, +    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: f32::from(size.unwrap_or(renderer.default_size())), +        bounds: Rectangle { x, y, ..bounds }, +        color: color.unwrap_or(style.text_color), +        font, +        horizontal_alignment, +        vertical_alignment, +    });  }  impl<'a, Message, Renderer> From<Text<Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: self::Renderer + 'a, +    Renderer: text::Renderer + 'a,  {      fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {          Element::new(text)      }  } -impl<Renderer: self::Renderer> Clone for Text<Renderer> { +impl<Renderer: text::Renderer> Clone for Text<Renderer> {      fn clone(&self) -> Self {          Self {              content: self.content.clone(), diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index d4d197d3..40c6c573 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -11,26 +11,31 @@ 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::text; +use crate::renderer; +use crate::text::{self, Text};  use crate::touch;  use crate::{ -    Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, -    Size, Widget, +    Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point, +    Rectangle, Size, Vector, Widget,  };  use std::u32; +pub use iced_style::text_input::{Style, StyleSheet}; +  /// A field that can be filled with text.  ///  /// # Example  /// ``` -/// # use iced_native::{text_input, renderer::Null}; +/// # use iced_native::renderer::Null; +/// # use iced_native::widget::text_input;  /// # -/// # pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Null>; +/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;  /// #[derive(Debug, Clone)]  /// enum Message {  ///     TextInputChanged(String), @@ -49,7 +54,7 @@ use std::u32;  /// ```  ///   #[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message, Renderer: self::Renderer> { +pub struct TextInput<'a, Message, Renderer: text::Renderer> {      state: &'a mut State,      placeholder: String,      value: Value, @@ -61,13 +66,13 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {      size: Option<u16>,      on_change: Box<dyn Fn(String) -> Message>,      on_submit: Option<Message>, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  }  impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>  where      Message: Clone, -    Renderer: self::Renderer, +    Renderer: text::Renderer,  {      /// Creates a new [`TextInput`].      /// @@ -97,7 +102,7 @@ where              size: None,              on_change: Box::new(on_change),              on_submit: None, -            style: Renderer::Style::default(), +            style_sheet: Default::default(),          }      } @@ -147,8 +152,11 @@ where      }      /// Sets the style of the [`TextInput`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      } @@ -160,7 +168,7 @@ where  impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: text::Renderer,  {      /// Draws the [`TextInput`] with the given [`Renderer`], overriding its      /// [`Value`] if provided. @@ -170,37 +178,165 @@ where          layout: Layout<'_>,          cursor_position: Point,          value: Option<&Value>, -    ) -> Renderer::Output { +    ) {          let value = value.unwrap_or(&self.value); +        let secure_value = self.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(); -        if self.is_secure { -            self::Renderer::draw( -                renderer, -                bounds, -                text_bounds, -                cursor_position, -                self.font, -                self.size.unwrap_or(renderer.default_size()), -                &self.placeholder, -                &value.secure(), -                &self.state, -                &self.style, -            ) +        let is_mouse_over = bounds.contains(cursor_position); + +        let style = if self.state.is_focused() { +            self.style_sheet.focused() +        } else if is_mouse_over { +            self.style_sheet.hovered()          } else { -            self::Renderer::draw( -                renderer, +            self.style_sheet.active() +        }; + +        renderer.fill_quad( +            renderer::Quad {                  bounds, -                text_bounds, -                cursor_position, -                self.font, -                self.size.unwrap_or(renderer.default_size()), -                &self.placeholder, -                value, -                &self.state, -                &self.style, -            ) +                border_radius: style.border_radius, +                border_width: style.border_width, +                border_color: style.border_color, +            }, +            style.background, +        ); + +        let text = value.to_string(); +        let size = self.size.unwrap_or(renderer.default_size()); + +        let (cursor, offset) = if self.state.is_focused() { +            match self.state.cursor.state(&value) { +                cursor::State::Index(position) => { +                    let (text_value_width, offset) = +                        measure_cursor_and_scroll_offset( +                            renderer, +                            text_bounds, +                            &value, +                            size, +                            position, +                            self.font, +                        ); + +                    ( +                        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, +                                border_width: 0.0, +                                border_color: Color::TRANSPARENT, +                            }, +                            self.style_sheet.value_color(), +                        )), +                        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, +                            self.font, +                        ); + +                    let (right_position, right_offset) = +                        measure_cursor_and_scroll_offset( +                            renderer, +                            text_bounds, +                            &value, +                            size, +                            right, +                            self.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, +                                border_width: 0.0, +                                border_color: Color::TRANSPARENT, +                            }, +                            self.style_sheet.selection_color(), +                        )), +                        if end == right { +                            right_offset +                        } else { +                            left_offset +                        }, +                    ) +                } +            } +        } else { +            (None, 0.0) +        }; + +        let text_width = renderer.measure_width( +            if text.is_empty() { +                &self.placeholder +            } else { +                &text +            }, +            size, +            self.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() { +                    &self.placeholder +                } else { +                    &text +                }, +                color: if text.is_empty() { +                    self.style_sheet.placeholder_color() +                } else { +                    self.style_sheet.value_color() +                }, +                font: self.font, +                bounds: Rectangle { +                    y: text_bounds.center_y(), +                    width: f32::INFINITY, +                    ..text_bounds +                }, +                size: f32::from(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);          }      }  } @@ -209,7 +345,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>      for TextInput<'a, Message, Renderer>  where      Message: Clone, -    Renderer: self::Renderer, +    Renderer: text::Renderer,  {      fn width(&self) -> Length {          self.width @@ -275,7 +411,8 @@ where                                      self.value.clone()                                  }; -                                renderer.find_cursor_position( +                                find_cursor_position( +                                    renderer,                                      text_layout.bounds(),                                      self.font,                                      self.size, @@ -294,16 +431,16 @@ where                              if self.is_secure {                                  self.state.cursor.select_all(&self.value);                              } else { -                                let position = renderer -                                    .find_cursor_position( -                                        text_layout.bounds(), -                                        self.font, -                                        self.size, -                                        &self.value, -                                        &self.state, -                                        target, -                                    ) -                                    .unwrap_or(0); +                                let position = find_cursor_position( +                                    renderer, +                                    text_layout.bounds(), +                                    self.font, +                                    self.size, +                                    &self.value, +                                    &self.state, +                                    target, +                                ) +                                .unwrap_or(0);                                  self.state.cursor.select_range(                                      self.value.previous_start_of_word(position), @@ -341,16 +478,16 @@ where                          self.value.clone()                      }; -                    let position = renderer -                        .find_cursor_position( -                            text_layout.bounds(), -                            self.font, -                            self.size, -                            &value, -                            &self.state, -                            target, -                        ) -                        .unwrap_or(0); +                    let position = find_cursor_position( +                        renderer, +                        text_layout.bounds(), +                        self.font, +                        self.size, +                        &value, +                        &self.state, +                        target, +                    ) +                    .unwrap_or(0);                      self.state.cursor.select_range(                          self.state.cursor.start(&value), @@ -621,14 +758,27 @@ where          event::Status::Ignored      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> mouse::Interaction { +        if layout.bounds().contains(cursor_position) { +            mouse::Interaction::Text +        } else { +            mouse::Interaction::default() +        } +    } +      fn draw(          &self,          renderer: &mut Renderer, -        _defaults: &Renderer::Defaults, +        _style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { +    ) {          self.draw(renderer, layout, cursor_position, None)      } @@ -644,87 +794,11 @@ where      }  } -/// The renderer of a [`TextInput`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`TextInput`] in your user interface. -/// -/// [renderer]: crate::renderer -pub trait Renderer: text::Renderer + Sized { -    /// The style supported by this renderer. -    type Style: Default; - -    /// Returns the width of the value of the [`TextInput`]. -    fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32; - -    /// Returns the current horizontal offset of the value of the -    /// [`TextInput`]. -    /// -    /// This is the amount of horizontal scrolling applied when the [`Value`] -    /// does not fit the [`TextInput`]. -    fn offset( -        &self, -        text_bounds: Rectangle, -        font: Self::Font, -        size: u16, -        value: &Value, -        state: &State, -    ) -> f32; - -    /// Draws a [`TextInput`]. -    /// -    /// It receives: -    /// - the bounds of the [`TextInput`] -    /// - the bounds of the text (i.e. the current value) -    /// - the cursor position -    /// - the placeholder to show when the value is empty -    /// - the current [`Value`] -    /// - the current [`State`] -    fn draw( -        &mut self, -        bounds: Rectangle, -        text_bounds: Rectangle, -        cursor_position: Point, -        font: Self::Font, -        size: u16, -        placeholder: &str, -        value: &Value, -        state: &State, -        style: &Self::Style, -    ) -> Self::Output; - -    /// Computes the position of the text cursor at the given X coordinate of -    /// a [`TextInput`]. -    fn find_cursor_position( -        &self, -        text_bounds: Rectangle, -        font: Self::Font, -        size: Option<u16>, -        value: &Value, -        state: &State, -        x: f32, -    ) -> Option<usize> { -        let size = size.unwrap_or(self.default_size()); - -        let offset = self.offset(text_bounds, font, size, &value, &state); - -        self.hit_test( -            &value.to_string(), -            size.into(), -            font, -            Size::INFINITY, -            Point::new(x + offset, text_bounds.height / 2.0), -            true, -        ) -        .map(text::Hit::cursor) -    } -} -  impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where      Message: 'a + Clone, -    Renderer: 'a + self::Renderer, +    Renderer: 'a + text::Renderer,  {      fn from(          text_input: TextInput<'a, Message, Renderer>, @@ -815,3 +889,88 @@ mod platform {          }      }  } + +fn offset<Renderer>( +    renderer: &Renderer, +    text_bounds: Rectangle, +    font: Renderer::Font, +    size: u16, +    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: u16, +    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: Renderer::Font, +    size: Option<u16>, +    value: &Value, +    state: &State, +    x: f32, +) -> Option<usize> +where +    Renderer: text::Renderer, +{ +    let size = size.unwrap_or(renderer.default_size()); + +    let offset = offset(renderer, text_bounds, font, size, &value, &state); + +    renderer +        .hit_test( +            &value.to_string(), +            size.into(), +            font, +            Size::INFINITY, +            Point::new(x + offset, text_bounds.height / 2.0), +            true, +        ) +        .map(text::Hit::cursor) +} diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs index 0b50a382..bac530e1 100644 --- a/native/src/widget/text_input/editor.rs +++ b/native/src/widget/text_input/editor.rs @@ -1,4 +1,4 @@ -use crate::text_input::{Cursor, Value}; +use crate::widget::text_input::{Cursor, Value};  pub struct Editor<'a> {      value: &'a mut Value, diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index c624be4c..2dcc3ffe 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -5,19 +5,22 @@ use crate::alignment;  use crate::event;  use crate::layout;  use crate::mouse; -use crate::row; +use crate::renderer;  use crate::text; +use crate::widget::{Row, Text};  use crate::{      Alignment, Clipboard, Element, Event, Hasher, Layout, Length, Point, -    Rectangle, Row, Text, Widget, +    Rectangle, Widget,  }; +pub use iced_style::toggler::{Style, StyleSheet}; +  /// A toggler widget  ///  /// # Example  ///  /// ``` -/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>; +/// # type Toggler<'a, Message> = iced_native::widget::Toggler<'a, Message, iced_native::renderer::Null>;  /// #  /// pub enum Message {  ///     TogglerToggled(bool), @@ -28,7 +31,7 @@ use crate::{  /// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b));  /// ```  #[allow(missing_debug_implementations)] -pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> { +pub struct Toggler<'a, Message, Renderer: text::Renderer> {      is_active: bool,      on_toggle: Box<dyn Fn(bool) -> Message>,      label: Option<String>, @@ -38,12 +41,13 @@ pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {      text_alignment: alignment::Horizontal,      spacing: u16,      font: Renderer::Font, -    style: Renderer::Style, +    style_sheet: Box<dyn StyleSheet + 'a>,  } -impl<Message, Renderer: self::Renderer + text::Renderer> -    Toggler<Message, Renderer> -{ +impl<'a, Message, Renderer: text::Renderer> Toggler<'a, Message, Renderer> { +    /// The default size of a [`Toggler`]. +    pub const DEFAULT_SIZE: u16 = 20; +      /// Creates a new [`Toggler`].      ///      /// It expects: @@ -65,12 +69,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>              on_toggle: Box::new(f),              label: label.into(),              width: Length::Fill, -            size: <Renderer as self::Renderer>::DEFAULT_SIZE, +            size: Self::DEFAULT_SIZE,              text_size: None,              text_alignment: alignment::Horizontal::Left,              spacing: 0,              font: Renderer::Font::default(), -            style: Renderer::Style::default(), +            style_sheet: Default::default(),          }      } @@ -111,15 +115,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>      }      /// Sets the style of the [`Toggler`]. -    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { -        self.style = style.into(); +    pub fn style( +        mut self, +        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, +    ) -> Self { +        self.style_sheet = style_sheet.into();          self      }  } -impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer> +impl<'a, Message, Renderer> Widget<Message, Renderer> +    for Toggler<'a, Message, Renderer>  where -    Renderer: self::Renderer + text::Renderer + row::Renderer, +    Renderer: text::Renderer,  {      fn width(&self) -> Length {          self.width @@ -183,50 +191,108 @@ where          }      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        _viewport: &Rectangle, +    ) -> mouse::Interaction { +        if layout.bounds().contains(cursor_position) { +            mouse::Interaction::Pointer +        } else { +            mouse::Interaction::default() +        } +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          _viewport: &Rectangle, -    ) -> Renderer::Output { -        let bounds = layout.bounds(); -        let mut children = layout.children(); +    ) { +        /// Makes sure that the border radius of the toggler looks good at every size. +        const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0; -        let label = match &self.label { -            Some(label) => { -                let label_layout = children.next().unwrap(); - -                Some(text::Renderer::draw( -                    renderer, -                    defaults, -                    label_layout.bounds(), -                    &label, -                    self.text_size.unwrap_or(renderer.default_size()), -                    self.font, -                    None, -                    self.text_alignment, -                    alignment::Vertical::Center, -                )) -            } +        /// 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; -            None => None, -        }; +        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.font, +                self.text_size, +                None, +                self.text_alignment, +                alignment::Vertical::Center, +            ); +        }          let toggler_layout = children.next().unwrap(); -        let toggler_bounds = toggler_layout.bounds(); +        let bounds = toggler_layout.bounds();          let is_mouse_over = bounds.contains(cursor_position); -        self::Renderer::draw( -            renderer, -            toggler_bounds, -            self.is_active, -            is_mouse_over, -            label, -            &self.style, -        ) +        let style = if is_mouse_over { +            self.style_sheet.hovered(self.is_active) +        } else { +            self.style_sheet.active(self.is_active) +        }; + +        let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO; +        let space = SPACE_RATIO * bounds.height as f32; + +        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_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_active { +                    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_width: 1.0, +                border_color: style +                    .foreground_border +                    .unwrap_or(style.foreground), +            }, +            style.foreground, +        );      }      fn hash_layout(&self, state: &mut Hasher) { @@ -237,45 +303,14 @@ where      }  } -/// The renderer of a [`Toggler`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Toggler`] in your user interface. -/// -/// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer { -    /// The style supported by this renderer. -    type Style: Default; - -    /// The default size of a [`Toggler`]. -    const DEFAULT_SIZE: u16; - -    /// Draws a [`Toggler`]. -    /// -    /// It receives: -    ///   * the bounds of the [`Toggler`] -    ///   * whether the [`Toggler`] is activated or not -    ///   * whether the mouse is over the [`Toggler`] or not -    ///   * the drawn label of the [`Toggler`] -    ///   * the style of the [`Toggler`] -    fn draw( -        &mut self, -        bounds: Rectangle, -        is_active: bool, -        is_mouse_over: bool, -        label: Option<Self::Output>, -        style: &Self::Style, -    ) -> Self::Output; -} - -impl<'a, Message, Renderer> From<Toggler<Message, Renderer>> +impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer + text::Renderer + row::Renderer, +    Renderer: 'a + text::Renderer,      Message: 'a,  {      fn from( -        toggler: Toggler<Message, Renderer>, +        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 index 276afd41..c35005e0 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -3,28 +3,36 @@ use std::hash::Hash;  use iced_core::Rectangle; +use crate::event; +use crate::layout; +use crate::mouse; +use crate::renderer; +use crate::text;  use crate::widget::container; -use crate::widget::text::{self, Text}; +use crate::widget::text::Text;  use crate::{ -    event, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, -    Widget, +    Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Size, +    Vector, Widget,  };  /// An element to display a widget over another.  #[allow(missing_debug_implementations)] -pub struct Tooltip<'a, Message, Renderer: self::Renderer> { +pub struct Tooltip<'a, Message, Renderer: text::Renderer> {      content: Element<'a, Message, Renderer>,      tooltip: Text<Renderer>,      position: Position, -    style: <Renderer as container::Renderer>::Style, +    style_sheet: Box<dyn container::StyleSheet + 'a>,      gap: u16,      padding: u16,  }  impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: text::Renderer,  { +    /// The default padding of a [`Tooltip`] drawn by this renderer. +    const DEFAULT_PADDING: u16 = 5; +      /// Creates an empty [`Tooltip`].      ///      /// [`Tooltip`]: struct.Tooltip.html @@ -37,9 +45,9 @@ where              content: content.into(),              tooltip: Text::new(tooltip.to_string()),              position, -            style: Default::default(), +            style_sheet: Default::default(),              gap: 0, -            padding: Renderer::DEFAULT_PADDING, +            padding: Self::DEFAULT_PADDING,          }      } @@ -72,9 +80,9 @@ where      /// Sets the style of the [`Tooltip`].      pub fn style(          mut self, -        style: impl Into<<Renderer as container::Renderer>::Style>, +        style_sheet: impl Into<Box<dyn container::StyleSheet + 'a>>,      ) -> Self { -        self.style = style.into(); +        self.style_sheet = style_sheet.into();          self      }  } @@ -97,7 +105,7 @@ pub enum Position {  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Tooltip<'a, Message, Renderer>  where -    Renderer: self::Renderer, +    Renderer: text::Renderer,  {      fn width(&self) -> Length {          self.content.width() @@ -134,27 +142,126 @@ where          )      } +    fn mouse_interaction( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +        viewport: &Rectangle, +    ) -> mouse::Interaction { +        self.content +            .mouse_interaction(layout, cursor_position, viewport) +    } +      fn draw(          &self,          renderer: &mut Renderer, -        defaults: &Renderer::Defaults, +        inherited_style: &renderer::Style,          layout: Layout<'_>,          cursor_position: Point,          viewport: &Rectangle, -    ) -> Renderer::Output { -        self::Renderer::draw( +    ) { +        self.content.draw(              renderer, -            defaults, -            cursor_position, +            inherited_style,              layout, +            cursor_position,              viewport, -            &self.content, -            &self.tooltip, -            self.position, -            &self.style, -            self.gap, -            self.padding, -        ) +        ); + +        let bounds = layout.bounds(); + +        if bounds.contains(cursor_position) { +            let gap = f32::from(self.gap); +            let style = self.style_sheet.style(); + +            let defaults = renderer::Style { +                text_color: style +                    .text_color +                    .unwrap_or(inherited_style.text_color), +            }; + +            let text_layout = Widget::<(), Renderer>::layout( +                &self.tooltip, +                renderer, +                &layout::Limits::new(Size::ZERO, viewport.size()) +                    .pad(Padding::new(self.padding)), +            ); + +            let padding = f32::from(self.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 self.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 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(*viewport, |renderer| { +                container::draw_background(renderer, &style, tooltip_bounds); + +                Widget::<(), Renderer>::draw( +                    &self.tooltip, +                    renderer, +                    &defaults, +                    Layout::with_offset( +                        Vector::new( +                            tooltip_bounds.x + padding, +                            tooltip_bounds.y + padding, +                        ), +                        &text_layout, +                    ), +                    cursor_position, +                    viewport, +                ); +            }); +        }      }      fn hash_layout(&self, state: &mut Hasher) { @@ -165,41 +272,10 @@ where      }  } -/// The renderer of a [`Tooltip`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`Tooltip`] in your user interface. -/// -/// [`Tooltip`]: struct.Tooltip.html -/// [renderer]: ../../renderer/index.html -pub trait Renderer: -    crate::Renderer + text::Renderer + container::Renderer -{ -    /// The default padding of a [`Tooltip`] drawn by this renderer. -    const DEFAULT_PADDING: u16; - -    /// Draws a [`Tooltip`]. -    /// -    /// [`Tooltip`]: struct.Tooltip.html -    fn draw<Message>( -        &mut self, -        defaults: &Self::Defaults, -        cursor_position: Point, -        content_layout: Layout<'_>, -        viewport: &Rectangle, -        content: &Element<'_, Message, Self>, -        tooltip: &Text<Self>, -        position: Position, -        style: &<Self as container::Renderer>::Style, -        gap: u16, -        padding: u16, -    ) -> Self::Output; -} -  impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer, +    Renderer: 'a + text::Renderer,      Message: 'a,  {      fn from( | 
