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