diff options
| author | 2023-11-29 22:28:31 +0100 | |
|---|---|---|
| committer | 2023-11-29 22:28:31 +0100 | |
| commit | e09b4e24dda51b8212d8ece52431dacaa3922a7b (patch) | |
| tree | 7005e181528134ebdde5bbbe5909273db9f30174 /core | |
| parent | 83c7870c569a2976923ee6243a19813094d44673 (diff) | |
| parent | 7f8b17604a31e00becc43130ec516c1a53552c88 (diff) | |
| download | iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.tar.gz iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.tar.bz2 iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.zip | |
Merge branch 'master' into feat/multi-window-support
Diffstat (limited to '')
36 files changed, 1035 insertions, 281 deletions
| diff --git a/core/Cargo.toml b/core/Cargo.toml index edf9e7c8..7db4fa53 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,24 +1,27 @@  [package]  name = "iced_core" -version = "0.9.0" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2021" -description = "The essential concepts of Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" +description = "The essential ideas of iced" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true  [dependencies] -bitflags = "1.2" -thiserror = "1" -log = "0.4.17" -twox-hash = { version = "1.5", default-features = false } +bitflags.workspace = true +log.workspace = true +thiserror.workspace = true +xxhash-rust.workspace = true +num-traits.workspace = true -[dependencies.palette] -version = "0.7" -optional = true +palette.workspace = true +palette.optional = true  [target.'cfg(target_arch = "wasm32")'.dependencies] -instant = "0.1" +instant.workspace = true  [target.'cfg(windows)'.dependencies.raw-window-handle]  version = "0.5.2" diff --git a/core/src/angle.rs b/core/src/angle.rs index 75a57c76..102b69cf 100644 --- a/core/src/angle.rs +++ b/core/src/angle.rs @@ -1,32 +1,72 @@  use crate::{Point, Rectangle, Vector}; -use std::f32::consts::PI; -#[derive(Debug, Copy, Clone, PartialEq)] +use std::f32::consts::{FRAC_PI_2, PI}; +use std::ops::RangeInclusive; +  /// Degrees +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]  pub struct Degrees(pub f32); -#[derive(Debug, Copy, Clone, PartialEq)]  /// Radians +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]  pub struct Radians(pub f32); +impl Radians { +    /// The range of radians of a circle. +    pub const RANGE: RangeInclusive<Radians> = Radians(0.0)..=Radians(2.0 * PI); +} +  impl From<Degrees> for Radians {      fn from(degrees: Degrees) -> Self { -        Radians(degrees.0 * PI / 180.0) +        Self(degrees.0 * PI / 180.0) +    } +} + +impl From<f32> for Radians { +    fn from(radians: f32) -> Self { +        Self(radians) +    } +} + +impl From<u8> for Radians { +    fn from(radians: u8) -> Self { +        Self(f32::from(radians)) +    } +} + +impl From<Radians> for f64 { +    fn from(radians: Radians) -> Self { +        Self::from(radians.0) +    } +} + +impl num_traits::FromPrimitive for Radians { +    fn from_i64(n: i64) -> Option<Self> { +        Some(Self(n as f32)) +    } + +    fn from_u64(n: u64) -> Option<Self> { +        Some(Self(n as f32)) +    } + +    fn from_f64(n: f64) -> Option<Self> { +        Some(Self(n as f32))      }  }  impl Radians { -    /// Calculates the line in which the [`Angle`] intercepts the `bounds`. +    /// 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 angle = self.0 - FRAC_PI_2; +        let r = Vector::new(f32::cos(angle), f32::sin(angle)); -        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 distance_to_rect = f32::max( +            f32::abs(r.x * bounds.width / 2.0), +            f32::abs(r.y * bounds.height / 2.0),          ); -        let start = bounds.center() + v1 * distance_to_rect; -        let end = bounds.center() - v1 * distance_to_rect; +        let start = bounds.center() - r * distance_to_rect; +        let end = bounds.center() + r * distance_to_rect;          (start, end)      } diff --git a/core/src/color.rs b/core/src/color.rs index 1392f28b..13077628 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -1,7 +1,7 @@  #[cfg(feature = "palette")]  use palette::rgb::{Srgb, Srgba}; -/// A color in the sRGB color space. +/// A color in the `sRGB` color space.  #[derive(Debug, Clone, Copy, PartialEq, Default)]  pub struct Color {      /// Red component, 0.0 - 1.0 @@ -89,6 +89,26 @@ impl Color {          }      } +    /// Creates a [`Color`] from its linear RGBA components. +    pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { +        // As described in: +        // https://en.wikipedia.org/wiki/SRGB +        fn gamma_component(u: f32) -> f32 { +            if u < 0.0031308 { +                12.92 * u +            } else { +                1.055 * u.powf(1.0 / 2.4) - 0.055 +            } +        } + +        Self { +            r: gamma_component(r), +            g: gamma_component(g), +            b: gamma_component(b), +            a, +        } +    } +      /// Converts the [`Color`] into its RGBA8 equivalent.      #[must_use]      pub fn into_rgba8(self) -> [u8; 4] { diff --git a/core/src/element.rs b/core/src/element.rs index 3268f14b..dea111af 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -5,7 +5,9 @@ use crate::overlay;  use crate::renderer;  use crate::widget;  use crate::widget::tree::{self, Tree}; -use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget}; +use crate::{ +    Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget, +};  use std::any::Any;  use std::borrow::Borrow; @@ -291,7 +293,7 @@ where      }      fn diff(&self, tree: &mut Tree) { -        self.widget.diff(tree) +        self.widget.diff(tree);      }      fn width(&self) -> Length { @@ -304,10 +306,11 @@ where      fn layout(          &self, +        tree: &mut Tree,          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        self.widget.layout(renderer, limits) +        self.widget.layout(tree, renderer, limits)      }      fn operate( @@ -325,11 +328,12 @@ where              fn container(                  &mut self,                  id: Option<&widget::Id>, +                bounds: Rectangle,                  operate_on_children: &mut dyn FnMut(                      &mut dyn widget::Operation<T>,                  ),              ) { -                self.operation.container(id, &mut |operation| { +                self.operation.container(id, bounds, &mut |operation| {                      operate_on_children(&mut MapOperation { operation });                  });              } @@ -346,8 +350,10 @@ where                  &mut self,                  state: &mut dyn widget::operation::Scrollable,                  id: Option<&widget::Id>, +                bounds: Rectangle, +                translation: Vector,              ) { -                self.operation.scrollable(state, id); +                self.operation.scrollable(state, id, bounds, translation);              }              fn text_input( @@ -380,6 +386,7 @@ where          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, B>, +        viewport: &Rectangle,      ) -> event::Status {          let mut local_messages = Vec::new();          let mut local_shell = Shell::new(&mut local_messages); @@ -392,6 +399,7 @@ where              renderer,              clipboard,              &mut local_shell, +            viewport,          );          shell.merge(local_shell, &self.mapper); @@ -410,7 +418,7 @@ where          viewport: &Rectangle,      ) {          self.widget -            .draw(tree, renderer, theme, style, layout, cursor, viewport) +            .draw(tree, renderer, theme, style, layout, cursor, viewport);      }      fn mouse_interaction( @@ -484,10 +492,11 @@ where      fn layout(          &self, +        tree: &mut Tree,          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        self.element.widget.layout(renderer, limits) +        self.element.widget.layout(tree, renderer, limits)      }      fn operate( @@ -499,7 +508,7 @@ where      ) {          self.element              .widget -            .operate(state, layout, renderer, operation) +            .operate(state, layout, renderer, operation);      }      fn on_event( @@ -511,10 +520,11 @@ where          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>, +        viewport: &Rectangle,      ) -> event::Status { -        self.element -            .widget -            .on_event(state, event, layout, cursor, renderer, clipboard, shell) +        self.element.widget.on_event( +            state, event, layout, cursor, renderer, clipboard, shell, viewport, +        )      }      fn draw( diff --git a/core/src/font.rs b/core/src/font.rs index bb425fd6..2b68decf 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -10,8 +10,8 @@ pub struct Font {      pub weight: Weight,      /// The [`Stretch`] of the [`Font`].      pub stretch: Stretch, -    /// Whether if the [`Font`] is monospaced or not. -    pub monospaced: bool, +    /// The [`Style`] of the [`Font`]. +    pub style: Style,  }  impl Font { @@ -20,13 +20,12 @@ impl Font {          family: Family::SansSerif,          weight: Weight::Normal,          stretch: Stretch::Normal, -        monospaced: false, +        style: Style::Normal,      };      /// A monospaced font with normal [`Weight`].      pub const MONOSPACE: Font = Font {          family: Family::Monospace, -        monospaced: true,          ..Self::DEFAULT      }; @@ -100,3 +99,13 @@ pub enum Stretch {      ExtraExpanded,      UltraExpanded,  } + +/// The style of some text. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Style { +    #[default] +    Normal, +    Italic, +    Oblique, +} diff --git a/core/src/gradient.rs b/core/src/gradient.rs index e19622fb..4711b044 100644 --- a/core/src/gradient.rs +++ b/core/src/gradient.rs @@ -6,10 +6,8 @@ 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`]. +    /// A linear gradient interpolates colors along a direction at a specific angle.      Linear(Linear),  } @@ -96,8 +94,8 @@ impl Linear {          mut self,          stops: impl IntoIterator<Item = ColorStop>,      ) -> Self { -        for stop in stops.into_iter() { -            self = self.add_stop(stop.offset, stop.color) +        for stop in stops { +            self = self.add_stop(stop.offset, stop.color);          }          self diff --git a/core/src/hasher.rs b/core/src/hasher.rs index fa52f16d..a13d78af 100644 --- a/core/src/hasher.rs +++ b/core/src/hasher.rs @@ -1,10 +1,11 @@  /// The hasher used to compare layouts. -#[derive(Debug, Default)] -pub struct Hasher(twox_hash::XxHash64); +#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways. +#[derive(Default)] +pub struct Hasher(xxhash_rust::xxh3::Xxh3);  impl core::hash::Hasher for Hasher {      fn write(&mut self, bytes: &[u8]) { -        self.0.write(bytes) +        self.0.write(bytes);      }      fn finish(&self) -> u64 { diff --git a/core/src/image.rs b/core/src/image.rs index 85d9d475..e9675316 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -164,6 +164,16 @@ impl std::fmt::Debug for Data {      }  } +/// Image filtering strategy. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum FilterMethod { +    /// Bilinear interpolation. +    #[default] +    Linear, +    /// Nearest neighbor. +    Nearest, +} +  /// A [`Renderer`] that can render raster graphics.  ///  /// [renderer]: crate::renderer @@ -178,5 +188,10 @@ pub trait Renderer: crate::Renderer {      /// Draws an image with the given [`Handle`] and inside the provided      /// `bounds`. -    fn draw(&mut self, handle: Self::Handle, bounds: Rectangle); +    fn draw( +        &mut self, +        handle: Self::Handle, +        filter_method: FilterMethod, +        bounds: Rectangle, +    );  } diff --git a/core/src/layout.rs b/core/src/layout.rs index 04954fb9..caf315b6 100644 --- a/core/src/layout.rs +++ b/core/src/layout.rs @@ -7,7 +7,7 @@ pub mod flex;  pub use limits::Limits;  pub use node::Node; -use crate::{Point, Rectangle, Vector}; +use crate::{Point, Rectangle, Size, Vector};  /// The bounds of a [`Node`] and its children, using absolute coordinates.  #[derive(Debug, Clone, Copy)] @@ -63,3 +63,36 @@ impl<'a> Layout<'a> {          })      }  } + +/// Produces a [`Node`] with two children nodes one right next to each other. +pub fn next_to_each_other( +    limits: &Limits, +    spacing: f32, +    left: impl FnOnce(&Limits) -> Node, +    right: impl FnOnce(&Limits) -> Node, +) -> Node { +    let mut left_node = left(limits); +    let left_size = left_node.size(); + +    let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0)); + +    let mut right_node = right(&right_limits); +    let right_size = right_node.size(); + +    let (left_y, right_y) = if left_size.height > right_size.height { +        (0.0, (left_size.height - right_size.height) / 2.0) +    } else { +        ((right_size.height - left_size.height) / 2.0, 0.0) +    }; + +    left_node.move_to(Point::new(0.0, left_y)); +    right_node.move_to(Point::new(left_size.width + spacing, right_y)); + +    Node::with_children( +        Size::new( +            left_size.width + spacing + right_size.width, +            left_size.height.max(right_size.height), +        ), +        vec![left_node, right_node], +    ) +} diff --git a/core/src/layout/flex.rs b/core/src/layout/flex.rs index 8b967849..c02b63d8 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -19,6 +19,7 @@  use crate::Element;  use crate::layout::{Limits, Node}; +use crate::widget;  use crate::{Alignment, Padding, Point, Size};  /// The main axis of a flex layout. @@ -66,6 +67,7 @@ pub fn resolve<Message, Renderer>(      spacing: f32,      align_items: Alignment,      items: &[Element<'_, Message, Renderer>], +    trees: &mut [widget::Tree],  ) -> Node  where      Renderer: crate::Renderer, @@ -81,7 +83,7 @@ where      let mut nodes: Vec<Node> = Vec::with_capacity(items.len());      nodes.resize(items.len(), Node::default()); -    for (i, child) in items.iter().enumerate() { +    for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {          let fill_factor = match axis {              Axis::Horizontal => child.as_widget().width(),              Axis::Vertical => child.as_widget().height(), @@ -94,7 +96,8 @@ where              let child_limits =                  Limits::new(Size::ZERO, Size::new(max_width, max_height)); -            let layout = child.as_widget().layout(renderer, &child_limits); +            let layout = +                child.as_widget().layout(tree, renderer, &child_limits);              let size = layout.size();              available -= axis.main(size); @@ -108,7 +111,7 @@ where      let remaining = available.max(0.0); -    for (i, child) in items.iter().enumerate() { +    for (i, (child, tree)) in items.iter().zip(trees).enumerate() {          let fill_factor = match axis {              Axis::Horizontal => child.as_widget().width(),              Axis::Vertical => child.as_widget().height(), @@ -133,7 +136,8 @@ where                  Size::new(max_width, max_height),              ); -            let layout = child.as_widget().layout(renderer, &child_limits); +            let layout = +                child.as_widget().layout(tree, renderer, &child_limits);              cross = cross.max(axis.cross(layout.size()));              nodes[i] = layout; diff --git a/core/src/layout/limits.rs b/core/src/layout/limits.rs index 5d3c1556..39a3d98b 100644 --- a/core/src/layout/limits.rs +++ b/core/src/layout/limits.rs @@ -2,7 +2,7 @@  use crate::{Length, Padding, Size};  /// A set of size constraints for layouting. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)]  pub struct Limits {      min: Size,      max: Size, diff --git a/core/src/lib.rs b/core/src/lib.rs index 76d775e7..54ea5839 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,29 +1,21 @@  //! The core library of [Iced].  //!  //! This library holds basic types that can be reused and re-exported in -//! different runtime implementations. For instance, both [`iced_native`] and -//! [`iced_web`] are built on top of `iced_core`. +//! different runtime implementations.  //!  //!   //!  //! [Iced]: https://github.com/iced-rs/iced -//! [`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"  )] +#![forbid(unsafe_code, rust_2018_idioms)]  #![deny(      missing_debug_implementations,      missing_docs,      unused_results, -    clippy::extra_unused_lifetimes, -    clippy::from_over_into, -    clippy::needless_borrow, -    clippy::new_without_default, -    clippy::useless_conversion +    rustdoc::broken_intra_doc_links  )] -#![forbid(unsafe_code, rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)]  pub mod alignment;  pub mod clipboard;  pub mod event; diff --git a/core/src/mouse/click.rs b/core/src/mouse/click.rs index 4a7d796c..6f3844be 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -24,7 +24,7 @@ pub enum Kind {  }  impl Kind { -    fn next(&self) -> Kind { +    fn next(self) -> Kind {          match self {              Kind::Single => Kind::Double,              Kind::Double => Kind::Triple, @@ -61,6 +61,11 @@ impl Click {          self.kind      } +    /// Returns the position of the [`Click`]. +    pub fn position(&self) -> Point { +        self.position +    } +      fn is_consecutive(&self, new_position: Point, time: Instant) -> bool {          let duration = if time > self.time {              Some(time - self.time) diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 2e05db93..af10afee 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -11,7 +11,7 @@ use crate::mouse;  use crate::renderer;  use crate::widget;  use crate::widget::Tree; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; +use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};  /// An interactive component that can be displayed on top of other widgets.  pub trait Overlay<Message, Renderer> @@ -25,10 +25,11 @@ where      ///      /// [`Node`]: layout::Node      fn layout( -        &self, +        &mut self,          renderer: &Renderer,          bounds: Size,          position: Point, +        translation: Vector,      ) -> layout::Node;      /// Draws the [`Overlay`] using the associated `Renderer`. diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index c2134343..a279fe28 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -13,6 +13,7 @@ use std::any::Any;  #[allow(missing_debug_implementations)]  pub struct Element<'a, Message, Renderer> {      position: Point, +    translation: Vector,      overlay: Box<dyn Overlay<Message, Renderer> + 'a>,  } @@ -25,7 +26,11 @@ where          position: Point,          overlay: Box<dyn Overlay<Message, Renderer> + 'a>,      ) -> Self { -        Self { position, overlay } +        Self { +            position, +            overlay, +            translation: Vector::ZERO, +        }      }      /// Returns the position of the [`Element`]. @@ -36,6 +41,7 @@ where      /// Translates the [`Element`].      pub fn translate(mut self, translation: Vector) -> Self {          self.position = self.position + translation; +        self.translation = self.translation + translation;          self      } @@ -48,19 +54,24 @@ where      {          Element {              position: self.position, +            translation: self.translation,              overlay: Box::new(Map::new(self.overlay, f)),          }      }      /// Computes the layout of the [`Element`] in the given bounds.      pub fn layout( -        &self, +        &mut self,          renderer: &Renderer,          bounds: Size,          translation: Vector,      ) -> layout::Node { -        self.overlay -            .layout(renderer, bounds, self.position + translation) +        self.overlay.layout( +            renderer, +            bounds, +            self.position + translation, +            self.translation + translation, +        )      }      /// Processes a runtime [`Event`]. @@ -98,7 +109,7 @@ where          layout: Layout<'_>,          cursor: mouse::Cursor,      ) { -        self.overlay.draw(renderer, theme, style, layout, cursor) +        self.overlay.draw(renderer, theme, style, layout, cursor);      }      /// Applies a [`widget::Operation`] to the [`Element`]. @@ -150,12 +161,13 @@ where      Renderer: crate::Renderer,  {      fn layout( -        &self, +        &mut self,          renderer: &Renderer,          bounds: Size,          position: Point, +        translation: Vector,      ) -> layout::Node { -        self.content.layout(renderer, bounds, position) +        self.content.layout(renderer, bounds, position, translation)      }      fn operate( @@ -172,11 +184,12 @@ where              fn container(                  &mut self,                  id: Option<&widget::Id>, +                bounds: Rectangle,                  operate_on_children: &mut dyn FnMut(                      &mut dyn widget::Operation<T>,                  ),              ) { -                self.operation.container(id, &mut |operation| { +                self.operation.container(id, bounds, &mut |operation| {                      operate_on_children(&mut MapOperation { operation });                  });              } @@ -193,8 +206,10 @@ where                  &mut self,                  state: &mut dyn widget::operation::Scrollable,                  id: Option<&widget::Id>, +                bounds: Rectangle, +                translation: Vector,              ) { -                self.operation.scrollable(state, id); +                self.operation.scrollable(state, id, bounds, translation);              }              fn text_input( @@ -202,7 +217,7 @@ where                  state: &mut dyn widget::operation::TextInput,                  id: Option<&widget::Id>,              ) { -                self.operation.text_input(state, id) +                self.operation.text_input(state, id);              }              fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) { @@ -259,7 +274,7 @@ where          layout: Layout<'_>,          cursor: mouse::Cursor,      ) { -        self.content.draw(renderer, theme, style, layout, cursor) +        self.content.draw(renderer, theme, style, layout, cursor);      }      fn is_over( diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index deffaad0..e1e9727a 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -4,7 +4,9 @@ use crate::mouse;  use crate::overlay;  use crate::renderer;  use crate::widget; -use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; +use crate::{ +    Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, Vector, +};  /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]  /// children. @@ -61,17 +63,16 @@ where      Renderer: crate::Renderer,  {      fn layout( -        &self, +        &mut self,          renderer: &Renderer,          bounds: Size, -        position: Point, +        _position: Point, +        translation: Vector,      ) -> layout::Node { -        let translation = position - Point::ORIGIN; -          layout::Node::with_children(              bounds,              self.children -                .iter() +                .iter_mut()                  .map(|child| child.layout(renderer, bounds, translation))                  .collect(),          ) @@ -138,12 +139,12 @@ where          renderer: &Renderer,          operation: &mut dyn widget::Operation<Message>,      ) { -        operation.container(None, &mut |operation| { +        operation.container(None, layout.bounds(), &mut |operation| {              self.children.iter_mut().zip(layout.children()).for_each(                  |(child, layout)| {                      child.operate(layout, renderer, operation);                  }, -            ) +            );          });      } diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 7ff324cb..c1c2eeac 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -74,9 +74,9 @@ impl Rectangle<f32> {      /// Returns true if the given [`Point`] is contained in the [`Rectangle`].      pub fn contains(&self, point: Point) -> bool {          self.x <= point.x -            && point.x <= self.x + self.width +            && point.x < self.x + self.width              && self.y <= point.y -            && point.y <= self.y + self.height +            && point.y < self.y + self.height      }      /// Returns true if the current [`Rectangle`] is completely within the given @@ -197,3 +197,18 @@ where          }      }  } + +impl<T> std::ops::Sub<Vector<T>> for Rectangle<T> +where +    T: std::ops::Sub<Output = T>, +{ +    type Output = Rectangle<T>; + +    fn sub(self, translation: Vector<T>) -> Self { +        Rectangle { +            x: self.x - translation.x, +            y: self.y - translation.y, +            ..self +        } +    } +} diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 7c73d2e4..1b327e56 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -5,26 +5,13 @@ mod null;  #[cfg(debug_assertions)]  pub use null::Null; -use crate::layout; -use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector}; +use crate::{Background, BorderRadius, Color, Rectangle, Vector};  /// A component that can be used by widgets to draw themselves on a screen.  pub trait Renderer: Sized {      /// The supported theme of the [`Renderer`].      type Theme; -    /// Lays out the elements of a user interface. -    /// -    /// You should override this if you need to perform any operations before or -    /// after layouting. For instance, trimming the measurements cache. -    fn layout<Message>( -        &mut self, -        element: &Element<'_, Message, Self>, -        limits: &layout::Limits, -    ) -> layout::Node { -        element.as_widget().layout(self, limits) -    } -      /// Draws the primitives recorded in the given closure in a new layer.      ///      /// The layer will clip its contents to the provided `bounds`. diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 5d49699e..da0f32de 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -1,6 +1,7 @@ +use crate::alignment;  use crate::renderer::{self, Renderer};  use crate::text::{self, Text}; -use crate::{Background, Font, Point, Rectangle, Size, Vector}; +use crate::{Background, Color, Font, Pixels, Point, Rectangle, Size, Vector};  use std::borrow::Cow; @@ -41,6 +42,8 @@ impl Renderer for Null {  impl text::Renderer for Null {      type Font = Font; +    type Paragraph = (); +    type Editor = ();      const ICON_FONT: Font = Font::DEFAULT;      const CHECKMARK_ICON: char = '0'; @@ -50,37 +53,117 @@ impl text::Renderer for Null {          Font::default()      } -    fn default_size(&self) -> f32 { -        16.0 +    fn default_size(&self) -> Pixels { +        Pixels(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, -        _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> { +    fn fill_paragraph( +        &mut self, +        _paragraph: &Self::Paragraph, +        _position: Point, +        _color: Color, +    ) { +    } + +    fn fill_editor( +        &mut self, +        _editor: &Self::Editor, +        _position: Point, +        _color: Color, +    ) { +    } + +    fn fill_text( +        &mut self, +        _paragraph: Text<'_, Self::Font>, +        _position: Point, +        _color: Color, +    ) { +    } +} + +impl text::Paragraph for () { +    type Font = Font; + +    fn with_text(_text: Text<'_, Self::Font>) -> Self {} + +    fn resize(&mut self, _new_bounds: Size) {} + +    fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference { +        text::Difference::None +    } + +    fn horizontal_alignment(&self) -> alignment::Horizontal { +        alignment::Horizontal::Left +    } + +    fn vertical_alignment(&self) -> alignment::Vertical { +        alignment::Vertical::Top +    } + +    fn grapheme_position(&self, _line: usize, _index: usize) -> Option<Point> { +        None +    } + +    fn min_bounds(&self) -> Size { +        Size::ZERO +    } + +    fn hit_test(&self, _point: Point) -> Option<text::Hit> { +        None +    } +} + +impl text::Editor for () { +    type Font = Font; + +    fn with_text(_text: &str) -> Self {} + +    fn cursor(&self) -> text::editor::Cursor { +        text::editor::Cursor::Caret(Point::ORIGIN) +    } + +    fn cursor_position(&self) -> (usize, usize) { +        (0, 0) +    } + +    fn selection(&self) -> Option<String> { +        None +    } + +    fn line(&self, _index: usize) -> Option<&str> {          None      } -    fn fill_text(&mut self, _text: Text<'_, Self::Font>) {} +    fn line_count(&self) -> usize { +        0 +    } + +    fn perform(&mut self, _action: text::editor::Action) {} + +    fn bounds(&self) -> Size { +        Size::ZERO +    } + +    fn update( +        &mut self, +        _new_bounds: Size, +        _new_font: Self::Font, +        _new_size: Pixels, +        _new_line_height: text::LineHeight, +        _new_highlighter: &mut impl text::Highlighter, +    ) { +    } + +    fn highlight<H: text::Highlighter>( +        &mut self, +        _font: Self::Font, +        _highlighter: &mut H, +        _format_highlight: impl Fn( +            &H::Highlight, +        ) -> text::highlighter::Format<Self::Font>, +    ) { +    }  } diff --git a/core/src/shell.rs b/core/src/shell.rs index 74a5c616..2952ceff 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -35,7 +35,7 @@ impl<'a, Message> Shell<'a, Message> {          self.messages.push(message);      } -    /// Requests a new frame to be drawn at the given [`Instant`]. +    /// Requests a new frame to be drawn.      pub fn request_redraw(&mut self, request: window::RedrawRequest) {          match self.redraw_request {              None => { @@ -48,7 +48,7 @@ impl<'a, Message> Shell<'a, Message> {          }      } -    /// Returns the requested [`Instant`] a redraw should happen, if any. +    /// Returns the request a redraw should happen, if any.      pub fn redraw_request(&self) -> Option<window::RedrawRequest> {          self.redraw_request      } @@ -71,7 +71,7 @@ impl<'a, Message> Shell<'a, Message> {          if self.is_layout_invalid {              self.is_layout_invalid = false; -            f() +            f();          }      } diff --git a/core/src/text.rs b/core/src/text.rs index fc8aa20e..546d0b5c 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -1,6 +1,15 @@  //! Draw and interact with text. +mod paragraph; + +pub mod editor; +pub mod highlighter; + +pub use editor::Editor; +pub use highlighter::Highlighter; +pub use paragraph::Paragraph; +  use crate::alignment; -use crate::{Color, Pixels, Point, Rectangle, Size}; +use crate::{Color, Pixels, Point, Size};  use std::borrow::Cow;  use std::hash::{Hash, Hasher}; @@ -12,17 +21,14 @@ pub struct Text<'a, Font> {      pub content: &'a str,      /// The bounds of the paragraph. -    pub bounds: Rectangle, +    pub bounds: Size,      /// The size of the [`Text`] in logical pixels. -    pub size: f32, +    pub size: Pixels,      /// 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, @@ -129,10 +135,43 @@ impl Hit {      }  } +/// The difference detected in some text. +/// +/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some +/// [`Text`]. +/// +/// [`compare`]: Paragraph::compare +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Difference { +    /// No difference. +    /// +    /// The text can be reused as it is! +    None, + +    /// A bounds difference. +    /// +    /// This normally means a relayout is necessary, but the shape of the text can +    /// be reused. +    Bounds, + +    /// A shape difference. +    /// +    /// The contents, alignment, sizes, fonts, or any other essential attributes +    /// of the shape of the text have changed. A complete reshape and relayout of +    /// the text is necessary. +    Shape, +} +  /// A renderer capable of measuring and drawing [`Text`].  pub trait Renderer: crate::Renderer {      /// The font type used. -    type Font: Copy; +    type Font: Copy + PartialEq; + +    /// The [`Paragraph`] of this [`Renderer`]. +    type Paragraph: Paragraph<Font = Self::Font> + 'static; + +    /// The [`Editor`] of this [`Renderer`]. +    type Editor: Editor<Font = Self::Font> + 'static;      /// The icon font of the backend.      const ICON_FONT: Self::Font; @@ -151,62 +190,35 @@ pub trait Renderer: crate::Renderer {      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>; +    fn default_size(&self) -> Pixels;      /// 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>); +    /// Draws the given [`Paragraph`] at the given position and with the given +    /// [`Color`]. +    fn fill_paragraph( +        &mut self, +        text: &Self::Paragraph, +        position: Point, +        color: Color, +    ); + +    /// Draws the given [`Editor`] at the given position and with the given +    /// [`Color`]. +    fn fill_editor( +        &mut self, +        editor: &Self::Editor, +        position: Point, +        color: Color, +    ); + +    /// Draws the given [`Text`] at the given position and with the given +    /// [`Color`]. +    fn fill_text( +        &mut self, +        text: Text<'_, Self::Font>, +        position: Point, +        color: Color, +    );  } diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs new file mode 100644 index 00000000..f3c6e342 --- /dev/null +++ b/core/src/text/editor.rs @@ -0,0 +1,181 @@ +//! Edit text. +use crate::text::highlighter::{self, Highlighter}; +use crate::text::LineHeight; +use crate::{Pixels, Point, Rectangle, Size}; + +use std::sync::Arc; + +/// A component that can be used by widgets to edit multi-line text. +pub trait Editor: Sized + Default { +    /// The font of the [`Editor`]. +    type Font: Copy + PartialEq + Default; + +    /// Creates a new [`Editor`] laid out with the given text. +    fn with_text(text: &str) -> Self; + +    /// Returns the current [`Cursor`] of the [`Editor`]. +    fn cursor(&self) -> Cursor; + +    /// Returns the current cursor position of the [`Editor`]. +    /// +    /// Line and column, respectively. +    fn cursor_position(&self) -> (usize, usize); + +    /// Returns the current selected text of the [`Editor`]. +    fn selection(&self) -> Option<String>; + +    /// Returns the text of the given line in the [`Editor`], if it exists. +    fn line(&self, index: usize) -> Option<&str>; + +    /// Returns the amount of lines in the [`Editor`]. +    fn line_count(&self) -> usize; + +    /// Performs an [`Action`] on the [`Editor`]. +    fn perform(&mut self, action: Action); + +    /// Returns the current boundaries of the [`Editor`]. +    fn bounds(&self) -> Size; + +    /// Updates the [`Editor`] with some new attributes. +    fn update( +        &mut self, +        new_bounds: Size, +        new_font: Self::Font, +        new_size: Pixels, +        new_line_height: LineHeight, +        new_highlighter: &mut impl Highlighter, +    ); + +    /// Runs a text [`Highlighter`] in the [`Editor`]. +    fn highlight<H: Highlighter>( +        &mut self, +        font: Self::Font, +        highlighter: &mut H, +        format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>, +    ); +} + +/// An interaction with an [`Editor`]. +#[derive(Debug, Clone, PartialEq)] +pub enum Action { +    /// Apply a [`Motion`]. +    Move(Motion), +    /// Select text with a given [`Motion`]. +    Select(Motion), +    /// Select the word at the current cursor. +    SelectWord, +    /// Select the line at the current cursor. +    SelectLine, +    /// Perform an [`Edit`]. +    Edit(Edit), +    /// Click the [`Editor`] at the given [`Point`]. +    Click(Point), +    /// Drag the mouse on the [`Editor`] to the given [`Point`]. +    Drag(Point), +    /// Scroll the [`Editor`] a certain amount of lines. +    Scroll { +        /// The amount of lines to scroll. +        lines: i32, +    }, +} + +impl Action { +    /// Returns whether the [`Action`] is an editing action. +    pub fn is_edit(&self) -> bool { +        matches!(self, Self::Edit(_)) +    } +} + +/// An action that edits text. +#[derive(Debug, Clone, PartialEq)] +pub enum Edit { +    /// Insert the given character. +    Insert(char), +    /// Paste the given text. +    Paste(Arc<String>), +    /// Break the current line. +    Enter, +    /// Delete the previous character. +    Backspace, +    /// Delete the next character. +    Delete, +} + +/// A cursor movement. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Motion { +    /// Move left. +    Left, +    /// Move right. +    Right, +    /// Move up. +    Up, +    /// Move down. +    Down, +    /// Move to the left boundary of a word. +    WordLeft, +    /// Move to the right boundary of a word. +    WordRight, +    /// Move to the start of the line. +    Home, +    /// Move to the end of the line. +    End, +    /// Move to the start of the previous window. +    PageUp, +    /// Move to the start of the next window. +    PageDown, +    /// Move to the start of the text. +    DocumentStart, +    /// Move to the end of the text. +    DocumentEnd, +} + +impl Motion { +    /// Widens the [`Motion`], if possible. +    pub fn widen(self) -> Self { +        match self { +            Self::Left => Self::WordLeft, +            Self::Right => Self::WordRight, +            Self::Home => Self::DocumentStart, +            Self::End => Self::DocumentEnd, +            _ => self, +        } +    } + +    /// Returns the [`Direction`] of the [`Motion`]. +    pub fn direction(&self) -> Direction { +        match self { +            Self::Left +            | Self::Up +            | Self::WordLeft +            | Self::Home +            | Self::PageUp +            | Self::DocumentStart => Direction::Left, +            Self::Right +            | Self::Down +            | Self::WordRight +            | Self::End +            | Self::PageDown +            | Self::DocumentEnd => Direction::Right, +        } +    } +} + +/// A direction in some text. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { +    /// <- +    Left, +    /// -> +    Right, +} + +/// The cursor of an [`Editor`]. +#[derive(Debug, Clone)] +pub enum Cursor { +    /// Cursor without a selection +    Caret(Point), + +    /// Cursor selecting a range of text +    Selection(Vec<Rectangle>), +} diff --git a/core/src/text/highlighter.rs b/core/src/text/highlighter.rs new file mode 100644 index 00000000..a0535228 --- /dev/null +++ b/core/src/text/highlighter.rs @@ -0,0 +1,88 @@ +//! Highlight text. +use crate::Color; + +use std::ops::Range; + +/// A type capable of highlighting text. +/// +/// A [`Highlighter`] highlights lines in sequence. When a line changes, +/// it must be notified and the lines after the changed one must be fed +/// again to the [`Highlighter`]. +pub trait Highlighter: 'static { +    /// The settings to configure the [`Highlighter`]. +    type Settings: PartialEq + Clone; + +    /// The output of the [`Highlighter`]. +    type Highlight; + +    /// The highlight iterator type. +    type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)> +    where +        Self: 'a; + +    /// Creates a new [`Highlighter`] from its [`Self::Settings`]. +    fn new(settings: &Self::Settings) -> Self; + +    /// Updates the [`Highlighter`] with some new [`Self::Settings`]. +    fn update(&mut self, new_settings: &Self::Settings); + +    /// Notifies the [`Highlighter`] that the line at the given index has changed. +    fn change_line(&mut self, line: usize); + +    /// Highlights the given line. +    /// +    /// If a line changed prior to this, the first line provided here will be the +    /// line that changed. +    fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>; + +    /// Returns the current line of the [`Highlighter`]. +    /// +    /// If `change_line` has been called, this will normally be the least index +    /// that changed. +    fn current_line(&self) -> usize; +} + +/// A highlighter that highlights nothing. +#[derive(Debug, Clone, Copy)] +pub struct PlainText; + +impl Highlighter for PlainText { +    type Settings = (); +    type Highlight = (); + +    type Iterator<'a> = std::iter::Empty<(Range<usize>, Self::Highlight)>; + +    fn new(_settings: &Self::Settings) -> Self { +        Self +    } + +    fn update(&mut self, _new_settings: &Self::Settings) {} + +    fn change_line(&mut self, _line: usize) {} + +    fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> { +        std::iter::empty() +    } + +    fn current_line(&self) -> usize { +        usize::MAX +    } +} + +/// The format of some text. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Format<Font> { +    /// The [`Color`] of the text. +    pub color: Option<Color>, +    /// The `Font` of the text. +    pub font: Option<Font>, +} + +impl<Font> Default for Format<Font> { +    fn default() -> Self { +        Self { +            color: None, +            font: None, +        } +    } +} diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs new file mode 100644 index 00000000..de1fb74d --- /dev/null +++ b/core/src/text/paragraph.rs @@ -0,0 +1,59 @@ +use crate::alignment; +use crate::text::{Difference, Hit, Text}; +use crate::{Point, Size}; + +/// A text paragraph. +pub trait Paragraph: Sized + Default { +    /// The font of this [`Paragraph`]. +    type Font: Copy + PartialEq; + +    /// Creates a new [`Paragraph`] laid out with the given [`Text`]. +    fn with_text(text: Text<'_, Self::Font>) -> Self; + +    /// Lays out the [`Paragraph`] with some new boundaries. +    fn resize(&mut self, new_bounds: Size); + +    /// Compares the [`Paragraph`] with some desired [`Text`] and returns the +    /// [`Difference`]. +    fn compare(&self, text: Text<'_, Self::Font>) -> Difference; + +    /// Returns the horizontal alignment of the [`Paragraph`]. +    fn horizontal_alignment(&self) -> alignment::Horizontal; + +    /// Returns the vertical alignment of the [`Paragraph`]. +    fn vertical_alignment(&self) -> alignment::Vertical; + +    /// Returns the minimum boundaries that can fit the contents of the +    /// [`Paragraph`]. +    fn min_bounds(&self) -> Size; + +    /// Tests whether the provided point is within the boundaries of the +    /// [`Paragraph`], returning information about the nearest character. +    fn hit_test(&self, point: Point) -> Option<Hit>; + +    /// Returns the distance to the given grapheme index in the [`Paragraph`]. +    fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>; + +    /// Updates the [`Paragraph`] to match the given [`Text`], if needed. +    fn update(&mut self, text: Text<'_, Self::Font>) { +        match self.compare(text) { +            Difference::None => {} +            Difference::Bounds => { +                self.resize(text.bounds); +            } +            Difference::Shape => { +                *self = Self::with_text(text); +            } +        } +    } + +    /// Returns the minimum width that can fit the contents of the [`Paragraph`]. +    fn min_width(&self) -> f32 { +        self.min_bounds().width +    } + +    /// Returns the minimum height that can fit the contents of the [`Paragraph`]. +    fn min_height(&self) -> f32 { +        self.min_bounds().height +    } +} diff --git a/core/src/widget.rs b/core/src/widget.rs index 79d86444..294d5984 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -33,12 +33,12 @@ use crate::{Clipboard, Length, 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.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 +/// [examples]: https://github.com/iced-rs/iced/tree/0.10/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.10/examples/bezier_tool +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.10/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.10/examples/geometry  /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.10/wgpu  pub trait Widget<Message, Renderer>  where      Renderer: crate::Renderer, @@ -55,6 +55,7 @@ where      /// user interface.      fn layout(          &self, +        tree: &mut Tree,          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node; @@ -62,7 +63,7 @@ where      /// Draws the [`Widget`] using the associated `Renderer`.      fn draw(          &self, -        state: &Tree, +        tree: &Tree,          renderer: &mut Renderer,          theme: &Renderer::Theme,          style: &renderer::Style, @@ -115,6 +116,7 @@ where          _renderer: &Renderer,          _clipboard: &mut dyn Clipboard,          _shell: &mut Shell<'_, Message>, +        _viewport: &Rectangle,      ) -> event::Status {          event::Status::Ignored      } diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index ad188c36..b91cf9ac 100644 --- a/core/src/widget/operation.rs +++ b/core/src/widget/operation.rs @@ -8,6 +8,7 @@ pub use scrollable::Scrollable;  pub use text_input::TextInput;  use crate::widget::Id; +use crate::{Rectangle, Vector};  use std::any::Any;  use std::fmt; @@ -23,6 +24,7 @@ pub trait Operation<T> {      fn container(          &mut self,          id: Option<&Id>, +        bounds: Rectangle,          operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),      ); @@ -30,7 +32,14 @@ pub trait Operation<T> {      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>) {} +    fn scrollable( +        &mut self, +        _state: &mut dyn Scrollable, +        _id: Option<&Id>, +        _bounds: Rectangle, +        _translation: Vector, +    ) { +    }      /// Operates on a widget that has text input.      fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {} @@ -92,6 +101,7 @@ where          fn container(              &mut self,              id: Option<&Id>, +            bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),          ) {              struct MapRef<'a, A> { @@ -102,11 +112,12 @@ where                  fn container(                      &mut self,                      id: Option<&Id>, +                    bounds: Rectangle,                      operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),                  ) {                      let Self { operation, .. } = self; -                    operation.container(id, &mut |operation| { +                    operation.container(id, bounds, &mut |operation| {                          operate_on_children(&mut MapRef { operation });                      });                  } @@ -115,8 +126,10 @@ where                      &mut self,                      state: &mut dyn Scrollable,                      id: Option<&Id>, +                    bounds: Rectangle, +                    translation: Vector,                  ) { -                    self.operation.scrollable(state, id); +                    self.operation.scrollable(state, id, bounds, translation);                  }                  fn focusable( @@ -145,15 +158,21 @@ where              MapRef {                  operation: operation.as_mut(),              } -            .container(id, operate_on_children); +            .container(id, bounds, 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 scrollable( +            &mut self, +            state: &mut dyn Scrollable, +            id: Option<&Id>, +            bounds: Rectangle, +            translation: Vector, +        ) { +            self.operation.scrollable(state, id, bounds, translation);          }          fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { @@ -197,6 +216,7 @@ pub fn scope<T: 'static>(          fn container(              &mut self,              id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),          ) {              if id == Some(&self.target) { diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs index 312e4894..68c22faa 100644 --- a/core/src/widget/operation/focusable.rs +++ b/core/src/widget/operation/focusable.rs @@ -1,6 +1,7 @@  //! Operate on widgets that can be focused.  use crate::widget::operation::{Operation, Outcome};  use crate::widget::Id; +use crate::Rectangle;  /// The internal state of a widget that can be focused.  pub trait Focusable { @@ -45,9 +46,10 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }      } @@ -80,9 +82,10 @@ where          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }          fn finish(&self) -> Outcome<T> { @@ -126,9 +129,10 @@ pub fn focus_previous<T>() -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }      } @@ -159,9 +163,10 @@ pub fn focus_next<T>() -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }      } @@ -185,9 +190,10 @@ pub fn find_focused() -> impl Operation<Id> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }          fn finish(&self) -> Outcome<Id> { diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs index f947344d..12161255 100644 --- a/core/src/widget/operation/scrollable.rs +++ b/core/src/widget/operation/scrollable.rs @@ -1,5 +1,6 @@  //! Operate on widgets that can be scrolled.  use crate::widget::{Id, Operation}; +use crate::{Rectangle, Vector};  /// The internal state of a widget that can be scrolled.  pub trait Scrollable { @@ -22,12 +23,19 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          } -        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { +        fn scrollable( +            &mut self, +            state: &mut dyn Scrollable, +            id: Option<&Id>, +            _bounds: Rectangle, +            _translation: Vector, +        ) {              if Some(&self.target) == id {                  state.snap_to(self.offset);              } @@ -49,12 +57,19 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          } -        fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { +        fn scrollable( +            &mut self, +            state: &mut dyn Scrollable, +            id: Option<&Id>, +            _bounds: Rectangle, +            _translation: Vector, +        ) {              if Some(&self.target) == id {                  state.scroll_to(self.offset);              } diff --git a/core/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs index 4c773e99..41731d4c 100644 --- a/core/src/widget/operation/text_input.rs +++ b/core/src/widget/operation/text_input.rs @@ -1,6 +1,7 @@  //! Operate on widgets that have text input.  use crate::widget::operation::Operation;  use crate::widget::Id; +use crate::Rectangle;  /// The internal state of a widget that has text input.  pub trait TextInput { @@ -34,9 +35,10 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }      } @@ -63,9 +65,10 @@ pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }      } @@ -93,9 +96,10 @@ pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }      } @@ -121,9 +125,10 @@ pub fn select_all<T>(target: Id) -> impl Operation<T> {          fn container(              &mut self,              _id: Option<&Id>, +            _bounds: Rectangle,              operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),          ) { -            operate_on_children(self) +            operate_on_children(self);          }      } diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 79df2b02..97e0acac 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -3,9 +3,9 @@ use crate::alignment;  use crate::layout;  use crate::mouse;  use crate::renderer; -use crate::text; -use crate::widget::Tree; -use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Widget}; +use crate::text::{self, Paragraph}; +use crate::widget::tree::{self, Tree}; +use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget};  use std::borrow::Cow; @@ -19,7 +19,7 @@ where      Renderer::Theme: StyleSheet,  {      content: Cow<'a, str>, -    size: Option<f32>, +    size: Option<Pixels>,      line_height: LineHeight,      width: Length,      height: Length, @@ -53,7 +53,7 @@ where      /// Sets the size of the [`Text`].      pub fn size(mut self, size: impl Into<Pixels>) -> Self { -        self.size = Some(size.into().0); +        self.size = Some(size.into());          self      } @@ -117,11 +117,23 @@ where      }  } +/// The internal state of a [`Text`] widget. +#[derive(Debug, Default)] +pub struct State<P: Paragraph>(P); +  impl<'a, Message, Renderer> Widget<Message, Renderer> for Text<'a, Renderer>  where      Renderer: text::Renderer,      Renderer::Theme: StyleSheet,  { +    fn tag(&self) -> tree::Tag { +        tree::Tag::of::<State<Renderer::Paragraph>>() +    } + +    fn state(&self) -> tree::State { +        tree::State::new(State(Renderer::Paragraph::default())) +    } +      fn width(&self) -> Length {          self.width      } @@ -132,30 +144,29 @@ where      fn layout(          &self, +        tree: &mut Tree,          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        let size = self.size.unwrap_or_else(|| renderer.default_size()); - -        let bounds = renderer.measure( +        layout( +            tree.state.downcast_mut::<State<Renderer::Paragraph>>(), +            renderer, +            limits, +            self.width, +            self.height,              &self.content, -            size,              self.line_height, -            self.font.unwrap_or_else(|| renderer.default_font()), -            limits.max(), +            self.size, +            self.font, +            self.horizontal_alignment, +            self.vertical_alignment,              self.shaping, -        ); - -        let size = limits.resolve(bounds); - -        layout::Node::new(size) +        )      }      fn draw(          &self, -        _state: &Tree, +        tree: &Tree,          renderer: &mut Renderer,          theme: &Renderer::Theme,          style: &renderer::Style, @@ -163,22 +174,60 @@ where          _cursor_position: mouse::Cursor,          _viewport: &Rectangle,      ) { +        let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); +          draw(              renderer,              style,              layout, -            &self.content, -            self.size, -            self.line_height, -            self.font, +            state,              theme.appearance(self.style.clone()), -            self.horizontal_alignment, -            self.vertical_alignment, -            self.shaping,          );      }  } +/// Produces the [`layout::Node`] of a [`Text`] widget. +pub fn layout<Renderer>( +    state: &mut State<Renderer::Paragraph>, +    renderer: &Renderer, +    limits: &layout::Limits, +    width: Length, +    height: Length, +    content: &str, +    line_height: LineHeight, +    size: Option<Pixels>, +    font: Option<Renderer::Font>, +    horizontal_alignment: alignment::Horizontal, +    vertical_alignment: alignment::Vertical, +    shaping: Shaping, +) -> layout::Node +where +    Renderer: text::Renderer, +{ +    let limits = limits.width(width).height(height); +    let bounds = limits.max(); + +    let size = size.unwrap_or_else(|| renderer.default_size()); +    let font = font.unwrap_or_else(|| renderer.default_font()); + +    let State(ref mut paragraph) = state; + +    paragraph.update(text::Text { +        content, +        bounds, +        size, +        line_height, +        font, +        horizontal_alignment, +        vertical_alignment, +        shaping, +    }); + +    let size = limits.resolve(paragraph.min_bounds()); + +    layout::Node::new(size) +} +  /// Draws text using the same logic as the [`Text`] widget.  ///  /// Specifically: @@ -193,44 +242,31 @@ pub fn draw<Renderer>(      renderer: &mut Renderer,      style: &renderer::Style,      layout: Layout<'_>, -    content: &str, -    size: Option<f32>, -    line_height: LineHeight, -    font: Option<Renderer::Font>, +    state: &State<Renderer::Paragraph>,      appearance: Appearance, -    horizontal_alignment: alignment::Horizontal, -    vertical_alignment: alignment::Vertical, -    shaping: Shaping,  ) where      Renderer: text::Renderer,  { +    let State(ref paragraph) = state;      let bounds = layout.bounds(); -    let x = match horizontal_alignment { +    let x = match paragraph.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 { +    let y = match paragraph.vertical_alignment() {          alignment::Vertical::Top => bounds.y,          alignment::Vertical::Center => bounds.center_y(),          alignment::Vertical::Bottom => bounds.y + bounds.height,      }; -    let size = size.unwrap_or_else(|| renderer.default_size()); - -    renderer.fill_text(crate::Text { -        content, -        size, -        line_height, -        bounds: Rectangle { x, y, ..bounds }, -        color: appearance.color.unwrap_or(style.text_color), -        font: font.unwrap_or_else(|| renderer.default_font()), -        horizontal_alignment, -        vertical_alignment, -        shaping, -    }); +    renderer.fill_paragraph( +        paragraph, +        Point::new(x, y), +        appearance.color.unwrap_or(style.text_color), +    );  }  impl<'a, Message, Renderer> From<Text<'a, Renderer>> diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index da269632..ff52b1ce 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -61,7 +61,7 @@ impl Tree {          Renderer: crate::Renderer,      {          if self.tag == new.borrow().tag() { -            new.borrow().diff(self) +            new.borrow().diff(self);          } else {              *self = Self::new(new);          } @@ -78,7 +78,7 @@ impl Tree {              new_children,              |tree, widget| tree.diff(widget.borrow()),              |widget| Self::new(widget.borrow()), -        ) +        );      }      /// Reconciliates the children of the tree with the provided list of widgets using custom @@ -107,6 +107,88 @@ impl Tree {      }  } +/// Reconciliates the `current_children` with the provided list of widgets using +/// custom logic both for diffing and creating new widget state. +/// +/// The algorithm will try to minimize the impact of diffing by querying the +/// `maybe_changed` closure. +pub fn diff_children_custom_with_search<T>( +    current_children: &mut Vec<Tree>, +    new_children: &[T], +    diff: impl Fn(&mut Tree, &T), +    maybe_changed: impl Fn(usize) -> bool, +    new_state: impl Fn(&T) -> Tree, +) { +    if new_children.is_empty() { +        current_children.clear(); +        return; +    } + +    if current_children.is_empty() { +        current_children.extend(new_children.iter().map(new_state)); +        return; +    } + +    let first_maybe_changed = maybe_changed(0); +    let last_maybe_changed = maybe_changed(current_children.len() - 1); + +    if current_children.len() > new_children.len() { +        if !first_maybe_changed && last_maybe_changed { +            current_children.truncate(new_children.len()); +        } else { +            let difference_index = if first_maybe_changed { +                0 +            } else { +                (1..current_children.len()) +                    .find(|&i| maybe_changed(i)) +                    .unwrap_or(0) +            }; + +            let _ = current_children.splice( +                difference_index +                    ..difference_index +                        + (current_children.len() - new_children.len()), +                std::iter::empty(), +            ); +        } +    } + +    if current_children.len() < new_children.len() { +        let first_maybe_changed = maybe_changed(0); +        let last_maybe_changed = maybe_changed(current_children.len() - 1); + +        if !first_maybe_changed && last_maybe_changed { +            current_children.extend( +                new_children[current_children.len()..].iter().map(new_state), +            ); +        } else { +            let difference_index = if first_maybe_changed { +                0 +            } else { +                (1..current_children.len()) +                    .find(|&i| maybe_changed(i)) +                    .unwrap_or(0) +            }; + +            let _ = current_children.splice( +                difference_index..difference_index, +                new_children[difference_index +                    ..difference_index +                        + (new_children.len() - current_children.len())] +                    .iter() +                    .map(new_state), +            ); +        } +    } + +    // TODO: Merge loop with extend logic (?) +    for (child_state, new) in +        current_children.iter_mut().zip(new_children.iter()) +    { +        diff(child_state, new); +    } +} +  /// The identifier of some widget state.  #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]  pub struct Tag(any::TypeId); diff --git a/core/src/window.rs b/core/src/window.rs index 10db31b6..448ffc45 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -1,5 +1,6 @@  //! Build window-based GUI applications.  pub mod icon; +pub mod settings;  mod event;  mod id; @@ -7,7 +8,6 @@ mod level;  mod mode;  mod position;  mod redraw_request; -mod settings;  mod user_attention;  pub use event::Event; diff --git a/core/src/window/icon.rs b/core/src/window/icon.rs index 31868ecf..5ef0eed7 100644 --- a/core/src/window/icon.rs +++ b/core/src/window/icon.rs @@ -3,7 +3,7 @@ use crate::Size;  use std::mem; -/// Builds an  [`Icon`] from its RGBA pixels in the sRGB color space. +/// Builds an  [`Icon`] from its RGBA pixels in the `sRGB` color space.  pub fn from_rgba(      rgba: Vec<u8>,      width: u32, @@ -49,7 +49,7 @@ impl Icon {  }  #[derive(Debug, thiserror::Error)] -/// An error produced when using [`Icon::from_rgba`] with invalid arguments. +/// An error produced when using [`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. diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index 3b4f0fd3..8a59e83c 100644 --- a/core/src/window/redraw_request.rs +++ b/core/src/window/redraw_request.rs @@ -13,7 +13,7 @@ pub enum RedrawRequest {  #[cfg(test)]  mod tests {      use super::*; -    use std::time::{Duration, Instant}; +    use crate::time::{Duration, Instant};      #[test]      fn ordering() { diff --git a/core/src/window/settings.rs b/core/src/window/settings.rs index eba27914..25df8159 100644 --- a/core/src/window/settings.rs +++ b/core/src/window/settings.rs @@ -1,5 +1,4 @@ -use crate::window::{Icon, Level, Position}; - +//! Configure your windows.  #[cfg(target_os = "windows")]  #[path = "settings/windows.rs"]  mod platform; @@ -8,6 +7,10 @@ mod platform;  #[path = "settings/macos.rs"]  mod platform; +#[cfg(target_os = "linux")] +#[path = "settings/linux.rs"] +mod platform; +  #[cfg(target_arch = "wasm32")]  #[path = "settings/wasm.rs"]  mod platform; @@ -15,13 +18,15 @@ mod platform;  #[cfg(not(any(      target_os = "windows",      target_os = "macos", +    target_os = "linux",      target_arch = "wasm32"  )))]  #[path = "settings/other.rs"]  mod platform; -pub use platform::PlatformSpecific; +use crate::window::{Icon, Level, Position}; +pub use platform::PlatformSpecific;  /// The window settings of an application.  #[derive(Debug, Clone)]  pub struct Settings { @@ -70,8 +75,8 @@ pub struct Settings {  }  impl Default for Settings { -    fn default() -> Settings { -        Settings { +    fn default() -> Self { +        Self {              size: (1024, 768),              position: Position::default(),              min_size: None, @@ -82,8 +87,8 @@ impl Default for Settings {              transparent: false,              level: Level::default(),              icon: None, -            platform_specific: Default::default(),              exit_on_close_request: true, +            platform_specific: PlatformSpecific::default(),          }      }  } diff --git a/core/src/window/settings/linux.rs b/core/src/window/settings/linux.rs new file mode 100644 index 00000000..009b9d9e --- /dev/null +++ b/core/src/window/settings/linux.rs @@ -0,0 +1,11 @@ +//! Platform specific settings for Linux. + +/// The platform specific window settings of an application. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct PlatformSpecific { +    /// Sets the application id of the window. +    /// +    /// As a best practice, it is suggested to select an application id that match +    /// the basename of the application’s .desktop file. +    pub application_id: String, +} | 
