diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml index e93a01ae..d4c94fcd 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -62,15 +62,15 @@ body: If you are using an older release, please upgrade to the latest one before filing an issue. options: + - crates.io release - master - - 0.7 validations: required: true - type: dropdown id: os attributes: - label: Operative System - description: Which operative system are you using? + label: Operating System + description: Which operating system are you using? options: - Windows - macOS diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index e69f4d63..09a7a4d5 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -20,13 +20,13 @@ jobs: -p iced_core \ -p iced_style \ -p iced_futures \ - -p iced_native \ - -p iced_lazy \ + -p iced_runtime \ -p iced_graphics \ -p iced_wgpu \ - -p iced_glow \ + -p iced_tiny_skia \ + -p iced_renderer \ + -p iced_widget \ -p iced_winit \ - -p iced_glutin \ -p iced - name: Write CNAME file run: echo 'docs.iced.rs' > ./target/doc/CNAME 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/CHANGELOG.md b/CHANGELOG.md index 1d9d1fb3..077f4af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.0] - 2023-04-13 +### Added +- `MouseArea` widget. [#1594](https://github.com/iced-rs/iced/pull/1594) +- `channel` helper in `subscription`. [#1786](https://github.com/iced-rs/iced/pull/1786) +- Configurable `width` for `Scrollable`. [#1749](https://github.com/iced-rs/iced/pull/1749) +- Support for disabled `TextInput`. [#1744](https://github.com/iced-rs/iced/pull/1744) +- Platform-specific window settings. [#1730](https://github.com/iced-rs/iced/pull/1730) +- Left and right colors for sliders. [#1643](https://github.com/iced-rs/iced/pull/1643) +- Icon for `TextInput`. [#1702](https://github.com/iced-rs/iced/pull/1702) +- Mouse over scrollbar flag for `scrollable::StyleSheet`. [#1669](https://github.com/iced-rs/iced/pull/1669) +- Better example for `Radio`. [#1762](https://github.com/iced-rs/iced/pull/1762) + +### Changed +- `wgpu` has been updated to `0.15` in `iced_wgpu`. [#1789](https://github.com/iced-rs/iced/pull/1789) +- `resvg` has been updated to `0.29` in `iced_graphics`. [#1733](https://github.com/iced-rs/iced/pull/1733) +- `subscription::run` now takes a function pointer. [#1723](https://github.com/iced-rs/iced/pull/1723) + +### Fixed +- Redundant `on_scroll` messages for `Scrollable`. [#1788](https://github.com/iced-rs/iced/pull/1788) +- Outdated items in `ROADMAP.md` [#1782](https://github.com/iced-rs/iced/pull/1782) +- Colons in shader labels causing compilation issues in `iced_wgpu`. [#1779](https://github.com/iced-rs/iced/pull/1779) +- Re-expose winit features for window servers in Linux. [#1777](https://github.com/iced-rs/iced/pull/1777) +- Replacement of application node in Wasm. [#1765](https://github.com/iced-rs/iced/pull/1765) +- `clippy` lints for Rust 1.68. [#1755](https://github.com/iced-rs/iced/pull/1755) +- Unnecessary `Component` rebuilds. [#1754](https://github.com/iced-rs/iced/pull/1754) +- Incorrect package name in checkbox example docs. [#1750](https://github.com/iced-rs/iced/pull/1750) +- Fullscreen only working on primary monitor. [#1742](https://github.com/iced-rs/iced/pull/1742) +- `Padding::fit` on irregular values for an axis. [#1734](https://github.com/iced-rs/iced/pull/1734) +- `Debug` implementation of `Font` displaying its bytes. [#1731](https://github.com/iced-rs/iced/pull/1731) +- Sliders bleeding over their rail. [#1721](https://github.com/iced-rs/iced/pull/1721) + +### Removed +- `Fill` variant for `Alignment`. [#1735](https://github.com/iced-rs/iced/pull/1735) + +Many thanks to... + +- @ahoneybun +- @bq-wrongway +- @bungoboingo +- @casperstorm +- @Davidster +- @ElhamAryanpur +- @FinnPerry +- @GyulyVGC +- @JungleTryne +- @lupd +- @mmstick +- @nicksenger +- @Night-Hunter-NF +- @tarkah +- @traxys +- @Xaeroxe + ## [0.8.0] - 2023-02-18 ### Added - Generic pixel units. [#1711](https://github.com/iced-rs/iced/pull/1711) @@ -414,7 +467,8 @@ Many thanks to... ### Added - First release! :tada: -[Unreleased]: https://github.com/iced-rs/iced/compare/0.8.0...HEAD +[Unreleased]: https://github.com/iced-rs/iced/compare/0.9.0...HEAD +[0.9.0]: https://github.com/iced-rs/iced/compare/0.8.0...0.9.0 [0.8.0]: https://github.com/iced-rs/iced/compare/0.7.0...0.8.0 [0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0 [0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0 diff --git a/Cargo.toml b/Cargo.toml index 7f89b05e..4a82c923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced" -version = "0.8.0" +version = "0.9.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A cross-platform GUI library inspired by Elm" @@ -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,10 @@ 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 broken "sRGB linear" blending to reproduce color management of the Web +web-colors = ["iced_renderer/web-colors"] +# Enables the advanced module +advanced = [] # Enables experimental multi-window support for iced_winit + wgpu. multi-window = ["iced_winit/multi-window"] @@ -57,37 +52,29 @@ members = [ "core", "futures", "graphics", - "glow", - "glutin", - "lazy", - "native", + "runtime", + "renderer", "style", + "tiny_skia", "wgpu", + "widget", "winit", "examples/*", ] [dependencies] -iced_core = { version = "0.8", path = "core" } +iced_core = { version = "0.9", path = "core" } iced_futures = { version = "0.6", path = "futures" } -iced_native = { version = "0.9", path = "native" } -iced_graphics = { version = "0.7", path = "graphics" } -iced_winit = { version = "0.8", path = "winit", features = ["application"] } -iced_glutin = { version = "0.7", path = "glutin", optional = true } -iced_glow = { version = "0.7", path = "glow", optional = true } -thiserror = "1.0" +iced_renderer = { version = "0.1", path = "renderer" } +iced_widget = { version = "0.1", path = "widget" } +iced_winit = { version = "0.9", path = "winit", features = ["application"] } +thiserror = "1" [dependencies.image_rs] version = "0.24" package = "image" optional = true -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.9", path = "wgpu", optional = true } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"], optional = true } - [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas", "qr_code"] diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md new file mode 100644 index 00000000..809371cb --- /dev/null +++ b/DEPENDENCIES.md @@ -0,0 +1,33 @@ +# Dependencies + +Iced requires some system dependencies to work, and not +all operating systems come with them installed. + +You can follow the provided instructions for your system to +get them, if your system isn't here, add it! + +## NixOS + +You can add this `shell.nix` to your project and use it by running `nix-shell`: + +```nix +{ pkgs ? import {} }: + +pkgs.mkShell rec { + buildInputs = with pkgs; [ + expat + fontconfig + freetype + freetype.dev + libGL + pkgconfig + xorg.libX11 + xorg.libXcursor + xorg.libXi + xorg.libXrandr + ]; + + LD_LIBRARY_PATH = + builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs; +} +``` diff --git a/README.md b/README.md index a5ebf230..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 @@ -68,7 +68,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap], Add `iced` as a dependency in your `Cargo.toml`: ```toml -iced = "0.8" +iced = "0.9" ``` If your project is using a Rust edition older than 2021, then you will need to @@ -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.8", 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/ROADMAP.md b/ROADMAP.md index ef6df0e4..f1893664 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -19,6 +19,7 @@ Once a step is completed, it is collapsed and added to this list: * [x] Custom styling ([#146]) * [x] Canvas for 2D graphics ([#193]) * [x] Basic overlay support ([#444]) + * [x] Animations [#31] [#24]: https://github.com/iced-rs/iced/issues/24 [#25]: https://github.com/iced-rs/iced/issues/25 @@ -29,6 +30,7 @@ Once a step is completed, it is collapsed and added to this list: [#146]: https://github.com/iced-rs/iced/pull/146 [#193]: https://github.com/iced-rs/iced/pull/193 [#444]: https://github.com/iced-rs/iced/pull/444 +[#31]: https://github.com/iced-rs/iced/issues/31 ### Multi-window support ([#27]) Open and control multiple windows at runtime. @@ -39,16 +41,7 @@ This approach should also allow us to perform custom optimizations for this part [#27]: https://github.com/iced-rs/iced/issues/27 -### Animations ([#31]) -Allow widgets to request a redraw at a specific time. - -This is a necessary feature to render loading spinners, a blinking text cursor, GIF images, etc. - -[`winit`] allows flexible control of its event loop. We may be able to use [`ControlFlow::WaitUntil`](https://docs.rs/winit/0.20.0-alpha3/winit/event_loop/enum.ControlFlow.html#variant.WaitUntil) for this purpose. - -[#31]: https://github.com/iced-rs/iced/issues/31 - -### Canvas widget for 3D graphics ([#32]) +### Canvas widget for 3D graphics (~~[#32]~~ [#343]) A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc. As a first approach, we could expose the underlying renderer directly here, and couple this widget with it ([`wgpu`] for now). Once [`wgpu`] gets WebGL or WebGPU support, this widget will be able to run on the web too. The renderer primitive could be a simple texture that the widget draws to. @@ -56,6 +49,7 @@ As a first approach, we could expose the underlying renderer directly here, and In the long run, we could expose a renderer-agnostic abstraction to perform the drawing. [#32]: https://github.com/iced-rs/iced/issues/32 +[#343]: https://github.com/iced-rs/iced/issues/343 ### Text shaping and font fallback ([#33]) [`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping]. 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 "] 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 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 for Background { @@ -14,8 +17,14 @@ impl From for Background { } } -impl From for Option { - fn from(color: Color) -> Self { - Some(Background::from(color)) +impl From for Background { + fn from(gradient: Gradient) -> Self { + Background::Gradient(gradient) + } +} + +impl From 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 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 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; + + /// 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/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 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 for Srgba { fn from(c: Color) -> Self { Srgba::new(c.r, c.g, c.b, c.a) @@ -199,15 +199,15 @@ impl From 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 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 for Srgb { fn from(c: Color) -> Self { Srgb::new(c.r, c.g, c.b) @@ -218,12 +218,12 @@ impl From 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 similarity index 84% rename from native/src/element.rs rename to 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 { + /// # 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 +159,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)), /// ) - /// } + /// }, /// ) /// } /// } @@ -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( @@ -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 similarity index 98% rename from native/src/event.rs rename to 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 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; 8], +} + +impl Linear { + /// Creates a new [`Linear`] gradient with the given angle in [`Radians`]. + pub fn new(angle: impl Into) -> 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, + ) -> 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 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 d3596b4d..76d775e7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -7,7 +7,7 @@ //! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true) //! //! [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 similarity index 100% rename from native/src/mouse/click.rs rename to 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 { + 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 { + 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 { + 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 { + 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 similarity index 88% rename from native/src/overlay.rs rename to 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> { + None + } } /// Returns a [`Group`] of overlay [`Element`] children. diff --git a/native/src/overlay/element.rs b/core/src/overlay/element.rs similarity index 81% rename from native/src/overlay/element.rs rename to 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> { + 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> { + 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 similarity index 79% rename from native/src/overlay/group.rs rename to 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: &::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> { + let children = self + .children + .iter_mut() + .zip(layout.children()) + .filter_map(|(child, layout)| child.overlay(layout, renderer)) + .collect::>(); + + (!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 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 77% rename from native/src/renderer.rs rename to 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 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 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 similarity index 72% rename from native/src/renderer/null.rs rename to 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_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, - ) -> (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 { 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..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 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, + ) -> 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; + + /// 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 60% rename from native/src/widget.rs rename to 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 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 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 56% rename from native/src/widget/operation/scrollable.rs rename to 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(target: Id, offset: RelativeOffset) -> impl Operation { 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(target: Id, offset: AbsoluteOffset) -> impl Operation { + struct ScrollTo { + target: Id, + offset: AbsoluteOffset, + } + + impl Operation for ScrollTo { + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + 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 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 70% rename from native/src/widget/text.rs rename to 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])); -/// ``` -/// -/// ![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 +20,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 +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) -> 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 +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 for Text<'a, Renderer> @@ -136,12 +139,16 @@ where let size = self.size.unwrap_or_else(|| renderer.default_size()); - let bounds = limits.max(); + let bounds = renderer.measure( + &self.content, + size, + self.line_height, + self.font.unwrap_or_else(|| renderer.default_font()), + limits.max(), + self.shaping, + ); - let (width, height) = - renderer.measure(&self.content, size, self.font.clone(), bounds); - - 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( 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 +218,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 +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, +} 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..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 similarity index 100% rename from native/src/window/event.rs rename to 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, + width: u32, + height: u32, +) -> Result { + const PIXEL_SIZE: usize = mem::size_of::() * 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, + size: Size, +} + +impl Icon { + /// Returns the raw data of the [`Icon`]. + pub fn into_raw(self) -> (Vec, Size) { + (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 similarity index 100% rename from native/src/window/id.rs rename to 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 similarity index 100% rename from native/src/window/mode.rs rename to 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 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/settings.rs b/core/src/window/settings.rs similarity index 55% rename from native/src/window/settings.rs rename to 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, + + /// 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 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 similarity index 100% rename from native/src/window/user_attention.rs rename to core/src/window/user_attention.rs diff --git a/docs/release_summary.py b/docs/release_summary.py new file mode 100644 index 00000000..cd4593b5 --- /dev/null +++ b/docs/release_summary.py @@ -0,0 +1,52 @@ +import re +import sys +import requests +from typing import List, Tuple + +if len(sys.argv) < 3: + print("Usage: python release_summary.py ") + exit(1) + +TOKEN = sys.argv[1] +HEADERS = {"Authorization": f"Bearer {TOKEN}"} +PR_COMMIT_REGEX = re.compile(r"(?i)Merge pull request #(\d+).*") + +def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> List[Tuple[str, int, str, str]]: + prs = [] + compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master" + compare_response = requests.get(compare_url, headers=HEADERS) + + if compare_response.status_code == 200: + compare_data = compare_response.json() + for commit in compare_data["commits"]: + match = PR_COMMIT_REGEX.search(commit["commit"]["message"]) + if match: + pr_number = int(match.group(1)) + pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}" + pr_response = requests.get(pr_url, headers=HEADERS) + if pr_response.status_code == 200: + pr_data = pr_response.json() + prs.append((pr_data["title"], pr_number, pr_data["html_url"], pr_data["user"]["login"])) + else: + print(f"Error fetching PR {pr_number}: {pr_response.status_code}") + else: + print(f"Error comparing branches: {compare_response.status_code}") + + return prs + +def print_pr_list(prs: List[Tuple[str, int, str, str]]): + for pr in prs: + print(f"- {pr[0]}. [#{pr[1]}]({pr[2]})") + +def print_authors(prs: List[Tuple[str, int, str, str]]): + authors = set(pr[3] for pr in prs) + print("\nAuthors:") + for author in sorted(authors, key=str.casefold): + print(f"- @{author}") + +if __name__ == "__main__": + repo = "iced-rs/iced" + previous_release_branch = sys.argv[2] + merged_prs = get_merged_prs_since_release(repo, previous_release_branch) + print_pr_list(merged_prs) + print_authors(merged_prs) 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..df565859 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -1,12 +1,13 @@ use std::{f32::consts::PI, time::Instant}; use iced::executor; +use iced::mouse; use iced::widget::canvas::{ - self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke, + self, stroke, Cache, Canvas, 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 +76,12 @@ impl canvas::Program for Arc { fn draw( &self, _state: &Self::State, + renderer: &Renderer, theme: &Theme, bounds: Rectangle, - _cursor: Cursor, + _cursor: mouse::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..310be28f 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -61,10 +61,8 @@ impl Sandbox for Example { mod bezier { use iced::mouse; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{ - self, Canvas, Cursor, Frame, Geometry, Path, Stroke, - }; - use iced::{Element, Length, Point, Rectangle, Theme}; + use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; + use iced::{Element, Length, Point, Rectangle, Renderer, Theme}; #[derive(Default)] pub struct State { @@ -100,10 +98,10 @@ mod bezier { state: &mut Self::State, event: Event, bounds: Rectangle, - cursor: Cursor, + cursor: mouse::Cursor, ) -> (event::Status, Option) { let cursor_position = - if let Some(position) = cursor.position_in(&bounds) { + if let Some(position) = cursor.position_in(bounds) { position } else { return (event::Status::Ignored, None); @@ -152,22 +150,26 @@ mod bezier { fn draw( &self, state: &Self::State, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, - cursor: Cursor, + cursor: mouse::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 { @@ -179,9 +181,9 @@ mod bezier { &self, _state: &Self::State, bounds: Rectangle, - cursor: Cursor, + cursor: mouse::Cursor, ) -> mouse::Interaction { - if cursor.is_over(&bounds) { + if cursor.is_over(bounds) { mouse::Interaction::Crosshair } else { mouse::Interaction::default() @@ -216,10 +218,15 @@ 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: mouse::Cursor, + ) -> Geometry { + let mut frame = Frame::new(renderer, bounds.size()); - if let Some(cursor_position) = cursor.position_in(&bounds) { + if let Some(cursor_position) = cursor.position_in(bounds) { match *self { Pending::One { from } => { let line = Path::line(from, cursor_position); 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..ef1a054d 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_slice()) + .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..fae77bc0 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,11 +1,10 @@ use iced::executor; -use iced::widget::canvas::{ - stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke, -}; +use iced::mouse; +use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; 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 +82,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, + _cursor: mouse::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/Cargo.toml b/examples/color_palette/Cargo.toml index 8fd37202..3be732bb 100644 --- a/examples/color_palette/Cargo.toml +++ b/examples/color_palette/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "palette"] } -palette = "0.6.0" +palette = "0.7.0" diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index a2df36c2..736a9d53 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,10 +1,14 @@ -use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}; +use iced::alignment::{self, Alignment}; +use iced::mouse; +use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path}; use iced::widget::{column, row, text, Slider}; use iced::{ - alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox, - Settings, Size, Vector, + Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings, + Size, Vector, +}; +use palette::{ + self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue, }; -use palette::{self, convert::FromColor, Hsl, Srgb}; use std::marker::PhantomData; use std::ops::RangeInclusive; @@ -49,12 +53,12 @@ impl Sandbox for ColorPalette { fn update(&mut self, message: Message) { let srgb = match message { - Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb), - Message::HslColorChanged(hsl) => palette::Srgb::from_color(hsl), - Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv), - Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb), - Message::LabColorChanged(lab) => palette::Srgb::from_color(lab), - Message::LchColorChanged(lch) => palette::Srgb::from_color(lch), + Message::RgbColorChanged(rgb) => Rgb::from(rgb), + Message::HslColorChanged(hsl) => Rgb::from_color(hsl), + Message::HsvColorChanged(hsv) => Rgb::from_color(hsv), + Message::HwbColorChanged(hwb) => Rgb::from_color(hwb), + Message::LabColorChanged(lab) => Rgb::from_color(lab), + Message::LchColorChanged(lch) => Rgb::from_color(lch), }; self.theme = Theme::new(srgb); @@ -63,7 +67,7 @@ impl Sandbox for ColorPalette { fn view(&self) -> Element { let base = self.theme.base; - let srgb = palette::Srgb::from(base); + let srgb = Rgb::from(base); let hsl = palette::Hsl::from_color(srgb); let hsv = palette::Hsv::from_color(srgb); let hwb = palette::Hwb::from_color(srgb); @@ -95,12 +99,10 @@ struct Theme { impl Theme { pub fn new(base: impl Into) -> Theme { - use palette::{Hue, Shade}; - let base = base.into(); // Convert to HSL color for manipulation - let hsl = Hsl::from_color(Srgb::from(base)); + let hsl = Hsl::from_color(Rgb::from(base)); let lower = [ hsl.shift_hue(-135.0).lighten(0.075), @@ -119,12 +121,12 @@ impl Theme { Theme { lower: lower .iter() - .map(|&color| Srgb::from_color(color).into()) + .map(|&color| Rgb::from_color(color).into()) .collect(), base, higher: higher .iter() - .map(|&color| Srgb::from_color(color).into()) + .map(|&color| Rgb::from_color(color).into()) .collect(), canvas_cache: canvas::Cache::default(), } @@ -209,14 +211,14 @@ impl Theme { text.vertical_alignment = alignment::Vertical::Bottom; - let hsl = Hsl::from_color(Srgb::from(self.base)); + let hsl = Hsl::from_color(Rgb::from(self.base)); for i in 0..self.len() { let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0); let graded = Hsl { lightness: 1.0 - pct, ..hsl }; - let color: Color = Srgb::from_color(graded).into(); + let color: Color = Rgb::from_color(graded).into(); let anchor = Point { x: (i as f32) * box_size.width, @@ -243,11 +245,12 @@ impl canvas::Program for Theme { fn draw( &self, _state: &Self::State, + renderer: &Renderer, _theme: &iced::Theme, bounds: Rectangle, - _cursor: Cursor, + _cursor: mouse::Cursor, ) -> Vec { - let theme = self.canvas_cache.draw(bounds.size(), |frame| { + let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| { self.draw(frame); }); @@ -351,7 +354,7 @@ impl ColorSpace for palette::Hsl { fn components(&self) -> [f32; 3] { [ - self.hue.to_positive_degrees(), + self.hue.into_positive_degrees(), self.saturation, self.lightness, ] @@ -360,7 +363,7 @@ impl ColorSpace for palette::Hsl { fn to_string(&self) -> String { format!( "hsl({:.1}, {:.1}%, {:.1}%)", - self.hue.to_positive_degrees(), + self.hue.into_positive_degrees(), 100.0 * self.saturation, 100.0 * self.lightness ) @@ -377,13 +380,17 @@ impl ColorSpace for palette::Hsv { } fn components(&self) -> [f32; 3] { - [self.hue.to_positive_degrees(), self.saturation, self.value] + [ + self.hue.into_positive_degrees(), + self.saturation, + self.value, + ] } fn to_string(&self) -> String { format!( "hsv({:.1}, {:.1}%, {:.1}%)", - self.hue.to_positive_degrees(), + self.hue.into_positive_degrees(), 100.0 * self.saturation, 100.0 * self.value ) @@ -405,7 +412,7 @@ impl ColorSpace for palette::Hwb { fn components(&self) -> [f32; 3] { [ - self.hue.to_positive_degrees(), + self.hue.into_positive_degrees(), self.whiteness, self.blackness, ] @@ -414,7 +421,7 @@ impl ColorSpace for palette::Hwb { fn to_string(&self) -> String { format!( "hwb({:.1}, {:.1}%, {:.1}%)", - self.hue.to_positive_degrees(), + self.hue.into_positive_degrees(), 100.0 * self.whiteness, 100.0 * self.blackness ) @@ -449,7 +456,7 @@ impl ColorSpace for palette::Lch { } fn components(&self) -> [f32; 3] { - [self.l, self.chroma, self.hue.to_positive_degrees()] + [self.l, self.chroma, self.hue.into_positive_degrees()] } fn to_string(&self) -> String { @@ -457,7 +464,7 @@ impl ColorSpace for palette::Lch { "Lch({:.1}, {:.1}, {:.1})", self.l, self.chroma, - self.hue.to_positive_degrees() + self.hue.into_positive_degrees() ) } } 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 bbf549e7..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; @@ -141,8 +134,8 @@ mod numeric_input { .map(u32::to_string) .as_deref() .unwrap_or(""), - Event::InputChanged, ) + .on_input(Event::InputChanged) .padding(10), button("+", Event::IncrementPressed), ] @@ -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..4b300116 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -1,9 +1,10 @@ //! 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::mouse; + use iced::{Color, Element, Length, Rectangle, Size}; pub struct CustomQuad { size: f32, @@ -48,7 +49,7 @@ mod quad { _theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, - _cursor_position: Point, + _cursor: mouse::Cursor, _viewport: &Rectangle, ) { renderer.fill_quad( 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..713bc62d 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -9,10 +9,11 @@ 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::mouse; + use iced::{Color, Element, Length, Rectangle, Size}; pub struct Circle { radius: f32, @@ -55,7 +56,7 @@ mod circle { _theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, - _cursor_position: Point, + _cursor: mouse::Cursor, _viewport: &Rectangle, ) { renderer.fill_quad( 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 39dd843f..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; @@ -18,10 +18,7 @@ pub struct Download { url: String, } -async fn download( - id: I, - state: State, -) -> (Option<(I, Progress)>, State) { +async fn download(id: I, state: State) -> ((I, Progress), State) { match state { State::Ready(url) => { let response = reqwest::get(&url).await; @@ -30,7 +27,7 @@ async fn download( Ok(response) => { if let Some(total) = response.content_length() { ( - Some((id, Progress::Started)), + (id, Progress::Started), State::Downloading { response, total, @@ -38,10 +35,10 @@ async fn download( }, ) } else { - (Some((id, Progress::Errored)), State::Finished) + ((id, Progress::Errored), State::Finished) } } - Err(_) => (Some((id, Progress::Errored)), State::Finished), + Err(_) => ((id, Progress::Errored), State::Finished), } } State::Downloading { @@ -55,7 +52,7 @@ async fn download( let percentage = (downloaded as f32 / total as f32) * 100.0; ( - Some((id, Progress::Advanced(percentage))), + (id, Progress::Advanced(percentage)), State::Downloading { response, total, @@ -63,8 +60,8 @@ async fn download( }, ) } - Ok(None) => (Some((id, Progress::Finished)), State::Finished), - Err(_) => (Some((id, Progress::Errored)), State::Finished), + Ok(None) => ((id, Progress::Finished), State::Finished), + Err(_) => ((id, Progress::Errored), State::Finished), }, State::Finished => { // We do not let the stream die, as it would start a 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 b57010c7..70659f52 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(window::Id), } @@ -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..e951d734 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) @@ -204,15 +204,14 @@ fn view_controls<'a>( mod grid { use crate::Preset; + use iced::alignment; + use iced::mouse; use iced::touch; use iced::widget::canvas; use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{ - Cache, Canvas, Cursor, Frame, Geometry, Path, Text, - }; + use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; use iced::{ - alignment, mouse, Color, Element, Length, Point, Rectangle, Size, - Theme, Vector, + Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; use rustc_hash::{FxHashMap, FxHashSet}; use std::future::Future; @@ -401,14 +400,14 @@ mod grid { interaction: &mut Interaction, event: Event, bounds: Rectangle, - cursor: Cursor, + cursor: mouse::Cursor, ) -> (event::Status, Option) { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { *interaction = Interaction::None; } let cursor_position = - if let Some(position) = cursor.position_in(&bounds) { + if let Some(position) = cursor.position_in(bounds) { position } else { return (event::Status::Ignored, None); @@ -536,13 +535,14 @@ mod grid { fn draw( &self, _interaction: &Interaction, + renderer: &Renderer, _theme: &Theme, bounds: Rectangle, - cursor: Cursor, + cursor: mouse::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,12 +565,11 @@ 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| { - Cell::at(self.project(position, frame.size())) - }); + let hovered_cell = cursor.position_in(bounds).map(|position| { + Cell::at(self.project(position, frame.size())) + }); if let Some(cell) = hovered_cell { frame.with_save(|frame| { @@ -626,38 +625,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] } @@ -667,13 +668,13 @@ mod grid { &self, interaction: &Interaction, bounds: Rectangle, - cursor: Cursor, + cursor: mouse::Cursor, ) -> mouse::Interaction { match interaction { Interaction::Drawing => mouse::Interaction::Crosshair, Interaction::Erasing => mouse::Interaction::Crosshair, Interaction::Panning { .. } => mouse::Interaction::Grabbing, - Interaction::None if cursor.is_over(&bounds) => { + Interaction::None if cursor.is_over(bounds) => { mouse::Interaction::Crosshair } _ => mouse::Interaction::default(), diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 22ede0e0..6068d651 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -6,6 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } -iced_graphics = { path = "../../graphics" } +iced = { path = "../..", features = ["advanced"] } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 9bacce7f..3bc7f46b 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -1,24 +1,12 @@ //! 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_native::layout; - use iced_native::widget::{self, Widget}; - use iced_native::{ - Element, Layout, Length, Point, Rectangle, Size, Vector, - }; + use iced::advanced::graphics::color; + use iced::advanced::layout::{self, Layout}; + use iced::advanced::renderer; + use iced::advanced::widget::{self, Widget}; + use iced::mouse; + use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector}; #[derive(Debug, Clone, Copy, Default)] pub struct Rainbow; @@ -27,10 +15,7 @@ mod rainbow { Rainbow } - impl Widget> for Rainbow - where - B: Backend, - { + impl Widget for Rainbow { fn width(&self) -> Length { Length::Fill } @@ -41,7 +26,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,17 +37,17 @@ 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, + cursor: mouse::Cursor, _viewport: &Rectangle, ) { - use iced_graphics::triangle::Mesh2D; - use iced_native::Renderer as _; + use iced::advanced::graphics::mesh::{self, Mesh, SolidVertex2D}; + use iced::advanced::Renderer as _; - let b = layout.bounds(); + let bounds = layout.bounds(); // R O Y G B I V let color_r = [1.0, 0.0, 0.0, 1.0]; @@ -75,61 +60,61 @@ mod rainbow { let color_v = [0.75, 0.0, 0.5, 1.0]; let posn_center = { - if b.contains(cursor_position) { - [cursor_position.x - b.x, cursor_position.y - b.y] + if let Some(cursor_position) = cursor.position_in(bounds) { + [cursor_position.x, cursor_position.y] } else { - [b.width / 2.0, b.height / 2.0] + [bounds.width / 2.0, bounds.height / 2.0] } }; let posn_tl = [0.0, 0.0]; - let posn_t = [b.width / 2.0, 0.0]; - let posn_tr = [b.width, 0.0]; - let posn_r = [b.width, b.height / 2.0]; - let posn_br = [b.width, b.height]; - let posn_b = [(b.width / 2.0), b.height]; - let posn_bl = [0.0, b.height]; - let posn_l = [0.0, b.height / 2.0]; + let posn_t = [bounds.width / 2.0, 0.0]; + let posn_tr = [bounds.width, 0.0]; + let posn_r = [bounds.width, bounds.height / 2.0]; + let posn_br = [bounds.width, bounds.height]; + let posn_b = [(bounds.width / 2.0), bounds.height]; + let posn_bl = [0.0, bounds.height]; + let posn_l = [0.0, bounds.height / 2.0]; - let mesh = Primitive::SolidMesh { - size: b.size(), - buffers: Mesh2D { + let mesh = Mesh::Solid { + size: bounds.size(), + buffers: mesh::Indexed { vertices: vec![ - ColoredVertex2D { + SolidVertex2D { position: posn_center, - color: [1.0, 1.0, 1.0, 1.0], + color: color::pack([1.0, 1.0, 1.0, 1.0]), }, - ColoredVertex2D { + SolidVertex2D { position: posn_tl, - color: color_r, + color: color::pack(color_r), }, - ColoredVertex2D { + SolidVertex2D { position: posn_t, - color: color_o, + color: color::pack(color_o), }, - ColoredVertex2D { + SolidVertex2D { position: posn_tr, - color: color_y, + color: color::pack(color_y), }, - ColoredVertex2D { + SolidVertex2D { position: posn_r, - color: color_g, + color: color::pack(color_g), }, - ColoredVertex2D { + SolidVertex2D { position: posn_br, - color: color_gb, + color: color::pack(color_gb), }, - ColoredVertex2D { + SolidVertex2D { position: posn_b, - color: color_b, + color: color::pack(color_b), }, - ColoredVertex2D { + SolidVertex2D { position: posn_bl, - color: color_i, + color: color::pack(color_i), }, - ColoredVertex2D { + SolidVertex2D { position: posn_l, - color: color_v, + color: color::pack(color_v), }, ], indices: vec![ @@ -145,16 +130,16 @@ mod rainbow { }, }; - renderer.with_translation(Vector::new(b.x, b.y), |renderer| { - renderer.draw_primitive(mesh); - }); + renderer.with_translation( + Vector::new(bounds.x, bounds.y), + |renderer| { + renderer.draw_mesh(mesh); + }, + ); } } - 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 78% rename from examples/integration_wgpu/Cargo.toml rename to examples/integration/Cargo.toml index eaa1df7e..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,8 +7,10 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu", features = ["webgl"] } -env_logger = "0.8" +iced_wgpu = { path = "../../wgpu" } +iced_widget = { path = "../../widget" } +iced_renderer = { path = "../../renderer", features = ["wgpu"] } +env_logger = "0.10" [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.7" 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