diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38b81842..a9a9b3f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,5 +37,5 @@ jobs: run: cargo build --package tour --target wasm32-unknown-unknown - name: Check compilation of `todos` example run: cargo build --package todos --target wasm32-unknown-unknown - - name: Check compilation of `integration_wgpu` example - run: cargo build --package integration_wgpu --target wasm32-unknown-unknown + - name: Check compilation of `integration` example + run: cargo build --package integration --target wasm32-unknown-unknown diff --git a/Cargo.toml b/Cargo.toml index 1f8eb017..479830ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,20 +13,18 @@ categories = ["gui"] [features] default = ["wgpu"] +# Enable the `wgpu` GPU-accelerated renderer backend +wgpu = ["iced_renderer/wgpu"] # Enables the `Image` widget -image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"] +image = ["iced_widget/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu?/svg", "iced_glow?/svg"] +svg = ["iced_widget/svg"] # Enables the `Canvas` widget -canvas = ["iced_graphics/canvas"] +canvas = ["iced_widget/canvas"] # Enables the `QRCode` widget -qr_code = ["iced_graphics/qr_code"] -# Enables the `iced_wgpu` renderer -wgpu = ["iced_wgpu"] -# Enables using system fonts -default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] -# Enables the `iced_glow` renderer. Overrides `iced_wgpu` -glow = ["iced_glow", "iced_glutin"] +qr_code = ["iced_widget/qr_code"] +# Enables lazy widgets +lazy = ["iced_widget/lazy"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -39,13 +37,8 @@ smol = ["iced_futures/smol"] palette = ["iced_core/palette"] # Enables querying system information system = ["iced_winit/system"] -# Enables chrome traces -chrome-trace = [ - "iced_winit/chrome-trace", - "iced_glutin?/trace", - "iced_wgpu?/tracing", - "iced_glow?/tracing", -] +# Enables the advanced module +advanced = [] [badges] maintenance = { status = "actively-developed" } @@ -55,12 +48,12 @@ members = [ "core", "futures", "graphics", - "glow", - "glutin", - "lazy", - "native", + "runtime", + "renderer", "style", + "tiny_skia", "wgpu", + "widget", "winit", "examples/*", ] @@ -68,24 +61,16 @@ members = [ [dependencies] iced_core = { version = "0.9", path = "core" } iced_futures = { version = "0.6", path = "futures" } -iced_native = { version = "0.10", path = "native" } -iced_graphics = { version = "0.8", path = "graphics" } +iced_renderer = { version = "0.1", path = "renderer" } +iced_widget = { version = "0.1", path = "widget" } iced_winit = { version = "0.9", path = "winit", features = ["application"] } -iced_glutin = { version = "0.8", path = "glutin", optional = true } -iced_glow = { version = "0.8", path = "glow", optional = true } -thiserror = "1.0" +thiserror = "1" [dependencies.image_rs] version = "0.24" package = "image" optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.10", path = "wgpu", optional = true } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.10", path = "wgpu", features = ["webgl"], optional = true } - [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas", "qr_code"] diff --git a/README.md b/README.md index fe2a4a64..c72fd770 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ Inspired by [Elm]. * First-class support for async actions (use futures!) * [Modular ecosystem] split into reusable parts: * A [renderer-agnostic native runtime] enabling integration with existing systems - * Two [built-in renderers] leveraging [`wgpu`] and [`glow`] + * Two [built-in renderers] leveraging [`wgpu`] and [`tiny-skia`] * [`iced_wgpu`] supporting Vulkan, Metal and DX12 - * [`iced_glow`] supporting OpenGL 2.1+ and OpenGL ES 2.0+ + * [`iced_tiny_skia`] offering a software alternative as a fallback * A [windowing shell] * A [web runtime] leveraging the DOM @@ -52,9 +52,9 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], [Modular ecosystem]: ECOSYSTEM.md [renderer-agnostic native runtime]: native/ [`wgpu`]: https://github.com/gfx-rs/wgpu -[`glow`]: https://github.com/grovesNL/glow +[`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia [`iced_wgpu`]: wgpu/ -[`iced_glow`]: glow/ +[`iced_tiny_skia`]: tiny_skia/ [built-in renderers]: ECOSYSTEM.md#Renderers [windowing shell]: winit/ [`dodrio`]: https://github.com/fitzgen/dodrio @@ -196,34 +196,6 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular: [`ggez`]: https://github.com/ggez/ggez [the ecosystem]: ECOSYSTEM.md -## Troubleshooting - -### `GraphicsAdapterNotFound` - -This occurs when the selected [built-in renderer] is not able to create a context. - -Often this will occur while using [`iced_wgpu`] as the renderer without -supported hardware (needs Vulkan, Metal or DX12). In this case, you could try using the -[`iced_glow`] renderer: - -First, check if it works with - -```console -cargo run --features iced/glow --package game_of_life -``` - -and then use it in your project with - -```toml -iced = { version = "0.9", default-features = false, features = ["glow"] } -``` - -__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0, -but if you don't, right now there's no software fallback, so it means your hardware -doesn't support Iced. - -[built-in renderer]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md#Renderers - ## Contributing / Feedback Contributions are greatly appreciated! If you want to contribute, please diff --git a/core/Cargo.toml b/core/Cargo.toml index 3a00a53e..92d9773f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -9,6 +9,8 @@ repository = "https://github.com/iced-rs/iced" [dependencies] bitflags = "1.2" +thiserror = "1" +twox-hash = { version = "1.5", default-features = false } [dependencies.palette] version = "0.6" 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; + + /// 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 { + None + } + + fn write(&mut self, _contents: String) {} +} diff --git a/native/src/element.rs b/core/src/element.rs similarity index 90% rename from native/src/element.rs rename to core/src/element.rs index 0a677d20..98c53737 100644 --- a/native/src/element.rs +++ b/core/src/element.rs @@ -90,41 +90,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 { + /// # 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; + /// # + /// # pub mod widget { + /// # pub struct Row { + /// # _t: std::marker::PhantomData, + /// # } + /// # + /// # impl Row { + /// # pub fn new() -> Self { + /// # unimplemented!() + /// # } + /// # + /// # pub fn spacing(mut self, _: u32) -> Self { + /// # unimplemented!() + /// # } + /// # + /// # pub fn push( + /// # mut self, + /// # _: iced_core::Element, + /// # ) -> Self { + /// # unimplemented!() + /// # } + /// # } + /// # } /// # } /// # - /// # use counter::Counter; - /// # - /// # struct ManyCounters { - /// # counters: Vec, - /// # } - /// # - /// # #[derive(Debug, Clone, Copy)] - /// # pub enum Message { - /// # Counter(usize, counter::Message) - /// # } - /// 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, + /// } + /// + /// #[derive(Debug, Clone, Copy)] + /// pub enum Message { + /// Counter(usize, counter::Message), + /// } /// /// impl ManyCounters { - /// pub fn view(&mut self) -> Row { + /// pub fn view(&mut self) -> Row { /// // We can quickly populate a `Row` by folding over our counters /// self.counters.iter_mut().enumerate().fold( /// Row::new().spacing(20), @@ -137,9 +161,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> { /// // Here we turn our `Element` into /// // an `Element` 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)), /// ) - /// } + /// }, /// ) /// } /// } diff --git a/native/src/event.rs b/core/src/event.rs similarity index 98% rename from native/src/event.rs rename to core/src/event.rs index bcfaf891..953cd73f 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 d8c34e5a..bb425fd6 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -1,19 +1,102 @@ +//! Load and use fonts. +use std::hash::Hash; + /// A font. -#[derive(Debug, Clone, Copy, Default)] -pub enum Font { - /// The default font. - /// - /// This is normally a font configured in a renderer or loaded from the - /// system. - #[default] - 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 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/graphics/src/gradient.rs b/core/src/gradient.rs similarity index 100% rename from graphics/src/gradient.rs rename to core/src/gradient.rs diff --git a/graphics/src/gradient/linear.rs b/core/src/gradient/linear.rs similarity index 100% rename from graphics/src/gradient/linear.rs rename to core/src/gradient/linear.rs diff --git a/native/src/hasher.rs b/core/src/hasher.rs similarity index 100% rename from native/src/hasher.rs rename to core/src/hasher.rs diff --git a/native/src/image.rs b/core/src/image.rs similarity index 95% rename from native/src/image.rs rename to 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 similarity index 100% rename from native/src/layout.rs rename to core/src/layout.rs diff --git a/native/src/layout/DRUID_LICENSE b/core/src/layout/DRUID_LICENSE similarity index 100% rename from native/src/layout/DRUID_LICENSE rename to core/src/layout/DRUID_LICENSE diff --git a/native/src/layout/flex.rs b/core/src/layout/flex.rs similarity index 100% rename from native/src/layout/flex.rs rename to core/src/layout/flex.rs diff --git a/native/src/layout/limits.rs b/core/src/layout/limits.rs similarity index 100% rename from native/src/layout/limits.rs rename to core/src/layout/limits.rs diff --git a/native/src/layout/node.rs b/core/src/layout/node.rs similarity index 100% rename from native/src/layout/node.rs rename to 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 for Length { + fn from(amount: Pixels) -> Self { + Length::Fixed(f32::from(amount)) + } +} + impl From for Length { fn from(amount: f32) -> Self { Length::Fixed(amount) diff --git a/core/src/lib.rs b/core/src/lib.rs index da3cb874..89dfb828 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,31 +25,57 @@ #![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 background; 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 background::Background; +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..0c405ce6 100644 --- a/core/src/mouse.rs +++ b/core/src/mouse.rs @@ -1,8 +1,11 @@ //! Handle mouse events. +pub mod click; + mod button; mod event; mod interaction; pub use button::Button; +pub use click::Click; pub use event::{Event, ScrollDelta}; pub use interaction::Interaction; diff --git a/native/src/mouse/click.rs b/core/src/mouse/click.rs similarity index 100% rename from native/src/mouse/click.rs rename to core/src/mouse/click.rs diff --git a/native/src/overlay.rs b/core/src/overlay.rs similarity index 99% rename from native/src/overlay.rs rename to core/src/overlay.rs index 6cada416..b9f3c735 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; diff --git a/native/src/overlay/element.rs b/core/src/overlay/element.rs similarity index 100% rename from native/src/overlay/element.rs rename to core/src/overlay/element.rs diff --git a/native/src/overlay/group.rs b/core/src/overlay/group.rs similarity index 97% rename from native/src/overlay/group.rs rename to core/src/overlay/group.rs index 1126f0cf..0c48df34 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. 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 for Pixels { Self(f32::from(amount)) } } + +impl From 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 { 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 { && 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 { } } + /// 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 { Rectangle { @@ -109,6 +147,16 @@ impl Rectangle { 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 for Rectangle { diff --git a/native/src/renderer.rs b/core/src/renderer.rs similarity index 99% rename from native/src/renderer.rs rename to core/src/renderer.rs index 2ac78982..d6247e39 100644 --- a/native/src/renderer.rs +++ b/core/src/renderer.rs @@ -1,6 +1,7 @@ //! Write your own renderer. #[cfg(debug_assertions)] mod null; + #[cfg(debug_assertions)] pub use null::Null; diff --git a/native/src/renderer/null.rs b/core/src/renderer/null.rs similarity index 75% rename from native/src/renderer/null.rs rename to core/src/renderer/null.rs index 9376d540..f0cc952e 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,20 +42,28 @@ 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_size(&self) -> f32 { - 20.0 + fn default_font(&self) -> Self::Font { + Font::default() } + fn default_size(&self) -> f32 { + 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, ) -> (f32, f32) { (0.0, 20.0) } @@ -62,8 +72,10 @@ impl text::Renderer for Null { &self, _contents: &str, _size: f32, + _line_height: text::LineHeight, _font: Self::Font, _bounds: Size, + _shaping: text::Shaping, _point: Point, _nearest_only: bool, ) -> Option { diff --git a/native/src/shell.rs b/core/src/shell.rs similarity index 100% rename from native/src/shell.rs rename to 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 { /// The width. pub width: T, diff --git a/native/src/svg.rs b/core/src/svg.rs similarity index 96% rename from native/src/svg.rs rename to 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, @@ -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..c154cc27 --- /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 for LineHeight { + fn from(factor: f32) -> Self { + Self::Relative(factor) + } +} + +impl From for LineHeight { + fn from(pixels: Pixels) -> Self { + Self::Absolute(pixels) + } +} + +impl Hash for LineHeight { + fn hash(&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, + ) -> (f32, f32); + + /// 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 (width, _) = self.measure( + content, + size, + LineHeight::Absolute(Pixels(size)), + font, + Size::INFINITY, + shaping, + ); + + 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; + + /// 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 similarity index 100% rename from native/src/touch.rs rename to core/src/touch.rs diff --git a/native/src/widget.rs b/core/src/widget.rs similarity index 66% rename from native/src/widget.rs rename to core/src/widget.rs index 6b83f1fa..769f8659 100644 --- a/native/src/widget.rs +++ b/core/src/widget.rs @@ -1,101 +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; -pub mod mouse_area; +//! 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 mouse_area::MouseArea; -#[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, Point, Rectangle, Shell}; /// A component that displays information and allows interaction. /// diff --git a/native/src/widget/id.rs b/core/src/widget/id.rs similarity index 97% rename from native/src/widget/id.rs rename to 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 { + /// 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), + ); + + /// 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 { + Outcome::None + } +} + +/// The result of an [`Operation`]. +pub enum Outcome { + /// The [`Operation`] produced no result. + None, + + /// The [`Operation`] produced some result. + Some(T), + + /// The [`Operation`] needs to be followed by another [`Operation`]. + Chain(Box>), +} + +impl fmt::Debug for Outcome +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( + operation: Box>, + f: impl Fn(A) -> B + 'static, +) -> impl Operation +where + A: 'static, + B: 'static, +{ + #[allow(missing_debug_implementations)] + struct Map { + operation: Box>, + f: Rc B>, + } + + impl Operation for Map + where + A: 'static, + B: 'static, + { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + struct MapRef<'a, A> { + operation: &'a mut dyn Operation, + } + + impl<'a, A, B> Operation for MapRef<'a, A> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + 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 { + 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( + target: Id, + operation: impl Operation + 'static, +) -> impl Operation { + struct ScopedOperation { + target: Id, + operation: Box>, + } + + impl Operation for ScopedOperation { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + if id == Some(&self.target) { + operate_on_children(self.operation.as_mut()); + } else { + operate_on_children(self); + } + } + + fn finish(&self) -> Outcome { + 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 similarity index 100% rename from native/src/widget/operation/focusable.rs rename to core/src/widget/operation/focusable.rs diff --git a/native/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs similarity index 100% rename from native/src/widget/operation/scrollable.rs rename to core/src/widget/operation/scrollable.rs diff --git a/native/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs similarity index 100% rename from native/src/widget/operation/text_input.rs rename to core/src/widget/operation/text_input.rs diff --git a/native/src/widget/text.rs b/core/src/widget/text.rs similarity index 71% rename from native/src/widget/text.rs rename to core/src/widget/text.rs index 3fee48f2..90af88b7 100644 --- a/native/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -4,27 +4,15 @@ use crate::layout; 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, Point, Rectangle, Size, 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])); -/// ``` -/// -/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true) #[allow(missing_debug_implementations)] pub struct Text<'a, Renderer> where @@ -33,11 +21,13 @@ where { content: Cow<'a, str>, size: Option, + line_height: LineHeight, width: Length, height: Length, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, - font: Renderer::Font, + font: Option, + shaping: Shaping, style: ::Style, } @@ -51,11 +41,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 +58,17 @@ where self } + /// Sets the [`LineHeight`] of the [`Text`]. + pub fn line_height(mut self, line_height: impl Into) -> 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) -> Self { - self.font = font.into(); + self.font = Some(font.into()); self } @@ -112,6 +110,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 for Text<'a, Renderer> @@ -138,8 +142,14 @@ where let bounds = limits.max(); - let (width, height) = - renderer.measure(&self.content, size, self.font.clone(), bounds); + let (width, height) = renderer.measure( + &self.content, + size, + self.line_height, + self.font.unwrap_or_else(|| renderer.default_font()), + bounds, + self.shaping, + ); let size = limits.resolve(Size::new(width, height)); @@ -162,10 +172,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 +198,12 @@ pub fn draw( layout: Layout<'_>, content: &str, size: Option, - font: Renderer::Font, + line_height: LineHeight, + font: Option, appearance: Appearance, horizontal_alignment: alignment::Horizontal, vertical_alignment: alignment::Vertical, + shaping: Shaping, ) where Renderer: text::Renderer, { @@ -207,14 +221,18 @@ pub fn draw( 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 +256,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, +} diff --git a/native/src/widget/tree.rs b/core/src/widget/tree.rs similarity index 100% rename from native/src/widget/tree.rs rename to core/src/widget/tree.rs diff --git a/core/src/window.rs b/core/src/window.rs new file mode 100644 index 00000000..81bd7e3d --- /dev/null +++ b/core/src/window.rs @@ -0,0 +1,13 @@ +//! Build window-based GUI applications. +pub mod icon; + +mod event; +mod mode; +mod redraw_request; +mod user_attention; + +pub use event::Event; +pub use icon::Icon; +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 similarity index 100% rename from native/src/window/event.rs rename to core/src/window/event.rs diff --git a/native/src/window/icon.rs b/core/src/window/icon.rs similarity index 100% rename from native/src/window/icon.rs rename to core/src/window/icon.rs diff --git a/native/src/window/mode.rs b/core/src/window/mode.rs similarity index 100% rename from native/src/window/mode.rs rename to core/src/window/mode.rs diff --git a/native/src/window/redraw_request.rs b/core/src/window/redraw_request.rs similarity index 100% rename from native/src/window/redraw_request.rs rename to core/src/window/redraw_request.rs diff --git a/native/src/window/user_attention.rs b/core/src/window/user_attention.rs similarity index 100% rename from native/src/window/user_attention.rs rename to core/src/window/user_attention.rs diff --git a/examples/README.md b/examples/README.md index 74cf145b..111e8910 100644 --- a/examples/README.md +++ b/examples/README.md @@ -93,8 +93,7 @@ A bunch of simpler examples exist: - [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). -- [`integration_opengl`](integration_opengl), a demonstration of how to integrate Iced in an existing OpenGL application. -- [`integration_wgpu`](integration_wgpu), a demonstration of how to integrate Iced in an existing [`wgpu`] application. +- [`integration`](integration), a demonstration of how to integrate Iced in an existing [`wgpu`] application. - [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized. - [`pick_list`](pick_list), a dropdown list of selectable options. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 7b6ea0e1..80ad0b5b 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -5,8 +5,8 @@ use iced::widget::canvas::{ self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke, }; use iced::{ - Application, Command, Element, Length, Point, Rectangle, Settings, - Subscription, Theme, + Application, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Subscription, Theme, }; pub fn main() -> iced::Result { @@ -75,11 +75,12 @@ impl canvas::Program for Arc { fn draw( &self, _state: &Self::State, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let geometry = self.cache.draw(bounds.size(), |frame| { + let geometry = self.cache.draw(renderer, bounds.size(), |frame| { let palette = theme.palette(); let center = frame.center(); diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 7c3916d4..f1c83a16 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -64,7 +64,7 @@ mod bezier { use iced::widget::canvas::{ self, Canvas, Cursor, Frame, Geometry, Path, Stroke, }; - use iced::{Element, Length, Point, Rectangle, Theme}; + use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] pub struct State { @@ -152,22 +152,26 @@ mod bezier { fn draw( &self, state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec { - let content = - self.state.cache.draw(bounds.size(), |frame: &mut Frame| { + let content = self.state.cache.draw( + renderer, + bounds.size(), + |frame: &mut Frame| { Curve::draw_all(self.curves, frame); frame.stroke( &Path::rectangle(Point::ORIGIN, frame.size()), Stroke::default().with_width(2.0), ); - }); + }, + ); if let Some(pending) = state { - let pending_curve = pending.draw(bounds, cursor); + let pending_curve = pending.draw(renderer, bounds, cursor); vec![content, pending_curve] } else { @@ -216,8 +220,13 @@ mod bezier { } impl Pending { - fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry { - let mut frame = Frame::new(bounds.size()); + fn draw( + &self, + renderer: &Renderer, + bounds: Rectangle, + cursor: Cursor, + ) -> Geometry { + let mut frame = Frame::new(renderer, bounds.size()); if let Some(cursor_position) = cursor.position_in(&bounds) { match *self { diff --git a/examples/checkbox/fonts/icons.ttf b/examples/checkbox/fonts/icons.ttf index a2046844..82f28481 100644 Binary files a/examples/checkbox/fonts/icons.ttf and b/examples/checkbox/fonts/icons.ttf differ diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs index 09950bb8..ef61a974 100644 --- a/examples/checkbox/src/main.rs +++ b/examples/checkbox/src/main.rs @@ -1,10 +1,9 @@ -use iced::widget::{checkbox, column, container}; -use iced::{Element, Font, Length, Sandbox, Settings}; +use iced::executor; +use iced::font::{self, Font}; +use iced::widget::{checkbox, column, container, text}; +use iced::{Application, Command, Element, Length, Settings, Theme}; -const ICON_FONT: Font = Font::External { - name: "Icons", - bytes: include_bytes!("../fonts/icons.ttf"), -}; +const ICON_FONT: Font = Font::with_name("icons"); pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -20,24 +19,35 @@ struct Example { enum Message { DefaultChecked(bool), CustomChecked(bool), + FontLoaded(Result<(), font::Error>), } -impl Sandbox for Example { +impl Application for Example { type Message = Message; + type Flags = (); + type Executor = executor::Default; + type Theme = Theme; - fn new() -> Self { - Default::default() + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + Self::default(), + font::load(include_bytes!("../fonts/icons.ttf").as_ref()) + .map(Message::FontLoaded), + ) } fn title(&self) -> String { String::from("Checkbox - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command { match message { Message::DefaultChecked(value) => self.default_checkbox = value, Message::CustomChecked(value) => self.custom_checkbox = value, + Message::FontLoaded(_) => (), } + + Command::none() } fn view(&self) -> Element { @@ -49,6 +59,8 @@ impl Sandbox for Example { font: ICON_FONT, code_point: '\u{e901}', size: None, + line_height: text::LineHeight::Relative(1.0), + shaping: text::Shaping::Basic, }); let content = column![default_checkbox, custom_checkbox].spacing(22); diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index a389c54f..6425e2da 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -4,8 +4,8 @@ use iced::widget::canvas::{ }; use iced::widget::{canvas, container}; use iced::{ - Application, Color, Command, Element, Length, Point, Rectangle, Settings, - Subscription, Theme, Vector, + Application, Color, Command, Element, Length, Point, Rectangle, Renderer, + Settings, Subscription, Theme, Vector, }; pub fn main() -> iced::Result { @@ -83,17 +83,18 @@ impl Application for Clock { } } -impl canvas::Program for Clock { +impl canvas::Program for Clock { type State = (); fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let clock = self.clock.draw(bounds.size(), |frame| { + let clock = self.clock.draw(renderer, bounds.size(), |frame| { let center = frame.center(); let radius = frame.width().min(frame.height()) / 2.0; diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index a2df36c2..5c4304ee 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,8 +1,8 @@ use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ - alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox, - Settings, Size, Vector, + alignment, Alignment, Color, Element, Length, Point, Rectangle, Renderer, + Sandbox, Settings, Size, Vector, }; use palette::{self, convert::FromColor, Hsl, Srgb}; use std::marker::PhantomData; @@ -243,11 +243,12 @@ impl canvas::Program for Theme { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &iced::Theme, bounds: Rectangle, _cursor: Cursor, ) -> Vec { - let theme = self.canvas_cache.draw(bounds.size(), |frame| { + let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| { self.draw(frame); }); diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml index dd435201..9db1e6b4 100644 --- a/examples/component/Cargo.toml +++ b/examples/component/Cargo.toml @@ -6,6 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } -iced_lazy = { path = "../../lazy" } +iced = { path = "../..", features = ["debug", "lazy"] } diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs index 21c2747c..010321a9 100644 --- a/examples/component/src/main.rs +++ b/examples/component/src/main.rs @@ -47,9 +47,8 @@ impl Sandbox for Component { mod numeric_input { use iced::alignment::{self, Alignment}; - use iced::widget::{self, button, row, text, text_input}; - use iced::{Element, Length}; - use iced_lazy::{self, Component}; + use iced::widget::{button, component, row, text, text_input, Component}; + use iced::{Element, Length, Renderer}; pub struct NumericInput { value: Option, @@ -82,13 +81,7 @@ mod numeric_input { } } - impl Component for NumericInput - where - Renderer: iced_native::text::Renderer + 'static, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, - { + impl Component for NumericInput { type State = (); type Event = Event; @@ -152,17 +145,12 @@ mod numeric_input { } } - impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> + impl<'a, Message> From> for Element<'a, Message, Renderer> where Message: 'a, - Renderer: 'static + iced_native::text::Renderer, - Renderer::Theme: widget::button::StyleSheet - + widget::text_input::StyleSheet - + widget::text::StyleSheet, { fn from(numeric_input: NumericInput) -> Self { - iced_lazy::component(numeric_input) + component(numeric_input) } } } diff --git a/examples/custom_quad/Cargo.toml b/examples/custom_quad/Cargo.toml index 39154786..f097c2dd 100644 --- a/examples/custom_quad/Cargo.toml +++ b/examples/custom_quad/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index 6509887c..b07f42ce 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -1,9 +1,9 @@ //! This example showcases a drawing a quad. mod quad { - use iced_native::layout::{self, Layout}; - use iced_native::renderer; - use iced_native::widget::{self, Widget}; - use iced_native::{Color, Element, Length, Point, Rectangle, Size}; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{Color, Element, Length, Point, Rectangle, Size}; pub struct CustomQuad { size: f32, diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml index 067aab4a..dda0efe8 100644 --- a/examples/custom_widget/Cargo.toml +++ b/examples/custom_widget/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index f6bb3b1e..7854548c 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -9,10 +9,10 @@ mod circle { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. - use iced_native::layout::{self, Layout}; - use iced_native::renderer; - use iced_native::widget::{self, Widget}; - use iced_native::{Color, Element, Length, Point, Rectangle, Size}; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{Color, Element, Length, Point, Rectangle, Size}; pub struct Circle { radius: f32, diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index f38679ea..212832f4 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -7,8 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["tokio"] } -iced_native = { path = "../../native" } -iced_futures = { path = "../../futures" } [dependencies.reqwest] version = "0.11" diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index cd7647e8..3b11cb76 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -1,4 +1,4 @@ -use iced_native::subscription; +use iced::subscription; use std::hash::Hash; diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml index 8c56e471..15ffc0af 100644 --- a/examples/events/Cargo.toml +++ b/examples/events/Cargo.toml @@ -7,4 +7,3 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 1b97018e..7f3a5e1d 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,12 +1,13 @@ use iced::alignment; use iced::executor; +use iced::subscription; use iced::widget::{button, checkbox, container, text, Column}; use iced::window; +use iced::Event; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_native::Event; pub fn main() -> iced::Result { Events::run(Settings { @@ -17,13 +18,13 @@ pub fn main() -> iced::Result { #[derive(Debug, Default)] struct Events { - last: Vec, + last: Vec, enabled: bool, } #[derive(Debug, Clone)] enum Message { - EventOccurred(iced_native::Event), + EventOccurred(Event), Toggled(bool), Exit, } @@ -70,7 +71,7 @@ impl Application for Events { } fn subscription(&self) -> Subscription { - iced_native::subscription::events().map(Message::EventOccurred) + subscription::events().map(Message::EventOccurred) } fn view(&self) -> Element { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index ed911160..eab8908b 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -145,7 +145,7 @@ impl Application for GameOfLife { self.grid .view() .map(move |message| Message::Grid(message, version)), - controls + controls, ]; container(content) @@ -211,8 +211,8 @@ mod grid { Cache, Canvas, Cursor, Frame, Geometry, Path, Text, }; use iced::{ - alignment, mouse, Color, Element, Length, Point, Rectangle, Size, - Theme, Vector, + alignment, mouse, Color, Element, Length, Point, Rectangle, Renderer, + Size, Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -536,13 +536,14 @@ mod grid { fn draw( &self, _interaction: &Interaction, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, cursor: Cursor, ) -> Vec { let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); - let life = self.life_cache.draw(bounds.size(), |frame| { + let life = self.life_cache.draw(renderer, bounds.size(), |frame| { let background = Path::rectangle(Point::ORIGIN, frame.size()); frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B)); @@ -565,7 +566,7 @@ mod grid { }); let overlay = { - let mut frame = Frame::new(bounds.size()); + let mut frame = Frame::new(renderer, bounds.size()); let hovered_cell = cursor.position_in(&bounds).map(|position| { @@ -626,38 +627,40 @@ mod grid { if self.scaling < 0.2 || !self.show_lines { vec![life, overlay] } else { - let grid = self.grid_cache.draw(bounds.size(), |frame| { - frame.translate(center); - frame.scale(self.scaling); - frame.translate(self.translation); - frame.scale(Cell::SIZE as f32); + let grid = + self.grid_cache.draw(renderer, bounds.size(), |frame| { + frame.translate(center); + frame.scale(self.scaling); + frame.translate(self.translation); + frame.scale(Cell::SIZE as f32); - let region = self.visible_region(frame.size()); - let rows = region.rows(); - let columns = region.columns(); - let (total_rows, total_columns) = - (rows.clone().count(), columns.clone().count()); - let width = 2.0 / Cell::SIZE as f32; - let color = Color::from_rgb8(70, 74, 83); + let region = self.visible_region(frame.size()); + let rows = region.rows(); + let columns = region.columns(); + let (total_rows, total_columns) = + (rows.clone().count(), columns.clone().count()); + let width = 2.0 / Cell::SIZE as f32; + let color = Color::from_rgb8(70, 74, 83); - frame.translate(Vector::new(-width / 2.0, -width / 2.0)); + frame + .translate(Vector::new(-width / 2.0, -width / 2.0)); - for row in region.rows() { - frame.fill_rectangle( - Point::new(*columns.start() as f32, row as f32), - Size::new(total_columns as f32, width), - color, - ); - } + for row in region.rows() { + frame.fill_rectangle( + Point::new(*columns.start() as f32, row as f32), + Size::new(total_columns as f32, width), + color, + ); + } - for column in region.columns() { - frame.fill_rectangle( - Point::new(column as f32, *rows.start() as f32), - Size::new(width, total_rows as f32), - color, - ); - } - }); + for column in region.columns() { + frame.fill_rectangle( + Point::new(column as f32, *rows.start() as f32), + Size::new(width, total_rows as f32), + color, + ); + } + }); vec![life, grid, overlay] } diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 22ede0e0..79fe52d5 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -6,6 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } +iced = { path = "../..", features = ["advanced"] } iced_graphics = { path = "../../graphics" } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 9bacce7f..5cb41184 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -1,23 +1,13 @@ //! This example showcases a simple native custom widget that renders using //! arbitrary low-level geometry. mod rainbow { - // For now, to implement a custom native widget you will need to add - // `iced_native` and `iced_wgpu` to your dependencies. - // - // Then, you simply need to define your widget type and implement the - // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. - // - // Of course, you can choose to make the implementation renderer-agnostic, - // if you wish to, by creating your own `Renderer` trait, which could be - // implemented by `iced_wgpu` and other renderers. - use iced_graphics::renderer::{self, Renderer}; - use iced_graphics::triangle::ColoredVertex2D; - use iced_graphics::{Backend, Primitive}; + use iced_graphics::primitive::{ColoredVertex2D, Primitive}; - use iced_native::layout; - use iced_native::widget::{self, Widget}; - use iced_native::{ - Element, Layout, Length, Point, Rectangle, Size, Vector, + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::{ + Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; #[derive(Debug, Clone, Copy, Default)] @@ -27,10 +17,7 @@ mod rainbow { Rainbow } - impl Widget> for Rainbow - where - B: Backend, - { + impl Widget for Rainbow { fn width(&self) -> Length { Length::Fill } @@ -41,7 +28,7 @@ mod rainbow { fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let size = limits.width(Length::Fill).resolve(Size::ZERO); @@ -52,15 +39,15 @@ mod rainbow { fn draw( &self, _tree: &widget::Tree, - renderer: &mut Renderer, - _theme: &T, + renderer: &mut Renderer, + _theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, ) { - use iced_graphics::triangle::Mesh2D; - use iced_native::Renderer as _; + use iced::advanced::Renderer as _; + use iced_graphics::primitive::Mesh2D; let b = layout.bounds(); @@ -151,10 +138,7 @@ mod rainbow { } } - impl<'a, Message, B, T> From for Element<'a, Message, Renderer> - where - B: Backend, - { + impl<'a, Message> From for Element<'a, Message, Renderer> { fn from(rainbow: Rainbow) -> Self { Self::new(rainbow) } diff --git a/examples/integration_wgpu/.gitignore b/examples/integration/.gitignore similarity index 100% rename from examples/integration_wgpu/.gitignore rename to examples/integration/.gitignore diff --git a/examples/integration_wgpu/Cargo.toml b/examples/integration/Cargo.toml similarity index 80% rename from examples/integration_wgpu/Cargo.toml rename to examples/integration/Cargo.toml index c0e4fd81..22914742 100644 --- a/examples/integration_wgpu/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "integration_wgpu" +name = "integration" version = "0.1.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" @@ -7,7 +7,9 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu", features = ["webgl"] } +iced_wgpu = { path = "../../wgpu" } +iced_widget = { path = "../../widget" } +iced_renderer = { path = "../../renderer", features = ["wgpu"] } env_logger = "0.10" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/integration_wgpu/README.md b/examples/integration/README.md similarity index 100% rename from examples/integration_wgpu/README.md rename to examples/integration/README.md diff --git a/examples/integration_wgpu/index.html b/examples/integration/index.html similarity index 81% rename from examples/integration_wgpu/index.html rename to examples/integration/index.html index 461e67a4..920bc4a0 100644 --- a/examples/integration_wgpu/index.html +++ b/examples/integration/index.html @@ -8,8 +8,8 @@

integration_wgpu