diff options
Diffstat (limited to '')
| -rw-r--r-- | core/Cargo.toml | 10 | ||||
| -rw-r--r-- | core/README.md | 2 | ||||
| -rw-r--r-- | core/src/angle.rs | 33 | ||||
| -rw-r--r-- | core/src/background.rs | 19 | ||||
| -rw-r--r-- | core/src/border_radius.rs | 22 | ||||
| -rw-r--r-- | core/src/clipboard.rs | 23 | ||||
| -rw-r--r-- | core/src/color.rs | 40 | ||||
| -rw-r--r-- | core/src/element.rs (renamed from native/src/element.rs) | 139 | ||||
| -rw-r--r-- | core/src/event.rs (renamed from native/src/event.rs) | 2 | ||||
| -rw-r--r-- | core/src/font.rs | 116 | ||||
| -rw-r--r-- | core/src/gradient.rs | 105 | ||||
| -rw-r--r-- | core/src/hasher.rs (renamed from native/src/hasher.rs) | 0 | ||||
| -rw-r--r-- | core/src/image.rs (renamed from native/src/image.rs) | 12 | ||||
| -rw-r--r-- | core/src/layout.rs (renamed from native/src/layout.rs) | 0 | ||||
| -rw-r--r-- | core/src/layout/DRUID_LICENSE (renamed from native/src/layout/DRUID_LICENSE) | 0 | ||||
| -rw-r--r-- | core/src/layout/flex.rs (renamed from native/src/layout/flex.rs) | 0 | ||||
| -rw-r--r-- | core/src/layout/limits.rs (renamed from native/src/layout/limits.rs) | 0 | ||||
| -rw-r--r-- | core/src/layout/node.rs (renamed from native/src/layout/node.rs) | 0 | ||||
| -rw-r--r-- | core/src/length.rs | 8 | ||||
| -rw-r--r-- | core/src/lib.rs | 34 | ||||
| -rw-r--r-- | core/src/mouse.rs | 5 | ||||
| -rw-r--r-- | core/src/mouse/button.rs | 2 | ||||
| -rw-r--r-- | core/src/mouse/click.rs (renamed from native/src/mouse/click.rs) | 0 | ||||
| -rw-r--r-- | core/src/mouse/cursor.rs | 52 | ||||
| -rw-r--r-- | core/src/mouse/interaction.rs | 10 | ||||
| -rw-r--r-- | core/src/overlay.rs (renamed from native/src/overlay.rs) | 25 | ||||
| -rw-r--r-- | core/src/overlay/element.rs (renamed from native/src/overlay/element.rs) | 83 | ||||
| -rw-r--r-- | core/src/overlay/group.rs (renamed from native/src/overlay/group.rs) | 47 | ||||
| -rw-r--r-- | core/src/pixels.rs | 6 | ||||
| -rw-r--r-- | core/src/rectangle.rs | 48 | ||||
| -rw-r--r-- | core/src/renderer.rs (renamed from native/src/renderer.rs) | 26 | ||||
| -rw-r--r-- | core/src/renderer/null.rs (renamed from native/src/renderer/null.rs) | 24 | ||||
| -rw-r--r-- | core/src/shell.rs (renamed from native/src/shell.rs) | 0 | ||||
| -rw-r--r-- | core/src/size.rs | 2 | ||||
| -rw-r--r-- | core/src/svg.rs (renamed from native/src/svg.rs) | 4 | ||||
| -rw-r--r-- | core/src/text.rs | 212 | ||||
| -rw-r--r-- | core/src/touch.rs (renamed from native/src/touch.rs) | 0 | ||||
| -rw-r--r-- | core/src/widget.rs (renamed from native/src/widget.rs) | 103 | ||||
| -rw-r--r-- | core/src/widget/id.rs (renamed from native/src/widget/id.rs) | 2 | ||||
| -rw-r--r-- | core/src/widget/operation.rs | 226 | ||||
| -rw-r--r-- | core/src/widget/operation/focusable.rs (renamed from native/src/widget/operation/focusable.rs) | 0 | ||||
| -rw-r--r-- | core/src/widget/operation/scrollable.rs (renamed from native/src/widget/operation/scrollable.rs) | 41 | ||||
| -rw-r--r-- | core/src/widget/operation/text_input.rs (renamed from native/src/widget/operation/text_input.rs) | 0 | ||||
| -rw-r--r-- | core/src/widget/text.rs (renamed from native/src/widget/text.rs) | 115 | ||||
| -rw-r--r-- | core/src/widget/tree.rs (renamed from native/src/widget/tree.rs) | 0 | ||||
| -rw-r--r-- | core/src/window.rs | 15 | ||||
| -rw-r--r-- | core/src/window/event.rs (renamed from native/src/window/event.rs) | 0 | ||||
| -rw-r--r-- | core/src/window/icon.rs | 80 | ||||
| -rw-r--r-- | core/src/window/id.rs (renamed from native/src/window/id.rs) | 0 | ||||
| -rw-r--r-- | core/src/window/level.rs | 19 | ||||
| -rw-r--r-- | core/src/window/mode.rs (renamed from native/src/window/mode.rs) | 0 | ||||
| -rw-r--r-- | core/src/window/position.rs | 0 | ||||
| -rw-r--r-- | core/src/window/redraw_request.rs (renamed from native/src/window/redraw_request.rs) | 0 | ||||
| -rw-r--r-- | core/src/window/settings.rs (renamed from native/src/window/settings.rs) | 32 | ||||
| -rw-r--r-- | core/src/window/user_attention.rs (renamed from native/src/window/user_attention.rs) | 0 | 
55 files changed, 1389 insertions, 355 deletions
| diff --git a/core/Cargo.toml b/core/Cargo.toml index 0d6310d3..55f2e85f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@  [package]  name = "iced_core" -version = "0.8.1" +version = "0.9.0"  authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]  edition = "2021"  description = "The essential concepts of Iced" @@ -9,10 +9,16 @@ repository = "https://github.com/iced-rs/iced"  [dependencies]  bitflags = "1.2" +thiserror = "1" +log = "0.4.17" +twox-hash = { version = "1.5", default-features = false }  [dependencies.palette] -version = "0.6" +version = "0.7"  optional = true  [target.'cfg(target_arch = "wasm32")'.dependencies]  instant = "0.1" + +[dev-dependencies] +approx = "0.5" diff --git a/core/README.md b/core/README.md index 64d92e78..519e0608 100644 --- a/core/README.md +++ b/core/README.md @@ -18,7 +18,7 @@ This crate is meant to be a starting point for an Iced runtime.  Add `iced_core` as a dependency in your `Cargo.toml`:  ```toml -iced_core = "0.8" +iced_core = "0.9"  ```  __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/core/src/angle.rs b/core/src/angle.rs new file mode 100644 index 00000000..75a57c76 --- /dev/null +++ b/core/src/angle.rs @@ -0,0 +1,33 @@ +use crate::{Point, Rectangle, Vector}; +use std::f32::consts::PI; + +#[derive(Debug, Copy, Clone, PartialEq)] +/// Degrees +pub struct Degrees(pub f32); + +#[derive(Debug, Copy, Clone, PartialEq)] +/// Radians +pub struct Radians(pub f32); + +impl From<Degrees> for Radians { +    fn from(degrees: Degrees) -> Self { +        Radians(degrees.0 * PI / 180.0) +    } +} + +impl Radians { +    /// Calculates the line in which the [`Angle`] intercepts the `bounds`. +    pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) { +        let v1 = Vector::new(f32::cos(self.0), f32::sin(self.0)); + +        let distance_to_rect = f32::min( +            f32::abs((bounds.y - bounds.center().y) / v1.y), +            f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x), +        ); + +        let start = bounds.center() + v1 * distance_to_rect; +        let end = bounds.center() - v1 * distance_to_rect; + +        (start, end) +    } +} diff --git a/core/src/background.rs b/core/src/background.rs index cfb95867..347c52c0 100644 --- a/core/src/background.rs +++ b/core/src/background.rs @@ -1,11 +1,14 @@ +use crate::gradient::{self, Gradient};  use crate::Color;  /// The background of some element.  #[derive(Debug, Clone, Copy, PartialEq)]  pub enum Background { -    /// A solid color +    /// A solid color.      Color(Color), -    // TODO: Add gradient and image variants +    /// Linearly interpolate between several colors. +    Gradient(Gradient), +    // TODO: Add image variant  }  impl From<Color> for Background { @@ -14,8 +17,14 @@ impl From<Color> for Background {      }  } -impl From<Color> for Option<Background> { -    fn from(color: Color) -> Self { -        Some(Background::from(color)) +impl From<Gradient> for Background { +    fn from(gradient: Gradient) -> Self { +        Background::Gradient(gradient) +    } +} + +impl From<gradient::Linear> for Background { +    fn from(gradient: gradient::Linear) -> Self { +        Background::Gradient(Gradient::Linear(gradient))      }  } diff --git a/core/src/border_radius.rs b/core/src/border_radius.rs new file mode 100644 index 00000000..a444dd74 --- /dev/null +++ b/core/src/border_radius.rs @@ -0,0 +1,22 @@ +/// The border radii for the corners of a graphics primitive in the order: +/// top-left, top-right, bottom-right, bottom-left. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct BorderRadius([f32; 4]); + +impl From<f32> for BorderRadius { +    fn from(w: f32) -> Self { +        Self([w; 4]) +    } +} + +impl From<[f32; 4]> for BorderRadius { +    fn from(radi: [f32; 4]) -> Self { +        Self(radi) +    } +} + +impl From<BorderRadius> for [f32; 4] { +    fn from(radi: BorderRadius) -> Self { +        radi.0 +    } +} diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs new file mode 100644 index 00000000..081b4004 --- /dev/null +++ b/core/src/clipboard.rs @@ -0,0 +1,23 @@ +//! Access the clipboard. + +/// A buffer for short-term storage and transfer within and between +/// applications. +pub trait Clipboard { +    /// Reads the current content of the [`Clipboard`] as text. +    fn read(&self) -> Option<String>; + +    /// Writes the given text contents to the [`Clipboard`]. +    fn write(&mut self, contents: String); +} + +/// A null implementation of the [`Clipboard`] trait. +#[derive(Debug, Clone, Copy)] +pub struct Null; + +impl Clipboard for Null { +    fn read(&self) -> Option<String> { +        None +    } + +    fn write(&mut self, _contents: String) {} +} diff --git a/core/src/color.rs b/core/src/color.rs index fe0a1856..1392f28b 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -183,15 +183,15 @@ macro_rules! color {  }  #[cfg(feature = "palette")] -/// Converts from palette's `Srgba` type to a [`Color`]. +/// Converts from palette's `Rgba` type to a [`Color`].  impl From<Srgba> for Color { -    fn from(srgba: Srgba) -> Self { -        Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha) +    fn from(rgba: Srgba) -> Self { +        Color::new(rgba.red, rgba.green, rgba.blue, rgba.alpha)      }  }  #[cfg(feature = "palette")] -/// Converts from [`Color`] to palette's `Srgba` type. +/// Converts from [`Color`] to palette's `Rgba` type.  impl From<Color> for Srgba {      fn from(c: Color) -> Self {          Srgba::new(c.r, c.g, c.b, c.a) @@ -199,15 +199,15 @@ impl From<Color> for Srgba {  }  #[cfg(feature = "palette")] -/// Converts from palette's `Srgb` type to a [`Color`]. +/// Converts from palette's `Rgb` type to a [`Color`].  impl From<Srgb> for Color { -    fn from(srgb: Srgb) -> Self { -        Color::new(srgb.red, srgb.green, srgb.blue, 1.0) +    fn from(rgb: Srgb) -> Self { +        Color::new(rgb.red, rgb.green, rgb.blue, 1.0)      }  }  #[cfg(feature = "palette")] -/// Converts from [`Color`] to palette's `Srgb` type. +/// Converts from [`Color`] to palette's `Rgb` type.  impl From<Color> for Srgb {      fn from(c: Color) -> Self {          Srgb::new(c.r, c.g, c.b) @@ -218,12 +218,12 @@ impl From<Color> for Srgb {  #[cfg(test)]  mod tests {      use super::*; -    use palette::Blend; +    use palette::blend::Blend;      #[test]      fn srgba_traits() {          let c = Color::from_rgb(0.5, 0.4, 0.3); -        // Round-trip conversion to the palette:Srgba type +        // Round-trip conversion to the palette::Srgba type          let s: Srgba = c.into();          let r: Color = s.into();          assert_eq!(c, r); @@ -231,6 +231,8 @@ mod tests {      #[test]      fn color_manipulation() { +        use approx::assert_relative_eq; +          let c1 = Color::from_rgb(0.5, 0.4, 0.3);          let c2 = Color::from_rgb(0.2, 0.5, 0.3); @@ -238,19 +240,15 @@ mod tests {          let l1 = Srgba::from(c1).into_linear();          let l2 = Srgba::from(c2).into_linear(); -        // Take the lighter of each of the RGB components +        // Take the lighter of each of the sRGB components          let lighter = l1.lighten(l2);          // Convert back to our Color -        let r: Color = Srgba::from_linear(lighter).into(); -        assert_eq!( -            r, -            Color { -                r: 0.5, -                g: 0.5, -                b: 0.3, -                a: 1.0 -            } -        ); +        let result: Color = Srgba::from_linear(lighter).into(); + +        assert_relative_eq!(result.r, 0.5); +        assert_relative_eq!(result.g, 0.5); +        assert_relative_eq!(result.b, 0.3); +        assert_relative_eq!(result.a, 1.0);      }  } diff --git a/native/src/element.rs b/core/src/element.rs index 0a677d20..3268f14b 100644 --- a/native/src/element.rs +++ b/core/src/element.rs @@ -5,9 +5,7 @@ use crate::overlay;  use crate::renderer;  use crate::widget;  use crate::widget::tree::{self, Tree}; -use crate::{ -    Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget, -}; +use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget};  use std::any::Any;  use std::borrow::Borrow; @@ -90,41 +88,65 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {      /// We compose the previous __messages__ with the index of the counter      /// producing them. Let's implement our __view logic__ now:      /// -    /// ``` +    /// ```no_run      /// # mod counter { -    /// #     type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; -    /// #      /// #     #[derive(Debug, Clone, Copy)]      /// #     pub enum Message {}      /// #     pub struct Counter;      /// #      /// #     impl Counter { -    /// #         pub fn view(&mut self) -> Text { -    /// #             Text::new("") +    /// #         pub fn view( +    /// #             &self, +    /// #         ) -> iced_core::Element<Message, iced_core::renderer::Null> { +    /// #             unimplemented!()      /// #         }      /// #     }      /// # }      /// # -    /// # mod iced_wgpu { -    /// #     pub use iced_native::renderer::Null as Renderer; -    /// # } +    /// # mod iced { +    /// #     pub use iced_core::renderer::Null as Renderer; +    /// #     pub use iced_core::Element;      /// # -    /// # use counter::Counter; +    /// #     pub mod widget { +    /// #         pub struct Row<Message> { +    /// #             _t: std::marker::PhantomData<Message>, +    /// #         }      /// # -    /// # struct ManyCounters { -    /// #     counters: Vec<Counter>, -    /// # } +    /// #         impl<Message> Row<Message> { +    /// #             pub fn new() -> Self { +    /// #                 unimplemented!() +    /// #             }      /// # -    /// # #[derive(Debug, Clone, Copy)] -    /// # pub enum Message { -    /// #    Counter(usize, counter::Message) +    /// #             pub fn spacing(mut self, _: u32) -> Self { +    /// #                 unimplemented!() +    /// #             } +    /// # +    /// #             pub fn push( +    /// #                 mut self, +    /// #                 _: iced_core::Element<Message, iced_core::renderer::Null>, +    /// #             ) -> Self { +    /// #                 unimplemented!() +    /// #             } +    /// #         } +    /// #     }      /// # } -    /// use iced_native::Element; -    /// use iced_native::widget::Row; -    /// use iced_wgpu::Renderer; +    /// # +    /// use counter::Counter; +    /// +    /// use iced::widget::Row; +    /// use iced::{Element, Renderer}; +    /// +    /// struct ManyCounters { +    ///     counters: Vec<Counter>, +    /// } +    /// +    /// #[derive(Debug, Clone, Copy)] +    /// pub enum Message { +    ///     Counter(usize, counter::Message), +    /// }      ///      /// impl ManyCounters { -    ///     pub fn view(&mut self) -> Row<Message, Renderer> { +    ///     pub fn view(&mut self) -> Row<Message> {      ///         // We can quickly populate a `Row` by folding over our counters      ///         self.counters.iter_mut().enumerate().fold(      ///             Row::new().spacing(20), @@ -137,9 +159,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {      ///                     // Here we turn our `Element<counter::Message>` into      ///                     // an `Element<Message>` by combining the `index` and the      ///                     // message of the `element`. -    ///                     element.map(move |message| Message::Counter(index, message)) +    ///                     element +    ///                         .map(move |message| Message::Counter(index, message)),      ///                 ) -    ///             } +    ///             },      ///         )      ///     }      /// } @@ -353,7 +376,7 @@ where          tree: &mut Tree,          event: Event,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, B>, @@ -365,7 +388,7 @@ where              tree,              event,              layout, -            cursor_position, +            cursor,              renderer,              clipboard,              &mut local_shell, @@ -383,35 +406,23 @@ where          theme: &Renderer::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,      ) { -        self.widget.draw( -            tree, -            renderer, -            theme, -            style, -            layout, -            cursor_position, -            viewport, -        ) +        self.widget +            .draw(tree, renderer, theme, style, layout, cursor, viewport)      }      fn mouse_interaction(          &self,          tree: &Tree,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,          renderer: &Renderer,      ) -> mouse::Interaction { -        self.widget.mouse_interaction( -            tree, -            layout, -            cursor_position, -            viewport, -            renderer, -        ) +        self.widget +            .mouse_interaction(tree, layout, cursor, viewport, renderer)      }      fn overlay<'b>( @@ -496,20 +507,14 @@ where          state: &mut Tree,          event: Event,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,      ) -> event::Status { -        self.element.widget.on_event( -            state, -            event, -            layout, -            cursor_position, -            renderer, -            clipboard, -            shell, -        ) +        self.element +            .widget +            .on_event(state, event, layout, cursor, renderer, clipboard, shell)      }      fn draw( @@ -519,7 +524,7 @@ where          theme: &Renderer::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,      ) {          fn explain_layout<Renderer: crate::Renderer>( @@ -542,15 +547,9 @@ where              }          } -        self.element.widget.draw( -            state, -            renderer, -            theme, -            style, -            layout, -            cursor_position, -            viewport, -        ); +        self.element +            .widget +            .draw(state, renderer, theme, style, layout, cursor, viewport);          explain_layout(renderer, self.color, layout);      } @@ -559,17 +558,13 @@ where          &self,          state: &Tree,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,          renderer: &Renderer,      ) -> mouse::Interaction { -        self.element.widget.mouse_interaction( -            state, -            layout, -            cursor_position, -            viewport, -            renderer, -        ) +        self.element +            .widget +            .mouse_interaction(state, layout, cursor, viewport, renderer)      }      fn overlay<'b>( diff --git a/native/src/event.rs b/core/src/event.rs index eb826399..870b3074 100644 --- a/native/src/event.rs +++ b/core/src/event.rs @@ -62,7 +62,7 @@ impl Status {      /// `Captured` takes precedence over `Ignored`:      ///      /// ``` -    /// use iced_native::event::Status; +    /// use iced_core::event::Status;      ///      /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored);      /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); diff --git a/core/src/font.rs b/core/src/font.rs index 3f9ad2b5..bb425fd6 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -1,24 +1,102 @@ +//! Load and use fonts. +use std::hash::Hash; +  /// A font. -#[derive(Debug, Clone, Copy)] -pub enum Font { -    /// The default font. -    /// -    /// This is normally a font configured in a renderer or loaded from the -    /// system. -    Default, - -    /// An external font. -    External { -        /// The name of the external font -        name: &'static str, - -        /// The bytes of the external font -        bytes: &'static [u8], -    }, +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct Font { +    /// The [`Family`] of the [`Font`]. +    pub family: Family, +    /// The [`Weight`] of the [`Font`]. +    pub weight: Weight, +    /// The [`Stretch`] of the [`Font`]. +    pub stretch: Stretch, +    /// Whether if the [`Font`] is monospaced or not. +    pub monospaced: bool,  } -impl Default for Font { -    fn default() -> Font { -        Font::Default +impl Font { +    /// A non-monospaced sans-serif font with normal [`Weight`]. +    pub const DEFAULT: Font = Font { +        family: Family::SansSerif, +        weight: Weight::Normal, +        stretch: Stretch::Normal, +        monospaced: false, +    }; + +    /// A monospaced font with normal [`Weight`]. +    pub const MONOSPACE: Font = Font { +        family: Family::Monospace, +        monospaced: true, +        ..Self::DEFAULT +    }; + +    /// Creates a non-monospaced [`Font`] with the given [`Family::Name`] and +    /// normal [`Weight`]. +    pub const fn with_name(name: &'static str) -> Self { +        Font { +            family: Family::Name(name), +            ..Self::DEFAULT +        }      }  } + +/// A font family. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Family { +    /// The name of a font family of choice. +    Name(&'static str), + +    /// Serif fonts represent the formal text style for a script. +    Serif, + +    /// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low +    /// contrast and have stroke endings that are plain — without any flaring, +    /// cross stroke, or other ornamentation. +    #[default] +    SansSerif, + +    /// Glyphs in cursive fonts generally use a more informal script style, and +    /// the result looks more like handwritten pen or brush writing than printed +    /// letterwork. +    Cursive, + +    /// Fantasy fonts are primarily decorative or expressive fonts that contain +    /// decorative or expressive representations of characters. +    Fantasy, + +    /// The sole criterion of a monospace font is that all glyphs have the same +    /// fixed width. +    Monospace, +} + +/// The weight of some text. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Weight { +    Thin, +    ExtraLight, +    Light, +    #[default] +    Normal, +    Medium, +    Semibold, +    Bold, +    ExtraBold, +    Black, +} + +/// The width of some text. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Stretch { +    UltraCondensed, +    ExtraCondensed, +    Condensed, +    SemiCondensed, +    #[default] +    Normal, +    SemiExpanded, +    Expanded, +    ExtraExpanded, +    UltraExpanded, +} diff --git a/core/src/gradient.rs b/core/src/gradient.rs new file mode 100644 index 00000000..e19622fb --- /dev/null +++ b/core/src/gradient.rs @@ -0,0 +1,105 @@ +//! Colors that transition progressively. +use crate::{Color, Radians}; + +use std::cmp::Ordering; + +#[derive(Debug, Clone, Copy, PartialEq)] +/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), +/// or conically (TBD). +/// +/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`]. +pub enum Gradient { +    /// A linear gradient interpolates colors along a direction at a specific [`Angle`]. +    Linear(Linear), +} + +impl Gradient { +    /// Adjust the opacity of the gradient by a multiplier applied to each color stop. +    pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self { +        match &mut self { +            Gradient::Linear(linear) => { +                for stop in linear.stops.iter_mut().flatten() { +                    stop.color.a *= alpha_multiplier; +                } +            } +        } + +        self +    } +} + +impl From<Linear> for Gradient { +    fn from(gradient: Linear) -> Self { +        Self::Linear(gradient) +    } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +/// A point along the gradient vector where the specified [`color`] is unmixed. +/// +/// [`color`]: Self::color +pub struct ColorStop { +    /// Offset along the gradient vector. +    pub offset: f32, + +    /// The color of the gradient at the specified [`offset`]. +    /// +    /// [`offset`]: Self::offset +    pub color: Color, +} + +/// A linear gradient. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Linear { +    /// How the [`Gradient`] is angled within its bounds. +    pub angle: Radians, +    /// [`ColorStop`]s along the linear gradient path. +    pub stops: [Option<ColorStop>; 8], +} + +impl Linear { +    /// Creates a new [`Linear`] gradient with the given angle in [`Radians`]. +    pub fn new(angle: impl Into<Radians>) -> Self { +        Self { +            angle: angle.into(), +            stops: [None; 8], +        } +    } + +    /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient. +    /// +    /// Any `offset` that is not within `0.0..=1.0` will be silently ignored. +    /// +    /// Any stop added after the 8th will be silently ignored. +    pub fn add_stop(mut self, offset: f32, color: Color) -> Self { +        if offset.is_finite() && (0.0..=1.0).contains(&offset) { +            let (Ok(index) | Err(index)) = +                self.stops.binary_search_by(|stop| match stop { +                    None => Ordering::Greater, +                    Some(stop) => stop.offset.partial_cmp(&offset).unwrap(), +                }); + +            if index < 8 { +                self.stops[index] = Some(ColorStop { offset, color }); +            } +        } else { +            log::warn!("Gradient color stop must be within 0.0..=1.0 range."); +        }; + +        self +    } + +    /// Adds multiple [`ColorStop`]s to the gradient. +    /// +    /// Any stop added after the 8th will be silently ignored. +    pub fn add_stops( +        mut self, +        stops: impl IntoIterator<Item = ColorStop>, +    ) -> Self { +        for stop in stops.into_iter() { +            self = self.add_stop(stop.offset, stop.color) +        } + +        self +    } +} diff --git a/native/src/hasher.rs b/core/src/hasher.rs index fa52f16d..fa52f16d 100644 --- a/native/src/hasher.rs +++ b/core/src/hasher.rs diff --git a/native/src/image.rs b/core/src/image.rs index 70fbade0..85d9d475 100644 --- a/native/src/image.rs +++ b/core/src/image.rs @@ -6,7 +6,7 @@ use std::path::PathBuf;  use std::sync::Arc;  /// A handle of some image data. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)]  pub struct Handle {      id: u64,      data: Data, @@ -110,6 +110,14 @@ impl std::hash::Hash for Bytes {      }  } +impl PartialEq for Bytes { +    fn eq(&self, other: &Self) -> bool { +        self.as_ref() == other.as_ref() +    } +} + +impl Eq for Bytes {} +  impl AsRef<[u8]> for Bytes {      fn as_ref(&self) -> &[u8] {          self.0.as_ref().as_ref() @@ -125,7 +133,7 @@ impl std::ops::Deref for Bytes {  }  /// The data of a raster image. -#[derive(Clone, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)]  pub enum Data {      /// File data      Path(PathBuf), diff --git a/native/src/layout.rs b/core/src/layout.rs index 04954fb9..04954fb9 100644 --- a/native/src/layout.rs +++ b/core/src/layout.rs diff --git a/native/src/layout/DRUID_LICENSE b/core/src/layout/DRUID_LICENSE index d6456956..d6456956 100644 --- a/native/src/layout/DRUID_LICENSE +++ b/core/src/layout/DRUID_LICENSE diff --git a/native/src/layout/flex.rs b/core/src/layout/flex.rs index 8b967849..8b967849 100644 --- a/native/src/layout/flex.rs +++ b/core/src/layout/flex.rs diff --git a/native/src/layout/limits.rs b/core/src/layout/limits.rs index 5d3c1556..5d3c1556 100644 --- a/native/src/layout/limits.rs +++ b/core/src/layout/limits.rs diff --git a/native/src/layout/node.rs b/core/src/layout/node.rs index 2b44a7d5..2b44a7d5 100644 --- a/native/src/layout/node.rs +++ b/core/src/layout/node.rs diff --git a/core/src/length.rs b/core/src/length.rs index bb925c4b..3adb996e 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -1,3 +1,5 @@ +use crate::Pixels; +  /// The strategy used to fill space in a specific dimension.  #[derive(Debug, Clone, Copy, PartialEq)]  pub enum Length { @@ -36,6 +38,12 @@ impl Length {      }  } +impl From<Pixels> for Length { +    fn from(amount: Pixels) -> Self { +        Length::Fixed(f32::from(amount)) +    } +} +  impl From<f32> for Length {      fn from(amount: f32) -> Self {          Length::Fixed(amount) diff --git a/core/src/lib.rs b/core/src/lib.rs index d3596b4d..76d775e7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -7,7 +7,7 @@  //!   //!  //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native  //! [`iced_web`]: https://github.com/iced-rs/iced_web  #![doc(      html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" @@ -25,31 +25,61 @@  #![forbid(unsafe_code, rust_2018_idioms)]  #![allow(clippy::inherent_to_string, clippy::type_complexity)]  pub mod alignment; +pub mod clipboard; +pub mod event; +pub mod font; +pub mod gradient; +pub mod image;  pub mod keyboard; +pub mod layout;  pub mod mouse; +pub mod overlay; +pub mod renderer; +pub mod svg; +pub mod text;  pub mod time; +pub mod touch; +pub mod widget; +pub mod window; +mod angle;  mod background; +mod border_radius;  mod color;  mod content_fit; -mod font; +mod element; +mod hasher;  mod length;  mod padding;  mod pixels;  mod point;  mod rectangle; +mod shell;  mod size;  mod vector;  pub use alignment::Alignment; +pub use angle::{Degrees, Radians};  pub use background::Background; +pub use border_radius::BorderRadius; +pub use clipboard::Clipboard;  pub use color::Color;  pub use content_fit::ContentFit; +pub use element::Element; +pub use event::Event;  pub use font::Font; +pub use gradient::Gradient; +pub use hasher::Hasher; +pub use layout::Layout;  pub use length::Length; +pub use overlay::Overlay;  pub use padding::Padding;  pub use pixels::Pixels;  pub use point::Point;  pub use rectangle::Rectangle; +pub use renderer::Renderer; +pub use shell::Shell;  pub use size::Size; +pub use text::Text;  pub use vector::Vector; +pub use widget::Widget; diff --git a/core/src/mouse.rs b/core/src/mouse.rs index 48214f65..d13a60fb 100644 --- a/core/src/mouse.rs +++ b/core/src/mouse.rs @@ -1,8 +1,13 @@  //! Handle mouse events. +pub mod click; +  mod button; +mod cursor;  mod event;  mod interaction;  pub use button::Button; +pub use click::Click; +pub use cursor::Cursor;  pub use event::{Event, ScrollDelta};  pub use interaction::Interaction; diff --git a/core/src/mouse/button.rs b/core/src/mouse/button.rs index aeb8a55d..3eec7f42 100644 --- a/core/src/mouse/button.rs +++ b/core/src/mouse/button.rs @@ -11,5 +11,5 @@ pub enum Button {      Middle,      /// Some other button. -    Other(u8), +    Other(u16),  } diff --git a/native/src/mouse/click.rs b/core/src/mouse/click.rs index 4a7d796c..4a7d796c 100644 --- a/native/src/mouse/click.rs +++ b/core/src/mouse/click.rs diff --git a/core/src/mouse/cursor.rs b/core/src/mouse/cursor.rs new file mode 100644 index 00000000..203526e9 --- /dev/null +++ b/core/src/mouse/cursor.rs @@ -0,0 +1,52 @@ +use crate::{Point, Rectangle, Vector}; + +/// The mouse cursor state. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum Cursor { +    /// The cursor has a defined position. +    Available(Point), + +    /// The cursor is currently unavailable (i.e. out of bounds or busy). +    #[default] +    Unavailable, +} + +impl Cursor { +    /// Returns the absolute position of the [`Cursor`], if available. +    pub fn position(self) -> Option<Point> { +        match self { +            Cursor::Available(position) => Some(position), +            Cursor::Unavailable => None, +        } +    } + +    /// Returns the absolute position of the [`Cursor`], if available and inside +    /// the given bounds. +    /// +    /// If the [`Cursor`] is not over the provided bounds, this method will +    /// return `None`. +    pub fn position_over(self, bounds: Rectangle) -> Option<Point> { +        self.position().filter(|p| bounds.contains(*p)) +    } + +    /// Returns the relative position of the [`Cursor`] inside the given bounds, +    /// if available. +    /// +    /// If the [`Cursor`] is not over the provided bounds, this method will +    /// return `None`. +    pub fn position_in(self, bounds: Rectangle) -> Option<Point> { +        self.position_over(bounds) +            .map(|p| p - Vector::new(bounds.x, bounds.y)) +    } + +    /// Returns the relative position of the [`Cursor`] from the given origin, +    /// if available. +    pub fn position_from(self, origin: Point) -> Option<Point> { +        self.position().map(|p| p - Vector::new(origin.x, origin.y)) +    } + +    /// Returns true if the [`Cursor`] is over the given `bounds`. +    pub fn is_over(self, bounds: Rectangle) -> bool { +        self.position_over(bounds).is_some() +    } +} diff --git a/core/src/mouse/interaction.rs b/core/src/mouse/interaction.rs index 664147a7..072033fd 100644 --- a/core/src/mouse/interaction.rs +++ b/core/src/mouse/interaction.rs @@ -1,7 +1,8 @@  /// The interaction of a mouse cursor. -#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Default)]  #[allow(missing_docs)]  pub enum Interaction { +    #[default]      Idle,      Pointer,      Grab, @@ -11,10 +12,5 @@ pub enum Interaction {      Grabbing,      ResizingHorizontally,      ResizingVertically, -} - -impl Default for Interaction { -    fn default() -> Interaction { -        Interaction::Idle -    } +    NotAllowed,  } diff --git a/native/src/overlay.rs b/core/src/overlay.rs index 6cada416..2e05db93 100644 --- a/native/src/overlay.rs +++ b/core/src/overlay.rs @@ -2,11 +2,8 @@  mod element;  mod group; -pub mod menu; -  pub use element::Element;  pub use group::Group; -pub use menu::Menu;  use crate::event::{self, Event};  use crate::layout; @@ -41,7 +38,7 @@ where          theme: &Renderer::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,      );      /// Applies a [`widget::Operation`] to the [`Overlay`]. @@ -69,7 +66,7 @@ where          &mut self,          _event: Event,          _layout: Layout<'_>, -        _cursor_position: Point, +        _cursor: mouse::Cursor,          _renderer: &Renderer,          _clipboard: &mut dyn Clipboard,          _shell: &mut Shell<'_, Message>, @@ -83,7 +80,7 @@ where      fn mouse_interaction(          &self,          _layout: Layout<'_>, -        _cursor_position: Point, +        _cursor: mouse::Cursor,          _viewport: &Rectangle,          _renderer: &Renderer,      ) -> mouse::Interaction { @@ -94,9 +91,23 @@ where      ///      /// By default, it returns true if the bounds of the `layout` contain      /// the `cursor_position`. -    fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { +    fn is_over( +        &self, +        layout: Layout<'_>, +        _renderer: &Renderer, +        cursor_position: Point, +    ) -> bool {          layout.bounds().contains(cursor_position)      } + +    /// Returns the nested overlay of the [`Overlay`], if there is any. +    fn overlay<'a>( +        &'a mut self, +        _layout: Layout<'_>, +        _renderer: &Renderer, +    ) -> Option<Element<'a, Message, Renderer>> { +        None +    }  }  /// Returns a [`Group`] of overlay [`Element`] children. diff --git a/native/src/overlay/element.rs b/core/src/overlay/element.rs index 237d25d1..c2134343 100644 --- a/native/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -68,35 +68,25 @@ where          &mut self,          event: Event,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,      ) -> event::Status { -        self.overlay.on_event( -            event, -            layout, -            cursor_position, -            renderer, -            clipboard, -            shell, -        ) +        self.overlay +            .on_event(event, layout, cursor, renderer, clipboard, shell)      }      /// Returns the current [`mouse::Interaction`] of the [`Element`].      pub fn mouse_interaction(          &self,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,          renderer: &Renderer,      ) -> mouse::Interaction { -        self.overlay.mouse_interaction( -            layout, -            cursor_position, -            viewport, -            renderer, -        ) +        self.overlay +            .mouse_interaction(layout, cursor, viewport, renderer)      }      /// Draws the [`Element`] and its children using the given [`Layout`]. @@ -106,10 +96,9 @@ where          theme: &Renderer::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,      ) { -        self.overlay -            .draw(renderer, theme, style, layout, cursor_position) +        self.overlay.draw(renderer, theme, style, layout, cursor)      }      /// Applies a [`widget::Operation`] to the [`Element`]. @@ -123,8 +112,22 @@ where      }      /// Returns true if the cursor is over the [`Element`]. -    pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { -        self.overlay.is_over(layout, cursor_position) +    pub fn is_over( +        &self, +        layout: Layout<'_>, +        renderer: &Renderer, +        cursor_position: Point, +    ) -> bool { +        self.overlay.is_over(layout, renderer, cursor_position) +    } + +    /// Returns the nested overlay of the [`Element`], if there is any. +    pub fn overlay<'b>( +        &'b mut self, +        layout: Layout<'_>, +        renderer: &Renderer, +    ) -> Option<Element<'b, Message, Renderer>> { +        self.overlay.overlay(layout, renderer)      }  } @@ -215,7 +218,7 @@ where          &mut self,          event: Event,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, B>, @@ -226,7 +229,7 @@ where          let event_status = self.content.on_event(              event,              layout, -            cursor_position, +            cursor,              renderer,              clipboard,              &mut local_shell, @@ -240,16 +243,12 @@ where      fn mouse_interaction(          &self,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,          renderer: &Renderer,      ) -> mouse::Interaction { -        self.content.mouse_interaction( -            layout, -            cursor_position, -            viewport, -            renderer, -        ) +        self.content +            .mouse_interaction(layout, cursor, viewport, renderer)      }      fn draw( @@ -258,13 +257,27 @@ where          theme: &Renderer::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,      ) { -        self.content -            .draw(renderer, theme, style, layout, cursor_position) +        self.content.draw(renderer, theme, style, layout, cursor)      } -    fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { -        self.content.is_over(layout, cursor_position) +    fn is_over( +        &self, +        layout: Layout<'_>, +        renderer: &Renderer, +        cursor_position: Point, +    ) -> bool { +        self.content.is_over(layout, renderer, cursor_position) +    } + +    fn overlay<'b>( +        &'b mut self, +        layout: Layout<'_>, +        renderer: &Renderer, +    ) -> Option<Element<'b, B, Renderer>> { +        self.content +            .overlay(layout, renderer) +            .map(|overlay| overlay.map(self.mapper))      }  } diff --git a/native/src/overlay/group.rs b/core/src/overlay/group.rs index 1126f0cf..deffaad0 100644 --- a/native/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -1,12 +1,10 @@ -use iced_core::{Point, Rectangle, Size}; -  use crate::event;  use crate::layout;  use crate::mouse;  use crate::overlay;  use crate::renderer;  use crate::widget; -use crate::{Clipboard, Event, Layout, Overlay, Shell}; +use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};  /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]  /// children. @@ -83,7 +81,7 @@ where          &mut self,          event: Event,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>, @@ -95,7 +93,7 @@ where                  child.on_event(                      event.clone(),                      layout, -                    cursor_position, +                    cursor,                      renderer,                      clipboard,                      shell, @@ -110,17 +108,17 @@ where          theme: &<Renderer as crate::Renderer>::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,      ) {          for (child, layout) in self.children.iter().zip(layout.children()) { -            child.draw(renderer, theme, style, layout, cursor_position); +            child.draw(renderer, theme, style, layout, cursor);          }      }      fn mouse_interaction(          &self,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,          renderer: &Renderer,      ) -> mouse::Interaction { @@ -128,12 +126,7 @@ where              .iter()              .zip(layout.children())              .map(|(child, layout)| { -                child.mouse_interaction( -                    layout, -                    cursor_position, -                    viewport, -                    renderer, -                ) +                child.mouse_interaction(layout, cursor, viewport, renderer)              })              .max()              .unwrap_or_default() @@ -154,11 +147,33 @@ where          });      } -    fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { +    fn is_over( +        &self, +        layout: Layout<'_>, +        renderer: &Renderer, +        cursor_position: Point, +    ) -> bool {          self.children              .iter()              .zip(layout.children()) -            .any(|(child, layout)| child.is_over(layout, cursor_position)) +            .any(|(child, layout)| { +                child.is_over(layout, renderer, cursor_position) +            }) +    } + +    fn overlay<'b>( +        &'b mut self, +        layout: Layout<'_>, +        renderer: &Renderer, +    ) -> Option<overlay::Element<'b, Message, Renderer>> { +        let children = self +            .children +            .iter_mut() +            .zip(layout.children()) +            .filter_map(|(child, layout)| child.overlay(layout, renderer)) +            .collect::<Vec<_>>(); + +        (!children.is_empty()).then(|| Group::with_children(children).overlay())      }  } diff --git a/core/src/pixels.rs b/core/src/pixels.rs index e42cd9f9..6a9e5c88 100644 --- a/core/src/pixels.rs +++ b/core/src/pixels.rs @@ -20,3 +20,9 @@ impl From<u16> for Pixels {          Self(f32::from(amount))      }  } + +impl From<Pixels> for f32 { +    fn from(pixels: Pixels) -> Self { +        pixels.0 +    } +} diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 4fe91519..7ff324cb 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -66,6 +66,11 @@ impl Rectangle<f32> {          Size::new(self.width, self.height)      } +    /// Returns the area of the [`Rectangle`]. +    pub fn area(&self) -> f32 { +        self.width * self.height +    } +      /// Returns true if the given [`Point`] is contained in the [`Rectangle`].      pub fn contains(&self, point: Point) -> bool {          self.x <= point.x @@ -74,6 +79,15 @@ impl Rectangle<f32> {              && point.y <= self.y + self.height      } +    /// Returns true if the current [`Rectangle`] is completely within the given +    /// `container`. +    pub fn is_within(&self, container: &Rectangle) -> bool { +        container.contains(self.position()) +            && container.contains( +                self.position() + Vector::new(self.width, self.height), +            ) +    } +      /// Computes the intersection with the given [`Rectangle`].      pub fn intersection(          &self, @@ -100,6 +114,30 @@ impl Rectangle<f32> {          }      } +    /// Returns whether the [`Rectangle`] intersects with the given one. +    pub fn intersects(&self, other: &Self) -> bool { +        self.intersection(other).is_some() +    } + +    /// Computes the union with the given [`Rectangle`]. +    pub fn union(&self, other: &Self) -> Self { +        let x = self.x.min(other.x); +        let y = self.y.min(other.y); + +        let lower_right_x = (self.x + self.width).max(other.x + other.width); +        let lower_right_y = (self.y + self.height).max(other.y + other.height); + +        let width = lower_right_x - x; +        let height = lower_right_y - y; + +        Rectangle { +            x, +            y, +            width, +            height, +        } +    } +      /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.      pub fn snap(self) -> Rectangle<u32> {          Rectangle { @@ -109,6 +147,16 @@ impl Rectangle<f32> {              height: self.height as u32,          }      } + +    /// Expands the [`Rectangle`] a given amount. +    pub fn expand(self, amount: f32) -> Self { +        Self { +            x: self.x - amount, +            y: self.y - amount, +            width: self.width + amount * 2.0, +            height: self.height + amount * 2.0, +        } +    }  }  impl std::ops::Mul<f32> for Rectangle<f32> { diff --git a/native/src/renderer.rs b/core/src/renderer.rs index 2ac78982..7c73d2e4 100644 --- a/native/src/renderer.rs +++ b/core/src/renderer.rs @@ -1,11 +1,12 @@  //! Write your own renderer.  #[cfg(debug_assertions)]  mod null; +  #[cfg(debug_assertions)]  pub use null::Null;  use crate::layout; -use crate::{Background, Color, Element, Rectangle, Vector}; +use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};  /// A component that can be used by widgets to draw themselves on a screen.  pub trait Renderer: Sized { @@ -59,29 +60,6 @@ pub struct Quad {      pub border_color: Color,  } -/// The border radi for the corners of a graphics primitive in the order: -/// top-left, top-right, bottom-right, bottom-left. -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct BorderRadius([f32; 4]); - -impl From<f32> for BorderRadius { -    fn from(w: f32) -> Self { -        Self([w; 4]) -    } -} - -impl From<[f32; 4]> for BorderRadius { -    fn from(radi: [f32; 4]) -> Self { -        Self(radi) -    } -} - -impl From<BorderRadius> for [f32; 4] { -    fn from(radi: BorderRadius) -> Self { -        radi.0 -    } -} -  /// The styling attributes of a [`Renderer`].  #[derive(Debug, Clone, Copy, PartialEq)]  pub struct Style { diff --git a/native/src/renderer/null.rs b/core/src/renderer/null.rs index 9376d540..5d49699e 100644 --- a/native/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,6 +1,8 @@  use crate::renderer::{self, Renderer};  use crate::text::{self, Text}; -use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; +use crate::{Background, Font, Point, Rectangle, Size, Vector}; + +use std::borrow::Cow;  /// A renderer that does nothing.  /// @@ -16,7 +18,7 @@ impl Null {  }  impl Renderer for Null { -    type Theme = Theme; +    type Theme = ();      fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {} @@ -40,30 +42,40 @@ impl Renderer for Null {  impl text::Renderer for Null {      type Font = Font; -    const ICON_FONT: Font = Font::Default; +    const ICON_FONT: Font = Font::DEFAULT;      const CHECKMARK_ICON: char = '0';      const ARROW_DOWN_ICON: char = '0'; +    fn default_font(&self) -> Self::Font { +        Font::default() +    } +      fn default_size(&self) -> f32 { -        20.0 +        16.0      } +    fn load_font(&mut self, _font: Cow<'static, [u8]>) {} +      fn measure(          &self,          _content: &str,          _size: f32, +        _line_height: text::LineHeight,          _font: Font,          _bounds: Size, -    ) -> (f32, f32) { -        (0.0, 20.0) +        _shaping: text::Shaping, +    ) -> Size { +        Size::new(0.0, 20.0)      }      fn hit_test(          &self,          _contents: &str,          _size: f32, +        _line_height: text::LineHeight,          _font: Self::Font,          _bounds: Size, +        _shaping: text::Shaping,          _point: Point,          _nearest_only: bool,      ) -> Option<text::Hit> { diff --git a/native/src/shell.rs b/core/src/shell.rs index 74a5c616..74a5c616 100644 --- a/native/src/shell.rs +++ b/core/src/shell.rs diff --git a/core/src/size.rs b/core/src/size.rs index fbe940ef..7ef2f602 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -1,7 +1,7 @@  use crate::{Padding, Vector};  /// An amount of space in 2 dimensions. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]  pub struct Size<T = f32> {      /// The width.      pub width: T, diff --git a/native/src/svg.rs b/core/src/svg.rs index 9b98877a..54e9434e 100644 --- a/native/src/svg.rs +++ b/core/src/svg.rs @@ -7,7 +7,7 @@ use std::path::PathBuf;  use std::sync::Arc;  /// A handle of Svg data. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)]  pub struct Handle {      id: u64,      data: Arc<Data>, @@ -57,7 +57,7 @@ impl Hash for Handle {  }  /// The data of a vectorial image. -#[derive(Clone, Hash)] +#[derive(Clone, Hash, PartialEq, Eq)]  pub enum Data {      /// File data      Path(PathBuf), diff --git a/core/src/text.rs b/core/src/text.rs new file mode 100644 index 00000000..fc8aa20e --- /dev/null +++ b/core/src/text.rs @@ -0,0 +1,212 @@ +//! Draw and interact with text. +use crate::alignment; +use crate::{Color, Pixels, Point, Rectangle, Size}; + +use std::borrow::Cow; +use std::hash::{Hash, Hasher}; + +/// A paragraph. +#[derive(Debug, Clone, Copy)] +pub struct Text<'a, Font> { +    /// The content of the paragraph. +    pub content: &'a str, + +    /// The bounds of the paragraph. +    pub bounds: Rectangle, + +    /// The size of the [`Text`] in logical pixels. +    pub size: f32, + +    /// The line height of the [`Text`]. +    pub line_height: LineHeight, + +    /// The color of the [`Text`]. +    pub color: Color, + +    /// The font of the [`Text`]. +    pub font: Font, + +    /// The horizontal alignment of the [`Text`]. +    pub horizontal_alignment: alignment::Horizontal, + +    /// The vertical alignment of the [`Text`]. +    pub vertical_alignment: alignment::Vertical, + +    /// The [`Shaping`] strategy of the [`Text`]. +    pub shaping: Shaping, +} + +/// The shaping strategy of some text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Shaping { +    /// No shaping and no font fallback. +    /// +    /// This shaping strategy is very cheap, but it will not display complex +    /// scripts properly nor try to find missing glyphs in your system fonts. +    /// +    /// You should use this strategy when you have complete control of the text +    /// and the font you are displaying in your application. +    /// +    /// This is the default. +    #[default] +    Basic, +    /// Advanced text shaping and font fallback. +    /// +    /// You will need to enable this flag if the text contains a complex +    /// script, the font used needs it, and/or multiple fonts in your system +    /// may be needed to display all of the glyphs. +    /// +    /// Advanced shaping is expensive! You should only enable it when necessary. +    Advanced, +} + +/// The height of a line of text in a paragraph. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum LineHeight { +    /// A factor of the size of the text. +    Relative(f32), + +    /// An absolute height in logical pixels. +    Absolute(Pixels), +} + +impl LineHeight { +    /// Returns the [`LineHeight`] in absolute logical pixels. +    pub fn to_absolute(self, text_size: Pixels) -> Pixels { +        match self { +            Self::Relative(factor) => Pixels(factor * text_size.0), +            Self::Absolute(pixels) => pixels, +        } +    } +} + +impl Default for LineHeight { +    fn default() -> Self { +        Self::Relative(1.3) +    } +} + +impl From<f32> for LineHeight { +    fn from(factor: f32) -> Self { +        Self::Relative(factor) +    } +} + +impl From<Pixels> for LineHeight { +    fn from(pixels: Pixels) -> Self { +        Self::Absolute(pixels) +    } +} + +impl Hash for LineHeight { +    fn hash<H: Hasher>(&self, state: &mut H) { +        match self { +            Self::Relative(factor) => { +                state.write_u8(0); +                factor.to_bits().hash(state); +            } +            Self::Absolute(pixels) => { +                state.write_u8(1); +                f32::from(*pixels).to_bits().hash(state); +            } +        } +    } +} + +/// The result of hit testing on text. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Hit { +    /// The point was within the bounds of the returned character index. +    CharOffset(usize), +} + +impl Hit { +    /// Computes the cursor position of the [`Hit`] . +    pub fn cursor(self) -> usize { +        match self { +            Self::CharOffset(i) => i, +        } +    } +} + +/// A renderer capable of measuring and drawing [`Text`]. +pub trait Renderer: crate::Renderer { +    /// The font type used. +    type Font: Copy; + +    /// The icon font of the backend. +    const ICON_FONT: Self::Font; + +    /// The `char` representing a ✔ icon in the [`ICON_FONT`]. +    /// +    /// [`ICON_FONT`]: Self::ICON_FONT +    const CHECKMARK_ICON: char; + +    /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`]. +    /// +    /// [`ICON_FONT`]: Self::ICON_FONT +    const ARROW_DOWN_ICON: char; + +    /// Returns the default [`Self::Font`]. +    fn default_font(&self) -> Self::Font; + +    /// Returns the default size of [`Text`]. +    fn default_size(&self) -> f32; + +    /// Measures the text in the given bounds and returns the minimum boundaries +    /// that can fit the contents. +    fn measure( +        &self, +        content: &str, +        size: f32, +        line_height: LineHeight, +        font: Self::Font, +        bounds: Size, +        shaping: Shaping, +    ) -> Size; + +    /// Measures the width of the text as if it were laid out in a single line. +    fn measure_width( +        &self, +        content: &str, +        size: f32, +        font: Self::Font, +        shaping: Shaping, +    ) -> f32 { +        let bounds = self.measure( +            content, +            size, +            LineHeight::Absolute(Pixels(size)), +            font, +            Size::INFINITY, +            shaping, +        ); + +        bounds.width +    } + +    /// 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, +        line_height: LineHeight, +        font: Self::Font, +        bounds: Size, +        shaping: Shaping, +        point: Point, +        nearest_only: bool, +    ) -> Option<Hit>; + +    /// Loads a [`Self::Font`] from its bytes. +    fn load_font(&mut self, font: Cow<'static, [u8]>); + +    /// Draws the given [`Text`]. +    fn fill_text(&mut self, text: Text<'_, Self::Font>); +} diff --git a/native/src/touch.rs b/core/src/touch.rs index 18120644..18120644 100644 --- a/native/src/touch.rs +++ b/core/src/touch.rs diff --git a/native/src/widget.rs b/core/src/widget.rs index 2b3ca7be..79d86444 100644 --- a/native/src/widget.rs +++ b/core/src/widget.rs @@ -1,98 +1,21 @@ -//! Use the built-in widgets or create your own. -//! -//! # Built-in widgets -//! Every built-in drawable widget has its own module with a `Renderer` trait -//! that must be implemented by a [renderer] before being able to use it as -//! a [`Widget`]. -//! -//! # Custom widgets -//! If you want to implement a custom widget, you simply need to implement the -//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or -//! source of inspiration. -//! -//! [renderer]: crate::renderer -pub mod button; -pub mod checkbox; -pub mod column; -pub mod container; -pub mod helpers; -pub mod image; +//! Create custom widgets and operate on them.  pub mod operation; -pub mod pane_grid; -pub mod pick_list; -pub mod progress_bar; -pub mod radio; -pub mod row; -pub mod rule; -pub mod scrollable; -pub mod slider; -pub mod space; -pub mod svg;  pub mod text; -pub mod text_input; -pub mod toggler; -pub mod tooltip;  pub mod tree; -pub mod vertical_slider; -mod action;  mod id; -#[doc(no_inline)] -pub use button::Button; -#[doc(no_inline)] -pub use checkbox::Checkbox; -#[doc(no_inline)] -pub use column::Column; -#[doc(no_inline)] -pub use container::Container; -#[doc(no_inline)] -pub use helpers::*; -#[doc(no_inline)] -pub use image::Image; -#[doc(no_inline)] -pub use pane_grid::PaneGrid; -#[doc(no_inline)] -pub use pick_list::PickList; -#[doc(no_inline)] -pub use progress_bar::ProgressBar; -#[doc(no_inline)] -pub use radio::Radio; -#[doc(no_inline)] -pub use row::Row; -#[doc(no_inline)] -pub use rule::Rule; -#[doc(no_inline)] -pub use scrollable::Scrollable; -#[doc(no_inline)] -pub use slider::Slider; -#[doc(no_inline)] -pub use space::Space; -#[doc(no_inline)] -pub use svg::Svg; -#[doc(no_inline)] -pub use text::Text; -#[doc(no_inline)] -pub use text_input::TextInput; -#[doc(no_inline)] -pub use toggler::Toggler; -#[doc(no_inline)] -pub use tooltip::Tooltip; -#[doc(no_inline)] -pub use tree::Tree; -#[doc(no_inline)] -pub use vertical_slider::VerticalSlider; - -pub use action::Action;  pub use id::Id;  pub use operation::Operation; +pub use text::Text; +pub use tree::Tree;  use crate::event::{self, Event}; -use crate::layout; +use crate::layout::{self, Layout};  use crate::mouse;  use crate::overlay;  use crate::renderer; -use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; +use crate::{Clipboard, Length, Rectangle, Shell};  /// A component that displays information and allows interaction.  /// @@ -110,12 +33,12 @@ use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell};  /// - [`geometry`], a custom widget showcasing how to draw geometry with the  /// `Mesh2D` primitive in [`iced_wgpu`].  /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry +/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry  /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu  pub trait Widget<Message, Renderer>  where      Renderer: crate::Renderer, @@ -144,7 +67,7 @@ where          theme: &Renderer::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor_position: Point, +        cursor: mouse::Cursor,          viewport: &Rectangle,      ); @@ -188,7 +111,7 @@ where          _state: &mut Tree,          _event: Event,          _layout: Layout<'_>, -        _cursor_position: Point, +        _cursor: mouse::Cursor,          _renderer: &Renderer,          _clipboard: &mut dyn Clipboard,          _shell: &mut Shell<'_, Message>, @@ -203,7 +126,7 @@ where          &self,          _state: &Tree,          _layout: Layout<'_>, -        _cursor_position: Point, +        _cursor: mouse::Cursor,          _viewport: &Rectangle,          _renderer: &Renderer,      ) -> mouse::Interaction { diff --git a/native/src/widget/id.rs b/core/src/widget/id.rs index 4b8fedf1..ae739bb7 100644 --- a/native/src/widget/id.rs +++ b/core/src/widget/id.rs @@ -24,7 +24,7 @@ impl Id {  }  #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Internal { +enum Internal {      Unique(usize),      Custom(borrow::Cow<'static, str>),  } diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs new file mode 100644 index 00000000..ad188c36 --- /dev/null +++ b/core/src/widget/operation.rs @@ -0,0 +1,226 @@ +//! Query or update internal widget state. +pub mod focusable; +pub mod scrollable; +pub mod text_input; + +pub use focusable::Focusable; +pub use scrollable::Scrollable; +pub use text_input::TextInput; + +use crate::widget::Id; + +use std::any::Any; +use std::fmt; +use std::rc::Rc; + +/// A piece of logic that can traverse the widget tree of an application in +/// order to query or update some widget state. +pub trait Operation<T> { +    /// Operates on a widget that contains other widgets. +    /// +    /// The `operate_on_children` function can be called to return control to +    /// the widget tree and keep traversing it. +    fn container( +        &mut self, +        id: Option<&Id>, +        operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), +    ); + +    /// Operates on a widget that can be focused. +    fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} + +    /// Operates on a widget that can be scrolled. +    fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} + +    /// Operates on a widget that has text input. +    fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} + +    /// Operates on a custom widget with some state. +    fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {} + +    /// Finishes the [`Operation`] and returns its [`Outcome`]. +    fn finish(&self) -> Outcome<T> { +        Outcome::None +    } +} + +/// The result of an [`Operation`]. +pub enum Outcome<T> { +    /// The [`Operation`] produced no result. +    None, + +    /// The [`Operation`] produced some result. +    Some(T), + +    /// The [`Operation`] needs to be followed by another [`Operation`]. +    Chain(Box<dyn Operation<T>>), +} + +impl<T> fmt::Debug for Outcome<T> +where +    T: fmt::Debug, +{ +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        match self { +            Self::None => write!(f, "Outcome::None"), +            Self::Some(output) => write!(f, "Outcome::Some({output:?})"), +            Self::Chain(_) => write!(f, "Outcome::Chain(...)"), +        } +    } +} + +/// Maps the output of an [`Operation`] using the given function. +pub fn map<A, B>( +    operation: Box<dyn Operation<A>>, +    f: impl Fn(A) -> B + 'static, +) -> impl Operation<B> +where +    A: 'static, +    B: 'static, +{ +    #[allow(missing_debug_implementations)] +    struct Map<A, B> { +        operation: Box<dyn Operation<A>>, +        f: Rc<dyn Fn(A) -> B>, +    } + +    impl<A, B> Operation<B> for Map<A, B> +    where +        A: 'static, +        B: 'static, +    { +        fn container( +            &mut self, +            id: Option<&Id>, +            operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), +        ) { +            struct MapRef<'a, A> { +                operation: &'a mut dyn Operation<A>, +            } + +            impl<'a, A, B> Operation<B> for MapRef<'a, A> { +                fn container( +                    &mut self, +                    id: Option<&Id>, +                    operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), +                ) { +                    let Self { operation, .. } = self; + +                    operation.container(id, &mut |operation| { +                        operate_on_children(&mut MapRef { operation }); +                    }); +                } + +                fn scrollable( +                    &mut self, +                    state: &mut dyn Scrollable, +                    id: Option<&Id>, +                ) { +                    self.operation.scrollable(state, id); +                } + +                fn focusable( +                    &mut self, +                    state: &mut dyn Focusable, +                    id: Option<&Id>, +                ) { +                    self.operation.focusable(state, id); +                } + +                fn text_input( +                    &mut self, +                    state: &mut dyn TextInput, +                    id: Option<&Id>, +                ) { +                    self.operation.text_input(state, id); +                } + +                fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { +                    self.operation.custom(state, id); +                } +            } + +            let Self { operation, .. } = self; + +            MapRef { +                operation: operation.as_mut(), +            } +            .container(id, operate_on_children); +        } + +        fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { +            self.operation.focusable(state, id); +        } + +        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { +            self.operation.scrollable(state, id); +        } + +        fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { +            self.operation.text_input(state, id); +        } + +        fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) { +            self.operation.custom(state, id); +        } + +        fn finish(&self) -> Outcome<B> { +            match self.operation.finish() { +                Outcome::None => Outcome::None, +                Outcome::Some(output) => Outcome::Some((self.f)(output)), +                Outcome::Chain(next) => Outcome::Chain(Box::new(Map { +                    operation: next, +                    f: self.f.clone(), +                })), +            } +        } +    } + +    Map { +        operation, +        f: Rc::new(f), +    } +} + +/// Produces an [`Operation`] that applies the given [`Operation`] to the +/// children of a container with the given [`Id`]. +pub fn scope<T: 'static>( +    target: Id, +    operation: impl Operation<T> + 'static, +) -> impl Operation<T> { +    struct ScopedOperation<Message> { +        target: Id, +        operation: Box<dyn Operation<Message>>, +    } + +    impl<Message: 'static> Operation<Message> for ScopedOperation<Message> { +        fn container( +            &mut self, +            id: Option<&Id>, +            operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>), +        ) { +            if id == Some(&self.target) { +                operate_on_children(self.operation.as_mut()); +            } else { +                operate_on_children(self); +            } +        } + +        fn finish(&self) -> Outcome<Message> { +            match self.operation.finish() { +                Outcome::Chain(next) => { +                    Outcome::Chain(Box::new(ScopedOperation { +                        target: self.target.clone(), +                        operation: next, +                    })) +                } +                outcome => outcome, +            } +        } +    } + +    ScopedOperation { +        target, +        operation: Box::new(operation), +    } +} diff --git a/native/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 312e4894..312e4894 100644 --- a/native/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs diff --git a/native/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index 3b20631f..f947344d 100644 --- a/native/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -5,6 +5,9 @@ use crate::widget::{Id, Operation};  pub trait Scrollable {      /// Snaps the scroll of the widget to the given `percentage` along the horizontal & vertical axis.      fn snap_to(&mut self, offset: RelativeOffset); + +    /// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis. +    fn scroll_to(&mut self, offset: AbsoluteOffset);  }  /// Produces an [`Operation`] that snaps the widget with the given [`Id`] to @@ -34,7 +37,43 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {      SnapTo { target, offset }  } -/// The amount of offset in each direction of a [`Scrollable`]. +/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] to +/// the provided [`AbsoluteOffset`]. +pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> { +    struct ScrollTo { +        target: Id, +        offset: AbsoluteOffset, +    } + +    impl<T> Operation<T> for ScrollTo { +        fn container( +            &mut self, +            _id: Option<&Id>, +            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), +        ) { +            operate_on_children(self) +        } + +        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { +            if Some(&self.target) == id { +                state.scroll_to(self.offset); +            } +        } +    } + +    ScrollTo { target, offset } +} + +/// The amount of absolute offset in each direction of a [`Scrollable`]. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct AbsoluteOffset { +    /// The amount of horizontal offset +    pub x: f32, +    /// The amount of vertical offset +    pub y: f32, +} + +/// The amount of relative offset in each direction of a [`Scrollable`].  ///  /// A value of `0.0` means start, while `1.0` means end.  #[derive(Debug, Clone, Copy, PartialEq, Default)] diff --git a/native/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs index 4c773e99..4c773e99 100644 --- a/native/src/widget/operation/text_input.rs +++ b/core/src/widget/operation/text_input.rs diff --git a/native/src/widget/text.rs b/core/src/widget/text.rs index 3fee48f2..79df2b02 100644 --- a/native/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -1,30 +1,17 @@  //! Write some text for your users to read.  use crate::alignment;  use crate::layout; +use crate::mouse;  use crate::renderer;  use crate::text;  use crate::widget::Tree; -use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget}; +use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget};  use std::borrow::Cow; -pub use iced_style::text::{Appearance, StyleSheet}; +pub use text::{LineHeight, Shaping};  /// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_native::Color; -/// # -/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>; -/// # -/// Text::new("I <3 iced!") -///     .size(40) -///     .style(Color::from([0.0, 0.0, 1.0])); -/// ``` -/// -///   #[allow(missing_debug_implementations)]  pub struct Text<'a, Renderer>  where @@ -33,11 +20,13 @@ where  {      content: Cow<'a, str>,      size: Option<f32>, +    line_height: LineHeight,      width: Length,      height: Length,      horizontal_alignment: alignment::Horizontal,      vertical_alignment: alignment::Vertical, -    font: Renderer::Font, +    font: Option<Renderer::Font>, +    shaping: Shaping,      style: <Renderer::Theme as StyleSheet>::Style,  } @@ -51,11 +40,13 @@ where          Text {              content: content.into(),              size: None, -            font: Default::default(), +            line_height: LineHeight::default(), +            font: None,              width: Length::Shrink,              height: Length::Shrink,              horizontal_alignment: alignment::Horizontal::Left,              vertical_alignment: alignment::Vertical::Top, +            shaping: Shaping::Basic,              style: Default::default(),          }      } @@ -66,11 +57,17 @@ where          self      } +    /// Sets the [`LineHeight`] of the [`Text`]. +    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self { +        self.line_height = line_height.into(); +        self +    } +      /// Sets the [`Font`] of the [`Text`].      ///      /// [`Font`]: crate::text::Renderer::Font      pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self { -        self.font = font.into(); +        self.font = Some(font.into());          self      } @@ -112,6 +109,12 @@ where          self.vertical_alignment = alignment;          self      } + +    /// Sets the [`Shaping`] strategy of the [`Text`]. +    pub fn shaping(mut self, shaping: Shaping) -> Self { +        self.shaping = shaping; +        self +    }  }  impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer> @@ -136,12 +139,16 @@ where          let size = self.size.unwrap_or_else(|| renderer.default_size()); -        let bounds = limits.max(); - -        let (width, height) = -            renderer.measure(&self.content, size, self.font.clone(), bounds); +        let bounds = renderer.measure( +            &self.content, +            size, +            self.line_height, +            self.font.unwrap_or_else(|| renderer.default_font()), +            limits.max(), +            self.shaping, +        ); -        let size = limits.resolve(Size::new(width, height)); +        let size = limits.resolve(bounds);          layout::Node::new(size)      } @@ -153,7 +160,7 @@ where          theme: &Renderer::Theme,          style: &renderer::Style,          layout: Layout<'_>, -        _cursor_position: Point, +        _cursor_position: mouse::Cursor,          _viewport: &Rectangle,      ) {          draw( @@ -162,10 +169,12 @@ where              layout,              &self.content,              self.size, -            self.font.clone(), -            theme.appearance(self.style), +            self.line_height, +            self.font, +            theme.appearance(self.style.clone()),              self.horizontal_alignment,              self.vertical_alignment, +            self.shaping,          );      }  } @@ -186,10 +195,12 @@ pub fn draw<Renderer>(      layout: Layout<'_>,      content: &str,      size: Option<f32>, -    font: Renderer::Font, +    line_height: LineHeight, +    font: Option<Renderer::Font>,      appearance: Appearance,      horizontal_alignment: alignment::Horizontal,      vertical_alignment: alignment::Vertical, +    shaping: Shaping,  ) where      Renderer: text::Renderer,  { @@ -207,14 +218,18 @@ pub fn draw<Renderer>(          alignment::Vertical::Bottom => bounds.y + bounds.height,      }; -    renderer.fill_text(crate::text::Text { +    let size = size.unwrap_or_else(|| renderer.default_size()); + +    renderer.fill_text(crate::Text {          content, -        size: size.unwrap_or_else(|| renderer.default_size()), +        size, +        line_height,          bounds: Rectangle { x, y, ..bounds },          color: appearance.color.unwrap_or(style.text_color), -        font, +        font: font.unwrap_or_else(|| renderer.default_font()),          horizontal_alignment,          vertical_alignment, +        shaping,      });  } @@ -238,22 +253,52 @@ where          Self {              content: self.content.clone(),              size: self.size, +            line_height: self.line_height,              width: self.width,              height: self.height,              horizontal_alignment: self.horizontal_alignment,              vertical_alignment: self.vertical_alignment, -            font: self.font.clone(), -            style: self.style, +            font: self.font, +            style: self.style.clone(), +            shaping: self.shaping,          }      }  } +impl<'a, Renderer> From<&'a str> for Text<'a, Renderer> +where +    Renderer: text::Renderer, +    Renderer::Theme: StyleSheet, +{ +    fn from(content: &'a str) -> Self { +        Self::new(content) +    } +} +  impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>  where      Renderer: text::Renderer + 'a,      Renderer::Theme: StyleSheet,  { -    fn from(contents: &'a str) -> Self { -        Text::new(contents).into() +    fn from(content: &'a str) -> Self { +        Text::from(content).into()      }  } + +/// The style sheet of some text. +pub trait StyleSheet { +    /// The supported style of the [`StyleSheet`]. +    type Style: Default + Clone; + +    /// Produces the [`Appearance`] of some text. +    fn appearance(&self, style: Self::Style) -> Appearance; +} + +/// The apperance of some text. +#[derive(Debug, Clone, Copy, Default)] +pub struct Appearance { +    /// The [`Color`] of the text. +    /// +    /// The default, `None`, means using the inherited color. +    pub color: Option<Color>, +} diff --git a/native/src/widget/tree.rs b/core/src/widget/tree.rs index da269632..da269632 100644 --- a/native/src/widget/tree.rs +++ b/core/src/widget/tree.rs diff --git a/core/src/window.rs b/core/src/window.rs new file mode 100644 index 00000000..a6dbdfb4 --- /dev/null +++ b/core/src/window.rs @@ -0,0 +1,15 @@ +//! Build window-based GUI applications. +pub mod icon; + +mod event; +mod level; +mod mode; +mod redraw_request; +mod user_attention; + +pub use event::Event; +pub use icon::Icon; +pub use level::Level; +pub use mode::Mode; +pub use redraw_request::RedrawRequest; +pub use user_attention::UserAttention; diff --git a/native/src/window/event.rs b/core/src/window/event.rs index e2fb5e66..e2fb5e66 100644 --- a/native/src/window/event.rs +++ b/core/src/window/event.rs diff --git a/core/src/window/icon.rs b/core/src/window/icon.rs new file mode 100644 index 00000000..31868ecf --- /dev/null +++ b/core/src/window/icon.rs @@ -0,0 +1,80 @@ +//! Change the icon of a window. +use crate::Size; + +use std::mem; + +/// Builds an  [`Icon`] from its RGBA pixels in the sRGB color space. +pub fn from_rgba( +    rgba: Vec<u8>, +    width: u32, +    height: u32, +) -> Result<Icon, Error> { +    const PIXEL_SIZE: usize = mem::size_of::<u8>() * 4; + +    if rgba.len() % PIXEL_SIZE != 0 { +        return Err(Error::ByteCountNotDivisibleBy4 { +            byte_count: rgba.len(), +        }); +    } + +    let pixel_count = rgba.len() / PIXEL_SIZE; + +    if pixel_count != (width * height) as usize { +        return Err(Error::DimensionsVsPixelCount { +            width, +            height, +            width_x_height: (width * height) as usize, +            pixel_count, +        }); +    } + +    Ok(Icon { +        rgba, +        size: Size::new(width, height), +    }) +} + +/// An window icon normally used for the titlebar or taskbar. +#[derive(Debug, Clone)] +pub struct Icon { +    rgba: Vec<u8>, +    size: Size<u32>, +} + +impl Icon { +    /// Returns the raw data of the [`Icon`]. +    pub fn into_raw(self) -> (Vec<u8>, Size<u32>) { +        (self.rgba, self.size) +    } +} + +#[derive(Debug, thiserror::Error)] +/// An error produced when using [`Icon::from_rgba`] with invalid arguments. +pub enum Error { +    /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be +    /// safely interpreted as 32bpp RGBA pixels. +    #[error( +        "The provided RGBA data (with length {byte_count}) isn't divisible \ +        by 4. Therefore, it cannot be safely interpreted as 32bpp RGBA pixels" +    )] +    ByteCountNotDivisibleBy4 { +        /// The length of the provided RGBA data. +        byte_count: usize, +    }, +    /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`. +    /// At least one of your arguments is incorrect. +    #[error( +        "The number of RGBA pixels ({pixel_count}) does not match the \ +        provided dimensions ({width}x{height})." +    )] +    DimensionsVsPixelCount { +        /// The provided width. +        width: u32, +        /// The provided height. +        height: u32, +        /// The product of `width` and `height`. +        width_x_height: usize, +        /// The amount of pixels of the provided RGBA data. +        pixel_count: usize, +    }, +} diff --git a/native/src/window/id.rs b/core/src/window/id.rs index 0a11b1aa..0a11b1aa 100644 --- a/native/src/window/id.rs +++ b/core/src/window/id.rs diff --git a/core/src/window/level.rs b/core/src/window/level.rs new file mode 100644 index 00000000..3878ecac --- /dev/null +++ b/core/src/window/level.rs @@ -0,0 +1,19 @@ +/// A window level groups windows with respect to their z-position. +/// +/// The relative ordering between windows in different window levels is fixed. +/// The z-order of a window within the same window level may change dynamically +/// on user interaction. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum Level { +    /// The default behavior. +    #[default] +    Normal, + +    /// The window will always be below normal windows. +    /// +    /// This is useful for a widget-based app. +    AlwaysOnBottom, + +    /// The window will always be on top of normal windows. +    AlwaysOnTop, +} diff --git a/native/src/window/mode.rs b/core/src/window/mode.rs index fdce8e23..fdce8e23 100644 --- a/native/src/window/mode.rs +++ b/core/src/window/mode.rs diff --git a/core/src/window/position.rs b/core/src/window/position.rs new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/core/src/window/position.rs diff --git a/native/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index 3b4f0fd3..3b4f0fd3 100644 --- a/native/src/window/redraw_request.rs +++ b/core/src/window/redraw_request.rs diff --git a/native/src/window/settings.rs b/core/src/window/settings.rs index 67798fbe..458b9232 100644 --- a/native/src/window/settings.rs +++ b/core/src/window/settings.rs @@ -1,4 +1,6 @@ -use crate::window::{Icon, Position}; +use crate::window::{Icon, Level, Position}; + +pub use iced_winit::settings::PlatformSpecific;  /// The window settings of an application.  #[derive(Debug, Clone)] @@ -27,11 +29,14 @@ pub struct Settings {      /// Whether the window should be transparent.      pub transparent: bool, -    /// Whether the window will always be on top of other windows. -    pub always_on_top: bool, +    /// The window [`Level`]. +    pub level: Level,      /// The icon of the window.      pub icon: Option<Icon>, + +    /// Platform specific settings. +    pub platform_specific: PlatformSpecific,  }  impl Default for Settings { @@ -45,8 +50,27 @@ impl Default for Settings {              resizable: true,              decorations: true,              transparent: false, -            always_on_top: false, +            level: Level::default(),              icon: None, +            platform_specific: Default::default(), +        } +    } +} + +impl From<Settings> for iced_winit::settings::Window { +    fn from(settings: Settings) -> Self { +        Self { +            size: settings.size, +            position: iced_winit::Position::from(settings.position), +            min_size: settings.min_size, +            max_size: settings.max_size, +            visible: settings.visible, +            resizable: settings.resizable, +            decorations: settings.decorations, +            transparent: settings.transparent, +            level: settings.level, +            icon: settings.icon.map(Icon::into), +            platform_specific: settings.platform_specific,          }      }  } diff --git a/native/src/window/user_attention.rs b/core/src/window/user_attention.rs index b03dfeef..b03dfeef 100644 --- a/native/src/window/user_attention.rs +++ b/core/src/window/user_attention.rs | 
