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 'core')
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, +} |