diff options
author | 2022-12-02 18:53:21 +0100 | |
---|---|---|
committer | 2022-12-02 18:53:21 +0100 | |
commit | 4029a1cdaaac1abbdcc141b20469a49670cd99b6 (patch) | |
tree | 71fa9d9c4aa1f02ce05771db43a4bb7bc6570e77 | |
parent | 676d8efe03ebdbeeb95aef96b8097395b788b1ab (diff) | |
parent | 8b55e9b9e6ba0b83038dd491dd34d95b4f9a381b (diff) | |
download | iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.gz iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.bz2 iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.zip |
Merge branch 'master' into non-uniform-border-radius-for-quads
147 files changed, 4786 insertions, 2142 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0fa47c..d098e8ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.0] - 2022-11-10 +### Added +- __[Stabilization of stateless widgets][stateless]__ (#1393) + The old widget API has been completely replaced by stateless widgets (introduced in #1284). Alongside the new API, there are a bunch of new helper functions and macros for easily describing view logic (like `row!` and `column!`). + +- __[First-class theming][theming]__ (#1362) + A complete overhaul of our styling primitives, introducing a `Theme` as a first-class concept of the library. + +- __[Widget operations][operations]__ (#1399) + An abstraction that can be used to traverse (and operate on) the widget tree of an application in order to query or update some widget state. + +- __[`Lazy` widget][lazy]__ (#1400) + A widget that can call some view logic lazily only when some data has changed. Thanks to @nicksenger! + +- __[Linear gradient support for `Canvas`][gradient]__ (#1448) + The `Canvas` widget can draw linear gradients now. Thanks to @bungoboingo! + +- __[Touch support for `Canvas`][touch]__ (#1305) + The `Canvas` widget now supports touch events. Thanks to @artursapek! + +- __[`Image` and `Svg` support for `iced_glow`][image]__ (#1485) + Our OpenGL renderer now is capable of rendering both the `Image` and `Svg` widgets. Thanks to @ids1024! + +[stateless]: https://github.com/iced-rs/iced/pull/1393 +[theming]: https://github.com/iced-rs/iced/pull/1362 +[operations]: https://github.com/iced-rs/iced/pull/1399 +[lazy]: https://github.com/iced-rs/iced/pull/1400 +[gradient]: https://github.com/iced-rs/iced/pull/1448 +[touch]: https://github.com/iced-rs/iced/pull/1305 +[image]: https://github.com/iced-rs/iced/pull/1485 + ## [0.4.2] - 2022-05-03 ### Fixed - `Padding` type not exposed in `iced`. @@ -257,7 +288,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First release! :tada: -[Unreleased]: https://github.com/iced-rs/iced/compare/0.4.2...HEAD +[Unreleased]: https://github.com/iced-rs/iced/compare/0.5.0...HEAD +[0.5.0]: https://github.com/iced-rs/iced/compare/0.4.2...0.5.0 [0.4.2]: https://github.com/iced-rs/iced/compare/0.4.1...0.4.2 [0.4.1]: https://github.com/iced-rs/iced/compare/0.4.0...0.4.1 [0.4.0]: https://github.com/iced-rs/iced/compare/0.3.0...0.4.0 @@ -1,6 +1,6 @@ [package] name = "iced" -version = "0.4.2" +version = "0.5.2" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A cross-platform GUI library inspired by Elm" @@ -10,14 +10,13 @@ documentation = "https://docs.rs/iced" readme = "README.md" keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] -resolver = "2" [features] default = ["wgpu"] # Enables the `Image` widget -image = ["iced_wgpu/image", "image_rs"] +image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu/svg"] +svg = ["iced_wgpu?/svg", "iced_glow?/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget @@ -25,11 +24,9 @@ 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"] +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"] -# Enables using system fonts for `iced_glow` -glow_default_system_font = ["iced_glow/default_system_font"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -58,61 +55,29 @@ members = [ "style", "wgpu", "winit", - "examples/arc", - "examples/bezier_tool", - "examples/clock", - "examples/color_palette", - "examples/component", - "examples/counter", - "examples/custom_widget", - "examples/download_progress", - "examples/events", - "examples/exit", - "examples/game_of_life", - "examples/integration_opengl", - "examples/integration_wgpu", - "examples/lazy", - "examples/modern_art", - "examples/multitouch", - "examples/pane_grid", - "examples/pick_list", - "examples/pokedex", - "examples/progress_bar", - "examples/qr_code", - "examples/scrollable", - "examples/sierpinski_triangle", - "examples/solar_system", - "examples/stopwatch", - "examples/styling", - "examples/svg", - "examples/system_information", - "examples/todos", - "examples/tooltip", - "examples/tour", - "examples/url_handler", - "examples/websocket", + "examples/*", ] [dependencies] -iced_core = { version = "0.5", path = "core" } -iced_futures = { version = "0.4", path = "futures" } -iced_native = { version = "0.5", path = "native" } -iced_graphics = { version = "0.3", path = "graphics" } -iced_winit = { version = "0.4", path = "winit", features = ["application"] } -iced_glutin = { version = "0.3", path = "glutin", optional = true } -iced_glow = { version = "0.3", path = "glow", optional = true } +iced_core = { version = "0.6", path = "core" } +iced_futures = { version = "0.5", path = "futures" } +iced_native = { version = "0.6", path = "native" } +iced_graphics = { version = "0.4", path = "graphics" } +iced_winit = { version = "0.5", path = "winit", features = ["application"] } +iced_glutin = { version = "0.4", path = "glutin", optional = true } +iced_glow = { version = "0.4", path = "glow", optional = true } thiserror = "1.0" [dependencies.image_rs] -version = "0.23" +version = "0.24" package = "image" optional = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_wgpu = { version = "0.5", path = "wgpu", optional = true } +iced_wgpu = { version = "0.6", path = "wgpu", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -iced_wgpu = { version = "0.5", path = "wgpu", features = ["webgl"], optional = true } +iced_wgpu = { version = "0.6", path = "wgpu", features = ["webgl"], optional = true } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] @@ -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.4" +iced = "0.5" ``` If your project is using a Rust edition older than 2021, then you will need to @@ -215,7 +215,7 @@ cargo run --features iced/glow --package game_of_life and then use it in your project with ```toml -iced = { version = "0.4", default-features = false, features = ["glow"] } +iced = { version = "0.5", default-features = false, features = ["glow"] } ``` __NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0, diff --git a/core/Cargo.toml b/core/Cargo.toml index c9c7686e..6fd0a38c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_core" -version = "0.5.0" +version = "0.6.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "The essential concepts of Iced" diff --git a/core/src/lib.rs b/core/src/lib.rs index 03ba8cca..383b4f73 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -7,7 +7,7 @@ //!  //! //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/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" diff --git a/core/src/padding.rs b/core/src/padding.rs index 22467d6b..140ad8ee 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -1,3 +1,5 @@ +use crate::Size; + /// An amount of space to pad for each side of a box /// /// You can leverage the `From` trait to build [`Padding`] conveniently: @@ -71,9 +73,21 @@ impl Padding { pub fn horizontal(self) -> u16 { self.left + self.right } + + /// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`]. + pub fn fit(self, inner: Size, outer: Size) -> Self { + let available = (outer - inner).max(Size::ZERO); + + Padding { + top: self.top.min((available.height as u16) / 2), + right: self.right.min((available.width as u16) / 2), + bottom: self.bottom.min((available.height as u16) / 2), + left: self.left.min((available.width as u16) / 2), + } + } } -impl std::convert::From<u16> for Padding { +impl From<u16> for Padding { fn from(p: u16) -> Self { Padding { top: p, @@ -84,7 +98,7 @@ impl std::convert::From<u16> for Padding { } } -impl std::convert::From<[u16; 2]> for Padding { +impl From<[u16; 2]> for Padding { fn from(p: [u16; 2]) -> Self { Padding { top: p[0], @@ -95,7 +109,7 @@ impl std::convert::From<[u16; 2]> for Padding { } } -impl std::convert::From<[u16; 4]> for Padding { +impl From<[u16; 4]> for Padding { fn from(p: [u16; 4]) -> Self { Padding { top: p[0], diff --git a/core/src/size.rs b/core/src/size.rs index 2db33a88..31f3171b 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -34,6 +34,22 @@ impl Size { height: self.height + padding.vertical() as f32, } } + + /// Returns the minimum of each component of this size and another + pub fn min(self, other: Self) -> Self { + Size { + width: self.width.min(other.width), + height: self.height.min(other.height), + } + } + + /// Returns the maximum of each component of this size and another + pub fn max(self, other: Self) -> Self { + Size { + width: self.width.max(other.width), + height: self.height.max(other.height), + } + } } impl From<[f32; 2]> for Size { @@ -68,3 +84,14 @@ impl From<Size> for Vector<f32> { Vector::new(size.width, size.height) } } + +impl std::ops::Sub for Size { + type Output = Size; + + fn sub(self, rhs: Self) -> Self::Output { + Size { + width: self.width - rhs.width, + height: self.height - rhs.height, + } + } +} diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml new file mode 100644 index 00000000..22ede0e0 --- /dev/null +++ b/examples/geometry/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "geometry" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } +iced_graphics = { path = "../../graphics" } diff --git a/examples/geometry/README.md b/examples/geometry/README.md new file mode 100644 index 00000000..4d5c81cb --- /dev/null +++ b/examples/geometry/README.md @@ -0,0 +1,18 @@ +## Geometry + +A custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../../wgpu). + +The __[`main`]__ file contains all the code of the example. + +<div align="center"> + <a href="https://gfycat.com/activeunfitkangaroo"> + <img src="https://thumbs.gfycat.com/ActiveUnfitKangaroo-small.gif"> + </a> +</div> + +You can run it with `cargo run`: +``` +cargo run --package geometry +``` + +[`main`]: src/main.rs diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs new file mode 100644 index 00000000..9bacce7f --- /dev/null +++ b/examples/geometry/src/main.rs @@ -0,0 +1,212 @@ +//! 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, + }; + + #[derive(Debug, Clone, Copy, Default)] + pub struct Rainbow; + + pub fn rainbow() -> Rainbow { + Rainbow + } + + impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow + where + B: Backend, + { + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer<B, T>, + limits: &layout::Limits, + ) -> layout::Node { + let size = limits.width(Length::Fill).resolve(Size::ZERO); + + layout::Node::new(Size::new(size.width, size.width)) + } + + fn draw( + &self, + _tree: &widget::Tree, + renderer: &mut Renderer<B, T>, + _theme: &T, + _style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + ) { + use iced_graphics::triangle::Mesh2D; + use iced_native::Renderer as _; + + let b = layout.bounds(); + + // R O Y G B I V + let color_r = [1.0, 0.0, 0.0, 1.0]; + let color_o = [1.0, 0.5, 0.0, 1.0]; + let color_y = [1.0, 1.0, 0.0, 1.0]; + let color_g = [0.0, 1.0, 0.0, 1.0]; + let color_gb = [0.0, 1.0, 0.5, 1.0]; + let color_b = [0.0, 0.2, 1.0, 1.0]; + let color_i = [0.5, 0.0, 1.0, 1.0]; + 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] + } else { + [b.width / 2.0, b.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 mesh = Primitive::SolidMesh { + size: b.size(), + buffers: Mesh2D { + vertices: vec![ + ColoredVertex2D { + position: posn_center, + color: [1.0, 1.0, 1.0, 1.0], + }, + ColoredVertex2D { + position: posn_tl, + color: color_r, + }, + ColoredVertex2D { + position: posn_t, + color: color_o, + }, + ColoredVertex2D { + position: posn_tr, + color: color_y, + }, + ColoredVertex2D { + position: posn_r, + color: color_g, + }, + ColoredVertex2D { + position: posn_br, + color: color_gb, + }, + ColoredVertex2D { + position: posn_b, + color: color_b, + }, + ColoredVertex2D { + position: posn_bl, + color: color_i, + }, + ColoredVertex2D { + position: posn_l, + color: color_v, + }, + ], + indices: vec![ + 0, 1, 2, // TL + 0, 2, 3, // T + 0, 3, 4, // TR + 0, 4, 5, // R + 0, 5, 6, // BR + 0, 6, 7, // B + 0, 7, 8, // BL + 0, 8, 1, // L + ], + }, + }; + + renderer.with_translation(Vector::new(b.x, b.y), |renderer| { + renderer.draw_primitive(mesh); + }); + } + } + + impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>> + where + B: Backend, + { + fn from(rainbow: Rainbow) -> Self { + Self::new(rainbow) + } + } +} + +use iced::widget::{column, container, scrollable}; +use iced::{Element, Length, Sandbox, Settings}; +use rainbow::rainbow; + +pub fn main() -> iced::Result { + Example::run(Settings::default()) +} + +struct Example; + +impl Sandbox for Example { + type Message = (); + + fn new() -> Self { + Self + } + + fn title(&self) -> String { + String::from("Custom 2D geometry - Iced") + } + + fn update(&mut self, _: ()) {} + + fn view(&self) -> Element<()> { + let content = column![ + rainbow(), + "In this example we draw a custom widget Rainbow, using \ + the Mesh2D primitive. This primitive supplies a list of \ + triangles, expressed as vertices and indices.", + "Move your cursor over it, and see the center vertex \ + follow you!", + "Every Vertex2D defines its own color. You could use the \ + Mesh2D primitive to render virtually any two-dimensional \ + geometry for your widget.", + ] + .padding(20) + .spacing(20) + .max_width(500); + + let scrollable = + scrollable(container(content).width(Length::Fill).center_x()); + + container(scrollable) + .width(Length::Fill) + .height(Length::Fill) + .center_y() + .into() + } +} diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml new file mode 100644 index 00000000..8770acac --- /dev/null +++ b/examples/modal/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "modal" +version = "0.1.0" +authors = ["tarkah <admin@tarkah.dev>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = [] } +iced_native = { path = "../../native" } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs new file mode 100644 index 00000000..25483cf3 --- /dev/null +++ b/examples/modal/src/main.rs @@ -0,0 +1,475 @@ +use iced::widget::{ + self, button, column, container, horizontal_space, row, text, text_input, +}; +use iced::{ + executor, keyboard, subscription, theme, Alignment, Application, Command, + Element, Event, Length, Settings, Subscription, +}; + +use self::modal::Modal; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Default)] +struct App { + show_modal: bool, + email: String, + password: String, +} + +#[derive(Debug, Clone)] +enum Message { + ShowModal, + HideModal, + Email(String), + Password(String), + Submit, + Event(Event), +} + +impl Application for App { + type Executor = executor::Default; + type Message = Message; + type Theme = iced::Theme; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command<Message>) { + (App::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Modal - Iced") + } + + fn subscription(&self) -> Subscription<Self::Message> { + subscription::events().map(Message::Event) + } + + fn update(&mut self, message: Message) -> Command<Message> { + match message { + Message::ShowModal => { + self.show_modal = true; + widget::focus_next() + } + Message::HideModal => { + self.hide_modal(); + Command::none() + } + Message::Email(email) => { + self.email = email; + Command::none() + } + Message::Password(password) => { + self.password = password; + Command::none() + } + Message::Submit => { + if !self.email.is_empty() && !self.password.is_empty() { + self.hide_modal(); + } + + Command::none() + } + Message::Event(event) => match event { + Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Tab, + modifiers, + }) => { + if modifiers.shift() { + widget::focus_previous() + } else { + widget::focus_next() + } + } + Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Escape, + .. + }) => { + self.hide_modal(); + Command::none() + } + _ => Command::none(), + }, + } + } + + fn view(&self) -> Element<Message> { + let content = container( + column![ + row![ + text("Top Left"), + horizontal_space(Length::Fill), + text("Top Right") + ] + .align_items(Alignment::Start) + .height(Length::Fill), + container( + button(text("Show Modal")).on_press(Message::ShowModal) + ) + .center_x() + .center_y() + .width(Length::Fill) + .height(Length::Fill), + row![ + text("Bottom Left"), + horizontal_space(Length::Fill), + text("Bottom Right") + ] + .align_items(Alignment::End) + .height(Length::Fill), + ] + .height(Length::Fill), + ) + .padding(10) + .width(Length::Fill) + .height(Length::Fill); + + if self.show_modal { + let modal = container( + column![ + text("Sign Up").size(24), + column![ + column![ + text("Email").size(12), + text_input( + "abc@123.com", + &self.email, + Message::Email + ) + .on_submit(Message::Submit) + .padding(5), + ] + .spacing(5), + column![ + text("Password").size(12), + text_input("", &self.password, Message::Password) + .on_submit(Message::Submit) + .password() + .padding(5), + ] + .spacing(5), + button(text("Submit")).on_press(Message::HideModal), + ] + .spacing(10) + ] + .spacing(20), + ) + .width(Length::Units(300)) + .padding(10) + .style(theme::Container::Box); + + Modal::new(content, modal) + .on_blur(Message::HideModal) + .into() + } else { + content.into() + } + } +} + +impl App { + fn hide_modal(&mut self) { + self.show_modal = false; + self.email.clear(); + self.password.clear(); + } +} + +mod modal { + use iced_native::alignment::Alignment; + use iced_native::widget::{self, Tree}; + use iced_native::{ + event, layout, mouse, overlay, renderer, Clipboard, Color, Element, + Event, Layout, Length, Point, Rectangle, Shell, Size, Widget, + }; + + /// A widget that centers a modal element over some base element + pub struct Modal<'a, Message, Renderer> { + base: Element<'a, Message, Renderer>, + modal: Element<'a, Message, Renderer>, + on_blur: Option<Message>, + } + + impl<'a, Message, Renderer> Modal<'a, Message, Renderer> { + /// Returns a new [`Modal`] + pub fn new( + base: impl Into<Element<'a, Message, Renderer>>, + modal: impl Into<Element<'a, Message, Renderer>>, + ) -> Self { + Self { + base: base.into(), + modal: modal.into(), + on_blur: None, + } + } + + /// Sets the message that will be produces when the background + /// of the [`Modal`] is pressed + pub fn on_blur(self, on_blur: Message) -> Self { + Self { + on_blur: Some(on_blur), + ..self + } + } + } + + impl<'a, Message, Renderer> Widget<Message, Renderer> + for Modal<'a, Message, Renderer> + where + Renderer: iced_native::Renderer, + Message: Clone, + { + fn children(&self) -> Vec<Tree> { + vec![Tree::new(&self.base), Tree::new(&self.modal)] + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&[&self.base, &self.modal]); + } + + fn width(&self) -> Length { + self.base.as_widget().width() + } + + fn height(&self) -> Length { + self.base.as_widget().height() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.base.as_widget().layout(renderer, limits) + } + + fn on_event( + &mut self, + state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.base.as_widget_mut().on_event( + &mut state.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + state: &Tree, + renderer: &mut Renderer, + theme: &<Renderer as iced_native::Renderer>::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.base.as_widget().draw( + &state.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + state: &'b mut Tree, + layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option<overlay::Element<'b, Message, Renderer>> { + Some(overlay::Element::new( + layout.position(), + Box::new(Overlay { + content: &mut self.modal, + tree: &mut state.children[1], + size: layout.bounds().size(), + on_blur: self.on_blur.clone(), + }), + )) + } + + fn mouse_interaction( + &self, + state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.base.as_widget().mouse_interaction( + &state.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn operate( + &self, + state: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + self.base.as_widget().operate( + &mut state.children[0], + layout, + operation, + ); + } + } + + struct Overlay<'a, 'b, Message, Renderer> { + content: &'b mut Element<'a, Message, Renderer>, + tree: &'b mut Tree, + size: Size, + on_blur: Option<Message>, + } + + impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> + for Overlay<'a, 'b, Message, Renderer> + where + Renderer: iced_native::Renderer, + Message: Clone, + { + fn layout( + &self, + renderer: &Renderer, + _bounds: Size, + position: Point, + ) -> layout::Node { + let limits = layout::Limits::new(Size::ZERO, self.size) + .width(Length::Fill) + .height(Length::Fill); + + let mut child = self.content.as_widget().layout(renderer, &limits); + child.align(Alignment::Center, Alignment::Center, limits.max()); + + let mut node = layout::Node::with_children(self.size, vec![child]); + node.move_to(position); + + node + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let content_bounds = layout.children().next().unwrap().bounds(); + + if let Some(message) = self.on_blur.as_ref() { + if let Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) = &event + { + if !content_bounds.contains(cursor_position) { + shell.publish(message.clone()); + return event::Status::Captured; + } + } + } + + self.content.as_widget_mut().on_event( + self.tree, + event, + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + renderer.fill_quad( + renderer::Quad { + bounds: layout.bounds(), + border_radius: 0.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Color { + a: 0.80, + ..Color::BLACK + }, + ); + + self.content.as_widget().draw( + self.tree, + renderer, + theme, + style, + layout.children().next().unwrap(), + cursor_position, + &layout.bounds(), + ); + } + + fn operate( + &mut self, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + self.content.as_widget().operate( + self.tree, + layout.children().next().unwrap(), + operation, + ); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + self.tree, + layout.children().next().unwrap(), + cursor_position, + viewport, + renderer, + ) + } + } + + impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> + for Element<'a, Message, Renderer> + where + Renderer: 'a + iced_native::Renderer, + Message: 'a + Clone, + { + fn from(modal: Modal<'a, Message, Renderer>) -> Self { + Element::new(modal) + } + } +} diff --git a/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml index a48361ae..4242d209 100644 --- a/examples/modern_art/Cargo.toml +++ b/examples/modern_art/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } rand = "0.8.5" +env_logger = "0.9" diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs index 0dd21c74..28ed3e21 100644 --- a/examples/modern_art/src/main.rs +++ b/examples/modern_art/src/main.rs @@ -9,6 +9,8 @@ use iced::{ use rand::{thread_rng, Rng}; fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + ModernArt::run(Settings { antialiasing: true, ..Settings::default() diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index ae8fa22b..c9f1376c 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -29,6 +29,8 @@ enum Message { Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), TogglePin(pane_grid::Pane), + Maximize(pane_grid::Pane), + Restore, Close(pane_grid::Pane), CloseFocused, } @@ -114,6 +116,10 @@ impl Application for Example { *is_pinned = !*is_pinned; } } + Message::Maximize(pane) => self.panes.maximize(&pane), + Message::Restore => { + self.panes.restore(); + } Message::Close(pane) => { if let Some((_, sibling)) = self.panes.close(&pane) { self.focus = Some(sibling); @@ -157,7 +163,7 @@ impl Application for Example { let focus = self.focus; let total_panes = self.panes.len(); - let pane_grid = PaneGrid::new(&self.panes, |id, pane| { + let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| { let is_focused = focus == Some(id); let pin_button = button( @@ -178,7 +184,12 @@ impl Application for Example { .spacing(5); let title_bar = pane_grid::TitleBar::new(title) - .controls(view_controls(id, total_panes, pane.is_pinned)) + .controls(view_controls( + id, + total_panes, + pane.is_pinned, + is_maximized, + )) .padding(10) .style(if is_focused { style::title_bar_focused @@ -314,16 +325,35 @@ fn view_controls<'a>( pane: pane_grid::Pane, total_panes: usize, is_pinned: bool, + is_maximized: bool, ) -> Element<'a, Message> { - let mut button = button(text("Close").size(14)) + let mut row = row![].spacing(5); + + if total_panes > 1 { + let toggle = { + let (content, message) = if is_maximized { + ("Restore", Message::Restore) + } else { + ("Maximize", Message::Maximize(pane)) + }; + button(text(content).size(14)) + .style(theme::Button::Secondary) + .padding(3) + .on_press(message) + }; + + row = row.push(toggle); + } + + let mut close = button(text("Close").size(14)) .style(theme::Button::Destructive) .padding(3); if total_panes > 1 && !is_pinned { - button = button.on_press(Message::Close(pane)); + close = close.on_press(Message::Close(pane)); } - button.into() + row.push(close).into() } mod style { diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 56787a99..9e303576 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -79,10 +79,14 @@ impl Application for SolarSystem { } fn style(&self) -> theme::Application { - theme::Application::Custom(|_theme| application::Appearance { - background_color: Color::BLACK, - text_color: Color::WHITE, - }) + fn dark_background(_theme: &Theme) -> application::Appearance { + application::Appearance { + background_color: Color::BLACK, + text_color: Color::WHITE, + } + } + + theme::Application::from(dark_background as fn(&Theme) -> _) } fn subscription(&self) -> Subscription<Message> { diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index be48ae8c..690d9c09 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -131,7 +131,11 @@ impl Application for Todos { task.update(task_message); if should_focus { - text_input::focus(Task::text_input_id(i)) + let id = Task::text_input_id(i); + Command::batch(vec![ + text_input::focus(id.clone()), + text_input::select_all(id), + ]) } else { Command::none() } diff --git a/futures/Cargo.toml b/futures/Cargo.toml index 61ee00a5..b69cb59b 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_futures" -version = "0.4.1" +version = "0.5.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "Commands, subscriptions, and runtimes for Iced" diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 0479c63c..30cf7581 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -125,9 +125,9 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { /// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how /// to listen to time. /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.4/examples -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.4/examples/download_progress -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.4/examples/stopwatch +/// [examples]: https://github.com/iced-rs/iced/tree/0.5/examples +/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress +/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch pub trait Recipe<Hasher: std::hash::Hasher, Event> { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 18215e9b..1977f4b6 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glow" -version = "0.3.0" +version = "0.4.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A glow renderer for iced" @@ -8,12 +8,22 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [features] +svg = ["iced_graphics/svg"] +image = ["iced_graphics/image"] +png = ["iced_graphics/png"] +jpeg = ["iced_graphics/jpeg"] +jpeg_rayon = ["iced_graphics/jpeg_rayon"] +gif = ["iced_graphics/gif"] +webp = ["iced_graphics/webp"] +pnm = ["iced_graphics/pnm"] +ico = ["iced_graphics/ico"] +bmp = ["iced_graphics/bmp"] +hdr = ["iced_graphics/hdr"] +dds = ["iced_graphics/dds"] +farbfeld = ["iced_graphics/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] -# Not supported yet! -image = [] -svg = [] [dependencies] glow = "0.11.1" @@ -24,11 +34,11 @@ bytemuck = "1.4" log = "0.4" [dependencies.iced_native] -version = "0.5" +version = "0.6" path = "../native" [dependencies.iced_graphics] -version = "0.3" +version = "0.4" path = "../graphics" features = ["font-fallback", "font-icons", "opengl"] diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 21af2ecf..416c3b94 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -1,3 +1,5 @@ +#[cfg(any(feature = "image", feature = "svg"))] +use crate::image; use crate::quad; use crate::text; use crate::{program, triangle}; @@ -15,6 +17,8 @@ use iced_native::{Font, Size}; /// [`iced`]: https://github.com/iced-rs/iced #[derive(Debug)] pub struct Backend { + #[cfg(any(feature = "image", feature = "svg"))] + image_pipeline: image::Pipeline, quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, @@ -32,10 +36,14 @@ impl Backend { let shader_version = program::Version::new(gl); + #[cfg(any(feature = "image", feature = "svg"))] + let image_pipeline = image::Pipeline::new(gl, &shader_version); let quad_pipeline = quad::Pipeline::new(gl, &shader_version); let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version); Self { + #[cfg(any(feature = "image", feature = "svg"))] + image_pipeline, quad_pipeline, text_pipeline, triangle_pipeline, @@ -70,6 +78,9 @@ impl Backend { viewport_size.height, ); } + + #[cfg(any(feature = "image", feature = "svg"))] + self.image_pipeline.trim_cache(gl); } fn flush( @@ -112,6 +123,21 @@ impl Backend { ); } + #[cfg(any(feature = "image", feature = "svg"))] + if !layer.images.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.image_pipeline.draw( + gl, + target_height, + scaled, + scale_factor, + &layer.images, + bounds, + ); + } + if !layer.text.is_empty() { for text in layer.text.iter() { // Target physical coordinates directly to avoid blurry text @@ -238,8 +264,8 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { - (50, 50) + fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { + self.image_pipeline.dimensions(handle) } } @@ -247,8 +273,8 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - _handle: &iced_native::svg::Handle, - ) -> (u32, u32) { - (50, 50) + handle: &iced_native::svg::Handle, + ) -> Size<u32> { + self.image_pipeline.viewport_dimensions(handle) } } diff --git a/glow/src/image.rs b/glow/src/image.rs new file mode 100644 index 00000000..955fd1ab --- /dev/null +++ b/glow/src/image.rs @@ -0,0 +1,243 @@ +mod storage; + +use storage::Storage; + +pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; + +use crate::program::{self, Shader}; +use crate::Transformation; + +#[cfg(feature = "image")] +use iced_graphics::image::raster; + +#[cfg(feature = "svg")] +use iced_graphics::image::vector; + +use iced_graphics::layer; +use iced_graphics::Rectangle; +use iced_graphics::Size; + +use glow::HasContext; + +use std::cell::RefCell; + +#[derive(Debug)] +pub(crate) struct Pipeline { + program: <glow::Context as HasContext>::Program, + vertex_array: <glow::Context as HasContext>::VertexArray, + vertex_buffer: <glow::Context as HasContext>::Buffer, + transform_location: <glow::Context as HasContext>::UniformLocation, + storage: Storage, + #[cfg(feature = "image")] + raster_cache: RefCell<raster::Cache<Storage>>, + #[cfg(feature = "svg")] + vector_cache: RefCell<vector::Cache<Storage>>, +} + +impl Pipeline { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + include_str!("shader/common/image.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + include_str!("shader/common/image.frag"), + ); + + program::create( + gl, + &[vertex_shader, fragment_shader], + &[(0, "i_Position")], + ) + }; + + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Get transform location"); + + unsafe { + gl.use_program(Some(program)); + + let transform: [f32; 16] = Transformation::identity().into(); + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + &transform, + ); + + gl.use_program(None); + } + + let vertex_buffer = + unsafe { gl.create_buffer().expect("Create vertex buffer") }; + let vertex_array = + unsafe { gl.create_vertex_array().expect("Create vertex array") }; + + unsafe { + gl.bind_vertex_array(Some(vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + + let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1]; + gl.buffer_data_size( + glow::ARRAY_BUFFER, + vertices.len() as i32, + glow::STATIC_DRAW, + ); + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(vertices), + ); + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32( + 0, + 2, + glow::UNSIGNED_BYTE, + false, + 0, + 0, + ); + + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_vertex_array(None); + } + + Pipeline { + program, + vertex_array, + vertex_buffer, + transform_location, + storage: Storage::default(), + #[cfg(feature = "image")] + raster_cache: RefCell::new(raster::Cache::default()), + #[cfg(feature = "svg")] + vector_cache: RefCell::new(vector::Cache::default()), + } + } + + #[cfg(feature = "image")] + pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { + self.raster_cache.borrow_mut().load(handle).dimensions() + } + + #[cfg(feature = "svg")] + pub fn viewport_dimensions( + &self, + handle: &iced_native::svg::Handle, + ) -> Size<u32> { + let mut cache = self.vector_cache.borrow_mut(); + let svg = cache.load(handle); + + svg.viewport_dimensions() + } + + pub fn draw( + &mut self, + mut gl: &glow::Context, + target_height: u32, + transformation: Transformation, + _scale_factor: f32, + images: &[layer::Image], + layer_bounds: Rectangle<u32>, + ) { + unsafe { + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + gl.enable(glow::SCISSOR_TEST); + } + + #[cfg(feature = "image")] + let mut raster_cache = self.raster_cache.borrow_mut(); + + #[cfg(feature = "svg")] + let mut vector_cache = self.vector_cache.borrow_mut(); + + for image in images { + let (entry, bounds) = match &image { + #[cfg(feature = "image")] + layer::Image::Raster { handle, bounds } => ( + raster_cache.upload(handle, &mut gl, &mut self.storage), + bounds, + ), + #[cfg(not(feature = "image"))] + layer::Image::Raster { handle: _, bounds } => (None, bounds), + + #[cfg(feature = "svg")] + layer::Image::Vector { handle, bounds } => { + let size = [bounds.width, bounds.height]; + ( + vector_cache.upload( + handle, + size, + _scale_factor, + &mut gl, + &mut self.storage, + ), + bounds, + ) + } + + #[cfg(not(feature = "svg"))] + layer::Image::Vector { handle: _, bounds } => (None, bounds), + }; + + unsafe { + gl.scissor( + layer_bounds.x as i32, + (target_height - (layer_bounds.y + layer_bounds.height)) + as i32, + layer_bounds.width as i32, + layer_bounds.height as i32, + ); + + if let Some(storage::Entry { texture, .. }) = entry { + gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) + } else { + continue; + } + + let translate = Transformation::translate(bounds.x, bounds.y); + let scale = Transformation::scale(bounds.width, bounds.height); + let transformation = transformation * translate * scale; + let matrix: [f32; 16] = transformation.into(); + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + &matrix, + ); + + gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); + + gl.bind_texture(glow::TEXTURE_2D, None); + } + } + + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_vertex_array(None); + gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); + } + } + + pub fn trim_cache(&mut self, mut gl: &glow::Context) { + #[cfg(feature = "image")] + self.raster_cache + .borrow_mut() + .trim(&mut self.storage, &mut gl); + + #[cfg(feature = "svg")] + self.vector_cache + .borrow_mut() + .trim(&mut self.storage, &mut gl); + } +} diff --git a/glow/src/image/storage.rs b/glow/src/image/storage.rs new file mode 100644 index 00000000..9bc20641 --- /dev/null +++ b/glow/src/image/storage.rs @@ -0,0 +1,78 @@ +use iced_graphics::image; +use iced_graphics::Size; + +use glow::HasContext; + +#[derive(Debug, Default)] +pub struct Storage; + +impl image::Storage for Storage { + type Entry = Entry; + type State<'a> = &'a glow::Context; + + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + gl: &mut &glow::Context, + ) -> Option<Self::Entry> { + unsafe { + let texture = gl.create_texture().expect("create texture"); + gl.bind_texture(glow::TEXTURE_2D, Some(texture)); + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::SRGB8_ALPHA8 as i32, + width as i32, + height as i32, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + Some(data), + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_S, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_T, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as _, + ); + gl.bind_texture(glow::TEXTURE_2D, None); + + Some(Entry { + size: Size::new(width, height), + texture, + }) + } + } + + fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { + unsafe { gl.delete_texture(entry.texture) } + } +} + +#[derive(Debug)] +pub struct Entry { + size: Size<u32>, + pub(super) texture: glow::NativeTexture, +} + +impl image::storage::Entry for Entry { + fn size(&self) -> Size<u32> { + self.size + } +} diff --git a/glow/src/lib.rs b/glow/src/lib.rs index de9c0002..e6ca0562 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -3,7 +3,7 @@ //!  //! //! [`glow`]: https://github.com/grovesNL/glow -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] @@ -24,6 +24,8 @@ pub use glow; mod backend; +#[cfg(any(feature = "image", feature = "svg"))] +mod image; mod program; mod quad; mod text; diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag index 42d0201f..9af0cb6e 100644 --- a/glow/src/shader/common/gradient.frag +++ b/glow/src/shader/common/gradient.frag @@ -1,20 +1,20 @@ #ifdef GL_ES - #ifdef GL_FRAGMENT_PRECISION_HIGH - precision highp float; - #else - precision mediump float; - #endif +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif #endif #ifdef HIGHER_THAN_300 - layout (location = 0) out vec4 fragColor; - #define gl_FragColor fragColor +layout (location = 0) out vec4 fragColor; +#define gl_FragColor fragColor #endif in vec2 raw_position; uniform vec4 gradient_direction; -uniform uint color_stops_size; +uniform int color_stops_size; // GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops //stored as color(vec4) -> offset(vec4) sequentially; uniform vec4 color_stops[32]; @@ -28,23 +28,23 @@ void main() { vec2 unit = normalize(gradient_vec); float coord_offset = dot(unit, current_vec) / length(gradient_vec); //if a gradient has a start/end stop that is identical, the mesh will have a transparent fill - fragColor = vec4(0.0, 0.0, 0.0, 0.0); + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); float min_offset = color_stops[1].x; - float max_offset = color_stops[color_stops_size - 1u].x; + float max_offset = color_stops[color_stops_size - 1].x; - for (uint i = 0u; i < color_stops_size - 2u; i += 2u) { - float curr_offset = color_stops[i+1u].x; - float next_offset = color_stops[i+3u].x; + for (int i = 0; i < color_stops_size - 2; i += 2) { + float curr_offset = color_stops[i+1].x; + float next_offset = color_stops[i+3].x; if (coord_offset <= min_offset) { //current coordinate is before the first defined offset, set it to the start color - fragColor = color_stops[0]; + gl_FragColor = color_stops[0]; } if (curr_offset <= coord_offset && coord_offset <= next_offset) { //current fragment is between the current offset processing & the next one, interpolate colors - fragColor = mix(color_stops[i], color_stops[i+2u], smoothstep( + gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep( curr_offset, next_offset, coord_offset @@ -53,7 +53,7 @@ void main() { if (coord_offset >= max_offset) { //current coordinate is before the last defined offset, set it to the last color - fragColor = color_stops[color_stops_size - 2u]; + gl_FragColor = color_stops[color_stops_size - 2]; } } } diff --git a/glow/src/shader/common/triangle.vert b/glow/src/shader/common/gradient.vert index fe505997..fe505997 100644 --- a/glow/src/shader/common/triangle.vert +++ b/glow/src/shader/common/gradient.vert diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/common/image.frag new file mode 100644 index 00000000..5e05abdf --- /dev/null +++ b/glow/src/shader/common/image.frag @@ -0,0 +1,22 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +uniform sampler2D tex; +in vec2 tex_pos; + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif +#ifdef GL_ES +#define texture texture2D +#endif + +void main() { + gl_FragColor = texture(tex, tex_pos); +} diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/common/image.vert new file mode 100644 index 00000000..93e541f2 --- /dev/null +++ b/glow/src/shader/common/image.vert @@ -0,0 +1,9 @@ +uniform mat4 u_Transform; + +in vec2 i_Position; +out vec2 tex_pos; + +void main() { + gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); + tex_pos = i_Position; +} diff --git a/glow/src/shader/common/triangle.frag b/glow/src/shader/common/solid.frag index ead40fe5..174ffdd3 100644 --- a/glow/src/shader/common/triangle.frag +++ b/glow/src/shader/common/solid.frag @@ -11,8 +11,8 @@ out vec4 fragColor; #define gl_FragColor fragColor #endif -uniform vec4 color; +in vec4 v_Color; void main() { - fragColor = color; + gl_FragColor = v_Color; } diff --git a/glow/src/shader/common/solid.vert b/glow/src/shader/common/solid.vert new file mode 100644 index 00000000..59ed88e5 --- /dev/null +++ b/glow/src/shader/common/solid.vert @@ -0,0 +1,11 @@ +uniform mat4 u_Transform; + +in vec2 i_Position; +in vec4 i_Color; + +out vec4 v_Color; + +void main() { + gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); + v_Color = i_Color; +} diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 0e27bcf2..d0205e08 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -1,74 +1,52 @@ //! Draw meshes of triangles. -mod gradient; -mod solid; - use crate::program; use crate::Transformation; +use iced_graphics::gradient::Gradient; use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::{self, Vertex2D}; +use iced_graphics::triangle::{ColoredVertex2D, Vertex2D}; use glow::HasContext; use std::marker::PhantomData; +const DEFAULT_VERTICES: usize = 1_000; +const DEFAULT_INDICES: usize = 1_000; + #[derive(Debug)] pub(crate) struct Pipeline { - vertex_array: <glow::Context as HasContext>::VertexArray, - vertices: Buffer<Vertex2D>, indices: Buffer<u32>, - programs: ProgramList, -} - -#[derive(Debug)] -struct ProgramList { solid: solid::Program, gradient: gradient::Program, } impl Pipeline { pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self { - let vertex_array = - unsafe { gl.create_vertex_array().expect("Create vertex array") }; - - unsafe { - gl.bind_vertex_array(Some(vertex_array)); - } - - let vertices = unsafe { - Buffer::new( - gl, - glow::ARRAY_BUFFER, - glow::DYNAMIC_DRAW, - std::mem::size_of::<Vertex2D>() as usize, - ) - }; - - let indices = unsafe { + let mut indices = unsafe { Buffer::new( gl, glow::ELEMENT_ARRAY_BUFFER, glow::DYNAMIC_DRAW, - std::mem::size_of::<u32>() as usize, + DEFAULT_INDICES, ) }; + let solid = solid::Program::new(gl, shader_version); + let gradient = gradient::Program::new(gl, shader_version); + unsafe { - let stride = std::mem::size_of::<Vertex2D>() as i32; + gl.bind_vertex_array(Some(solid.vertex_array)); + indices.bind(gl, 0); - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); + gl.bind_vertex_array(Some(gradient.vertex_array)); + indices.bind(gl, 0); gl.bind_vertex_array(None); - }; + } Self { - vertex_array, - vertices, indices, - programs: ProgramList { - solid: solid::Program::new(gl, shader_version), - gradient: gradient::Program::new(gl, shader_version), - }, + solid, + gradient, } } @@ -83,50 +61,83 @@ impl Pipeline { unsafe { gl.enable(glow::MULTISAMPLE); gl.enable(glow::SCISSOR_TEST); - gl.bind_vertex_array(Some(self.vertex_array)) } - //count the total amount of vertices & indices we need to handle - let (total_vertices, total_indices) = mesh::attribute_count_of(meshes); + // Count the total amount of vertices & indices we need to handle + let count = mesh::attribute_count_of(meshes); // Then we ensure the current attribute buffers are big enough, resizing if necessary unsafe { - self.vertices.bind(gl, total_vertices); - self.indices.bind(gl, total_indices); + self.indices.bind(gl, count.indices); } // We upload all the vertices and indices upfront - let mut vertex_offset = 0; + let mut solid_vertex_offset = 0; + let mut gradient_vertex_offset = 0; let mut index_offset = 0; for mesh in meshes { - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - (vertex_offset * std::mem::size_of::<Vertex2D>()) as i32, - bytemuck::cast_slice(&mesh.buffers.vertices), - ); + let indices = mesh.indices(); + unsafe { gl.buffer_sub_data_u8_slice( glow::ELEMENT_ARRAY_BUFFER, (index_offset * std::mem::size_of::<u32>()) as i32, - bytemuck::cast_slice(&mesh.buffers.indices), + bytemuck::cast_slice(indices), ); - vertex_offset += mesh.buffers.vertices.len(); - index_offset += mesh.buffers.indices.len(); + index_offset += indices.len(); + } + + match mesh { + Mesh::Solid { buffers, .. } => { + unsafe { + self.solid.vertices.bind(gl, count.solid_vertices); + + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + (solid_vertex_offset + * std::mem::size_of::<ColoredVertex2D>()) + as i32, + bytemuck::cast_slice(&buffers.vertices), + ); + } + + solid_vertex_offset += buffers.vertices.len(); + } + Mesh::Gradient { buffers, .. } => { + unsafe { + self.gradient + .vertices + .bind(gl, count.gradient_vertices); + + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + (gradient_vertex_offset + * std::mem::size_of::<Vertex2D>()) + as i32, + bytemuck::cast_slice(&buffers.vertices), + ); + } + + gradient_vertex_offset += buffers.vertices.len(); + } } } // Then we draw each mesh using offsets - let mut last_vertex = 0; + let mut last_solid_vertex = 0; + let mut last_gradient_vertex = 0; let mut last_index = 0; for mesh in meshes { - let transform = transformation - * Transformation::translate(mesh.origin.x, mesh.origin.y); + let indices = mesh.indices(); + let origin = mesh.origin(); + + let transform = + transformation * Transformation::translate(origin.x, origin.y); - let clip_bounds = (mesh.clip_bounds * scale_factor).snap(); + let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); unsafe { gl.scissor( @@ -136,29 +147,126 @@ impl Pipeline { clip_bounds.width as i32, clip_bounds.height as i32, ); + } + + match mesh { + Mesh::Solid { buffers, .. } => unsafe { + gl.use_program(Some(self.solid.program)); + gl.bind_vertex_array(Some(self.solid.vertex_array)); - match mesh.style { - triangle::Style::Solid(color) => { - self.programs.solid.use_program(gl, color, &transform); + if transform != self.solid.uniforms.transform { + gl.uniform_matrix_4_f32_slice( + Some(&self.solid.uniforms.transform_location), + false, + transform.as_ref(), + ); + + self.solid.uniforms.transform = transform; } - triangle::Style::Gradient(gradient) => { - self.programs - .gradient - .use_program(gl, gradient, &transform); + + gl.draw_elements_base_vertex( + glow::TRIANGLES, + indices.len() as i32, + glow::UNSIGNED_INT, + (last_index * std::mem::size_of::<u32>()) as i32, + last_solid_vertex as i32, + ); + + last_solid_vertex += buffers.vertices.len(); + }, + Mesh::Gradient { + buffers, gradient, .. + } => unsafe { + gl.use_program(Some(self.gradient.program)); + gl.bind_vertex_array(Some(self.gradient.vertex_array)); + + if transform != self.gradient.uniforms.transform { + gl.uniform_matrix_4_f32_slice( + Some(&self.gradient.uniforms.locations.transform), + false, + transform.as_ref(), + ); + + self.gradient.uniforms.transform = transform; } - } - gl.draw_elements_base_vertex( - glow::TRIANGLES, - mesh.buffers.indices.len() as i32, - glow::UNSIGNED_INT, - (last_index * std::mem::size_of::<u32>()) as i32, - last_vertex as i32, - ); + if &self.gradient.uniforms.gradient != *gradient { + match gradient { + Gradient::Linear(linear) => { + gl.uniform_4_f32( + Some( + &self + .gradient + .uniforms + .locations + .gradient_direction, + ), + linear.start.x, + linear.start.y, + linear.end.x, + linear.end.y, + ); + + gl.uniform_1_i32( + Some( + &self + .gradient + .uniforms + .locations + .color_stops_size, + ), + (linear.color_stops.len() * 2) as i32, + ); + + let mut stops = [0.0; 128]; + + for (index, stop) in linear + .color_stops + .iter() + .enumerate() + .take(16) + { + let [r, g, b, a] = stop.color.into_linear(); + + stops[index * 8] = r; + stops[(index * 8) + 1] = g; + stops[(index * 8) + 2] = b; + stops[(index * 8) + 3] = a; + stops[(index * 8) + 4] = stop.offset; + stops[(index * 8) + 5] = 0.; + stops[(index * 8) + 6] = 0.; + stops[(index * 8) + 7] = 0.; + } + + gl.uniform_4_f32_slice( + Some( + &self + .gradient + .uniforms + .locations + .color_stops, + ), + &stops, + ); + } + } + + self.gradient.uniforms.gradient = (*gradient).clone(); + } + + gl.draw_elements_base_vertex( + glow::TRIANGLES, + indices.len() as i32, + glow::UNSIGNED_INT, + (last_index * std::mem::size_of::<u32>()) as i32, + last_gradient_vertex as i32, + ); - last_vertex += mesh.buffers.vertices.len(); - last_index += mesh.buffers.indices.len(); + last_gradient_vertex += buffers.vertices.len(); + }, } + + last_index += indices.len(); } unsafe { @@ -169,47 +277,8 @@ impl Pipeline { } } -/// A simple shader program. Uses [`triangle.vert`] for its vertex shader and only binds position -/// attribute location. -pub(super) fn program( - gl: &glow::Context, - shader_version: &program::Version, - fragment_shader: &'static str, -) -> <glow::Context as HasContext>::Program { - unsafe { - let vertex_shader = program::Shader::vertex( - gl, - shader_version, - include_str!("shader/common/triangle.vert"), - ); - - let fragment_shader = - program::Shader::fragment(gl, shader_version, fragment_shader); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[(0, "i_Position")], - ) - } -} - -pub fn set_transform( - gl: &glow::Context, - location: <glow::Context as HasContext>::UniformLocation, - transform: Transformation, -) { - unsafe { - gl.uniform_matrix_4_f32_slice( - Some(&location), - false, - transform.as_ref(), - ); - } -} - #[derive(Debug)] -struct Buffer<T> { +pub struct Buffer<T> { raw: <glow::Context as HasContext>::Buffer, target: u32, usage: u32, @@ -253,3 +322,268 @@ impl<T> Buffer<T> { } } } + +mod solid { + use crate::program; + use crate::triangle; + use glow::{Context, HasContext, NativeProgram}; + use iced_graphics::triangle::ColoredVertex2D; + use iced_graphics::Transformation; + + #[derive(Debug)] + pub struct Program { + pub program: <Context as HasContext>::Program, + pub vertex_array: <glow::Context as HasContext>::VertexArray, + pub vertices: triangle::Buffer<ColoredVertex2D>, + pub uniforms: Uniforms, + } + + impl Program { + pub fn new(gl: &Context, shader_version: &program::Version) -> Self { + let program = unsafe { + let vertex_shader = program::Shader::vertex( + gl, + shader_version, + include_str!("shader/common/solid.vert"), + ); + + let fragment_shader = program::Shader::fragment( + gl, + shader_version, + include_str!("shader/common/solid.frag"), + ); + + program::create( + gl, + &[vertex_shader, fragment_shader], + &[(0, "i_Position"), (1, "i_Color")], + ) + }; + + let vertex_array = unsafe { + gl.create_vertex_array().expect("Create vertex array") + }; + + let vertices = unsafe { + triangle::Buffer::new( + gl, + glow::ARRAY_BUFFER, + glow::DYNAMIC_DRAW, + super::DEFAULT_VERTICES, + ) + }; + + unsafe { + gl.bind_vertex_array(Some(vertex_array)); + + let stride = std::mem::size_of::<ColoredVertex2D>() as i32; + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32( + 0, + 2, + glow::FLOAT, + false, + stride, + 0, + ); + + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32( + 1, + 4, + glow::FLOAT, + false, + stride, + 4 * 2, + ); + + gl.bind_vertex_array(None); + }; + + Self { + program, + vertex_array, + vertices, + uniforms: Uniforms::new(gl, program), + } + } + } + + #[derive(Debug)] + pub struct Uniforms { + pub transform: Transformation, + pub transform_location: <Context as HasContext>::UniformLocation, + } + + impl Uniforms { + fn new(gl: &Context, program: NativeProgram) -> Self { + let transform = Transformation::identity(); + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Solid - Get u_Transform."); + + unsafe { + gl.use_program(Some(program)); + + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + transform.as_ref(), + ); + + gl.use_program(None); + } + + Self { + transform, + transform_location, + } + } + } +} + +mod gradient { + use crate::program; + use crate::triangle; + use glow::{Context, HasContext, NativeProgram}; + use iced_graphics::gradient::{self, Gradient}; + use iced_graphics::triangle::Vertex2D; + use iced_graphics::Transformation; + + #[derive(Debug)] + pub struct Program { + pub program: <Context as HasContext>::Program, + pub vertex_array: <glow::Context as HasContext>::VertexArray, + pub vertices: triangle::Buffer<Vertex2D>, + pub uniforms: Uniforms, + } + + impl Program { + pub fn new(gl: &Context, shader_version: &program::Version) -> Self { + let program = unsafe { + let vertex_shader = program::Shader::vertex( + gl, + shader_version, + include_str!("shader/common/gradient.vert"), + ); + + let fragment_shader = program::Shader::fragment( + gl, + shader_version, + include_str!("shader/common/gradient.frag"), + ); + + program::create( + gl, + &[vertex_shader, fragment_shader], + &[(0, "i_Position")], + ) + }; + + let vertex_array = unsafe { + gl.create_vertex_array().expect("Create vertex array") + }; + + let vertices = unsafe { + triangle::Buffer::new( + gl, + glow::ARRAY_BUFFER, + glow::DYNAMIC_DRAW, + super::DEFAULT_VERTICES, + ) + }; + + unsafe { + gl.bind_vertex_array(Some(vertex_array)); + + let stride = std::mem::size_of::<Vertex2D>() as i32; + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32( + 0, + 2, + glow::FLOAT, + false, + stride, + 0, + ); + + gl.bind_vertex_array(None); + }; + + Self { + program, + vertex_array, + vertices, + uniforms: Uniforms::new(gl, program), + } + } + } + + #[derive(Debug)] + pub struct Uniforms { + pub gradient: Gradient, + pub transform: Transformation, + pub locations: Locations, + } + + #[derive(Debug)] + pub struct Locations { + pub gradient_direction: <Context as HasContext>::UniformLocation, + pub color_stops_size: <Context as HasContext>::UniformLocation, + //currently the maximum number of stops is 16 due to lack of SSBO in GL2.1 + pub color_stops: <Context as HasContext>::UniformLocation, + pub transform: <Context as HasContext>::UniformLocation, + } + + impl Uniforms { + fn new(gl: &Context, program: NativeProgram) -> Self { + let gradient_direction = unsafe { + gl.get_uniform_location(program, "gradient_direction") + } + .expect("Gradient - Get gradient_direction."); + + let color_stops_size = + unsafe { gl.get_uniform_location(program, "color_stops_size") } + .expect("Gradient - Get color_stops_size."); + + let color_stops = unsafe { + gl.get_uniform_location(program, "color_stops") + .expect("Gradient - Get color_stops.") + }; + + let transform = Transformation::identity(); + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Solid - Get u_Transform."); + + unsafe { + gl.use_program(Some(program)); + + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + transform.as_ref(), + ); + + gl.use_program(None); + } + + Self { + gradient: Gradient::Linear(gradient::Linear { + start: Default::default(), + end: Default::default(), + color_stops: vec![], + }), + transform: Transformation::identity(), + locations: Locations { + gradient_direction, + color_stops_size, + color_stops, + transform: transform_location, + }, + } + } + } +} diff --git a/glow/src/triangle/gradient.rs b/glow/src/triangle/gradient.rs deleted file mode 100644 index 5225612e..00000000 --- a/glow/src/triangle/gradient.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::program::Version; -use crate::triangle; -use glow::{Context, HasContext, NativeProgram}; -use iced_graphics::gradient::Gradient; -use iced_graphics::gradient::Linear; -use iced_graphics::Transformation; - -#[derive(Debug)] -pub struct Program { - pub program: <Context as HasContext>::Program, - pub uniform_data: UniformData, -} - -#[derive(Debug)] -pub struct UniformData { - gradient: Gradient, - transform: Transformation, - uniform_locations: UniformLocations, -} - -#[derive(Debug)] -struct UniformLocations { - gradient_direction_location: <Context as HasContext>::UniformLocation, - color_stops_size_location: <Context as HasContext>::UniformLocation, - //currently the maximum number of stops is 16 due to lack of SSBO in GL2.1 - color_stops_location: <Context as HasContext>::UniformLocation, - transform_location: <Context as HasContext>::UniformLocation, -} - -impl Program { - pub fn new(gl: &Context, shader_version: &Version) -> Self { - let program = triangle::program( - gl, - shader_version, - include_str!("../shader/common/gradient.frag"), - ); - - Self { - program, - uniform_data: UniformData::new(gl, program), - } - } - - pub fn write_uniforms( - &mut self, - gl: &Context, - gradient: &Gradient, - transform: &Transformation, - ) { - if transform != &self.uniform_data.transform { - triangle::set_transform( - gl, - self.uniform_data.uniform_locations.transform_location, - *transform, - ); - } - - if &self.uniform_data.gradient != gradient { - match gradient { - Gradient::Linear(linear) => unsafe { - gl.uniform_4_f32( - Some( - &self - .uniform_data - .uniform_locations - .gradient_direction_location, - ), - linear.start.x, - linear.start.y, - linear.end.x, - linear.end.y, - ); - - gl.uniform_1_u32( - Some( - &self - .uniform_data - .uniform_locations - .color_stops_size_location, - ), - (linear.color_stops.len() * 2) as u32, - ); - - let mut stops = [0.0; 128]; - - for (index, stop) in - linear.color_stops.iter().enumerate().take(16) - { - let [r, g, b, a] = stop.color.into_linear(); - - stops[index * 8] = r; - stops[(index * 8) + 1] = g; - stops[(index * 8) + 2] = b; - stops[(index * 8) + 3] = a; - stops[(index * 8) + 4] = stop.offset; - stops[(index * 8) + 5] = 0.; - stops[(index * 8) + 6] = 0.; - stops[(index * 8) + 7] = 0.; - } - - gl.uniform_4_f32_slice( - Some( - &self - .uniform_data - .uniform_locations - .color_stops_location, - ), - &stops, - ); - }, - } - - self.uniform_data.gradient = gradient.clone(); - } - } - - pub fn use_program( - &mut self, - gl: &Context, - gradient: &Gradient, - transform: &Transformation, - ) { - unsafe { gl.use_program(Some(self.program)) } - self.write_uniforms(gl, gradient, transform); - } -} - -impl UniformData { - fn new(gl: &Context, program: NativeProgram) -> Self { - let gradient_direction_location = - unsafe { gl.get_uniform_location(program, "gradient_direction") } - .expect("Gradient - Get gradient_direction."); - - let color_stops_size_location = - unsafe { gl.get_uniform_location(program, "color_stops_size") } - .expect("Gradient - Get color_stops_size."); - - let color_stops_location = unsafe { - gl.get_uniform_location(program, "color_stops") - .expect("Gradient - Get color_stops.") - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Gradient - Get u_Transform."); - - Self { - gradient: Gradient::Linear(Linear { - start: Default::default(), - end: Default::default(), - color_stops: vec![], - }), - transform: Transformation::identity(), - uniform_locations: UniformLocations { - gradient_direction_location, - color_stops_size_location, - color_stops_location, - transform_location, - }, - } - } -} diff --git a/glow/src/triangle/solid.rs b/glow/src/triangle/solid.rs deleted file mode 100644 index fb3d40c3..00000000 --- a/glow/src/triangle/solid.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::program::Version; -use crate::{triangle, Color}; -use glow::{Context, HasContext, NativeProgram}; -use iced_graphics::Transformation; - -#[derive(Debug)] -pub struct Program { - program: <Context as HasContext>::Program, - uniform_data: UniformData, -} - -#[derive(Debug)] -struct UniformData { - pub color: Color, - pub color_location: <Context as HasContext>::UniformLocation, - pub transform: Transformation, - pub transform_location: <Context as HasContext>::UniformLocation, -} - -impl UniformData { - fn new(gl: &Context, program: NativeProgram) -> Self { - Self { - color: Color::TRANSPARENT, - color_location: unsafe { - gl.get_uniform_location(program, "color") - } - .expect("Solid - Get color."), - transform: Transformation::identity(), - transform_location: unsafe { - gl.get_uniform_location(program, "u_Transform") - } - .expect("Solid - Get u_Transform."), - } - } -} - -impl Program { - pub fn new(gl: &Context, shader_version: &Version) -> Self { - let program = triangle::program( - gl, - shader_version, - include_str!("../shader/common/triangle.frag"), - ); - - Self { - program, - uniform_data: UniformData::new(gl, program), - } - } - - pub fn write_uniforms( - &mut self, - gl: &Context, - color: &Color, - transform: &Transformation, - ) { - if transform != &self.uniform_data.transform { - triangle::set_transform( - gl, - self.uniform_data.transform_location, - *transform, - ) - } - - if color != &self.uniform_data.color { - let [r, g, b, a] = color.into_linear(); - - unsafe { - gl.uniform_4_f32( - Some(&self.uniform_data.color_location), - r, - g, - b, - a, - ); - } - - self.uniform_data.color = *color; - } - } - - pub fn use_program( - &mut self, - gl: &Context, - color: &Color, - transform: &Transformation, - ) { - unsafe { gl.use_program(Some(self.program)) } - self.write_uniforms(gl, color, transform) - } -} diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 65b16947..730ee6f7 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_glutin" -version = "0.3.0" +version = "0.4.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A glutin runtime for Iced" @@ -23,15 +23,15 @@ git = "https://github.com/iced-rs/glutin" rev = "da8d291486b4c9bec12487a46c119c4b1d386abf" [dependencies.iced_native] -version = "0.5" +version = "0.6" path = "../native" [dependencies.iced_winit] -version = "0.4" +version = "0.5" path = "../winit" features = ["application"] [dependencies.iced_graphics] -version = "0.3" +version = "0.4" path = "../graphics" features = ["opengl"] diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 3b0e5236..b601f37c 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_graphics" -version = "0.3.1" +version = "0.4.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" @@ -11,28 +11,44 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +svg = ["resvg", "usvg", "tiny-skia"] +image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] +png = ["image_rs/png"] +jpeg = ["image_rs/jpeg"] +jpeg_rayon = ["image_rs/jpeg_rayon"] +gif = ["image_rs/gif"] +webp = ["image_rs/webp"] +pnm = ["image_rs/pnm"] +ico = ["image_rs/ico"] +bmp = ["image_rs/bmp"] +hdr = ["image_rs/hdr"] +dds = ["image_rs/dds"] +farbfeld = ["image_rs/farbfeld"] canvas = ["lyon"] qr_code = ["qrcode", "canvas"] font-source = ["font-kit"] font-fallback = [] font-icons = [] opengl = [] +image_rs = ["kamadak-exif"] [dependencies] glam = "0.21.3" +log = "0.4" raw-window-handle = "0.5" thiserror = "1.0" +bitflags = "1.2" [dependencies.bytemuck] version = "1.4" features = ["derive"] [dependencies.iced_native] -version = "0.5" +version = "0.6" path = "../native" [dependencies.iced_style] -version = "0.4" +version = "0.5" path = "../style" [dependencies.lyon] @@ -48,6 +64,28 @@ default-features = false version = "0.10" optional = true +[dependencies.image_rs] +version = "0.24" +package = "image" +default-features = false +optional = true + +[dependencies.resvg] +version = "0.18" +optional = true + +[dependencies.usvg] +version = "0.18" +optional = true + +[dependencies.tiny-skia] +version = "0.6" +optional = true + +[dependencies.kamadak-exif] +version = "0.5" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 7e0af2cc..2f8e9fc3 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -66,11 +66,11 @@ pub trait Text { /// A graphics backend that supports image rendering. pub trait Image { /// Returns the dimensions of the provided image. - fn dimensions(&self, handle: &image::Handle) -> (u32, u32); + fn dimensions(&self, handle: &image::Handle) -> Size<u32>; } /// A graphics backend that supports SVG rendering. pub trait Svg { /// Returns the viewport dimensions of the provided SVG. - fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32); + fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>; } diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index 64f9e4b3..83f25238 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -9,7 +9,7 @@ use crate::{Color, Point, Size}; /// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), /// or conically (TBD). pub enum Gradient { - /// A linear gradient interpolates colors along a direction from its [`start`] to its [`end`] + /// A linear gradient interpolates colors along a direction from its `start` to its `end` /// point. Linear(Linear), } @@ -23,10 +23,15 @@ impl Gradient { #[derive(Debug, 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, } diff --git a/graphics/src/gradient/linear.rs b/graphics/src/gradient/linear.rs index 9928c1eb..c886db47 100644 --- a/graphics/src/gradient/linear.rs +++ b/graphics/src/gradient/linear.rs @@ -2,7 +2,10 @@ use crate::gradient::{ColorStop, Gradient, Position}; use crate::{Color, Point}; -/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`]. +/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`]. +/// +/// [`Fill`]: crate::widget::canvas::Fill +/// [`Stroke`]: crate::widget::canvas::Stroke #[derive(Debug, Clone, PartialEq)] pub struct Linear { /// The point where the linear gradient begins. diff --git a/graphics/src/image.rs b/graphics/src/image.rs new file mode 100644 index 00000000..04f4ff9d --- /dev/null +++ b/graphics/src/image.rs @@ -0,0 +1,10 @@ +//! Render images. +#[cfg(feature = "image_rs")] +pub mod raster; + +#[cfg(feature = "svg")] +pub mod vector; + +pub mod storage; + +pub use storage::Storage; diff --git a/wgpu/src/image/raster.rs b/graphics/src/image/raster.rs index 2b4d4af3..da46c30f 100644 --- a/wgpu/src/image/raster.rs +++ b/graphics/src/image/raster.rs @@ -1,43 +1,53 @@ -use crate::image::atlas::{self, Atlas}; +//! Raster image loading and caching. +use crate::image::Storage; +use crate::Size; + use iced_native::image; -use std::collections::{HashMap, HashSet}; use bitflags::bitflags; +use std::collections::{HashMap, HashSet}; +/// Entry in cache corresponding to an image handle #[derive(Debug)] -pub enum Memory { - Host(::image_rs::ImageBuffer<::image_rs::Bgra<u8>, Vec<u8>>), - Device(atlas::Entry), +pub enum Memory<T: Storage> { + /// Image data on host + Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>), + /// Storage entry + Device(T::Entry), + /// Image not found NotFound, + /// Invalid image data Invalid, } -impl Memory { - pub fn dimensions(&self) -> (u32, u32) { +impl<T: Storage> Memory<T> { + /// Width and height of image + pub fn dimensions(&self) -> Size<u32> { + use crate::image::storage::Entry; + match self { - Memory::Host(image) => image.dimensions(), + Memory::Host(image) => { + let (width, height) = image.dimensions(); + + Size::new(width, height) + } Memory::Device(entry) => entry.size(), - Memory::NotFound => (1, 1), - Memory::Invalid => (1, 1), + Memory::NotFound => Size::new(1, 1), + Memory::Invalid => Size::new(1, 1), } } } +/// Caches image raster data #[derive(Debug)] -pub struct Cache { - map: HashMap<u64, Memory>, +pub struct Cache<T: Storage> { + map: HashMap<u64, Memory<T>>, hits: HashSet<u64>, } -impl Cache { - pub fn new() -> Self { - Self { - map: HashMap::new(), - hits: HashSet::new(), - } - } - - pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { +impl<T: Storage> Cache<T> { + /// Load image + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> { if self.contains(handle) { return self.get(handle).unwrap(); } @@ -53,7 +63,7 @@ impl Cache { }) .unwrap_or_else(Operation::empty); - Memory::Host(operation.perform(image.to_bgra8())) + Memory::Host(operation.perform(image.to_rgba8())) } else { Memory::NotFound } @@ -65,12 +75,12 @@ impl Cache { .ok() .unwrap_or_else(Operation::empty); - Memory::Host(operation.perform(image.to_bgra8())) + Memory::Host(operation.perform(image.to_rgba8())) } else { Memory::Invalid } } - image::Data::Pixels { + image::Data::Rgba { width, height, pixels, @@ -91,19 +101,19 @@ impl Cache { self.get(handle).unwrap() } + /// Load image and upload raster data pub fn upload( &mut self, handle: &image::Handle, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - atlas: &mut Atlas, - ) -> Option<&atlas::Entry> { + state: &mut T::State<'_>, + storage: &mut T, + ) -> Option<&T::Entry> { let memory = self.load(handle); if let Memory::Host(image) = memory { let (width, height) = image.dimensions(); - let entry = atlas.upload(width, height, image, device, encoder)?; + let entry = storage.upload(width, height, image, state)?; *memory = Memory::Device(entry); } @@ -115,7 +125,8 @@ impl Cache { } } - pub fn trim(&mut self, atlas: &mut Atlas) { + /// Trim cache misses from cache + pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { let hits = &self.hits; self.map.retain(|k, memory| { @@ -123,7 +134,7 @@ impl Cache { if !retain { if let Memory::Device(entry) = memory { - atlas.remove(entry); + storage.remove(entry, state); } } @@ -133,13 +144,13 @@ impl Cache { self.hits.clear(); } - fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { + fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> { let _ = self.hits.insert(handle.id()); self.map.get_mut(&handle.id()) } - fn insert(&mut self, handle: &image::Handle, memory: Memory) { + fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) { let _ = self.map.insert(handle.id(), memory); } @@ -148,6 +159,15 @@ impl Cache { } } +impl<T: Storage> Default for Cache<T> { + fn default() -> Self { + Self { + map: HashMap::new(), + hits: HashSet::new(), + } + } +} + bitflags! { struct Operation: u8 { const FLIP_HORIZONTALLY = 0b001; diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs new file mode 100644 index 00000000..2098c7b2 --- /dev/null +++ b/graphics/src/image/storage.rs @@ -0,0 +1,31 @@ +//! Store images. +use crate::Size; + +use std::fmt::Debug; + +/// Stores cached image data for use in rendering +pub trait Storage { + /// The type of an [`Entry`] in the [`Storage`]. + type Entry: Entry; + + /// State provided to upload or remove a [`Self::Entry`]. + type State<'a>; + + /// Upload the image data of a [`Self::Entry`]. + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + state: &mut Self::State<'_>, + ) -> Option<Self::Entry>; + + /// Romve a [`Self::Entry`] from the [`Storage`]. + fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); +} + +/// An entry in some [`Storage`], +pub trait Entry: Debug { + /// The [`Size`] of the [`Entry`]. + fn size(&self) -> Size<u32>; +} diff --git a/wgpu/src/image/vector.rs b/graphics/src/image/vector.rs index b08a0aa2..42f4b500 100644 --- a/wgpu/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -1,46 +1,45 @@ -use crate::image::atlas::{self, Atlas}; +//! Vector image loading and caching +use crate::image::Storage; use iced_native::svg; +use iced_native::Size; use std::collections::{HashMap, HashSet}; use std::fs; +/// Entry in cache corresponding to an svg handle pub enum Svg { + /// Parsed svg Loaded(usvg::Tree), + /// Svg not found or failed to parse NotFound, } impl Svg { - pub fn viewport_dimensions(&self) -> (u32, u32) { + /// Viewport width and height + pub fn viewport_dimensions(&self) -> Size<u32> { match self { Svg::Loaded(tree) => { let size = tree.svg_node().size; - (size.width() as u32, size.height() as u32) + Size::new(size.width() as u32, size.height() as u32) } - Svg::NotFound => (1, 1), + Svg::NotFound => Size::new(1, 1), } } } +/// Caches svg vector and raster data #[derive(Debug)] -pub struct Cache { +pub struct Cache<T: Storage> { svgs: HashMap<u64, Svg>, - rasterized: HashMap<(u64, u32, u32), atlas::Entry>, + rasterized: HashMap<(u64, u32, u32), T::Entry>, svg_hits: HashSet<u64>, rasterized_hits: HashSet<(u64, u32, u32)>, } -impl Cache { - pub fn new() -> Self { - Self { - svgs: HashMap::new(), - rasterized: HashMap::new(), - svg_hits: HashSet::new(), - rasterized_hits: HashSet::new(), - } - } - +impl<T: Storage> Cache<T> { + /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { if self.svgs.contains_key(&handle.id()) { return self.svgs.get(&handle.id()).unwrap(); @@ -73,15 +72,15 @@ impl Cache { self.svgs.get(&handle.id()).unwrap() } + /// Load svg and upload raster data pub fn upload( &mut self, handle: &svg::Handle, [width, height]: [f32; 2], scale: f32, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - texture_atlas: &mut Atlas, - ) -> Option<&atlas::Entry> { + state: &mut T::State<'_>, + storage: &mut T, + ) -> Option<&T::Entry> { let id = handle.id(); let (width, height) = ( @@ -122,16 +121,8 @@ impl Cache { img.as_mut(), )?; - let mut rgba = img.take(); - rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); - - let allocation = texture_atlas.upload( - width, - height, - bytemuck::cast_slice(rgba.as_slice()), - device, - encoder, - )?; + let allocation = + storage.upload(width, height, img.data(), state)?; log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); @@ -144,7 +135,8 @@ impl Cache { } } - pub fn trim(&mut self, atlas: &mut Atlas) { + /// Load svg and upload raster data + pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; @@ -153,7 +145,7 @@ impl Cache { let retain = rasterized_hits.contains(k); if !retain { - atlas.remove(entry); + storage.remove(entry, state); } retain @@ -163,6 +155,17 @@ impl Cache { } } +impl<T: Storage> Default for Cache<T> { + fn default() -> Self { + Self { + svgs: HashMap::new(), + rasterized: HashMap::new(), + svg_hits: HashSet::new(), + rasterized_hits: HashSet::new(), + } + } +} + impl std::fmt::Debug for Svg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index e95934b0..fd670f48 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -166,10 +166,27 @@ impl<'a> Layer<'a> { border_color: border_color.into_linear(), }); } - Primitive::Mesh2D { + Primitive::SolidMesh { buffers, size } => { + let layer = &mut layers[current_layer]; + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + *size, + ); + + // Only draw visible content + if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { + layer.meshes.push(Mesh::Solid { + origin: Point::new(translation.x, translation.y), + buffers, + clip_bounds, + }); + } + } + Primitive::GradientMesh { buffers, size, - style, + gradient, } => { let layer = &mut layers[current_layer]; @@ -180,11 +197,11 @@ impl<'a> Layer<'a> { // Only draw visible content if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { - layer.meshes.push(Mesh { + layer.meshes.push(Mesh::Gradient { origin: Point::new(translation.x, translation.y), buffers, clip_bounds, - style, + gradient, }); } } diff --git a/graphics/src/layer/mesh.rs b/graphics/src/layer/mesh.rs index 979081f1..7661c5c9 100644 --- a/graphics/src/layer/mesh.rs +++ b/graphics/src/layer/mesh.rs @@ -1,31 +1,93 @@ //! A collection of triangle primitives. use crate::triangle; -use crate::{Point, Rectangle}; +use crate::{Gradient, Point, Rectangle}; /// A mesh of triangles. #[derive(Debug, Clone, Copy)] -pub struct Mesh<'a> { - /// The origin of the vertices of the [`Mesh`]. - pub origin: Point, +pub enum Mesh<'a> { + /// A mesh of triangles with a solid color. + Solid { + /// The origin of the vertices of the [`Mesh`]. + origin: Point, - /// The vertex and index buffers of the [`Mesh`]. - pub buffers: &'a triangle::Mesh2D, + /// The vertex and index buffers of the [`Mesh`]. + buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>, - /// The clipping bounds of the [`Mesh`]. - pub clip_bounds: Rectangle<f32>, + /// The clipping bounds of the [`Mesh`]. + clip_bounds: Rectangle<f32>, + }, + /// A mesh of triangles with a gradient color. + Gradient { + /// The origin of the vertices of the [`Mesh`]. + origin: Point, - /// The shader of the [`Mesh`]. - pub style: &'a triangle::Style, + /// The vertex and index buffers of the [`Mesh`]. + buffers: &'a triangle::Mesh2D<triangle::Vertex2D>, + + /// The clipping bounds of the [`Mesh`]. + clip_bounds: Rectangle<f32>, + + /// The gradient to apply to the [`Mesh`]. + gradient: &'a Gradient, + }, +} + +impl Mesh<'_> { + /// Returns the origin of the [`Mesh`]. + pub fn origin(&self) -> Point { + match self { + Self::Solid { origin, .. } | Self::Gradient { origin, .. } => { + *origin + } + } + } + + /// Returns the indices of the [`Mesh`]. + pub fn indices(&self) -> &[u32] { + match self { + Self::Solid { buffers, .. } => &buffers.indices, + Self::Gradient { buffers, .. } => &buffers.indices, + } + } + + /// Returns the clip bounds of the [`Mesh`]. + pub fn clip_bounds(&self) -> Rectangle<f32> { + match self { + Self::Solid { clip_bounds, .. } + | Self::Gradient { clip_bounds, .. } => *clip_bounds, + } + } +} + +/// The result of counting the attributes of a set of meshes. +#[derive(Debug, Clone, Copy, Default)] +pub struct AttributeCount { + /// The total amount of solid vertices. + pub solid_vertices: usize, + + /// The total amount of gradient vertices. + pub gradient_vertices: usize, + + /// The total amount of indices. + pub indices: usize, } /// Returns the number of total vertices & total indices of all [`Mesh`]es. -pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> (usize, usize) { +pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount { meshes .iter() - .map(|Mesh { buffers, .. }| { - (buffers.vertices.len(), buffers.indices.len()) - }) - .fold((0, 0), |(total_v, total_i), (v, i)| { - (total_v + v, total_i + i) + .fold(AttributeCount::default(), |mut count, mesh| { + match mesh { + Mesh::Solid { buffers, .. } => { + count.solid_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + Mesh::Gradient { buffers, .. } => { + count.gradient_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + } + + count }) } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index ec28ee58..d39dd90c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -30,6 +30,7 @@ mod viewport; pub mod backend; pub mod font; pub mod gradient; +pub mod image; pub mod layer; pub mod overlay; pub mod renderer; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 84f04624..6f1b6f26 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -3,6 +3,7 @@ use iced_native::svg; use iced_native::{Background, Color, Font, Rectangle, Size, Vector}; use crate::alignment; +use crate::gradient::Gradient; use crate::triangle; use std::sync::Arc; @@ -77,20 +78,32 @@ pub enum Primitive { /// The primitive to translate content: Box<Primitive>, }, - /// A low-level primitive to render a mesh of triangles. + /// A low-level primitive to render a mesh of triangles with a solid color. /// /// It can be used to render many kinds of geometry freely. - Mesh2D { - /// The vertex and index buffers of the mesh - buffers: triangle::Mesh2D, + SolidMesh { + /// The vertices and indices of the mesh. + buffers: triangle::Mesh2D<triangle::ColoredVertex2D>, + + /// The size of the drawable region of the mesh. + /// + /// Any geometry that falls out of this region will be clipped. + size: Size, + }, + /// A low-level primitive to render a mesh of triangles with a gradient. + /// + /// It can be used to render many kinds of geometry freely. + GradientMesh { + /// The vertices and indices of the mesh. + buffers: triangle::Mesh2D<triangle::Vertex2D>, /// The size of the drawable region of the mesh. /// /// Any geometry that falls out of this region will be clipped. size: Size, - /// The shader of the mesh - style: triangle::Style, + /// The [`Gradient`] to apply to the mesh. + gradient: Gradient, }, /// A cached primitive. /// diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index ff0dad2b..65350037 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -183,7 +183,7 @@ where { type Handle = image::Handle; - fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &image::Handle) -> Size<u32> { self.backend().dimensions(handle) } @@ -196,7 +196,7 @@ impl<B, T> svg::Renderer for Renderer<B, T> where B: Backend + backend::Svg, { - fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &svg::Handle) -> Size<u32> { self.backend().viewport_dimensions(handle) } diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index 04ff6d21..f52b2339 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,13 +1,12 @@ //! Draw geometry using meshes of triangles. -use crate::{Color, Gradient}; - use bytemuck::{Pod, Zeroable}; /// A set of [`Vertex2D`] and indices representing a list of triangles. #[derive(Clone, Debug)] -pub struct Mesh2D { +pub struct Mesh2D<T> { /// The vertices of the mesh - pub vertices: Vec<Vertex2D>, + pub vertices: Vec<T>, + /// The list of vertex indices that defines the triangles of the mesh. /// /// Therefore, this list should always have a length that is a multiple of 3. @@ -22,23 +21,13 @@ pub struct Vertex2D { pub position: [f32; 2], } -#[derive(Debug, Clone, PartialEq)] -/// Supported shaders for triangle primitives. -pub enum Style { - /// Fill a primitive with a solid color. - Solid(Color), - /// Fill a primitive with an interpolated color. - Gradient(Gradient), -} - -impl From<Color> for Style { - fn from(color: Color) -> Self { - Self::Solid(color) - } -} +/// A two-dimensional vertex with a color. +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +#[repr(C)] +pub struct ColoredVertex2D { + /// The vertex position in 2D space. + pub position: [f32; 2], -impl From<Gradient> for Style { - fn from(gradient: Gradient) -> Self { - Self::Gradient(gradient) - } + /// The color of the vertex in __linear__ RGBA. + pub color: [f32; 4], } diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index a14940d9..b070d0a6 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -13,6 +13,7 @@ mod cursor; mod frame; mod geometry; mod program; +mod style; mod text; pub use crate::gradient::{self, Gradient}; @@ -25,6 +26,7 @@ pub use geometry::Geometry; pub use path::Path; pub use program::Program; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; +pub use style::Style; pub use text::Text; use crate::{Backend, Primitive, Renderer}; diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs index c69fc0d7..e954ebb5 100644 --- a/graphics/src/widget/canvas/fill.rs +++ b/graphics/src/widget/canvas/fill.rs @@ -1,14 +1,14 @@ //! Fill [crate::widget::canvas::Geometry] with a certain style. use crate::{Color, Gradient}; -pub use crate::triangle::Style; +pub use crate::widget::canvas::Style; /// The style used to fill geometry. #[derive(Debug, Clone)] pub struct Fill { /// The color or gradient of the fill. /// - /// By default, it is set to [`FillStyle::Solid`] `BLACK`. + /// By default, it is set to [`Style::Solid`] with [`Color::BLACK`]. pub style: Style, /// The fill rule defines how to determine what is inside and what is diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index cf6c6928..d68548ae 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,7 +1,6 @@ use crate::gradient::Gradient; use crate::triangle; -use crate::triangle::Vertex2D; -use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Text}; +use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text}; use crate::Primitive; use iced_native::{Point, Rectangle, Size, Vector}; @@ -23,8 +22,16 @@ pub struct Frame { stroke_tessellator: tessellation::StrokeTessellator, } +enum Buffer { + Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>), + Gradient( + tessellation::VertexBuffers<triangle::Vertex2D, u32>, + Gradient, + ), +} + struct BufferStack { - stack: Vec<(tessellation::VertexBuffers<Vertex2D, u32>, triangle::Style)>, + stack: Vec<Buffer>, } impl BufferStack { @@ -32,22 +39,64 @@ impl BufferStack { Self { stack: Vec::new() } } - fn get( - &mut self, - mesh_style: triangle::Style, - ) -> tessellation::BuffersBuilder<'_, Vertex2D, u32, Vertex2DBuilder> { - match self.stack.last_mut() { - Some((_, current_style)) if current_style == &mesh_style => {} - _ => { - self.stack - .push((tessellation::VertexBuffers::new(), mesh_style)); + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(gradient) => match self.stack.last() { + Some(Buffer::Gradient(_, last)) if gradient == last => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + gradient.clone(), + )); + } + }, + } + + self.stack.last_mut().unwrap() + } + + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) } - }; + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } - tessellation::BuffersBuilder::new( - &mut self.stack.last_mut().unwrap().0, - Vertex2DBuilder, - ) + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } } } @@ -73,11 +122,11 @@ impl Transform { point.y = transformed.y; } - fn transform_style(&self, style: triangle::Style) -> triangle::Style { + fn transform_style(&self, style: Style) -> Style { match style { - triangle::Style::Solid(color) => triangle::Style::Solid(color), - triangle::Style::Gradient(gradient) => { - triangle::Style::Gradient(self.transform_gradient(gradient)) + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) } } } @@ -145,7 +194,7 @@ impl Frame { let mut buffer = self .buffers - .get(self.transforms.current.transform_style(style)); + .get_fill(&self.transforms.current.transform_style(style)); let options = tessellation::FillOptions::default().with_fill_rule(rule.into()); @@ -154,7 +203,7 @@ impl Frame { self.fill_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } else { let path = path.transformed(&self.transforms.current.raw); @@ -162,7 +211,7 @@ impl Frame { self.fill_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } .expect("Tessellate path."); @@ -180,7 +229,7 @@ impl Frame { let mut buffer = self .buffers - .get(self.transforms.current.transform_style(style)); + .get_fill(&self.transforms.current.transform_style(style)); let top_left = self.transforms.current.raw.transform_point( @@ -199,7 +248,7 @@ impl Frame { .tessellate_rectangle( &lyon::math::Box2D::new(top_left, top_left + size), &options, - &mut buffer, + buffer.as_mut(), ) .expect("Fill rectangle"); } @@ -211,7 +260,7 @@ impl Frame { let mut buffer = self .buffers - .get(self.transforms.current.transform_style(stroke.style)); + .get_stroke(&self.transforms.current.transform_style(stroke.style)); let mut options = tessellation::StrokeOptions::default(); options.line_width = stroke.width; @@ -229,7 +278,7 @@ impl Frame { self.stroke_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } else { let path = path.transformed(&self.transforms.current.raw); @@ -237,7 +286,7 @@ impl Frame { self.stroke_tessellator.tessellate_path( path.raw(), &options, - &mut buffer, + buffer.as_mut(), ) } .expect("Stroke path"); @@ -382,16 +431,31 @@ impl Frame { } fn into_primitives(mut self) -> Vec<Primitive> { - for (buffer, style) in self.buffers.stack { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::Mesh2D { - buffers: triangle::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - style, - }) + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::SolidMesh { + buffers: triangle::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }) + } + } + Buffer::Gradient(buffer, gradient) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::GradientMesh { + buffers: triangle::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + gradient, + }) + } + } } } @@ -401,25 +465,66 @@ impl Frame { struct Vertex2DBuilder; -impl tessellation::FillVertexConstructor<Vertex2D> for Vertex2DBuilder { - fn new_vertex(&mut self, vertex: tessellation::FillVertex<'_>) -> Vertex2D { +impl tessellation::FillVertexConstructor<triangle::Vertex2D> + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> triangle::Vertex2D { + let position = vertex.position(); + + triangle::Vertex2D { + position: [position.x, position.y], + } + } +} + +impl tessellation::StrokeVertexConstructor<triangle::Vertex2D> + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> triangle::Vertex2D { + let position = vertex.position(); + + triangle::Vertex2D { + position: [position.x, position.y], + } + } +} + +struct TriangleVertex2DBuilder([f32; 4]); + +impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D> + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> triangle::ColoredVertex2D { let position = vertex.position(); - Vertex2D { + triangle::ColoredVertex2D { position: [position.x, position.y], + color: self.0, } } } -impl tessellation::StrokeVertexConstructor<Vertex2D> for Vertex2DBuilder { +impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D> + for TriangleVertex2DBuilder +{ fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> Vertex2D { + ) -> triangle::ColoredVertex2D { let position = vertex.position(); - Vertex2D { + triangle::ColoredVertex2D { position: [position.x, position.y], + color: self.0, } } } diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs index f9b8e447..4c19251d 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -1,5 +1,5 @@ //! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. -pub use crate::triangle::Style; +pub use crate::widget::canvas::Style; use iced_native::Color; @@ -8,7 +8,7 @@ use iced_native::Color; pub struct Stroke<'a> { /// The color or gradient of the stroke. /// - /// By default, it is set to [`StrokeStyle::Solid`] `BLACK`. + /// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`]. pub style: Style, /// The distance between the two edges of the stroke. pub width: f32, diff --git a/graphics/src/widget/canvas/style.rs b/graphics/src/widget/canvas/style.rs new file mode 100644 index 00000000..6794f2e7 --- /dev/null +++ b/graphics/src/widget/canvas/style.rs @@ -0,0 +1,23 @@ +use crate::{Color, Gradient}; + +/// The coloring style of some drawing. +#[derive(Debug, Clone, PartialEq)] +pub enum Style { + /// A solid [`Color`]. + Solid(Color), + + /// A [`Gradient`] color. + Gradient(Gradient), +} + +impl From<Color> for Style { + fn from(color: Color) -> Self { + Self::Solid(color) + } +} + +impl From<Gradient> for Style { + fn from(gradient: Gradient) -> Self { + Self::Gradient(gradient) + } +} diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 52255666..db4ba45d 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -40,7 +40,7 @@ pub trait Compositor: Sized { height: u32, ); - /// Returns [`GraphicsInformation`] used by this [`Compositor`]. + /// Returns [`Information`] used by this [`Compositor`]. fn fetch_information(&self) -> Information; /// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`]. diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs index 722e4d9c..a45a7ca1 100644 --- a/graphics/src/window/gl_compositor.rs +++ b/graphics/src/window/gl_compositor.rs @@ -54,7 +54,7 @@ pub trait GLCompositor: Sized { /// Resizes the viewport of the [`GLCompositor`]. fn resize_viewport(&mut self, physical_size: Size<u32>); - /// Returns [`GraphicsInformation`] used by this [`Compositor`]. + /// Returns [`Information`] used by this [`GLCompositor`]. fn fetch_information(&self) -> Information; /// Presents the primitives of the [`Renderer`] to the next frame of the diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml index 12e4e313..51459148 100644 --- a/lazy/Cargo.toml +++ b/lazy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_lazy" -version = "0.1.1" +version = "0.2.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "Lazy widgets for Iced" @@ -14,5 +14,5 @@ categories = ["gui"] ouroboros = "0.13" [dependencies.iced_native] -version = "0.5" +version = "0.6" path = "../native" diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 8987b993..3d7b8758 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -11,7 +11,7 @@ use iced_native::{ }; use ouroboros::self_referencing; -use std::cell::{Ref, RefCell}; +use std::cell::RefCell; use std::marker::PhantomData; /// A reusable, custom widget that uses The Elm Architecture. @@ -260,6 +260,14 @@ where ) { self.operation.focusable(state, id); } + + fn text_input( + &mut self, + state: &mut dyn widget::operation::TextInput, + id: Option<&widget::Id>, + ) { + self.operation.text_input(state, id); + } } self.with_element(|element| { @@ -314,25 +322,25 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { let overlay = OverlayBuilder { instance: self, - instance_ref_builder: |instance| instance.state.borrow(), tree, types: PhantomData, overlay_builder: |instance, tree| { - instance - .as_ref() - .unwrap() - .borrow_element() - .as_ref() - .unwrap() - .as_widget() - .overlay(&mut tree.children[0], layout, renderer) + instance.state.get_mut().as_mut().unwrap().with_element_mut( + move |element| { + element.as_mut().unwrap().as_widget_mut().overlay( + &mut tree.children[0], + layout, + renderer, + ) + }, + ) }, } .build(); @@ -354,15 +362,11 @@ where #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Event, S> { - instance: &'a Instance<'b, Message, Renderer, Event, S>, + instance: &'a mut Instance<'b, Message, Renderer, Event, S>, tree: &'a mut Tree, types: PhantomData<(Message, Event, S)>, - #[borrows(instance)] - #[covariant] - instance_ref: Ref<'this, Option<State<'a, Message, Renderer, Event, S>>>, - - #[borrows(instance_ref, mut tree)] + #[borrows(mut instance, mut tree)] #[covariant] overlay: Option<overlay::Element<'this, Event, Renderer>>, } @@ -506,7 +510,6 @@ where self.overlay = Some( OverlayBuilder { instance: overlay.instance, - instance_ref_builder: |instance| instance.state.borrow(), tree: overlay.tree, types: PhantomData, overlay_builder: |_, _| None, diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs index d61cc77e..2611dd10 100644 --- a/lazy/src/lazy.rs +++ b/lazy/src/lazy.rs @@ -207,7 +207,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -216,12 +216,12 @@ where cached: self, tree: &mut tree.children[0], types: PhantomData, - element_ref_builder: |cached| cached.element.borrow(), - element_builder: |element_ref| { - element_ref.as_ref().unwrap().borrow() - }, - overlay_builder: |element, tree| { - element.as_widget().overlay(tree, layout, renderer) + overlay_builder: |cached, tree| { + Rc::get_mut(cached.element.get_mut().as_mut().unwrap()) + .unwrap() + .get_mut() + .as_widget_mut() + .overlay(tree, layout, renderer) }, } .build(); @@ -237,20 +237,11 @@ where #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { - cached: &'a Lazy<'b, Message, Renderer, Dependency, View>, + cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>, tree: &'a mut Tree, types: PhantomData<(Message, Dependency, View)>, - #[borrows(cached)] - #[covariant] - element_ref: - Ref<'this, Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>, - - #[borrows(element_ref)] - #[covariant] - element: Ref<'this, Element<'static, Message, Renderer>>, - - #[borrows(element, mut tree)] + #[borrows(mut cached, mut tree)] #[covariant] overlay: Option<overlay::Element<'this, Message, Renderer>>, } diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 0b7ae6de..5e1b5dff 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -235,18 +235,20 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { + use std::ops::DerefMut; + let state = tree.state.downcast_ref::<State>(); let overlay = OverlayBuilder { content: self.content.borrow_mut(), tree: state.tree.borrow_mut(), types: PhantomData, - overlay_builder: |content, tree| { + overlay_builder: |content: &mut RefMut<Content<_, _>>, tree| { content.update( tree, renderer, @@ -254,16 +256,18 @@ where &self.view, ); + let Content { + element, layout, .. + } = content.deref_mut(); + let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &content.layout, + layout.bounds().position() - Point::ORIGIN, + layout, ); - content.element.as_widget().overlay( - tree, - content_layout, - renderer, - ) + element + .as_widget_mut() + .overlay(tree, content_layout, renderer) }, } .build(); diff --git a/native/Cargo.toml b/native/Cargo.toml index b4945c05..558909be 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_native" -version = "0.5.1" +version = "0.6.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A renderer-agnostic library for native GUIs" @@ -16,14 +16,14 @@ unicode-segmentation = "1.6" num-traits = "0.2" [dependencies.iced_core] -version = "0.5" +version = "0.6" path = "../core" [dependencies.iced_futures] -version = "0.4" +version = "0.5" path = "../futures" features = ["thread-pool"] [dependencies.iced_style] -version = "0.4" +version = "0.5" path = "../style" diff --git a/native/src/element.rs b/native/src/element.rs index 955e1bee..2f1adeff 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -316,6 +316,22 @@ where ) { self.operation.focusable(state, id); } + + fn scrollable( + &mut self, + state: &mut dyn widget::operation::Scrollable, + id: Option<&widget::Id>, + ) { + self.operation.scrollable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn widget::operation::TextInput, + id: Option<&widget::Id>, + ) { + self.operation.text_input(state, id); + } } self.widget @@ -389,7 +405,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -544,7 +560,7 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, state: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, diff --git a/native/src/image.rs b/native/src/image.rs index 516eb2db..06fd7ae6 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -1,6 +1,7 @@ //! Load and draw raster graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size}; +use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; @@ -21,15 +22,19 @@ impl Handle { } /// Creates an image [`Handle`] containing the image pixels directly. This - /// function expects the input data to be provided as a `Vec<u8>` of BGRA + /// function expects the input data to be provided as a `Vec<u8>` of RGBA /// pixels. /// /// This is useful if you have already decoded your image. - pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle { - Self::from_data(Data::Pixels { + pub fn from_pixels( + width: u32, + height: u32, + pixels: impl Into<Cow<'static, [u8]>>, + ) -> Handle { + Self::from_data(Data::Rgba { width, height, - pixels, + pixels: pixels.into(), }) } @@ -39,8 +44,8 @@ impl Handle { /// /// This is useful if you already have your image loaded in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: Vec<u8>) -> Handle { - Self::from_data(Data::Bytes(bytes)) + pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle { + Self::from_data(Data::Bytes(bytes.into())) } fn from_data(data: Data) -> Handle { @@ -86,16 +91,16 @@ pub enum Data { Path(PathBuf), /// In-memory data - Bytes(Vec<u8>), + Bytes(Cow<'static, [u8]>), - /// Decoded image pixels in BGRA format. - Pixels { + /// Decoded image pixels in RGBA format. + Rgba { /// The width of the image. width: u32, /// The height of the image. height: u32, /// The pixels. - pixels: Vec<u8>, + pixels: Cow<'static, [u8]>, }, } @@ -104,7 +109,7 @@ impl std::fmt::Debug for Data { match self { Data::Path(path) => write!(f, "Path({:?})", path), Data::Bytes(_) => write!(f, "Bytes(...)"), - Data::Pixels { width, height, .. } => { + Data::Rgba { width, height, .. } => { write!(f, "Pixels({} * {})", width, height) } } @@ -121,7 +126,7 @@ pub trait Renderer: crate::Renderer { type Handle: Clone + Hash; /// Returns the dimensions of an image for the given [`Handle`]. - fn dimensions(&self, handle: &Self::Handle) -> (u32, u32); + fn dimensions(&self, handle: &Self::Handle) -> Size<u32>; /// Draws an image with the given [`Handle`] and inside the provided /// `bounds`. diff --git a/native/src/lib.rs b/native/src/lib.rs index 02269265..62b8f372 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -23,8 +23,8 @@ //! - Build a new renderer, see the [renderer] module. //! - Build a custom widget, start at the [`Widget`] trait. //! -//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.4/core -//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.4/winit +//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.5/core +//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.5/winit //! [`druid`]: https://github.com/xi-editor/druid //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle //! [renderer]: crate::renderer diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 905d3389..0b05b058 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -11,7 +11,7 @@ use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; -use crate::widget::tree::{self, Tree}; +use crate::widget::Tree; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; /// An interactive component that can be displayed on top of other widgets. @@ -42,31 +42,9 @@ where cursor_position: Point, ); - /// Returns the [`Tag`] of the [`Widget`]. - /// - /// [`Tag`]: tree::Tag - fn tag(&self) -> tree::Tag { - tree::Tag::stateless() - } - - /// Returns the [`State`] of the [`Widget`]. - /// - /// [`State`]: tree::State - fn state(&self) -> tree::State { - tree::State::None - } - - /// Returns the state [`Tree`] of the children of the [`Widget`]. - fn children(&self) -> Vec<Tree> { - Vec::new() - } - - /// Reconciliates the [`Widget`] with the provided [`Tree`]. - fn diff(&self, _tree: &mut Tree) {} - - /// Applies an [`Operation`] to the [`Widget`]. + /// Applies a [`widget::Operation`] to the [`Overlay`]. fn operate( - &self, + &mut self, _layout: Layout<'_>, _operation: &mut dyn widget::Operation<Message>, ) { @@ -115,7 +93,7 @@ where /// This method will generally only be used by advanced users that are /// implementing the [`Widget`](crate::Widget) trait. pub fn from_children<'a, Message, Renderer>( - children: &'a [crate::Element<'_, Message, Renderer>], + children: &'a mut [crate::Element<'_, Message, Renderer>], tree: &'a mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -124,11 +102,11 @@ where Renderer: crate::Renderer, { children - .iter() + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|((child, state), layout)| { - child.as_widget().overlay(state, layout, renderer) + child.as_widget_mut().overlay(state, layout, renderer) }) .next() } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index b919c221..4f5ef32a 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -104,9 +104,9 @@ where .draw(renderer, theme, style, layout, cursor_position) } - /// Applies an [`Operation`] to the [`Element`]. + /// Applies a [`widget::Operation`] to the [`Element`]. pub fn operate( - &self, + &mut self, layout: Layout<'_>, operation: &mut dyn widget::Operation<Message>, ) { @@ -141,6 +141,57 @@ where self.content.layout(renderer, bounds, position) } + fn operate( + &mut self, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<B>, + ) { + struct MapOperation<'a, B> { + operation: &'a mut dyn widget::Operation<B>, + } + + impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> { + fn container( + &mut self, + id: Option<&widget::Id>, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation<T>, + ), + ) { + self.operation.container(id, &mut |operation| { + operate_on_children(&mut MapOperation { operation }); + }); + } + + fn focusable( + &mut self, + state: &mut dyn widget::operation::Focusable, + id: Option<&widget::Id>, + ) { + self.operation.focusable(state, id); + } + + fn scrollable( + &mut self, + state: &mut dyn widget::operation::Scrollable, + id: Option<&widget::Id>, + ) { + self.operation.scrollable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn widget::operation::TextInput, + id: Option<&widget::Id>, + ) { + self.operation.text_input(state, id) + } + } + + self.content + .operate(layout, &mut MapOperation { operation }); + } + fn on_event( &mut self, event: Event, diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index dbcf23d4..099b1a97 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -9,7 +9,7 @@ use crate::text::{self, Text}; use crate::touch; use crate::widget::container::{self, Container}; use crate::widget::scrollable::{self, Scrollable}; -use crate::widget::tree::{self, Tree}; +use crate::widget::Tree; use crate::{ Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, Widget, @@ -178,7 +178,7 @@ where font, text_size, padding, - style, + style: style.clone(), })); state.tree.diff(&container as &dyn Widget<_, _>); @@ -199,18 +199,6 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { - fn tag(&self) -> tree::Tag { - self.container.tag() - } - - fn state(&self) -> tree::State { - self.container.state() - } - - fn children(&self) -> Vec<Tree> { - self.container.children() - } - fn layout( &self, renderer: &Renderer, @@ -288,7 +276,7 @@ where layout: Layout<'_>, cursor_position: Point, ) { - let appearance = theme.appearance(self.style); + let appearance = theme.appearance(&self.style); let bounds = layout.bounds(); renderer.fill_quad( @@ -460,7 +448,7 @@ where _cursor_position: Point, viewport: &Rectangle, ) { - let appearance = theme.appearance(self.style); + let appearance = theme.appearance(&self.style); let bounds = layout.bounds(); let text_size = diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 9775c84b..d24801d4 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -155,7 +155,7 @@ where /// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket /// connection open. /// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.4/examples/websocket +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.5/examples/websocket pub fn unfold<I, T, Fut, Message>( id: I, initial: T, diff --git a/native/src/svg.rs b/native/src/svg.rs index f86fec5b..a8e481d2 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -1,6 +1,7 @@ //! Load and draw vector graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size}; +use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; @@ -24,7 +25,7 @@ impl Handle { /// /// This is useful if you already have your SVG data in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle { + pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle { Self::from_data(Data::Bytes(bytes.into())) } @@ -64,7 +65,7 @@ pub enum Data { /// In-memory data /// /// Can contain an SVG string or a gzip compressed data. - Bytes(Vec<u8>), + Bytes(Cow<'static, [u8]>), } impl std::fmt::Debug for Data { @@ -81,7 +82,7 @@ impl std::fmt::Debug for Data { /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. - fn dimensions(&self, handle: &Handle) -> (u32, u32); + fn dimensions(&self, handle: &Handle) -> Size<u32>; /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`. fn draw(&mut self, handle: Handle, bounds: Rectangle); diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 344ba4d6..6b853da8 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -18,8 +18,8 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// The [`integration_opengl`] & [`integration_wgpu`] examples use a /// [`UserInterface`] to integrate Iced in an existing graphical application. /// -/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_opengl -/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_wgpu +/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_opengl +/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_wgpu #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, @@ -190,7 +190,7 @@ where let mut state = State::Updated; let mut manual_overlay = - ManuallyDrop::new(self.root.as_widget().overlay( + ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -226,7 +226,7 @@ where ); manual_overlay = - ManuallyDrop::new(self.root.as_widget().overlay( + ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, Layout::new(&self.base), renderer, @@ -285,6 +285,10 @@ where &mut shell, ); + if matches!(event_status, event::Status::Captured) { + self.overlay = None; + } + shell.revalidate_layout(|| { self.base = renderer.layout( &self.root, @@ -391,11 +395,11 @@ where let viewport = Rectangle::with_size(self.bounds); - let base_cursor = if let Some(overlay) = self.root.as_widget().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { + let base_cursor = if let Some(overlay) = self + .root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + { let overlay_layout = self .overlay .take() @@ -448,7 +452,7 @@ where overlay .as_ref() .and_then(|layout| { - root.as_widget() + root.as_widget_mut() .overlay(&mut self.state, Layout::new(base), renderer) .map(|overlay| { let overlay_interaction = overlay.mouse_interaction( @@ -492,14 +496,19 @@ where operation, ); - if let Some(layout) = self.overlay.as_ref() { - if let Some(overlay) = self.root.as_widget().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { - overlay.operate(Layout::new(layout), operation); + if let Some(mut overlay) = self.root.as_widget_mut().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { + if self.overlay.is_none() { + self.overlay = Some(overlay.layout(renderer, self.bounds)); } + + overlay.operate( + Layout::new(self.overlay.as_ref().unwrap()), + operation, + ); } } diff --git a/native/src/widget.rs b/native/src/widget.rs index 8890b8e7..36d679a4 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -107,12 +107,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.4/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry +/// [examples]: https://github.com/iced-rs/iced/tree/0.5/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry /// [`lyon`]: https://github.com/nical/lyon -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/wgpu pub trait Widget<Message, Renderer> where Renderer: crate::Renderer, @@ -208,7 +208,7 @@ where /// Returns the overlay of the [`Widget`], if there is any. fn overlay<'a>( - &'a self, + &'a mut self, _state: &'a mut Tree, _layout: Layout<'_>, _renderer: &Renderer, diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 766e902b..9aa79dec 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -1,8 +1,10 @@ -use crate::widget::operation::{self, Operation}; +use crate::widget::operation::{self, Focusable, Operation, Scrollable}; use crate::widget::Id; use iced_futures::MaybeSend; +use std::rc::Rc; + /// An operation to be performed on the widget tree. #[allow(missing_debug_implementations)] pub struct Action<T>(Box<dyn Operation<T>>); @@ -24,7 +26,7 @@ impl<T> Action<T> { { Action(Box::new(Map { operation: self.0, - f: Box::new(f), + f: Rc::new(f), })) } @@ -37,7 +39,7 @@ impl<T> Action<T> { #[allow(missing_debug_implementations)] struct Map<A, B> { operation: Box<dyn Operation<A>>, - f: Box<dyn Fn(A) -> B>, + f: Rc<dyn Fn(A) -> B>, } impl<A, B> Operation<B> for Map<A, B> @@ -50,30 +52,44 @@ where id: Option<&Id>, operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), ) { - struct MapRef<'a, A, B> { + struct MapRef<'a, A> { operation: &'a mut dyn Operation<A>, - f: &'a dyn Fn(A) -> B, } - impl<'a, A, B> Operation<B> for MapRef<'a, A, B> { + impl<'a, A, B> Operation<B> for MapRef<'a, A> { fn container( &mut self, id: Option<&Id>, operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>), ) { - let Self { operation, f } = self; + let Self { operation, .. } = self; operation.container(id, &mut |operation| { - operate_on_children(&mut MapRef { operation, f }); + 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); + } } - let Self { operation, f } = self; + let Self { operation, .. } = self; MapRef { operation: operation.as_mut(), - f, } .container(id, operate_on_children); } @@ -85,4 +101,35 @@ where ) { self.operation.focusable(state, id); } + + fn scrollable( + &mut self, + state: &mut dyn operation::Scrollable, + id: Option<&Id>, + ) { + self.operation.scrollable(state, id); + } + + fn text_input( + &mut self, + state: &mut dyn operation::TextInput, + id: Option<&Id>, + ) { + self.operation.text_input(state, id); + } + + fn finish(&self) -> operation::Outcome<B> { + match self.operation.finish() { + operation::Outcome::None => operation::Outcome::None, + operation::Outcome::Some(output) => { + operation::Outcome::Some((self.f)(output)) + } + operation::Outcome::Chain(next) => { + operation::Outcome::Chain(Box::new(Map { + operation: next, + f: self.f.clone(), + })) + } + } + } } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 14759cfd..bbd9451c 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -231,7 +231,7 @@ where cursor_position, self.on_press.is_some(), theme, - self.style, + &self.style, || tree.state.downcast_ref::<State>(), ); @@ -260,12 +260,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -361,7 +361,7 @@ pub fn draw<'a, Renderer: crate::Renderer>( style_sheet: &dyn StyleSheet< Style = <Renderer::Theme as StyleSheet>::Style, >, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, state: impl FnOnce() -> &'a State, ) -> Appearance where @@ -426,12 +426,13 @@ pub fn layout<Renderer>( padding: Padding, layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits.width(width).height(height).pad(padding); + let limits = limits.width(width).height(height); - let mut content = layout_content(renderer, &limits); - content.move_to(Point::new(padding.left.into(), padding.top.into())); + let mut content = layout_content(renderer, &limits.pad(padding)); + let padding = padding.fit(content.size(), limits.max()); + let size = limits.pad(padding).resolve(content.size()).pad(padding); - let size = limits.resolve(content.size()).pad(padding); + content.move_to(Point::new(padding.left.into(), padding.top.into())); layout::Node::with_children(size, vec![content]) } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 8f30037a..bec5c448 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -224,9 +224,9 @@ where let mut children = layout.children(); let custom_style = if is_mouse_over { - theme.hovered(self.style, self.is_checked) + theme.hovered(&self.style, self.is_checked) } else { - theme.active(self.style, self.is_checked) + theme.active(&self.style, self.is_checked) }; { diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index a8b0f183..8030778b 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -242,12 +242,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index b0c4938a..16d0cb61 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -228,7 +228,7 @@ where cursor_position: Point, viewport: &Rectangle, ) { - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); draw_background(renderer, &style, layout.bounds()); @@ -248,12 +248,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, @@ -293,11 +293,11 @@ pub fn layout<Renderer>( .max_width(max_width) .max_height(max_height) .width(width) - .height(height) - .pad(padding); + .height(height); - let mut content = layout_content(renderer, &limits.loose()); - let size = limits.resolve(content.size()); + let mut content = layout_content(renderer, &limits.pad(padding).loose()); + let padding = padding.fit(content.size(), limits.max()); + let size = limits.pad(padding).resolve(content.size()); content.move_to(Point::new(padding.left.into(), padding.top.into())); content.align( @@ -309,7 +309,7 @@ pub fn layout<Renderer>( layout::Node::with_children(size.pad(padding), vec![content]) } -/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`. +/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`. pub fn draw_background<Renderer>( renderer: &mut Renderer, appearance: &Appearance, diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 79751878..3bce9e60 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -1,4 +1,5 @@ //! Helper functions to create pure widgets. +use crate::overlay; use crate::widget; use crate::{Element, Length}; @@ -18,7 +19,7 @@ macro_rules! column { ); } -/// Creates a [Row`] with the given children. +/// Creates a [`Row`] with the given children. /// /// [`Row`]: widget::Row #[macro_export] @@ -84,6 +85,7 @@ pub fn button<'a, Message, Renderer>( where Renderer: crate::Renderer, Renderer::Theme: widget::button::StyleSheet, + <Renderer::Theme as widget::button::StyleSheet>::Style: Default, { widget::Button::new(content) } @@ -208,7 +210,12 @@ where T: ToString + Eq + 'static, [T]: ToOwned<Owned = Vec<T>>, Renderer: crate::text::Renderer, - Renderer::Theme: widget::pick_list::StyleSheet, + Renderer::Theme: widget::pick_list::StyleSheet + + widget::scrollable::StyleSheet + + overlay::menu::StyleSheet + + widget::container::StyleSheet, + <Renderer::Theme as overlay::menu::StyleSheet>::Style: + From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>, { widget::PickList::new(options, selected, on_selected) } diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 91d68e34..8bd8ca1e 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -85,7 +85,7 @@ where { // The raw w/h of the underlying image let image_size = { - let (width, height) = renderer.dimensions(handle); + let Size { width, height } = renderer.dimensions(handle); Size::new(width as f32, height as f32) }; @@ -149,7 +149,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index b1fe596c..9c83287e 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -108,7 +108,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let mut size = limits .width(self.width) @@ -409,7 +409,7 @@ pub fn image_size<Renderer>( where Renderer: image::Renderer, { - let (width, height) = renderer.dimensions(handle); + let Size { width, height } = renderer.dimensions(handle); let (width, height) = { let dimensions = (width as f32, height as f32); diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index ef636aa2..a0aa4117 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -1,9 +1,11 @@ //! 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; @@ -28,6 +30,9 @@ pub trait Operation<T> { /// 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>) {} + /// Finishes the [`Operation`] and returns its [`Outcome`]. fn finish(&self) -> Outcome<T> { Outcome::None @@ -58,3 +63,46 @@ where } } } + +/// Produces an [`Operation`] that applies the given [`Operation`] to the +/// children of a container with the given [`Id`]. +pub fn scoped<T: 'static>( + target: Id, + operation: impl Operation<T> + 'static, +) -> impl Operation<T> { + struct ScopedOperation<Message> { + target: Id, + operation: Box<dyn Operation<Message>>, + } + + impl<Message: 'static> Operation<Message> for ScopedOperation<Message> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>), + ) { + if id == Some(&self.target) { + operate_on_children(self.operation.as_mut()); + } else { + operate_on_children(self); + } + } + + fn finish(&self) -> Outcome<Message> { + match self.operation.finish() { + Outcome::Chain(next) => { + Outcome::Chain(Box::new(ScopedOperation { + target: self.target.clone(), + operation: next, + })) + } + outcome => outcome, + } + } + } + + ScopedOperation { + target, + operation: Box::new(operation), + } +} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index f17bf178..0067006b 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -167,3 +167,37 @@ pub fn focus_next<T>() -> impl Operation<T> { count(|count| FocusNext { count, current: 0 }) } + +/// Produces an [`Operation`] that searches for the current focused widget +/// and stores its ID. This ignores widgets that do not have an ID. +pub fn find_focused() -> impl Operation<Id> { + struct FindFocused { + focused: Option<Id>, + } + + impl Operation<Id> for FindFocused { + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + if state.is_focused() && id.is_some() { + self.focused = id.cloned(); + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>), + ) { + operate_on_children(self) + } + + fn finish(&self) -> Outcome<Id> { + if let Some(id) = &self.focused { + Outcome::Some(id.clone()) + } else { + Outcome::None + } + } + } + + FindFocused { focused: None } +} diff --git a/native/src/widget/operation/text_input.rs b/native/src/widget/operation/text_input.rs new file mode 100644 index 00000000..4c773e99 --- /dev/null +++ b/native/src/widget/operation/text_input.rs @@ -0,0 +1,131 @@ +//! Operate on widgets that have text input. +use crate::widget::operation::Operation; +use crate::widget::Id; + +/// The internal state of a widget that has text input. +pub trait TextInput { + /// Moves the cursor of the text input to the front of the input text. + fn move_cursor_to_front(&mut self); + /// Moves the cursor of the text input to the end of the input text. + fn move_cursor_to_end(&mut self); + /// Moves the cursor of the text input to an arbitrary location. + fn move_cursor_to(&mut self, position: usize); + /// Selects all the content of the text input. + fn select_all(&mut self); +} + +/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the +/// front. +pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> { + struct MoveCursor { + target: Id, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.move_cursor_to_front(); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target } +} + +/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the +/// end. +pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> { + struct MoveCursor { + target: Id, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.move_cursor_to_end(); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target } +} + +/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the +/// provided position. +pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> { + struct MoveCursor { + target: Id, + position: usize, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.move_cursor_to(self.position); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target, position } +} + +/// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`]. +pub fn select_all<T>(target: Id) -> impl Operation<T> { + struct MoveCursor { + target: Id, + } + + impl<T> Operation<T> for MoveCursor { + fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.select_all(); + } + _ => {} + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + MoveCursor { target } +} diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 45090ecf..edad993e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -6,7 +6,7 @@ //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! -//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid +//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid mod axis; mod configuration; mod content; @@ -38,6 +38,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::touch; +use crate::widget; use crate::widget::container; use crate::widget::tree::{self, Tree}; use crate::{ @@ -85,7 +86,7 @@ use crate::{ /// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); /// /// let pane_grid = -/// PaneGrid::new(&state, |pane, state| { +/// PaneGrid::new(&state, |pane, state, is_maximized| { /// pane_grid::Content::new(match state { /// PaneState::SomePane => text("This is some pane"), /// PaneState::AnotherKindOfPane => text("This is another kind of pane"), @@ -100,8 +101,7 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { - state: &'a state::Internal, - elements: Vec<(Pane, Content<'a, Message, Renderer>)>, + contents: Contents<'a, Content<'a, Message, Renderer>>, width: Length, height: Length, spacing: u16, @@ -119,22 +119,35 @@ where /// Creates a [`PaneGrid`] with the given [`State`] and view function. /// /// The view function will be called to display each [`Pane`] present in the - /// [`State`]. + /// [`State`]. [`bool`] is set if the pane is maximized. pub fn new<T>( state: &'a State<T>, - view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>, + view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>, ) -> Self { - let elements = { - state - .panes - .iter() - .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) - .collect() + let contents = if let Some((pane, pane_state)) = + state.maximized.and_then(|pane| { + state.panes.get(&pane).map(|pane_state| (pane, pane_state)) + }) { + Contents::Maximized( + pane, + view(pane, pane_state, true), + Node::Pane(pane), + ) + } else { + Contents::All( + state + .panes + .iter() + .map(|(pane, pane_state)| { + (*pane, view(*pane, pane_state, false)) + }) + .collect(), + &state.internal, + ) }; Self { - elements, - state: &state.internal, + contents, width: Length::Fill, height: Length::Fill, spacing: 0, @@ -208,6 +221,12 @@ where self.style = style.into(); self } + + fn drag_enabled(&self) -> bool { + (!self.contents.is_maximized()) + .then(|| self.on_drag.is_some()) + .unwrap_or_default() + } } impl<'a, Message, Renderer> Widget<Message, Renderer> @@ -225,18 +244,25 @@ where } fn children(&self) -> Vec<Tree> { - self.elements + self.contents .iter() .map(|(_, content)| content.state()) .collect() } fn diff(&self, tree: &mut Tree) { - tree.diff_children_custom( - &self.elements, - |state, (_, content)| content.diff(state), - |(_, content)| content.state(), - ) + match &self.contents { + Contents::All(contents, _) => tree.diff_children_custom( + contents, + |state, (_, content)| content.diff(state), + |(_, content)| content.state(), + ), + Contents::Maximized(_, content, _) => tree.diff_children_custom( + &[content], + |state, content| content.diff(state), + |content| content.state(), + ), + } } fn width(&self) -> Length { @@ -255,15 +281,32 @@ where layout( renderer, limits, - self.state, + self.contents.layout(), self.width, self.height, self.spacing, - self.elements.iter().map(|(pane, content)| (*pane, content)), - |element, renderer, limits| element.layout(renderer, limits), + self.contents.iter(), + |content, renderer, limits| content.layout(renderer, limits), ) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + operation.container(None, &mut |operation| { + self.contents + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|(((_pane, content), state), layout)| { + content.operate(state, layout, operation); + }) + }); + } + fn on_event( &mut self, tree: &mut Tree, @@ -276,28 +319,34 @@ where ) -> event::Status { let action = tree.state.downcast_mut::<state::Action>(); + let on_drag = if self.drag_enabled() { + &self.on_drag + } else { + &None + }; + let event_status = update( action, - self.state, + self.contents.layout(), &event, layout, cursor_position, shell, self.spacing, - self.elements.iter().map(|(pane, content)| (*pane, content)), + self.contents.iter(), &self.on_click, - &self.on_drag, + on_drag, &self.on_resize, ); let picked_pane = action.picked_pane().map(|(pane, _)| pane); - self.elements + self.contents .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .map(|(((pane, content), tree), layout)| { - let is_picked = picked_pane == Some(*pane); + let is_picked = picked_pane == Some(pane); content.on_event( tree, @@ -323,14 +372,14 @@ where ) -> mouse::Interaction { mouse_interaction( tree.state.downcast_ref(), - self.state, + self.contents.layout(), layout, cursor_position, self.spacing, self.on_resize.as_ref().map(|(leeway, _)| *leeway), ) .unwrap_or_else(|| { - self.elements + self.contents .iter() .zip(&tree.children) .zip(layout.children()) @@ -341,7 +390,7 @@ where cursor_position, viewport, renderer, - self.on_drag.is_some(), + self.drag_enabled(), ) }) .max() @@ -361,7 +410,7 @@ where ) { draw( tree.state.downcast_ref(), - self.state, + self.contents.layout(), layout, cursor_position, renderer, @@ -370,11 +419,11 @@ where viewport, self.spacing, self.on_resize.as_ref().map(|(leeway, _)| *leeway), - self.style, - self.elements + &self.style, + self.contents .iter() .zip(&tree.children) - .map(|((pane, content), tree)| (*pane, (content, tree))), + .map(|((pane, content), tree)| (pane, (content, tree))), |(content, tree), renderer, style, @@ -395,13 +444,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'_, Message, Renderer>> { - self.elements - .iter() + self.contents + .iter_mut() .zip(&mut tree.children) .zip(layout.children()) .filter_map(|(((_, pane), tree), layout)| { @@ -429,24 +478,24 @@ where pub fn layout<Renderer, T>( renderer: &Renderer, limits: &layout::Limits, - state: &state::Internal, + node: &Node, width: Length, height: Length, spacing: u16, - elements: impl Iterator<Item = (Pane, T)>, - layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, + contents: impl Iterator<Item = (Pane, T)>, + layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { let limits = limits.width(width).height(height); let size = limits.resolve(Size::ZERO); - let regions = state.pane_regions(f32::from(spacing), size); - let children = elements - .filter_map(|(pane, element)| { + let regions = node.pane_regions(f32::from(spacing), size); + let children = contents + .filter_map(|(pane, content)| { let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); - let mut node = layout_element( - element, + let mut node = layout_content( + content, renderer, &layout::Limits::new(size, size), ); @@ -464,13 +513,13 @@ pub fn layout<Renderer, T>( /// accordingly. pub fn update<'a, Message, T: Draggable>( action: &mut state::Action, - state: &state::Internal, + node: &Node, event: &Event, layout: Layout<'_>, cursor_position: Point, shell: &mut Shell<'_, Message>, spacing: u16, - elements: impl Iterator<Item = (Pane, T)>, + contents: impl Iterator<Item = (Pane, T)>, on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_resize: &Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, @@ -492,7 +541,7 @@ pub fn update<'a, Message, T: Draggable>( cursor_position.y - bounds.y, ); - let splits = state.split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -514,7 +563,7 @@ pub fn update<'a, Message, T: Draggable>( layout, cursor_position, shell, - elements, + contents, on_click, on_drag, ); @@ -526,7 +575,7 @@ pub fn update<'a, Message, T: Draggable>( layout, cursor_position, shell, - elements, + contents, on_click, on_drag, ); @@ -539,7 +588,7 @@ pub fn update<'a, Message, T: Draggable>( | Event::Touch(touch::Event::FingerLost { .. }) => { if let Some((pane, _)) = action.picked_pane() { if let Some(on_drag) = on_drag { - let mut dropped_region = elements + let mut dropped_region = contents .zip(layout.children()) .filter(|(_, layout)| { layout.bounds().contains(cursor_position) @@ -570,7 +619,7 @@ pub fn update<'a, Message, T: Draggable>( if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); - let splits = state.split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -609,13 +658,13 @@ fn click_pane<'a, Message, T>( layout: Layout<'_>, cursor_position: Point, shell: &mut Shell<'_, Message>, - elements: impl Iterator<Item = (Pane, T)>, + contents: impl Iterator<Item = (Pane, T)>, on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, ) where T: Draggable, { - let mut clicked_region = elements + let mut clicked_region = contents .zip(layout.children()) .filter(|(_, layout)| layout.bounds().contains(cursor_position)); @@ -642,7 +691,7 @@ fn click_pane<'a, Message, T>( /// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. pub fn mouse_interaction( action: &state::Action, - state: &state::Internal, + node: &Node, layout: Layout<'_>, cursor_position: Point, spacing: u16, @@ -658,7 +707,7 @@ pub fn mouse_interaction( let bounds = layout.bounds(); let splits = - state.split_regions(f32::from(spacing), bounds.size()); + node.split_regions(f32::from(spacing), bounds.size()); let relative_cursor = Point::new( cursor_position.x - bounds.x, @@ -687,7 +736,7 @@ pub fn mouse_interaction( /// Draws a [`PaneGrid`]. pub fn draw<Renderer, T>( action: &state::Action, - state: &state::Internal, + node: &Node, layout: Layout<'_>, cursor_position: Point, renderer: &mut Renderer, @@ -696,8 +745,8 @@ pub fn draw<Renderer, T>( viewport: &Rectangle, spacing: u16, resize_leeway: Option<u16>, - style: <Renderer::Theme as StyleSheet>::Style, - elements: impl Iterator<Item = (Pane, T)>, + style: &<Renderer::Theme as StyleSheet>::Style, + contents: impl Iterator<Item = (Pane, T)>, draw_pane: impl Fn( T, &mut Renderer, @@ -717,7 +766,7 @@ pub fn draw<Renderer, T>( .and_then(|(split, axis)| { let bounds = layout.bounds(); - let splits = state.split_regions(f32::from(spacing), bounds.size()); + let splits = node.split_regions(f32::from(spacing), bounds.size()); let (_axis, region, ratio) = splits.get(&split)?; @@ -736,7 +785,7 @@ pub fn draw<Renderer, T>( ); let splits = - state.split_regions(f32::from(spacing), bounds.size()); + node.split_regions(f32::from(spacing), bounds.size()); let (_split, axis, region) = hovered_split( splits.iter(), @@ -759,7 +808,7 @@ pub fn draw<Renderer, T>( let mut render_picked_pane = None; - for ((id, pane), layout) in elements.zip(layout.children()) { + for ((id, pane), layout) in contents.zip(layout.children()) { match picked_pane { Some((dragging, origin)) if id == dragging => { render_picked_pane = Some((pane, origin, layout)); @@ -897,3 +946,49 @@ fn hovered_split<'a>( }) .next() } + +/// The visible contents of the [`PaneGrid`] +#[derive(Debug)] +pub enum Contents<'a, T> { + /// All panes are visible + All(Vec<(Pane, T)>, &'a state::Internal), + /// A maximized pane is visible + Maximized(Pane, T, Node), +} + +impl<'a, T> Contents<'a, T> { + /// Returns the layout [`Node`] of the [`Contents`] + pub fn layout(&self) -> &Node { + match self { + Contents::All(_, state) => state.layout(), + Contents::Maximized(_, _, layout) => layout, + } + } + + /// Returns an iterator over the values of the [`Contents`] + pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> { + match self { + Contents::All(contents, _) => Box::new( + contents.iter().map(|(pane, content)| (*pane, content)), + ), + Contents::Maximized(pane, content, _) => { + Box::new(std::iter::once((*pane, content))) + } + } + } + + fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> { + match self { + Contents::All(contents, _) => Box::new( + contents.iter_mut().map(|(pane, content)| (*pane, content)), + ), + Contents::Maximized(pane, content, _) => { + Box::new(std::iter::once((*pane, content))) + } + } + } + + fn is_maximized(&self) -> bool { + matches!(self, Self::Maximized(..)) + } +} diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index c236d820..5f269d1f 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -5,7 +5,7 @@ use crate::overlay; use crate::renderer; use crate::widget::container; use crate::widget::pane_grid::{Draggable, TitleBar}; -use crate::widget::Tree; +use crate::widget::{self, Tree}; use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// The content of a [`Pane`]. @@ -87,7 +87,7 @@ where /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`]. /// - /// [`Renderer`]: iced_native::Renderer + /// [`Renderer`]: crate::Renderer pub fn draw( &self, tree: &Tree, @@ -103,7 +103,7 @@ where let bounds = layout.bounds(); { - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); container::draw_background(renderer, &style, bounds); } @@ -183,6 +183,33 @@ where } } + pub(crate) fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + let body_layout = if let Some(title_bar) = &self.title_bar { + let mut children = layout.children(); + + title_bar.operate( + &mut tree.children[1], + children.next().unwrap(), + operation, + ); + + children.next().unwrap() + } else { + layout + }; + + self.body.as_widget().operate( + &mut tree.children[0], + body_layout, + operation, + ); + } + pub(crate) fn on_event( &mut self, tree: &mut Tree, @@ -278,12 +305,12 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - if let Some(title_bar) = self.title_bar.as_ref() { + if let Some(title_bar) = self.title_bar.as_mut() { let mut children = layout.children(); let title_bar_layout = children.next()?; @@ -294,14 +321,14 @@ where match title_bar.overlay(title_bar_state, title_bar_layout, renderer) { Some(overlay) => Some(overlay), - None => self.body.as_widget().overlay( + None => self.body.as_widget_mut().overlay( body_state, children.next()?, renderer, ), } } else { - self.body.as_widget().overlay( + self.body.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index cdca6267..c4ae0a0e 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -4,9 +4,9 @@ use crate::widget::pane_grid::{ Axis, Configuration, Direction, Node, Pane, Split, }; -use crate::{Point, Rectangle, Size}; +use crate::{Point, Size}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; /// The state of a [`PaneGrid`]. /// @@ -31,6 +31,11 @@ pub struct State<T> { /// /// [`PaneGrid`]: crate::widget::PaneGrid pub internal: Internal, + + /// The maximized [`Pane`] of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub(super) maximized: Option<Pane>, } impl<T> State<T> { @@ -52,7 +57,11 @@ impl<T> State<T> { let internal = Internal::from_configuration(&mut panes, config.into(), 0); - State { panes, internal } + State { + panes, + internal, + maximized: None, + } } /// Returns the total amount of panes in the [`State`]. @@ -153,6 +162,7 @@ impl<T> State<T> { node.split(new_split, axis, new_pane); let _ = self.panes.insert(new_pane, state); + let _ = self.maximized.take(); Some((new_pane, new_split)) } @@ -194,12 +204,39 @@ impl<T> State<T> { /// Closes the given [`Pane`] and returns its internal state and its closest /// sibling, if it exists. pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { + if self.maximized == Some(*pane) { + let _ = self.maximized.take(); + } + if let Some(sibling) = self.internal.layout.remove(pane) { self.panes.remove(pane).map(|state| (state, sibling)) } else { None } } + + /// Maximize the given [`Pane`]. Only this pane will be rendered by the + /// [`PaneGrid`] until [`Self::restore()`] is called. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub fn maximize(&mut self, pane: &Pane) { + self.maximized = Some(*pane); + } + + /// Restore the currently maximized [`Pane`] to it's normal size. All panes + /// will be rendered by the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub fn restore(&mut self) { + let _ = self.maximized.take(); + } + + /// Returns the maximized [`Pane`] of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub fn maximized(&self) -> Option<Pane> { + self.maximized + } } /// The internal state of a [`PaneGrid`]. @@ -226,11 +263,13 @@ impl Internal { let Internal { layout: a, last_id: next_id, + .. } = Self::from_configuration(panes, *a, next_id); let Internal { layout: b, last_id: next_id, + .. } = Self::from_configuration(panes, *b, next_id); ( @@ -304,25 +343,8 @@ impl Action { } impl Internal { - /// Calculates the current [`Pane`] regions from the [`PaneGrid`] layout. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn pane_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap<Pane, Rectangle> { - self.layout.pane_regions(spacing, size) - } - - /// Calculates the current [`Split`] regions from the [`PaneGrid`] layout. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn split_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap<Split, (Axis, Rectangle, f32)> { - self.layout.split_regions(spacing, size) + /// The layout [`Node`] of the [`Internal`] state + pub fn layout(&self) -> &Node { + &self.layout } } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index eb85f924..28e4670f 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -4,7 +4,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget::container; -use crate::widget::Tree; +use crate::widget::{self, Tree}; use crate::{ Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, }; @@ -114,7 +114,7 @@ where /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`]. /// - /// [`Renderer`]: iced_native::Renderer + /// [`Renderer`]: crate::Renderer pub fn draw( &self, tree: &Tree, @@ -129,7 +129,7 @@ where use container::StyleSheet; let bounds = layout.bounds(); - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); let inherited_style = renderer::Style { text_color: style.text_color.unwrap_or(inherited_style.text_color), }; @@ -257,6 +257,44 @@ where layout::Node::with_children(node.size().pad(self.padding), vec![node]) } + pub(crate) fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation<Message>, + ) { + let mut children = layout.children(); + let padded = children.next().unwrap(); + + let mut children = padded.children(); + let title_layout = children.next().unwrap(); + let mut show_title = true; + + if let Some(controls) = &self.controls { + let controls_layout = children.next().unwrap(); + + if title_layout.bounds().width + controls_layout.bounds().width + > padded.bounds().width + { + show_title = false; + } + + controls.as_widget().operate( + &mut tree.children[1], + controls_layout, + operation, + ) + }; + + if show_title { + self.content.as_widget().operate( + &mut tree.children[0], + title_layout, + operation, + ) + } + } + pub(crate) fn on_event( &mut self, tree: &mut Tree, @@ -357,7 +395,7 @@ where } pub(crate) fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, @@ -377,13 +415,13 @@ where let controls_state = states.next().unwrap(); content - .as_widget() + .as_widget_mut() .overlay(title_state, title_layout, renderer) .or_else(move || { - controls.as_ref().and_then(|controls| { + controls.as_mut().and_then(|controls| { let controls_layout = children.next()?; - controls.as_widget().overlay( + controls.as_widget_mut().overlay( controls_state, controls_layout, renderer, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index a92ea655..52cb1ad1 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -9,6 +9,8 @@ use crate::overlay::menu::{self, Menu}; use crate::renderer; use crate::text::{self, Text}; use crate::touch; +use crate::widget::container; +use crate::widget::scrollable; use crate::widget::tree::{self, Tree}; use crate::{ Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, @@ -42,7 +44,12 @@ where T: ToString + Eq, [T]: ToOwned<Owned = Vec<T>>, Renderer: text::Renderer, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { /// The default padding of a [`PickList`]. pub const DEFAULT_PADDING: Padding = Padding::new(5); @@ -114,7 +121,12 @@ where [T]: ToOwned<Owned = Vec<T>>, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { fn tag(&self) -> tree::Tag { tree::Tag::of::<State<T>>() @@ -202,12 +214,12 @@ where &self.font, self.placeholder.as_deref(), self.selected.as_ref(), - self.style, + &self.style, ) } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, @@ -221,7 +233,7 @@ where self.text_size, self.font.clone(), &self.options, - self.style, + self.style.clone(), ) } } @@ -233,7 +245,12 @@ where [T]: ToOwned<Owned = Vec<T>>, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self { Self::new(pick_list) @@ -456,7 +473,12 @@ where T: Clone + ToString, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + <Renderer::Theme as menu::StyleSheet>::Style: + From<<Renderer::Theme as StyleSheet>::Style>, { if state.is_open { let bounds = layout.bounds(); @@ -493,7 +515,7 @@ pub fn draw<T, Renderer>( font: &Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index 5f8892fe..f059026f 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -124,7 +124,7 @@ where / (range_end - range_start) }; - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); renderer.fill_quad( renderer::Quad { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 10e89870..b95ccc5b 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -230,9 +230,9 @@ where let mut children = layout.children(); let custom_style = if is_mouse_over { - theme.hovered(self.style, self.is_selected) + theme.hovered(&self.style, self.is_selected) } else { - theme.active(self.style, self.is_selected) + theme.active(&self.style, self.is_selected) }; { diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index eda7c2d3..c689ac13 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -229,12 +229,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - overlay::from_children(&self.children, tree, layout, renderer) + overlay::from_children(&mut self.children, tree, layout, renderer) } } diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs index 1d1c04cf..2dc7b6f0 100644 --- a/native/src/widget/rule.rs +++ b/native/src/widget/rule.rs @@ -88,7 +88,7 @@ where _viewport: &Rectangle, ) { let bounds = layout.bounds(); - let style = theme.style(self.style); + let style = theme.appearance(&self.style); let bounds = if self.is_horizontal { let line_y = (bounds.y + (bounds.height / 2.0) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index b445e505..a5e97aa7 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -233,7 +233,7 @@ where self.scrollbar_width, self.scrollbar_margin, self.scroller_width, - self.style, + &self.style, |renderer, layout, cursor_position, viewport| { self.content.as_widget().draw( &tree.children[0], @@ -276,13 +276,13 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { self.content - .as_widget() + .as_widget_mut() .overlay( &mut tree.children[0], layout.children().next().unwrap(), @@ -334,6 +334,12 @@ impl Id { } } +impl From<Id> for widget::Id { + fn from(id: Id) -> Self { + id.0 + } +} + /// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`] /// to the provided `percentage`. pub fn snap_to<Message: 'static>(id: Id, percentage: f32) -> Command<Message> { @@ -627,7 +633,7 @@ pub fn draw<Renderer>( scrollbar_width: u16, scrollbar_margin: u16, scroller_width: u16, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle), ) where Renderer: crate::Renderer, diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 011454de..87030a4d 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -222,7 +222,7 @@ where self.value, &self.range, theme, - self.style, + &self.style, ) } @@ -353,7 +353,7 @@ pub fn draw<T, R>( value: T, range: &RangeInclusive<T>, style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>, - style: <R::Theme as StyleSheet>::Style, + style: &<R::Theme as StyleSheet>::Style, ) where T: Into<f64> + Copy, R: crate::Renderer, diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index aa68bfb8..1015ed0a 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -83,7 +83,7 @@ where limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); // The size to be available to the widget prior to `Shrink`ing @@ -120,7 +120,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index dab6e874..be9e775e 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -74,7 +74,7 @@ where self } - /// Sets the [`Color`] of the [`Text`]. + /// Sets the style of the [`Text`]. pub fn style( mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>, diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index ae0305dc..9391d1dd 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -165,7 +165,7 @@ where } /// Draws the [`TextInput`] with the given [`Renderer`], overriding its - /// [`text_input::Value`] if provided. + /// [`Value`] if provided. /// /// [`Renderer`]: text::Renderer pub fn draw( @@ -188,7 +188,7 @@ where self.size, &self.font, self.is_secure, - self.style, + &self.style, ) } } @@ -233,6 +233,7 @@ where let state = tree.state.downcast_mut::<State>(); operation.focusable(state, self.id.as_ref().map(|id| &id.0)); + operation.text_input(state, self.id.as_ref().map(|id| &id.0)); } fn on_event( @@ -284,7 +285,7 @@ where self.size, &self.font, self.is_secure, - self.style, + &self.style, ) } @@ -332,11 +333,43 @@ impl Id { } } +impl From<Id> for widget::Id { + fn from(id: Id) -> Self { + id.0 + } +} + /// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. pub fn focus<Message: 'static>(id: Id) -> Command<Message> { Command::widget(operation::focusable::focus(id.0)) } +/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// end. +pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> { + Command::widget(operation::text_input::move_cursor_to_end(id.0)) +} + +/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// front. +pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> { + Command::widget(operation::text_input::move_cursor_to_front(id.0)) +} + +/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the +/// provided position. +pub fn move_cursor_to<Message: 'static>( + id: Id, + position: usize, +) -> Command<Message> { + Command::widget(operation::text_input::move_cursor_to(id.0, position)) +} + +/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`]. +pub fn select_all<Message: 'static>(id: Id) -> Command<Message> { + Command::widget(operation::text_input::select_all(id.0)) +} + /// Computes the layout of a [`TextInput`]. pub fn layout<Renderer>( renderer: &Renderer, @@ -350,6 +383,8 @@ where { let text_size = size.unwrap_or_else(|| renderer.default_size()); + let padding = padding.fit(Size::ZERO, limits.max()); + let limits = limits .pad(padding) .width(width) @@ -742,7 +777,7 @@ pub fn draw<Renderer>( size: Option<u16>, font: &Renderer::Font, is_secure: bool, - style: <Renderer::Theme as StyleSheet>::Style, + style: &<Renderer::Theme as StyleSheet>::Style, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -997,6 +1032,24 @@ impl operation::Focusable for State { } } +impl operation::TextInput for State { + fn move_cursor_to_front(&mut self) { + State::move_cursor_to_front(self) + } + + fn move_cursor_to_end(&mut self) { + State::move_cursor_to_end(self) + } + + fn move_cursor_to(&mut self, position: usize) { + State::move_cursor_to(self, position) + } + + fn select_all(&mut self) { + State::select_all(self) + } +} + mod platform { use crate::keyboard; diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index c5c2d82a..37cafd92 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -260,9 +260,9 @@ where let is_mouse_over = bounds.contains(cursor_position); let style = if is_mouse_over { - theme.hovered(self.style, self.is_active) + theme.hovered(&self.style, self.is_active) } else { - theme.active(self.style, self.is_active) + theme.active(&self.style, self.is_active) }; let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO; diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 674f2ba6..084dc269 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -201,7 +201,7 @@ where self.gap, self.padding, self.snap_within_viewport, - self.style, + &self.style, |renderer, limits| { Widget::<(), Renderer>::layout(tooltip, renderer, limits) }, @@ -221,12 +221,12 @@ where } fn overlay<'b>( - &'b self, + &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option<overlay::Element<'b, Message, Renderer>> { - self.content.as_widget().overlay( + self.content.as_widget_mut().overlay( &mut tree.children[0], layout, renderer, @@ -275,7 +275,7 @@ pub fn draw<Renderer>( gap: u16, padding: u16, snap_within_viewport: bool, - style: <Renderer::Theme as container::StyleSheet>::Style, + style: &<Renderer::Theme as container::StyleSheet>::Style, layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, draw_text: impl FnOnce( &mut Renderer, diff --git a/native/src/widget/tree.rs b/native/src/widget/tree.rs index a8b1a185..0af40c33 100644 --- a/native/src/widget/tree.rs +++ b/native/src/widget/tree.rs @@ -30,7 +30,7 @@ impl Tree { } } - /// Creates a new [`Tree`] for the provided [`Element`]. + /// Creates a new [`Tree`] for the provided [`Widget`]. pub fn new<'a, Message, Renderer>( widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>, ) -> Self @@ -46,10 +46,10 @@ impl Tree { } } - /// Reconciliates the current tree with the provided [`Element`]. + /// Reconciliates the current tree with the provided [`Widget`]. /// - /// If the tag of the [`Element`] matches the tag of the [`Tree`], then the - /// [`Element`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). + /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the + /// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called). /// /// Otherwise, the whole [`Tree`] is recreated. /// @@ -67,7 +67,7 @@ impl Tree { } } - /// Reconciliates the children of the tree with the provided list of [`Element`]. + /// Reconciliates the children of the tree with the provided list of widgets. pub fn diff_children<'a, Message, Renderer>( &mut self, new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>], @@ -81,7 +81,7 @@ impl Tree { ) } - /// Reconciliates the children of the tree with the provided list of [`Element`] using custom + /// Reconciliates the children of the tree with the provided list of widgets using custom /// logic both for diffing and creating new widget state. pub fn diff_children_custom<T>( &mut self, diff --git a/src/application.rs b/src/application.rs index 23ce034e..0f514f66 100644 --- a/src/application.rs +++ b/src/application.rs @@ -39,15 +39,15 @@ pub use iced_native::application::{Appearance, StyleSheet}; /// to listen to time. /// - [`todos`], a todos tracker inspired by [TodoMVC]. /// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.4/examples -/// [`clock`]: https://github.com/iced-rs/iced/tree/0.4/examples/clock -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.4/examples/download_progress -/// [`events`]: https://github.com/iced-rs/iced/tree/0.4/examples/events -/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.4/examples/game_of_life -/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.4/examples/pokedex -/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.4/examples/solar_system -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.4/examples/stopwatch -/// [`todos`]: https://github.com/iced-rs/iced/tree/0.4/examples/todos +/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.5/examples +/// [`clock`]: https://github.com/iced-rs/iced/tree/0.5/examples/clock +/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress +/// [`events`]: https://github.com/iced-rs/iced/tree/0.5/examples/events +/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.5/examples/game_of_life +/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.5/examples/pokedex +/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.5/examples/solar_system +/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch +/// [`todos`]: https://github.com/iced-rs/iced/tree/0.5/examples/todos /// [`Sandbox`]: crate::Sandbox /// [`Canvas`]: crate::widget::Canvas /// [PokéAPI]: https://pokeapi.co/ @@ -148,9 +148,8 @@ pub trait Application: Sized { Self::Theme::default() } - /// Returns the current [`Style`] of the [`Theme`]. + /// Returns the current `Style` of the [`Theme`]. /// - /// [`Style`]: <Self::Theme as StyleSheet>::Style /// [`Theme`]: Self::Theme fn style(&self) -> <Self::Theme as StyleSheet>::Style { <Self::Theme as StyleSheet>::Style::default() @@ -26,11 +26,11 @@ //! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md //! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/0.4/master/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs -//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.4/wgpu -//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.4/winit +//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.5/wgpu +//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.5/winit //! [`dodrio`]: https://github.com/fitzgen/dodrio //! [web runtime]: https://github.com/iced-rs/iced_web -//! [examples]: https://github.com/iced-rs/iced/tree/0.4/examples +//! [examples]: https://github.com/iced-rs/iced/tree/0.5/examples //! [repository]: https://github.com/iced-rs/iced //! //! # Overview diff --git a/src/overlay.rs b/src/overlay.rs index a7003751..c0f4c492 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -3,6 +3,8 @@ /// A generic [`Overlay`]. /// /// This is an alias of an `iced_native` element with a default `Renderer`. +/// +/// [`Overlay`]: iced_native::Overlay pub type Element<'a, Message, Renderer = crate::Renderer> = iced_native::overlay::Element<'a, Message, Renderer>; diff --git a/src/sandbox.rs b/src/sandbox.rs index bdb6ad5a..dc69b6bb 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -34,19 +34,19 @@ use crate::{Application, Command, Element, Error, Settings, Subscription}; /// - [`tour`], a simple UI tour that can run both on native platforms and the /// web! /// -/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.4/examples -/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool -/// [`counter`]: https://github.com/iced-rs/iced/tree/0.4/examples/counter -/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget -/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry -/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid -/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.4/examples/progress_bar -/// [`styling`]: https://github.com/iced-rs/iced/tree/0.4/examples/styling -/// [`svg`]: https://github.com/iced-rs/iced/tree/0.4/examples/svg -/// [`tour`]: https://github.com/iced-rs/iced/tree/0.4/examples/tour +/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.5/examples +/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool +/// [`counter`]: https://github.com/iced-rs/iced/tree/0.5/examples/counter +/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget +/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry +/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid +/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.5/examples/progress_bar +/// [`styling`]: https://github.com/iced-rs/iced/tree/0.5/examples/styling +/// [`svg`]: https://github.com/iced-rs/iced/tree/0.5/examples/svg +/// [`tour`]: https://github.com/iced-rs/iced/tree/0.5/examples/tour /// [`Canvas widget`]: crate::widget::Canvas /// [the overview]: index.html#overview -/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/wgpu +/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/wgpu /// [`Svg` widget]: crate::widget::Svg /// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg /// diff --git a/src/widget.rs b/src/widget.rs index 9f09cb8f..7c67a599 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -56,7 +56,7 @@ pub mod pane_grid { //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! - //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid + //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid pub use iced_native::widget::pane_grid::{ Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent, Split, State, StyleSheet, @@ -120,7 +120,8 @@ pub mod toggler { pub mod text_input { //! Display fields that can be filled with text. pub use iced_native::widget::text_input::{ - focus, Appearance, Id, StyleSheet, + focus, move_cursor_to, move_cursor_to_end, move_cursor_to_front, + select_all, Appearance, Id, StyleSheet, }; /// A field that can be filled with text. diff --git a/src/window.rs b/src/window.rs index eb5e17a6..2018053f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -9,4 +9,4 @@ pub use position::Position; pub use settings::Settings; #[cfg(not(target_arch = "wasm32"))] -pub use crate::runtime::window::{move_to, resize}; +pub use crate::runtime::window::*; diff --git a/style/Cargo.toml b/style/Cargo.toml index 4a482a4f..559b4462 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_style" -version = "0.4.0" +version = "0.5.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "The default set of styles of Iced" @@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [dependencies.iced_core] -version = "0.5" +version = "0.6" path = "../core" features = ["palette"] diff --git a/style/src/application.rs b/style/src/application.rs index d48c6a34..e9a1f4ff 100644 --- a/style/src/application.rs +++ b/style/src/application.rs @@ -1,13 +1,23 @@ +//! Change the appearance of an application. use iced_core::Color; +/// A set of rules that dictate the style of an application. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; - fn appearance(&self, style: Self::Style) -> Appearance; + /// Returns the [`Appearance`] of the application for the provided [`Style`]. + /// + /// [`Style`]: Self::Style + fn appearance(&self, style: &Self::Style) -> Appearance; } +/// The appearance of an application. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Appearance { + /// The background [`Color`] of the application. pub background_color: Color, + + /// The default text [`Color`] of the application. pub text_color: Color, } diff --git a/style/src/button.rs b/style/src/button.rs index c63a6b71..a564a2b7 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -1,14 +1,20 @@ -//! Allow your users to perform actions by pressing a button. +//! Change the apperance of a button. use iced_core::{Background, Color, Vector}; /// The appearance of a button. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The amount of offset to apply to the shadow of the button. pub shadow_offset: Vector, + /// The [`Background`] of the button. pub background: Option<Background>, + /// The border radius of the button. pub border_radius: f32, + /// The border width of the button. pub border_width: f32, + /// The border [`Color`] of the button. pub border_color: Color, + /// The text [`Color`] of the button. pub text_color: Color, } @@ -27,11 +33,14 @@ impl std::default::Default for Appearance { /// A set of rules that dictate the style of a button. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; - fn active(&self, style: Self::Style) -> Appearance; + /// Produces the active [`Appearance`] of a button. + fn active(&self, style: &Self::Style) -> Appearance; - fn hovered(&self, style: Self::Style) -> Appearance { + /// Produces the hovered [`Appearance`] of a button. + fn hovered(&self, style: &Self::Style) -> Appearance { let active = self.active(style); Appearance { @@ -40,14 +49,16 @@ pub trait StyleSheet { } } - fn pressed(&self, style: Self::Style) -> Appearance { + /// Produces the pressed [`Appearance`] of a button. + fn pressed(&self, style: &Self::Style) -> Appearance { Appearance { shadow_offset: Vector::default(), ..self.active(style) } } - fn disabled(&self, style: Self::Style) -> Appearance { + /// Produces the disabled [`Appearance`] of a button. + fn disabled(&self, style: &Self::Style) -> Appearance { let active = self.active(style); Appearance { diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs index ba54b0a2..827b3225 100644 --- a/style/src/checkbox.rs +++ b/style/src/checkbox.rs @@ -1,22 +1,31 @@ -//! Show toggle controls using checkboxes. +//! Change the appearance of a checkbox. use iced_core::{Background, Color}; /// The appearance of a checkbox. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The [`Background`] of the checkbox. pub background: Background, + /// The checkmark [`Color`] of the checkbox. pub checkmark_color: Color, + /// The border radius of the checkbox. pub border_radius: f32, + /// The border width of the checkbox. pub border_width: f32, + /// The border [`Color`] of the checkbox. pub border_color: Color, + /// The text [`Color`] of the checkbox. pub text_color: Option<Color>, } /// A set of rules that dictate the style of a checkbox. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; - fn active(&self, style: Self::Style, is_checked: bool) -> Appearance; + /// Produces the active [`Appearance`] of a checkbox. + fn active(&self, style: &Self::Style, is_checked: bool) -> Appearance; - fn hovered(&self, style: Self::Style, is_checked: bool) -> Appearance; + /// Produces the hovered [`Appearance`] of a checkbox. + fn hovered(&self, style: &Self::Style, is_checked: bool) -> Appearance; } diff --git a/style/src/container.rs b/style/src/container.rs index 184310fa..560b2d5b 100644 --- a/style/src/container.rs +++ b/style/src/container.rs @@ -1,13 +1,18 @@ -//! Decorate content and apply alignment. +//! Change the appearance of a container. use iced_core::{Background, Color}; /// The appearance of a container. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The text [`Color`] of the container. pub text_color: Option<Color>, + /// The [`Background`] of the container. pub background: Option<Background>, + /// The border radius of the container. pub border_radius: f32, + /// The border width of the container. pub border_width: f32, + /// The border [`Color`] of the container. pub border_color: Color, } @@ -25,8 +30,9 @@ impl std::default::Default for Appearance { /// A set of rules that dictate the [`Appearance`] of a container. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; /// Produces the [`Appearance`] of a container. - fn appearance(&self, style: Self::Style) -> Appearance; + fn appearance(&self, style: &Self::Style) -> Appearance; } diff --git a/style/src/lib.rs b/style/src/lib.rs index 0dde9582..3242602c 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -15,6 +15,7 @@ clippy::new_without_default, clippy::useless_conversion )] +#![deny(missing_docs, unused_results)] #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub use iced_core::{Background, Color}; diff --git a/style/src/menu.rs b/style/src/menu.rs index 6ef3e2a2..7d878748 100644 --- a/style/src/menu.rs +++ b/style/src/menu.rs @@ -1,19 +1,30 @@ +//! Change the appearance of menus. use iced_core::{Background, Color}; /// The appearance of a menu. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The text [`Color`] of the menu. pub text_color: Color, + /// The [`Background`] of the menu. pub background: Background, + /// The border width of the menu. pub border_width: f32, + /// The border radius of the menu. pub border_radius: f32, + /// The border [`Color`] of the menu. pub border_color: Color, + /// The text [`Color`] of a selected option in the menu. pub selected_text_color: Color, + /// The background [`Color`] of a selected option in the menu. pub selected_background: Background, } +/// The style sheet of a menu. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default + Clone; - fn appearance(&self, style: Self::Style) -> Appearance; + /// Produces the [`Appearance`] of a menu. + fn appearance(&self, style: &Self::Style) -> Appearance; } diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs index 5bae353f..fd8fc05f 100644 --- a/style/src/pane_grid.rs +++ b/style/src/pane_grid.rs @@ -1,16 +1,16 @@ -//! Let your users split regions of your application and organize layout -//! dynamically. +//! Change the appearance of a pane grid. use iced_core::Color; /// A set of rules that dictate the style of a container. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; /// The [`Line`] to draw when a split is picked. - fn picked_split(&self, style: Self::Style) -> Option<Line>; + fn picked_split(&self, style: &Self::Style) -> Option<Line>; /// The [`Line`] to draw when a split is hovered. - fn hovered_split(&self, style: Self::Style) -> Option<Line>; + fn hovered_split(&self, style: &Self::Style) -> Option<Line>; } /// A line. diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs index 2bafe932..8d93dff2 100644 --- a/style/src/pick_list.rs +++ b/style/src/pick_list.rs @@ -1,29 +1,33 @@ +//! Change the appearance of a pick list. use iced_core::{Background, Color}; -use crate::container; -use crate::menu; -use crate::scrollable; - /// The appearance of a pick list. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The text [`Color`] of the pick list. pub text_color: Color, + /// The placeholder [`Color`] of the pick list. pub placeholder_color: Color, + /// The [`Background`] of the pick list. pub background: Background, + /// The border radius of the pick list. pub border_radius: f32, + /// The border width of the pick list. pub border_width: f32, + /// The border color of the pick list. pub border_color: Color, + /// The size of the arrow icon of the pick list. pub icon_size: f32, } /// A set of rules that dictate the style of a container. -pub trait StyleSheet: - container::StyleSheet + menu::StyleSheet + scrollable::StyleSheet -{ - type Style: Default + Copy + Into<<Self as menu::StyleSheet>::Style>; +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default + Clone; - fn active(&self, style: <Self as StyleSheet>::Style) -> Appearance; + /// Produces the active [`Appearance`] of a pick list. + fn active(&self, style: &<Self as StyleSheet>::Style) -> Appearance; - /// Produces the style of a container. - fn hovered(&self, style: <Self as StyleSheet>::Style) -> Appearance; + /// Produces the hovered [`Appearance`] of a pick list. + fn hovered(&self, style: &<Self as StyleSheet>::Style) -> Appearance; } diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs index 768e7c9c..fb1819fc 100644 --- a/style/src/progress_bar.rs +++ b/style/src/progress_bar.rs @@ -1,17 +1,22 @@ -//! Provide progress feedback to your users. +//! Change the appearance of a progress bar. use iced_core::Background; /// The appearance of a progress bar. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The [`Background`] of the progress bar. pub background: Background, + /// The [`Background`] of the bar of the progress bar. pub bar: Background, + /// The border radius of the progress bar. pub border_radius: f32, } /// A set of rules that dictate the style of a progress bar. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; - fn appearance(&self, style: Self::Style) -> Appearance; + /// Produces the [`Appearance`] of the progress bar. + fn appearance(&self, style: &Self::Style) -> Appearance; } diff --git a/style/src/radio.rs b/style/src/radio.rs index d14ea33e..06c49029 100644 --- a/style/src/radio.rs +++ b/style/src/radio.rs @@ -1,21 +1,29 @@ -//! Create choices using radio buttons. +//! Change the appearance of radio buttons. use iced_core::{Background, Color}; /// The appearance of a radio button. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The [`Background`] of the radio button. pub background: Background, + /// The [`Color`] of the dot of the radio button. pub dot_color: Color, + /// The border width of the radio button. pub border_width: f32, + /// The border [`Color`] of the radio button. pub border_color: Color, + /// The text [`Color`] of the radio button. pub text_color: Option<Color>, } /// A set of rules that dictate the style of a radio button. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; - fn active(&self, style: Self::Style, is_selected: bool) -> Appearance; + /// Produces the active [`Appearance`] of a radio button. + fn active(&self, style: &Self::Style, is_selected: bool) -> Appearance; - fn hovered(&self, style: Self::Style, is_selected: bool) -> Appearance; + /// Produces the hovered [`Appearance`] of a radio button. + fn hovered(&self, style: &Self::Style, is_selected: bool) -> Appearance; } diff --git a/style/src/rule.rs b/style/src/rule.rs index af334912..b7380747 100644 --- a/style/src/rule.rs +++ b/style/src/rule.rs @@ -1,4 +1,4 @@ -//! Display a horizontal or vertical rule for dividing content. +//! Change the appearance of a rule. use iced_core::Color; /// The appearance of a rule. @@ -16,10 +16,11 @@ pub struct Appearance { /// A set of rules that dictate the style of a rule. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; /// Produces the style of a rule. - fn style(&self, style: Self::Style) -> Appearance; + fn appearance(&self, style: &Self::Style) -> Appearance; } /// The fill mode of a rule. diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs index 8da7409c..c6d7d537 100644 --- a/style/src/scrollable.rs +++ b/style/src/scrollable.rs @@ -1,37 +1,47 @@ -//! Navigate an endless amount of content with a scrollbar. +//! Change the appearance of a scrollable. use iced_core::{Background, Color}; /// The appearance of a scrollable. #[derive(Debug, Clone, Copy)] pub struct Scrollbar { + /// The [`Background`] of a scrollable. pub background: Option<Background>, + /// The border radius of a scrollable. pub border_radius: f32, + /// The border width of a scrollable. pub border_width: f32, + /// The border [`Color`] of a scrollable. pub border_color: Color, + /// The appearance of the [`Scroller`] of a scrollable. pub scroller: Scroller, } /// The appearance of the scroller of a scrollable. #[derive(Debug, Clone, Copy)] pub struct Scroller { + /// The [`Color`] of the scroller. pub color: Color, + /// The border radius of the scroller. pub border_radius: f32, + /// The border width of the scroller. pub border_width: f32, + /// The border [`Color`] of the scroller. pub border_color: Color, } /// A set of rules that dictate the style of a scrollable. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; /// Produces the style of an active scrollbar. - fn active(&self, style: Self::Style) -> Scrollbar; + fn active(&self, style: &Self::Style) -> Scrollbar; /// Produces the style of an hovered scrollbar. - fn hovered(&self, style: Self::Style) -> Scrollbar; + fn hovered(&self, style: &Self::Style) -> Scrollbar; /// Produces the style of a scrollbar that is being dragged. - fn dragging(&self, style: Self::Style) -> Scrollbar { + fn dragging(&self, style: &Self::Style) -> Scrollbar { self.hovered(style) } } diff --git a/style/src/slider.rs b/style/src/slider.rs index 0ff0449b..4b52fad3 100644 --- a/style/src/slider.rs +++ b/style/src/slider.rs @@ -1,39 +1,56 @@ -//! Display an interactive selector of a single value from a range of values. +//! Change the apperance of a slider. use iced_core::Color; /// The appearance of a slider. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The colors of the rail of the slider. pub rail_colors: (Color, Color), + /// The appearance of the [`Handle`] of the slider. pub handle: Handle, } /// The appearance of the handle of a slider. #[derive(Debug, Clone, Copy)] pub struct Handle { + /// The shape of the handle. pub shape: HandleShape, + /// The [`Color`] of the handle. pub color: Color, + /// The border width of the handle. pub border_width: f32, + /// The border [`Color`] of the handle. pub border_color: Color, } /// The shape of the handle of a slider. #[derive(Debug, Clone, Copy)] pub enum HandleShape { - Circle { radius: f32 }, - Rectangle { width: u16, border_radius: f32 }, + /// A circular handle. + Circle { + /// The radius of the circle. + radius: f32, + }, + /// A rectangular shape. + Rectangle { + /// The width of the rectangle. + width: u16, + /// The border radius of the corners of the rectangle. + border_radius: f32, + }, } /// A set of rules that dictate the style of a slider. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; /// Produces the style of an active slider. - fn active(&self, style: Self::Style) -> Appearance; + fn active(&self, style: &Self::Style) -> Appearance; /// Produces the style of an hovered slider. - fn hovered(&self, style: Self::Style) -> Appearance; + fn hovered(&self, style: &Self::Style) -> Appearance; /// Produces the style of a slider that is being dragged. - fn dragging(&self, style: Self::Style) -> Appearance; + fn dragging(&self, style: &Self::Style) -> Appearance; } diff --git a/style/src/text.rs b/style/src/text.rs index 6e3aeef8..f31c2306 100644 --- a/style/src/text.rs +++ b/style/src/text.rs @@ -1,12 +1,20 @@ +//! Change the appearance of text. use iced_core::Color; +/// The style sheet of some text. pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. type Style: Default + Copy; + /// Produces the [`Appearance`] of some text. fn appearance(&self, style: Self::Style) -> Appearance; } +/// The apperance of some text. #[derive(Debug, Clone, Copy, Default)] pub struct Appearance { + /// The [`Color`] of the text. + /// + /// The default, `None`, means using the inherited color. pub color: Option<Color>, } diff --git a/style/src/text_input.rs b/style/src/text_input.rs index af86617b..d97016dc 100644 --- a/style/src/text_input.rs +++ b/style/src/text_input.rs @@ -1,33 +1,41 @@ -//! Display fields that can be filled with text. +//! Change the appearance of a text input. use iced_core::{Background, Color}; /// The appearance of a text input. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The [`Background`] of the text input. pub background: Background, + /// The border radius of the text input. pub border_radius: f32, + /// The border width of the text input. pub border_width: f32, + /// The border [`Color`] of the text input. pub border_color: Color, } /// A set of rules that dictate the style of a text input. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; /// Produces the style of an active text input. - fn active(&self, style: Self::Style) -> Appearance; + fn active(&self, style: &Self::Style) -> Appearance; /// Produces the style of a focused text input. - fn focused(&self, style: Self::Style) -> Appearance; + fn focused(&self, style: &Self::Style) -> Appearance; - fn placeholder_color(&self, style: Self::Style) -> Color; + /// Produces the [`Color`] of the placeholder of a text input. + fn placeholder_color(&self, style: &Self::Style) -> Color; - fn value_color(&self, style: Self::Style) -> Color; + /// Produces the [`Color`] of the value of a text input. + fn value_color(&self, style: &Self::Style) -> Color; - fn selection_color(&self, style: Self::Style) -> Color; + /// Produces the [`Color`] of the selection of a text input. + fn selection_color(&self, style: &Self::Style) -> Color; /// Produces the style of an hovered text input. - fn hovered(&self, style: Self::Style) -> Appearance { + fn hovered(&self, style: &Self::Style) -> Appearance { self.focused(style) } } diff --git a/style/src/theme.rs b/style/src/theme.rs index a253e990..d7ebb827 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -1,3 +1,4 @@ +//! Use the built-in theme and styles. pub mod palette; use self::palette::Extended; @@ -19,20 +20,29 @@ use crate::text; use crate::text_input; use crate::toggler; -use iced_core::{Background, Color}; +use iced_core::{Background, Color, Vector}; -#[derive(Debug, Clone, PartialEq)] +use std::rc::Rc; + +/// A built-in theme. +#[derive(Debug, Clone, PartialEq, Default)] pub enum Theme { + /// The built-in light variant. + #[default] Light, + /// The built-in dark variant. Dark, + /// A [`Theme`] that uses a [`Custom`] palette. Custom(Box<Custom>), } impl Theme { + /// Creates a new custom [`Theme`] from the given [`Palette`]. pub fn custom(palette: Palette) -> Self { Self::Custom(Box::new(Custom::new(palette))) } + /// Returns the [`Palette`] of the [`Theme`]. pub fn palette(&self) -> Palette { match self { Self::Light => Palette::LIGHT, @@ -41,6 +51,7 @@ impl Theme { } } + /// Returns the [`palette::Extended`] of the [`Theme`]. pub fn extended_palette(&self) -> &palette::Extended { match self { Self::Light => &palette::EXTENDED_LIGHT, @@ -50,12 +61,7 @@ impl Theme { } } -impl Default for Theme { - fn default() -> Self { - Self::Light - } -} - +/// A [`Theme`] with a customized [`Palette`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Custom { palette: Palette, @@ -63,6 +69,7 @@ pub struct Custom { } impl Custom { + /// Creates a [`Custom`] theme from the given [`Palette`]. pub fn new(palette: Palette) -> Self { Self { palette, @@ -71,22 +78,20 @@ impl Custom { } } -#[derive(Debug, Clone, Copy)] +/// The style of an application. +#[derive(Default)] pub enum Application { + /// The default style. + #[default] Default, - Custom(fn(Theme) -> application::Appearance), -} - -impl Default for Application { - fn default() -> Self { - Self::Default - } + /// A custom style. + Custom(Box<dyn application::StyleSheet<Style = Theme>>), } impl application::StyleSheet for Theme { type Style = Application; - fn appearance(&self, style: Self::Style) -> application::Appearance { + fn appearance(&self, style: &Self::Style) -> application::Appearance { let palette = self.extended_palette(); match style { @@ -94,33 +99,49 @@ impl application::StyleSheet for Theme { background_color: palette.background.base.color, text_color: palette.background.base.text, }, - Application::Custom(f) => f(self.clone()), + Application::Custom(custom) => custom.appearance(self), } } } -/* - * Button - */ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +impl application::StyleSheet for fn(&Theme) -> application::Appearance { + type Style = Theme; + + fn appearance(&self, style: &Self::Style) -> application::Appearance { + (self)(style) + } +} + +impl From<fn(&Theme) -> application::Appearance> for Application { + fn from(f: fn(&Theme) -> application::Appearance) -> Self { + Self::Custom(Box::new(f)) + } +} + +/// The style of a button. +#[derive(Default)] pub enum Button { + /// The primary style. + #[default] Primary, + /// The secondary style. Secondary, + /// The positive style. Positive, + /// The destructive style. Destructive, + /// The text style. + /// + /// Useful for links! Text, -} - -impl Default for Button { - fn default() -> Self { - Self::Primary - } + /// A custom style. + Custom(Box<dyn button::StyleSheet<Style = Theme>>), } impl button::StyleSheet for Theme { type Style = Button; - fn active(&self, style: Self::Style) -> button::Appearance { + fn active(&self, style: &Self::Style) -> button::Appearance { let palette = self.extended_palette(); let appearance = button::Appearance { @@ -143,19 +164,25 @@ impl button::StyleSheet for Theme { text_color: palette.background.base.text, ..appearance }, + Button::Custom(custom) => custom.active(self), } } - fn hovered(&self, style: Self::Style) -> button::Appearance { - let active = self.active(style); + fn hovered(&self, style: &Self::Style) -> button::Appearance { let palette = self.extended_palette(); + if let Button::Custom(custom) = style { + return custom.hovered(self); + } + + let active = self.active(style); + let background = match style { Button::Primary => Some(palette.primary.base.color), Button::Secondary => Some(palette.background.strong.color), Button::Positive => Some(palette.success.strong.color), Button::Destructive => Some(palette.danger.strong.color), - Button::Text => None, + Button::Text | Button::Custom(_) => None, }; button::Appearance { @@ -163,23 +190,56 @@ impl button::StyleSheet for Theme { ..active } } + + fn pressed(&self, style: &Self::Style) -> button::Appearance { + if let Button::Custom(custom) = style { + return custom.pressed(self); + } + + button::Appearance { + shadow_offset: Vector::default(), + ..self.active(style) + } + } + + fn disabled(&self, style: &Self::Style) -> button::Appearance { + if let Button::Custom(custom) = style { + return custom.disabled(self); + } + + let active = self.active(style); + + button::Appearance { + shadow_offset: Vector::default(), + background: active.background.map(|background| match background { + Background::Color(color) => Background::Color(Color { + a: color.a * 0.5, + ..color + }), + }), + text_color: Color { + a: active.text_color.a * 0.5, + ..active.text_color + }, + ..active + } + } } -/* - * Checkbox - */ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The style of a checkbox. +#[derive(Default)] pub enum Checkbox { + /// The primary style. + #[default] Primary, + /// The secondary style. Secondary, + /// The success style. Success, + /// The danger style. Danger, -} - -impl Default for Checkbox { - fn default() -> Self { - Self::Primary - } + /// A custom style. + Custom(Box<dyn checkbox::StyleSheet<Style = Theme>>), } impl checkbox::StyleSheet for Theme { @@ -187,7 +247,7 @@ impl checkbox::StyleSheet for Theme { fn active( &self, - style: Self::Style, + style: &Self::Style, is_checked: bool, ) -> checkbox::Appearance { let palette = self.extended_palette(); @@ -217,12 +277,13 @@ impl checkbox::StyleSheet for Theme { palette.danger.base, is_checked, ), + Checkbox::Custom(custom) => custom.active(self, is_checked), } } fn hovered( &self, - style: Self::Style, + style: &Self::Style, is_checked: bool, ) -> checkbox::Appearance { let palette = self.extended_palette(); @@ -252,6 +313,7 @@ impl checkbox::StyleSheet for Theme { palette.danger.base, is_checked, ), + Checkbox::Custom(custom) => custom.hovered(self, is_checked), } } } @@ -276,32 +338,28 @@ fn checkbox_appearance( } } -/* - * Container - */ -#[derive(Clone, Copy)] +/// The style of a container. +#[derive(Default)] pub enum Container { + /// No style. + #[default] Transparent, + /// A simple box. Box, - Custom(fn(&Theme) -> container::Appearance), -} - -impl Default for Container { - fn default() -> Self { - Self::Transparent - } + /// A custom style. + Custom(Box<dyn container::StyleSheet<Style = Theme>>), } impl From<fn(&Theme) -> container::Appearance> for Container { fn from(f: fn(&Theme) -> container::Appearance) -> Self { - Self::Custom(f) + Self::Custom(Box::new(f)) } } impl container::StyleSheet for Theme { type Style = Container; - fn appearance(&self, style: Self::Style) -> container::Appearance { + fn appearance(&self, style: &Self::Style) -> container::Appearance { match style { Container::Transparent => Default::default(), Container::Box => { @@ -315,257 +373,389 @@ impl container::StyleSheet for Theme { border_color: Color::TRANSPARENT, } } - Container::Custom(f) => f(self), + Container::Custom(custom) => custom.appearance(self), } } } -/* - * Slider - */ -impl slider::StyleSheet for Theme { - type Style = (); +impl container::StyleSheet for fn(&Theme) -> container::Appearance { + type Style = Theme; - fn active(&self, _style: Self::Style) -> slider::Appearance { - let palette = self.extended_palette(); + fn appearance(&self, style: &Self::Style) -> container::Appearance { + (self)(style) + } +} - let handle = slider::Handle { - shape: slider::HandleShape::Rectangle { - width: 8, - border_radius: 4.0, - }, - color: Color::WHITE, - border_color: Color::WHITE, - border_width: 1.0, - }; +/// The style of a slider. +#[derive(Default)] +pub enum Slider { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Box<dyn slider::StyleSheet<Style = Theme>>), +} - slider::Appearance { - rail_colors: (palette.primary.base.color, Color::TRANSPARENT), - handle: slider::Handle { - color: palette.background.base.color, - border_color: palette.primary.base.color, - ..handle - }, +impl slider::StyleSheet for Theme { + type Style = Slider; + + fn active(&self, style: &Self::Style) -> slider::Appearance { + match style { + Slider::Default => { + let palette = self.extended_palette(); + + let handle = slider::Handle { + shape: slider::HandleShape::Rectangle { + width: 8, + border_radius: 4.0, + }, + color: Color::WHITE, + border_color: Color::WHITE, + border_width: 1.0, + }; + + slider::Appearance { + rail_colors: ( + palette.primary.base.color, + Color::TRANSPARENT, + ), + handle: slider::Handle { + color: palette.background.base.color, + border_color: palette.primary.base.color, + ..handle + }, + } + } + Slider::Custom(custom) => custom.active(self), } } - fn hovered(&self, style: Self::Style) -> slider::Appearance { - let active = self.active(style); - let palette = self.extended_palette(); + fn hovered(&self, style: &Self::Style) -> slider::Appearance { + match style { + Slider::Default => { + let active = self.active(style); + let palette = self.extended_palette(); - slider::Appearance { - handle: slider::Handle { - color: palette.primary.weak.color, - ..active.handle - }, - ..active + slider::Appearance { + handle: slider::Handle { + color: palette.primary.weak.color, + ..active.handle + }, + ..active + } + } + Slider::Custom(custom) => custom.hovered(self), } } - fn dragging(&self, style: Self::Style) -> slider::Appearance { - let active = self.active(style); - let palette = self.extended_palette(); + fn dragging(&self, style: &Self::Style) -> slider::Appearance { + match style { + Slider::Default => { + let active = self.active(style); + let palette = self.extended_palette(); - slider::Appearance { - handle: slider::Handle { - color: palette.primary.base.color, - ..active.handle - }, - ..active + slider::Appearance { + handle: slider::Handle { + color: palette.primary.base.color, + ..active.handle + }, + ..active + } + } + Slider::Custom(custom) => custom.dragging(self), } } } -/* - * Menu - */ +/// The style of a menu. +#[derive(Clone, Default)] +pub enum Menu { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Rc<dyn menu::StyleSheet<Style = Theme>>), +} + impl menu::StyleSheet for Theme { - type Style = (); + type Style = Menu; - fn appearance(&self, _style: Self::Style) -> menu::Appearance { - let palette = self.extended_palette(); + fn appearance(&self, style: &Self::Style) -> menu::Appearance { + match style { + Menu::Default => { + let palette = self.extended_palette(); - menu::Appearance { - text_color: palette.background.weak.text, - background: palette.background.weak.color.into(), - border_width: 1.0, - border_radius: 0.0, - border_color: palette.background.strong.color, - selected_text_color: palette.primary.strong.text, - selected_background: palette.primary.strong.color.into(), + menu::Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + border_width: 1.0, + border_radius: 0.0, + border_color: palette.background.strong.color, + selected_text_color: palette.primary.strong.text, + selected_background: palette.primary.strong.color.into(), + } + } + Menu::Custom(custom) => custom.appearance(self), + } + } +} + +impl From<PickList> for Menu { + fn from(pick_list: PickList) -> Self { + match pick_list { + PickList::Default => Self::Default, + PickList::Custom(_, menu) => Self::Custom(menu), } } } -/* - * Pick List - */ +/// The style of a pick list. +#[derive(Clone, Default)] +pub enum PickList { + /// The default style. + #[default] + Default, + /// A custom style. + Custom( + Rc<dyn pick_list::StyleSheet<Style = Theme>>, + Rc<dyn menu::StyleSheet<Style = Theme>>, + ), +} + impl pick_list::StyleSheet for Theme { - type Style = (); + type Style = PickList; - fn active(&self, _style: ()) -> pick_list::Appearance { - let palette = self.extended_palette(); + fn active(&self, style: &Self::Style) -> pick_list::Appearance { + match style { + PickList::Default => { + let palette = self.extended_palette(); - pick_list::Appearance { - text_color: palette.background.weak.text, - background: palette.background.weak.color.into(), - placeholder_color: palette.background.strong.color, - border_radius: 2.0, - border_width: 1.0, - border_color: palette.background.strong.color, - icon_size: 0.7, + pick_list::Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + placeholder_color: palette.background.strong.color, + border_radius: 2.0, + border_width: 1.0, + border_color: palette.background.strong.color, + icon_size: 0.7, + } + } + PickList::Custom(custom, _) => custom.active(self), } } - fn hovered(&self, _style: ()) -> pick_list::Appearance { - let palette = self.extended_palette(); + fn hovered(&self, style: &Self::Style) -> pick_list::Appearance { + match style { + PickList::Default => { + let palette = self.extended_palette(); - pick_list::Appearance { - text_color: palette.background.weak.text, - background: palette.background.weak.color.into(), - placeholder_color: palette.background.strong.color, - border_radius: 2.0, - border_width: 1.0, - border_color: palette.primary.strong.color, - icon_size: 0.7, + pick_list::Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + placeholder_color: palette.background.strong.color, + border_radius: 2.0, + border_width: 1.0, + border_color: palette.primary.strong.color, + icon_size: 0.7, + } + } + PickList::Custom(custom, _) => custom.active(self), } } } -/* - * Radio - */ +/// The style of a radio button. +#[derive(Default)] +pub enum Radio { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Box<dyn radio::StyleSheet<Style = Theme>>), +} + impl radio::StyleSheet for Theme { - type Style = (); + type Style = Radio; fn active( &self, - _style: Self::Style, - _is_selected: bool, + style: &Self::Style, + is_selected: bool, ) -> radio::Appearance { - let palette = self.extended_palette(); + match style { + Radio::Default => { + let palette = self.extended_palette(); - radio::Appearance { - background: Color::TRANSPARENT.into(), - dot_color: palette.primary.strong.color, - border_width: 1.0, - border_color: palette.primary.strong.color, - text_color: None, + radio::Appearance { + background: Color::TRANSPARENT.into(), + dot_color: palette.primary.strong.color, + border_width: 1.0, + border_color: palette.primary.strong.color, + text_color: None, + } + } + Radio::Custom(custom) => custom.active(self, is_selected), } } fn hovered( &self, - style: Self::Style, + style: &Self::Style, is_selected: bool, ) -> radio::Appearance { - let active = self.active(style, is_selected); - let palette = self.extended_palette(); + match style { + Radio::Default => { + let active = self.active(style, is_selected); + let palette = self.extended_palette(); - radio::Appearance { - dot_color: palette.primary.strong.color, - background: palette.primary.weak.color.into(), - ..active + radio::Appearance { + dot_color: palette.primary.strong.color, + background: palette.primary.weak.color.into(), + ..active + } + } + Radio::Custom(custom) => custom.hovered(self, is_selected), } } } -/* - * Toggler - */ +/// The style of a toggler. +#[derive(Default)] +pub enum Toggler { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Box<dyn toggler::StyleSheet<Style = Theme>>), +} + impl toggler::StyleSheet for Theme { - type Style = (); + type Style = Toggler; fn active( &self, - _style: Self::Style, + style: &Self::Style, is_active: bool, ) -> toggler::Appearance { - let palette = self.extended_palette(); + match style { + Toggler::Default => { + let palette = self.extended_palette(); - toggler::Appearance { - background: if is_active { - palette.primary.strong.color - } else { - palette.background.strong.color - }, - background_border: None, - foreground: if is_active { - palette.primary.strong.text - } else { - palette.background.base.color - }, - foreground_border: None, + toggler::Appearance { + background: if is_active { + palette.primary.strong.color + } else { + palette.background.strong.color + }, + background_border: None, + foreground: if is_active { + palette.primary.strong.text + } else { + palette.background.base.color + }, + foreground_border: None, + } + } + Toggler::Custom(custom) => custom.active(self, is_active), } } fn hovered( &self, - style: Self::Style, + style: &Self::Style, is_active: bool, ) -> toggler::Appearance { - let palette = self.extended_palette(); + match style { + Toggler::Default => { + let palette = self.extended_palette(); - toggler::Appearance { - foreground: if is_active { - Color { - a: 0.5, - ..palette.primary.strong.text + toggler::Appearance { + foreground: if is_active { + Color { + a: 0.5, + ..palette.primary.strong.text + } + } else { + palette.background.weak.color + }, + ..self.active(style, is_active) } - } else { - palette.background.weak.color - }, - ..self.active(style, is_active) + } + Toggler::Custom(custom) => custom.hovered(self, is_active), } } } -/* - * Pane Grid - */ +/// The style of a pane grid. +#[derive(Default)] +pub enum PaneGrid { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Box<dyn pane_grid::StyleSheet<Style = Theme>>), +} + impl pane_grid::StyleSheet for Theme { - type Style = (); + type Style = PaneGrid; - fn picked_split(&self, _style: Self::Style) -> Option<pane_grid::Line> { - let palette = self.extended_palette(); + fn picked_split(&self, style: &Self::Style) -> Option<pane_grid::Line> { + match style { + PaneGrid::Default => { + let palette = self.extended_palette(); - Some(pane_grid::Line { - color: palette.primary.strong.color, - width: 2.0, - }) + Some(pane_grid::Line { + color: palette.primary.strong.color, + width: 2.0, + }) + } + PaneGrid::Custom(custom) => custom.picked_split(self), + } } - fn hovered_split(&self, _style: Self::Style) -> Option<pane_grid::Line> { - let palette = self.extended_palette(); + fn hovered_split(&self, style: &Self::Style) -> Option<pane_grid::Line> { + match style { + PaneGrid::Default => { + let palette = self.extended_palette(); - Some(pane_grid::Line { - color: palette.primary.base.color, - width: 2.0, - }) + Some(pane_grid::Line { + color: palette.primary.base.color, + width: 2.0, + }) + } + PaneGrid::Custom(custom) => custom.hovered_split(self), + } } } -/* - * Progress Bar - */ -#[derive(Clone, Copy)] +/// The style of a progress bar. +#[derive(Default)] pub enum ProgressBar { + /// The primary style. + #[default] Primary, + /// The success style. Success, + /// The danger style. Danger, - Custom(fn(&Theme) -> progress_bar::Appearance), + /// A custom style. + Custom(Box<dyn progress_bar::StyleSheet<Style = Theme>>), } -impl Default for ProgressBar { - fn default() -> Self { - Self::Primary +impl From<fn(&Theme) -> progress_bar::Appearance> for ProgressBar { + fn from(f: fn(&Theme) -> progress_bar::Appearance) -> Self { + Self::Custom(Box::new(f)) } } impl progress_bar::StyleSheet for Theme { type Style = ProgressBar; - fn appearance(&self, style: Self::Style) -> progress_bar::Appearance { + fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance { + if let ProgressBar::Custom(custom) = style { + return custom.appearance(self); + } + let palette = self.extended_palette(); let from_palette = |bar: Color| progress_bar::Appearance { @@ -578,30 +768,39 @@ impl progress_bar::StyleSheet for Theme { ProgressBar::Primary => from_palette(palette.primary.base.color), ProgressBar::Success => from_palette(palette.success.base.color), ProgressBar::Danger => from_palette(palette.danger.base.color), - ProgressBar::Custom(f) => f(self), + ProgressBar::Custom(custom) => custom.appearance(self), } } } -/* - * Rule - */ -#[derive(Clone, Copy)] +impl progress_bar::StyleSheet for fn(&Theme) -> progress_bar::Appearance { + type Style = Theme; + + fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance { + (self)(style) + } +} + +/// The style of a rule. +#[derive(Default)] pub enum Rule { + /// The default style. + #[default] Default, - Custom(fn(&Theme) -> rule::Appearance), + /// A custom style. + Custom(Box<dyn rule::StyleSheet<Style = Theme>>), } -impl Default for Rule { - fn default() -> Self { - Self::Default +impl From<fn(&Theme) -> rule::Appearance> for Rule { + fn from(f: fn(&Theme) -> rule::Appearance) -> Self { + Self::Custom(Box::new(f)) } } impl rule::StyleSheet for Theme { type Style = Rule; - fn style(&self, style: Self::Style) -> rule::Appearance { + fn appearance(&self, style: &Self::Style) -> rule::Appearance { let palette = self.extended_palette(); match style { @@ -611,66 +810,92 @@ impl rule::StyleSheet for Theme { radius: 0.0, fill_mode: rule::FillMode::Full, }, - Rule::Custom(f) => f(self), + Rule::Custom(custom) => custom.appearance(self), } } } -/* - * Scrollable - */ +impl rule::StyleSheet for fn(&Theme) -> rule::Appearance { + type Style = Theme; + + fn appearance(&self, style: &Self::Style) -> rule::Appearance { + (self)(style) + } +} + +/// The style of a scrollable. +#[derive(Default)] +pub enum Scrollable { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Box<dyn scrollable::StyleSheet<Style = Theme>>), +} + impl scrollable::StyleSheet for Theme { - type Style = (); + type Style = Scrollable; - fn active(&self, _style: Self::Style) -> scrollable::Scrollbar { - let palette = self.extended_palette(); + fn active(&self, style: &Self::Style) -> scrollable::Scrollbar { + match style { + Scrollable::Default => { + let palette = self.extended_palette(); - scrollable::Scrollbar { - background: palette.background.weak.color.into(), - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - scroller: scrollable::Scroller { - color: palette.background.strong.color, - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, + scrollable::Scrollbar { + background: palette.background.weak.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: palette.background.strong.color, + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + } + } + Scrollable::Custom(custom) => custom.active(self), } } - fn hovered(&self, _style: Self::Style) -> scrollable::Scrollbar { - let palette = self.extended_palette(); + fn hovered(&self, style: &Self::Style) -> scrollable::Scrollbar { + match style { + Scrollable::Default => { + let palette = self.extended_palette(); - scrollable::Scrollbar { - background: palette.background.weak.color.into(), - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - scroller: scrollable::Scroller { - color: palette.primary.strong.color, - border_radius: 2.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, + scrollable::Scrollbar { + background: palette.background.weak.color.into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: palette.primary.strong.color, + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + } + } + Scrollable::Custom(custom) => custom.hovered(self), + } + } + + fn dragging(&self, style: &Self::Style) -> scrollable::Scrollbar { + match style { + Scrollable::Default => self.hovered(style), + Scrollable::Custom(custom) => custom.dragging(self), } } } -/* - * Text - */ -#[derive(Clone, Copy)] +/// The style of text. +#[derive(Clone, Copy, Default)] pub enum Text { + /// The default style. + #[default] Default, + /// Colored text. Color(Color), - Custom(fn(&Theme) -> text::Appearance), -} - -impl Default for Text { - fn default() -> Self { - Self::Default - } } impl From<Color> for Text { @@ -686,18 +911,28 @@ impl text::StyleSheet for Theme { match style { Text::Default => Default::default(), Text::Color(c) => text::Appearance { color: Some(c) }, - Text::Custom(f) => f(self), } } } -/* - * Text Input - */ +/// The style of a text input. +#[derive(Default)] +pub enum TextInput { + /// The default style. + #[default] + Default, + /// A custom style. + Custom(Box<dyn text_input::StyleSheet<Style = Theme>>), +} + impl text_input::StyleSheet for Theme { - type Style = (); + type Style = TextInput; + + fn active(&self, style: &Self::Style) -> text_input::Appearance { + if let TextInput::Custom(custom) = style { + return custom.active(self); + } - fn active(&self, _style: Self::Style) -> text_input::Appearance { let palette = self.extended_palette(); text_input::Appearance { @@ -708,7 +943,11 @@ impl text_input::StyleSheet for Theme { } } - fn hovered(&self, _style: Self::Style) -> text_input::Appearance { + fn hovered(&self, style: &Self::Style) -> text_input::Appearance { + if let TextInput::Custom(custom) = style { + return custom.hovered(self); + } + let palette = self.extended_palette(); text_input::Appearance { @@ -719,7 +958,11 @@ impl text_input::StyleSheet for Theme { } } - fn focused(&self, _style: Self::Style) -> text_input::Appearance { + fn focused(&self, style: &Self::Style) -> text_input::Appearance { + if let TextInput::Custom(custom) = style { + return custom.focused(self); + } + let palette = self.extended_palette(); text_input::Appearance { @@ -730,19 +973,31 @@ impl text_input::StyleSheet for Theme { } } - fn placeholder_color(&self, _style: Self::Style) -> Color { + fn placeholder_color(&self, style: &Self::Style) -> Color { + if let TextInput::Custom(custom) = style { + return custom.placeholder_color(self); + } + let palette = self.extended_palette(); palette.background.strong.color } - fn value_color(&self, _style: Self::Style) -> Color { + fn value_color(&self, style: &Self::Style) -> Color { + if let TextInput::Custom(custom) = style { + return custom.value_color(self); + } + let palette = self.extended_palette(); palette.background.base.text } - fn selection_color(&self, _style: Self::Style) -> Color { + fn selection_color(&self, style: &Self::Style) -> Color { + if let TextInput::Custom(custom) = style { + return custom.selection_color(self); + } + let palette = self.extended_palette(); palette.primary.weak.color diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs index b3a10d28..0f15494b 100644 --- a/style/src/theme/palette.rs +++ b/style/src/theme/palette.rs @@ -1,18 +1,26 @@ +//! Define the colors of a theme. use iced_core::Color; use once_cell::sync::Lazy; use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb}; +/// A color palette. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Palette { + /// The background [`Color`] of the [`Palette`]. pub background: Color, + /// The text [`Color`] of the [`Palette`]. pub text: Color, + /// The primary [`Color`] of the [`Palette`]. pub primary: Color, + /// The success [`Color`] of the [`Palette`]. pub success: Color, + /// The danger [`Color`] of the [`Palette`]. pub danger: Color, } impl Palette { + /// The built-in light variant of a [`Palette`]. pub const LIGHT: Self = Self { background: Color::WHITE, text: Color::BLACK, @@ -33,6 +41,7 @@ impl Palette { ), }; + /// The built-in dark variant of a [`Palette`]. pub const DARK: Self = Self { background: Color::from_rgb( 0x20 as f32 / 255.0, @@ -58,21 +67,31 @@ impl Palette { }; } +/// An extended set of colors generated from a [`Palette`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Extended { + /// The set of background colors. pub background: Background, + /// The set of primary colors. pub primary: Primary, + /// The set of secondary colors. pub secondary: Secondary, + /// The set of success colors. pub success: Success, + /// The set of danger colors. pub danger: Danger, } +/// The built-in light variant of an [`Extended`] palette. pub static EXTENDED_LIGHT: Lazy<Extended> = Lazy::new(|| Extended::generate(Palette::LIGHT)); + +/// The built-in dark variant of an [`Extended`] palette. pub static EXTENDED_DARK: Lazy<Extended> = Lazy::new(|| Extended::generate(Palette::DARK)); impl Extended { + /// Generates an [`Extended`] palette from a simple [`Palette`]. pub fn generate(palette: Palette) -> Self { Self { background: Background::new(palette.background, palette.text), @@ -96,13 +115,22 @@ impl Extended { } } +/// A pair of background and text colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Pair { + /// The background color. pub color: Color, + + /// The text color. + /// + /// It's guaranteed to be readable on top of the background [`color`]. + /// + /// [`color`]: Self::color pub text: Color, } impl Pair { + /// Creates a new [`Pair`] from a background [`Color`] and some text [`Color`]. pub fn new(color: Color, text: Color) -> Self { Self { color, @@ -111,14 +139,19 @@ impl Pair { } } +/// A set of background colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Background { + /// The base background color. pub base: Pair, + /// A weaker version of the base background color. pub weak: Pair, + /// A stronger version of the base background color. pub strong: Pair, } impl Background { + /// Generates a set of [`Background`] colors from the base and text colors. pub fn new(base: Color, text: Color) -> Self { let weak = mix(base, text, 0.15); let strong = mix(base, text, 0.40); @@ -131,14 +164,19 @@ impl Background { } } +/// A set of primary colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Primary { + /// The base primary color. pub base: Pair, + /// A weaker version of the base primary color. pub weak: Pair, + /// A stronger version of the base primary color. pub strong: Pair, } impl Primary { + /// Generates a set of [`Primary`] colors from the base, background, and text colors. pub fn generate(base: Color, background: Color, text: Color) -> Self { let weak = mix(base, background, 0.4); let strong = deviate(base, 0.1); @@ -151,14 +189,19 @@ impl Primary { } } +/// A set of secondary colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Secondary { + /// The base secondary color. pub base: Pair, + /// A weaker version of the base secondary color. pub weak: Pair, + /// A stronger version of the base secondary color. pub strong: Pair, } impl Secondary { + /// Generates a set of [`Secondary`] colors from the base and text colors. pub fn generate(base: Color, text: Color) -> Self { let base = mix(base, text, 0.2); let weak = mix(base, text, 0.1); @@ -172,14 +215,19 @@ impl Secondary { } } +/// A set of success colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Success { + /// The base success color. pub base: Pair, + /// A weaker version of the base success color. pub weak: Pair, + /// A stronger version of the base success color. pub strong: Pair, } impl Success { + /// Generates a set of [`Success`] colors from the base, background, and text colors. pub fn generate(base: Color, background: Color, text: Color) -> Self { let weak = mix(base, background, 0.4); let strong = deviate(base, 0.1); @@ -192,14 +240,19 @@ impl Success { } } +/// A set of danger colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Danger { + /// The base danger color. pub base: Pair, + /// A weaker version of the base danger color. pub weak: Pair, + /// A stronger version of the base danger color. pub strong: Pair, } impl Danger { + /// Generates a set of [`Danger`] colors from the base, background, and text colors. pub fn generate(base: Color, background: Color, text: Color) -> Self { let weak = mix(base, background, 0.4); let strong = deviate(base, 0.1); diff --git a/style/src/toggler.rs b/style/src/toggler.rs index 0acf8e97..abc73f2a 100644 --- a/style/src/toggler.rs +++ b/style/src/toggler.rs @@ -1,20 +1,31 @@ -//! Show toggle controls using togglers. +//! Change the appearance of a toggler. use iced_core::Color; /// The appearance of a toggler. #[derive(Debug, Clone, Copy)] pub struct Appearance { + /// The background [`Color`] of the toggler. pub background: Color, + /// The [`Color`] of the background border of the toggler. pub background_border: Option<Color>, + /// The foreground [`Color`] of the toggler. pub foreground: Color, + /// The [`Color`] of the foreground border of the toggler. pub foreground_border: Option<Color>, } /// A set of rules that dictate the style of a toggler. pub trait StyleSheet { - type Style: Default + Copy; + /// The supported style of the [`StyleSheet`]. + type Style: Default; - fn active(&self, style: Self::Style, is_active: bool) -> Appearance; + /// Returns the active [`Appearance`] of the toggler for the provided [`Style`]. + /// + /// [`Style`]: Self::Style + fn active(&self, style: &Self::Style, is_active: bool) -> Appearance; - fn hovered(&self, style: Self::Style, is_active: bool) -> Appearance; + /// Returns the hovered [`Appearance`] of the toggler for the provided [`Style`]. + /// + /// [`Style`]: Self::Style + fn hovered(&self, style: &Self::Style, is_active: bool) -> Appearance; } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 9a57e58b..b1fb0dd1 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_wgpu" -version = "0.5.1" +version = "0.6.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A wgpu renderer for Iced" @@ -8,19 +8,19 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [features] -svg = ["resvg", "usvg", "tiny-skia"] -image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] -png = ["image_rs/png"] -jpeg = ["image_rs/jpeg"] -jpeg_rayon = ["image_rs/jpeg_rayon"] -gif = ["image_rs/gif"] -webp = ["image_rs/webp"] -pnm = ["image_rs/pnm"] -ico = ["image_rs/ico"] -bmp = ["image_rs/bmp"] -hdr = ["image_rs/hdr"] -dds = ["image_rs/dds"] -farbfeld = ["image_rs/farbfeld"] +svg = ["iced_graphics/svg"] +image = ["iced_graphics/image"] +png = ["iced_graphics/png"] +jpeg = ["iced_graphics/jpeg"] +jpeg_rayon = ["iced_graphics/jpeg_rayon"] +gif = ["iced_graphics/gif"] +webp = ["iced_graphics/webp"] +pnm = ["iced_graphics/pnm"] +ico = ["iced_graphics/ico"] +bmp = ["iced_graphics/bmp"] +hdr = ["iced_graphics/hdr"] +dds = ["iced_graphics/dds"] +farbfeld = ["iced_graphics/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] @@ -35,7 +35,6 @@ raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" futures = "0.3" -kamadak-exif = "0.5" bitflags = "1.2" [dependencies.bytemuck] @@ -43,32 +42,14 @@ version = "1.9" features = ["derive"] [dependencies.iced_native] -version = "0.5" +version = "0.6" path = "../native" [dependencies.iced_graphics] -version = "0.3" +version = "0.4" path = "../graphics" features = ["font-fallback", "font-icons"] -[dependencies.image_rs] -version = "0.23" -package = "image" -default-features = false -optional = true - -[dependencies.resvg] -version = "0.18" -optional = true - -[dependencies.usvg] -version = "0.18" -optional = true - -[dependencies.tiny-skia] -version = "0.6" -optional = true - [dependencies.encase] version = "0.3.0" features = ["glam"] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 80026673..946eb712 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -10,7 +10,7 @@ use iced_graphics::{Primitive, Viewport}; use iced_native::alignment; use iced_native::{Font, Size}; -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))] use crate::image; /// A [`wgpu`] graphics backend for [`iced`]. @@ -23,7 +23,7 @@ pub struct Backend { text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, default_text_size: u16, @@ -47,7 +47,7 @@ impl Backend { let triangle_pipeline = triangle::Pipeline::new(device, format, settings.antialiasing); - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] let image_pipeline = image::Pipeline::new(device, format); Self { @@ -55,7 +55,7 @@ impl Backend { text_pipeline, triangle_pipeline, - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, default_text_size: settings.default_text_size, @@ -98,8 +98,8 @@ impl Backend { ); } - #[cfg(any(feature = "image_rs", feature = "svg"))] - self.image_pipeline.trim_cache(); + #[cfg(any(feature = "image", feature = "svg"))] + self.image_pipeline.trim_cache(device, encoder); } fn flush( @@ -148,7 +148,7 @@ impl Backend { ); } - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] { if !layer.images.is_empty() { let scaled = transformation @@ -294,9 +294,9 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { self.image_pipeline.dimensions(handle) } } @@ -306,7 +306,7 @@ impl backend::Svg for Backend { fn viewport_dimensions( &self, handle: &iced_native::svg::Handle, - ) -> (u32, u32) { + ) -> Size<u32> { self.image_pipeline.viewport_dimensions(handle) } } diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs index c0c48c74..18be03dd 100644 --- a/wgpu/src/buffer/dynamic.rs +++ b/wgpu/src/buffer/dynamic.rs @@ -1,10 +1,13 @@ //! Utilities for uniform buffer operations. use encase::private::WriteInto; use encase::ShaderType; + +use std::fmt; use std::marker::PhantomData; /// A dynamic buffer is any type of buffer which does not have a static offset. -pub(crate) struct Buffer<T: ShaderType> { +#[derive(Debug)] +pub struct Buffer<T: ShaderType> { offsets: Vec<wgpu::DynamicOffset>, cpu: Internal, gpu: wgpu::Buffer, @@ -24,6 +27,7 @@ impl<T: ShaderType + WriteInto> Buffer<T> { ) } + #[cfg(not(target_arch = "wasm32"))] /// Creates a new dynamic storage buffer. pub fn storage(device: &wgpu::Device, label: &'static str) -> Self { Buffer::new( @@ -91,6 +95,7 @@ impl<T: ShaderType + WriteInto> Buffer<T> { Internal::Uniform(_) => { wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST } + #[cfg(not(target_arch = "wasm32"))] Internal::Storage(_) => { wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST } @@ -154,6 +159,8 @@ impl<T: ShaderType + WriteInto> Buffer<T> { // Currently supported dynamic buffers. enum Internal { Uniform(encase::DynamicUniformBuffer<Vec<u8>>), + #[cfg(not(target_arch = "wasm32"))] + //storage buffers are not supported on wgpu wasm target (yet) Storage(encase::DynamicStorageBuffer<Vec<u8>>), } @@ -168,6 +175,7 @@ impl Internal { .write(value) .expect("Error when writing to dynamic uniform buffer.") as u32, + #[cfg(not(target_arch = "wasm32"))] Internal::Storage(buf) => buf .write(value) .expect("Error when writing to dynamic storage buffer.") @@ -179,6 +187,7 @@ impl Internal { pub(super) fn get_ref(&self) -> &Vec<u8> { match self { Internal::Uniform(buf) => buf.as_ref(), + #[cfg(not(target_arch = "wasm32"))] Internal::Storage(buf) => buf.as_ref(), } } @@ -190,6 +199,7 @@ impl Internal { buf.as_mut().clear(); buf.set_offset(0); } + #[cfg(not(target_arch = "wasm32"))] Internal::Storage(buf) => { buf.as_mut().clear(); buf.set_offset(0); @@ -197,3 +207,13 @@ impl Internal { } } } + +impl fmt::Debug for Internal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Uniform(_) => write!(f, "Internal::Uniform(_)"), + #[cfg(not(target_arch = "wasm32"))] + Self::Storage(_) => write!(f, "Internal::Storage(_)"), + } + } +} diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs index cf06790c..ef87422f 100644 --- a/wgpu/src/buffer/static.rs +++ b/wgpu/src/buffer/static.rs @@ -8,7 +8,7 @@ const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128; /// A generic buffer struct useful for items which have no alignment requirements /// (e.g. Vertex, Index buffers) & no dynamic offsets. #[derive(Debug)] -pub(crate) struct Buffer<T> { +pub struct Buffer<T> { //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer offsets: Vec<wgpu::BufferAddress>, label: &'static str, diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index d964aed7..d06815bb 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,22 +1,23 @@ mod atlas; -#[cfg(feature = "image_rs")] -mod raster; +#[cfg(feature = "image")] +use iced_graphics::image::raster; #[cfg(feature = "svg")] -mod vector; +use iced_graphics::image::vector; use crate::Transformation; use atlas::Atlas; use iced_graphics::layer; -use iced_native::Rectangle; +use iced_native::{Rectangle, Size}; + use std::cell::RefCell; use std::mem; use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] use iced_native::image; #[cfg(feature = "svg")] @@ -24,10 +25,10 @@ use iced_native::svg; #[derive(Debug)] pub struct Pipeline { - #[cfg(feature = "image_rs")] - raster_cache: RefCell<raster::Cache>, + #[cfg(feature = "image")] + raster_cache: RefCell<raster::Cache<Atlas>>, #[cfg(feature = "svg")] - vector_cache: RefCell<vector::Cache>, + vector_cache: RefCell<vector::Cache<Atlas>>, pipeline: wgpu::RenderPipeline, uniforms: wgpu::Buffer, @@ -242,11 +243,11 @@ impl Pipeline { }); Pipeline { - #[cfg(feature = "image_rs")] - raster_cache: RefCell::new(raster::Cache::new()), + #[cfg(feature = "image")] + raster_cache: RefCell::new(raster::Cache::default()), #[cfg(feature = "svg")] - vector_cache: RefCell::new(vector::Cache::new()), + vector_cache: RefCell::new(vector::Cache::default()), pipeline, uniforms: uniforms_buffer, @@ -261,8 +262,8 @@ impl Pipeline { } } - #[cfg(feature = "image_rs")] - pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + #[cfg(feature = "image")] + pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> { let mut cache = self.raster_cache.borrow_mut(); let memory = cache.load(handle); @@ -270,7 +271,7 @@ impl Pipeline { } #[cfg(feature = "svg")] - pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> { let mut cache = self.vector_cache.borrow_mut(); let svg = cache.load(handle); @@ -290,7 +291,7 @@ impl Pipeline { ) { let instances: &mut Vec<Instance> = &mut Vec::new(); - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); #[cfg(feature = "svg")] @@ -298,12 +299,11 @@ impl Pipeline { for image in images { match &image { - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( handle, - device, - encoder, + &mut (device, encoder), &mut self.texture_atlas, ) { add_instances( @@ -314,7 +314,7 @@ impl Pipeline { ); } } - #[cfg(not(feature = "image_rs"))] + #[cfg(not(feature = "image"))] layer::Image::Raster { .. } => {} #[cfg(feature = "svg")] @@ -325,8 +325,7 @@ impl Pipeline { handle, size, _scale, - device, - encoder, + &mut (device, encoder), &mut self.texture_atlas, ) { add_instances( @@ -446,12 +445,20 @@ impl Pipeline { } } - pub fn trim_cache(&mut self) { - #[cfg(feature = "image_rs")] - self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); + pub fn trim_cache( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) { + #[cfg(feature = "image")] + self.raster_cache + .borrow_mut() + .trim(&mut self.texture_atlas, &mut (device, encoder)); #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); + self.vector_cache + .borrow_mut() + .trim(&mut self.texture_atlas, &mut (device, encoder)); } } @@ -509,15 +516,18 @@ fn add_instances( add_instance(image_position, image_size, allocation, instances); } atlas::Entry::Fragmented { fragments, size } => { - let scaling_x = image_size[0] / size.0 as f32; - let scaling_y = image_size[1] / size.1 as f32; + let scaling_x = image_size[0] / size.width as f32; + let scaling_y = image_size[1] / size.height as f32; for fragment in fragments { let allocation = &fragment.allocation; let [x, y] = image_position; let (fragment_x, fragment_y) = fragment.position; - let (fragment_width, fragment_height) = allocation.size(); + let Size { + width: fragment_width, + height: fragment_height, + } = allocation.size(); let position = [ x + fragment_x as f32 * scaling_x, @@ -543,7 +553,7 @@ fn add_instance( instances: &mut Vec<Instance>, ) { let (x, y) = allocation.position(); - let (width, height) = allocation.size(); + let Size { width, height } = allocation.size(); let layer = allocation.layer(); let instance = Instance { diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 953dd4e2..eafe2f96 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -4,8 +4,6 @@ mod allocation; mod allocator; mod layer; -use std::num::NonZeroU32; - pub use allocation::Allocation; pub use entry::Entry; pub use layer::Layer; @@ -14,6 +12,11 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; +use iced_graphics::image; +use iced_graphics::Size; + +use std::num::NonZeroU32; + #[derive(Debug)] pub struct Atlas { texture: wgpu::Texture, @@ -35,7 +38,7 @@ impl Atlas { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, @@ -61,99 +64,6 @@ impl Atlas { self.layers.len() } - pub fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) -> Option<Entry> { - use wgpu::util::DeviceExt; - - let entry = { - let current_size = self.layers.len(); - let entry = self.allocate(width, height)?; - - // We grow the internal texture after allocating if necessary - let new_layers = self.layers.len() - current_size; - self.grow(new_layers, device, encoder); - - entry - }; - - log::info!("Allocated atlas entry: {:?}", entry); - - // It is a webgpu requirement that: - // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate padded_width by rounding width up to the next - // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; - let padding = (align - (4 * width) % align) % align; - let padded_width = (4 * width + padding) as usize; - let padded_data_size = padded_width * height as usize; - - let mut padded_data = vec![0; padded_data_size]; - - for row in 0..height as usize { - let offset = row * padded_width; - - padded_data[offset..offset + 4 * width as usize].copy_from_slice( - &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], - ) - } - - let buffer = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced_wgpu::image staging buffer"), - contents: &padded_data, - usage: wgpu::BufferUsages::COPY_SRC, - }); - - match &entry { - Entry::Contiguous(allocation) => { - self.upload_allocation( - &buffer, width, height, padding, 0, allocation, encoder, - ); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - let (x, y) = fragment.position; - let offset = (y * padded_width as u32 + 4 * x) as usize; - - self.upload_allocation( - &buffer, - width, - height, - padding, - offset, - &fragment.allocation, - encoder, - ); - } - } - } - - log::info!("Current atlas: {:?}", self); - - Some(entry) - } - - pub fn remove(&mut self, entry: &Entry) { - log::info!("Removing atlas entry: {:?}", entry); - - match entry { - Entry::Contiguous(allocation) => { - self.deallocate(allocation); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - self.deallocate(&fragment.allocation); - } - } - } - } - fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> { // Allocate one layer if texture fits perfectly if width == SIZE && height == SIZE { @@ -204,7 +114,7 @@ impl Atlas { } return Some(Entry::Fragmented { - size: (width, height), + size: Size::new(width, height), fragments, }); } @@ -284,7 +194,7 @@ impl Atlas { encoder: &mut wgpu::CommandEncoder, ) { let (x, y) = allocation.position(); - let (width, height) = allocation.size(); + let Size { width, height } = allocation.size(); let layer = allocation.layer(); let extent = wgpu::Extent3d { @@ -336,7 +246,7 @@ impl Atlas { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, @@ -388,3 +298,100 @@ impl Atlas { }); } } + +impl image::Storage for Atlas { + type Entry = Entry; + type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); + + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + (device, encoder): &mut Self::State<'_>, + ) -> Option<Self::Entry> { + use wgpu::util::DeviceExt; + + let entry = { + let current_size = self.layers.len(); + let entry = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + entry + }; + + log::info!("Allocated atlas entry: {:?}", entry); + + // It is a webgpu requirement that: + // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 + // So we calculate padded_width by rounding width up to the next + // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; + let padding = (align - (4 * width) % align) % align; + let padded_width = (4 * width + padding) as usize; + let padded_data_size = padded_width * height as usize; + + let mut padded_data = vec![0; padded_data_size]; + + for row in 0..height as usize { + let offset = row * padded_width; + + padded_data[offset..offset + 4 * width as usize].copy_from_slice( + &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], + ) + } + + let buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("iced_wgpu::image staging buffer"), + contents: &padded_data, + usage: wgpu::BufferUsages::COPY_SRC, + }); + + match &entry { + Entry::Contiguous(allocation) => { + self.upload_allocation( + &buffer, width, height, padding, 0, allocation, encoder, + ); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.position; + let offset = (y * padded_width as u32 + 4 * x) as usize; + + self.upload_allocation( + &buffer, + width, + height, + padding, + offset, + &fragment.allocation, + encoder, + ); + } + } + } + + log::info!("Current atlas: {:?}", self); + + Some(entry) + } + + fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } +} diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 59b7239f..43aba875 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -1,5 +1,7 @@ use crate::image::atlas::{self, allocator}; +use iced_graphics::Size; + #[derive(Debug)] pub enum Allocation { Partial { @@ -19,10 +21,10 @@ impl Allocation { } } - pub fn size(&self) -> (u32, u32) { + pub fn size(&self) -> Size<u32> { match self { Allocation::Partial { region, .. } => region.size(), - Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), + Allocation::Full { .. } => Size::new(atlas::SIZE, atlas::SIZE), } } diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 7a4ff5b1..03effdcb 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -46,10 +46,10 @@ impl Region { (rectangle.min.x as u32, rectangle.min.y as u32) } - pub fn size(&self) -> (u32, u32) { + pub fn size(&self) -> iced_graphics::Size<u32> { let size = self.allocation.rectangle.size(); - (size.width as u32, size.height as u32) + iced_graphics::Size::new(size.width as u32, size.height as u32) } } diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 9b3f16df..69c05a50 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,17 +1,19 @@ use crate::image::atlas; +use iced_graphics::image; +use iced_graphics::Size; + #[derive(Debug)] pub enum Entry { Contiguous(atlas::Allocation), Fragmented { - size: (u32, u32), + size: Size<u32>, fragments: Vec<Fragment>, }, } -impl Entry { - #[cfg(feature = "image_rs")] - pub fn size(&self) -> (u32, u32) { +impl image::storage::Entry for Entry { + fn size(&self) -> Size<u32> { match self { Entry::Contiguous(allocation) => allocation.size(), Entry::Fragmented { size, .. } => *size, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1295516b..74152945 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -16,7 +16,7 @@ //! - Meshes of triangles, useful to draw geometry freely. //! //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph @@ -39,13 +39,13 @@ #![cfg_attr(docsrs, feature(doc_cfg))] pub mod settings; -pub mod triangle; pub mod window; mod backend; mod buffer; mod quad; mod text; +mod triangle; pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport}; pub use iced_native::Theme; @@ -56,7 +56,7 @@ pub use settings::Settings; pub(crate) use iced_graphics::Transformation; -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))] mod image; /// A [`wgpu`] graphics renderer for [`iced`]. diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl index 68a8fea3..b24402f8 100644 --- a/wgpu/src/shader/solid.wgsl +++ b/wgpu/src/shader/solid.wgsl @@ -1,17 +1,30 @@ -struct Uniforms { +struct Globals { transform: mat4x4<f32>, - color: vec4<f32> } -@group(0) @binding(0) -var<uniform> uniforms: Uniforms; +@group(0) @binding(0) var<uniform> globals: Globals; + +struct VertexInput { + @location(0) position: vec2<f32>, + @location(1) color: vec4<f32>, +} + +struct VertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) color: vec4<f32>, +} @vertex -fn vs_main(@location(0) input: vec2<f32>) -> @builtin(position) vec4<f32> { - return uniforms.transform * vec4<f32>(input.xy, 0.0, 1.0); +fn vs_main(input: VertexInput) -> VertexOutput { + var out: VertexOutput; + + out.color = input.color; + out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0); + + return out; } @fragment -fn fs_main() -> @location(0) vec4<f32> { - return uniforms.color; +fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> { + return input.color; } diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl deleted file mode 100644 index b24402f8..00000000 --- a/wgpu/src/shader/triangle.wgsl +++ /dev/null @@ -1,30 +0,0 @@ -struct Globals { - transform: mat4x4<f32>, -} - -@group(0) @binding(0) var<uniform> globals: Globals; - -struct VertexInput { - @location(0) position: vec2<f32>, - @location(1) color: vec4<f32>, -} - -struct VertexOutput { - @builtin(position) position: vec4<f32>, - @location(0) color: vec4<f32>, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - - out.color = input.color; - out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0); - - return out; -} - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> { - return input.color; -} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index f9abf2b5..b33b488a 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,64 +1,27 @@ //! Draw meshes of triangles. -mod gradient; mod msaa; -mod solid; use crate::buffer::r#static::Buffer; use crate::settings; use crate::Transformation; use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::{self, Vertex2D}; +use iced_graphics::triangle::ColoredVertex2D; use iced_graphics::Size; -use core::fmt; -use std::fmt::Formatter; - -/// Triangle pipeline for all mesh layers in a [`iced_graphics::Canvas`] widget. #[derive(Debug)] -pub(crate) struct Pipeline { +pub struct Pipeline { blit: Option<msaa::Blit>, - vertex_buffer: Buffer<Vertex2D>, index_buffer: Buffer<u32>, index_strides: Vec<u32>, - pipelines: PipelineList, -} - -/// Supported triangle pipelines for different fills. -pub(crate) struct PipelineList { solid: solid::Pipeline, - gradient: gradient::Pipeline, -} -impl fmt::Debug for PipelineList { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("TrianglePipelines").finish() - } -} - -impl PipelineList { - /// Resets each pipeline's buffers. - fn clear(&mut self) { - self.solid.buffer.clear(); - self.gradient.uniform_buffer.clear(); - self.gradient.storage_buffer.clear(); - } - - /// Writes the contents of each pipeline's CPU buffer to the GPU, resizing the GPU buffer - /// beforehand if necessary. - fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - ) { - self.solid.write(device, staging_belt, encoder); - self.gradient.write(device, staging_belt, encoder); - } + /// Gradients are currently not supported on WASM targets due to their need of storage buffers. + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Pipeline, } impl Pipeline { - /// Creates supported pipelines, listed in [TrianglePipelines]. pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, @@ -66,25 +29,19 @@ impl Pipeline { ) -> Pipeline { Pipeline { blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), - vertex_buffer: Buffer::new( - device, - "iced_wgpu::triangle vertex buffer", - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ), index_buffer: Buffer::new( device, "iced_wgpu::triangle vertex buffer", wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, ), index_strides: Vec::new(), - pipelines: PipelineList { - solid: solid::Pipeline::new(device, format, antialiasing), - gradient: gradient::Pipeline::new(device, format, antialiasing), - }, + solid: solid::Pipeline::new(device, format, antialiasing), + + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Pipeline::new(device, format, antialiasing), } } - /// Draws the contents of the current layer's meshes to the [target]. pub fn draw( &mut self, device: &wgpu::Device, @@ -96,67 +53,185 @@ impl Pipeline { scale_factor: f32, meshes: &[Mesh<'_>], ) { - //count the total amount of vertices & indices we need to handle - let (total_vertices, total_indices) = mesh::attribute_count_of(meshes); + // Count the total amount of vertices & indices we need to handle + let count = mesh::attribute_count_of(meshes); // Then we ensure the current attribute buffers are big enough, resizing if necessary. - - //We are not currently using the return value of these functions as we have no system in - //place to calculate mesh diff, or to know whether or not that would be more performant for - //the majority of use cases. Therefore we will write GPU data every frame (for now). - let _ = self.vertex_buffer.resize(device, total_vertices); - let _ = self.index_buffer.resize(device, total_indices); - - //prepare dynamic buffers & data store for writing + // We are not currently using the return value of these functions as we have no system in + // place to calculate mesh diff, or to know whether or not that would be more performant for + // the majority of use cases. Therefore we will write GPU data every frame (for now). + let _ = self.index_buffer.resize(device, count.indices); + let _ = self.solid.vertices.resize(device, count.solid_vertices); + + #[cfg(not(target_arch = "wasm32"))] + let _ = self + .gradient + .vertices + .resize(device, count.gradient_vertices); + + // Prepare dynamic buffers & data store for writing self.index_strides.clear(); - self.pipelines.clear(); + self.solid.vertices.clear(); + self.solid.uniforms.clear(); + + #[cfg(not(target_arch = "wasm32"))] + { + self.gradient.uniforms.clear(); + self.gradient.vertices.clear(); + self.gradient.storage.clear(); + } - let mut vertex_offset = 0; + let mut solid_vertex_offset = 0; let mut index_offset = 0; + #[cfg(not(target_arch = "wasm32"))] + let mut gradient_vertex_offset = 0; + for mesh in meshes { - let transform = transformation - * Transformation::translate(mesh.origin.x, mesh.origin.y); + let origin = mesh.origin(); + let indices = mesh.indices(); - //write to both buffers - let new_vertex_offset = self.vertex_buffer.write( - device, - staging_belt, - encoder, - vertex_offset, - &mesh.buffers.vertices, - ); + let transform = + transformation * Transformation::translate(origin.x, origin.y); let new_index_offset = self.index_buffer.write( device, staging_belt, encoder, index_offset, - &mesh.buffers.indices, + indices, ); - vertex_offset += new_vertex_offset; index_offset += new_index_offset; - - self.index_strides.push(mesh.buffers.indices.len() as u32); + self.index_strides.push(indices.len() as u32); //push uniform data to CPU buffers - match mesh.style { - triangle::Style::Solid(color) => { - self.pipelines.solid.push(transform, color); + match mesh { + Mesh::Solid { buffers, .. } => { + self.solid.uniforms.push(&solid::Uniforms::new(transform)); + + let written_bytes = self.solid.vertices.write( + device, + staging_belt, + encoder, + solid_vertex_offset, + &buffers.vertices, + ); + + solid_vertex_offset += written_bytes; } - triangle::Style::Gradient(gradient) => { - self.pipelines.gradient.push(transform, gradient); + #[cfg(not(target_arch = "wasm32"))] + Mesh::Gradient { + buffers, gradient, .. + } => { + let written_bytes = self.gradient.vertices.write( + device, + staging_belt, + encoder, + gradient_vertex_offset, + &buffers.vertices, + ); + + gradient_vertex_offset += written_bytes; + + match gradient { + iced_graphics::Gradient::Linear(linear) => { + use glam::{IVec4, Vec4}; + + let start_offset = self.gradient.color_stop_offset; + let end_offset = (linear.color_stops.len() as i32) + + start_offset + - 1; + + self.gradient.uniforms.push(&gradient::Uniforms { + transform: transform.into(), + direction: Vec4::new( + linear.start.x, + linear.start.y, + linear.end.x, + linear.end.y, + ), + stop_range: IVec4::new( + start_offset, + end_offset, + 0, + 0, + ), + }); + + self.gradient.color_stop_offset = end_offset + 1; + + let stops: Vec<gradient::ColorStop> = linear + .color_stops + .iter() + .map(|stop| { + let [r, g, b, a] = stop.color.into_linear(); + + gradient::ColorStop { + offset: stop.offset, + color: Vec4::new(r, g, b, a), + } + }) + .collect(); + + self.gradient + .color_stops_pending_write + .color_stops + .extend(stops); + } + } } + #[cfg(target_arch = "wasm32")] + Mesh::Gradient { .. } => {} + } + } + + // Write uniform data to GPU + if count.solid_vertices > 0 { + let uniforms_resized = self.solid.uniforms.resize(device); + + if uniforms_resized { + self.solid.bind_group = solid::Pipeline::bind_group( + device, + self.solid.uniforms.raw(), + &self.solid.bind_group_layout, + ) } + + self.solid.uniforms.write(device, staging_belt, encoder); } - //write uniform data to GPU - self.pipelines.write(device, staging_belt, encoder); + #[cfg(not(target_arch = "wasm32"))] + if count.gradient_vertices > 0 { + // First write the pending color stops to the CPU buffer + self.gradient + .storage + .push(&self.gradient.color_stops_pending_write); + + // Resize buffers if needed + let uniforms_resized = self.gradient.uniforms.resize(device); + let storage_resized = self.gradient.storage.resize(device); + + if uniforms_resized || storage_resized { + self.gradient.bind_group = gradient::Pipeline::bind_group( + device, + self.gradient.uniforms.raw(), + self.gradient.storage.raw(), + &self.gradient.bind_group_layout, + ); + } + + // Write to GPU + self.gradient.uniforms.write(device, staging_belt, encoder); + self.gradient.storage.write(device, staging_belt, encoder); + + // Cleanup + self.gradient.color_stop_offset = 0; + self.gradient.color_stops_pending_write.color_stops.clear(); + } - //configure the render pass now that the data is uploaded to the GPU + // Configure render pass { - //configure antialiasing pass let (attachment, resolve_target, load) = if let Some(blit) = &mut self.blit { @@ -186,11 +261,12 @@ impl Pipeline { }); let mut num_solids = 0; + #[cfg(not(target_arch = "wasm32"))] let mut num_gradients = 0; let mut last_is_solid = None; for (index, mesh) in meshes.iter().enumerate() { - let clip_bounds = (mesh.clip_bounds * scale_factor).snap(); + let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); render_pass.set_scissor_rect( clip_bounds.x, @@ -199,46 +275,57 @@ impl Pipeline { clip_bounds.height, ); - match mesh.style { - triangle::Style::Solid(_) => { + match mesh { + Mesh::Solid { .. } => { if !last_is_solid.unwrap_or(false) { - self.pipelines - .solid - .set_render_pass_pipeline(&mut render_pass); + render_pass.set_pipeline(&self.solid.pipeline); last_is_solid = Some(true); } - self.pipelines.solid.configure_render_pass( - &mut render_pass, - num_solids, + render_pass.set_bind_group( + 0, + &self.solid.bind_group, + &[self.solid.uniforms.offset_at_index(num_solids)], + ); + + render_pass.set_vertex_buffer( + 0, + self.solid.vertices.slice_from_index(num_solids), ); num_solids += 1; } - triangle::Style::Gradient(_) => { + #[cfg(not(target_arch = "wasm32"))] + Mesh::Gradient { .. } => { if last_is_solid.unwrap_or(true) { - self.pipelines - .gradient - .set_render_pass_pipeline(&mut render_pass); + render_pass.set_pipeline(&self.gradient.pipeline); last_is_solid = Some(false); } - self.pipelines.gradient.configure_render_pass( - &mut render_pass, - num_gradients, + render_pass.set_bind_group( + 0, + &self.gradient.bind_group, + &[self + .gradient + .uniforms + .offset_at_index(num_gradients)], + ); + + render_pass.set_vertex_buffer( + 0, + self.gradient + .vertices + .slice_from_index(num_gradients), ); num_gradients += 1; } + #[cfg(target_arch = "wasm32")] + Mesh::Gradient { .. } => {} }; - render_pass.set_vertex_buffer( - 0, - self.vertex_buffer.slice_from_index(index), - ); - render_pass.set_index_buffer( self.index_buffer.slice_from_index(index), wgpu::IndexFormat::Uint32, @@ -252,7 +339,6 @@ impl Pipeline { } } - self.vertex_buffer.clear(); self.index_buffer.clear(); if let Some(blit) = &mut self.blit { @@ -261,19 +347,6 @@ impl Pipeline { } } -//utility functions for individual pipelines with shared functionality -fn vertex_buffer_layout<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::<Vertex2D>() as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[wgpu::VertexAttribute { - format: wgpu::VertexFormat::Float32x2, - offset: 0, - shader_location: 0, - }], - } -} - fn fragment_target( texture_format: wgpu::TextureFormat, ) -> Option<wgpu::ColorTargetState> { @@ -301,3 +374,360 @@ fn multisample_state( alpha_to_coverage_enabled: false, } } + +mod solid { + use crate::buffer::dynamic; + use crate::buffer::r#static::Buffer; + use crate::settings; + use crate::triangle; + use encase::ShaderType; + use iced_graphics::Transformation; + + #[derive(Debug)] + pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, + pub vertices: Buffer<triangle::ColoredVertex2D>, + pub uniforms: dynamic::Buffer<Uniforms>, + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, + } + + #[derive(Debug, Clone, Copy, ShaderType)] + pub struct Uniforms { + transform: glam::Mat4, + } + + impl Uniforms { + pub fn new(transform: Transformation) -> Self { + Self { + transform: transform.into(), + } + } + } + + impl Pipeline { + /// Creates a new [SolidPipeline] using `solid.wgsl` shader. + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + antialiasing: Option<settings::Antialiasing>, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::solid vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::solid uniforms", + ); + + let bind_group_layout = device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu::triangle::solid bind group layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(Uniforms::min_size()), + }, + count: None, + }], + }, + ); + + let bind_group = + Self::bind_group(device, uniforms.raw(), &bind_group_layout); + + let layout = device.create_pipeline_layout( + &wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu::triangle::solid pipeline layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }, + ); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some( + "iced_wgpu::triangle::solid create shader module", + ), + source: wgpu::ShaderSource::Wgsl( + std::borrow::Cow::Borrowed(include_str!( + "shader/solid.wgsl" + )), + ), + }); + + let pipeline = device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::triangle::solid pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::< + triangle::ColoredVertex2D, + >() + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + // Color + 1 => Float32x4, + ), + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[triangle::fragment_target(format)], + }), + primitive: triangle::primitive_state(), + depth_stencil: None, + multisample: triangle::multisample_state(antialiasing), + multiview: None, + }, + ); + + Self { + pipeline, + vertices, + uniforms, + bind_group_layout, + bind_group, + } + } + + pub fn bind_group( + device: &wgpu::Device, + buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::solid bind group"), + layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }], + }) + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +mod gradient { + use crate::buffer::dynamic; + use crate::buffer::r#static::Buffer; + use crate::settings; + use crate::triangle; + + use encase::ShaderType; + use glam::{IVec4, Vec4}; + use iced_graphics::triangle::Vertex2D; + + #[derive(Debug)] + pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, + pub vertices: Buffer<Vertex2D>, + pub uniforms: dynamic::Buffer<Uniforms>, + pub storage: dynamic::Buffer<Storage>, + pub color_stop_offset: i32, + //Need to store these and then write them all at once + //or else they will be padded to 256 and cause gaps in the storage buffer + pub color_stops_pending_write: Storage, + pub bind_group_layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, + } + + #[derive(Debug, ShaderType)] + pub struct Uniforms { + pub transform: glam::Mat4, + //xy = start, zw = end + pub direction: Vec4, + //x = start stop, y = end stop, zw = padding + pub stop_range: IVec4, + } + + #[derive(Debug, ShaderType)] + pub struct ColorStop { + pub color: Vec4, + pub offset: f32, + } + + #[derive(Debug, ShaderType)] + pub struct Storage { + #[size(runtime)] + pub color_stops: Vec<ColorStop>, + } + + impl Pipeline { + /// Creates a new [GradientPipeline] using `gradient.wgsl` shader. + pub(super) fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + antialiasing: Option<settings::Antialiasing>, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::gradient vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::gradient uniforms", + ); + + //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static + // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work + let storage = dynamic::Buffer::storage( + device, + "iced_wgpu::triangle::gradient storage", + ); + + let bind_group_layout = device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some( + "iced_wgpu::triangle::gradient bind group layout", + ), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(Uniforms::min_size()), + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { + read_only: true, + }, + has_dynamic_offset: false, + min_binding_size: Some(Storage::min_size()), + }, + count: None, + }, + ], + }, + ); + + let bind_group = Pipeline::bind_group( + device, + uniforms.raw(), + storage.raw(), + &bind_group_layout, + ); + + let layout = device.create_pipeline_layout( + &wgpu::PipelineLayoutDescriptor { + label: Some( + "iced_wgpu::triangle::gradient pipeline layout", + ), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }, + ); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some( + "iced_wgpu::triangle::gradient create shader module", + ), + source: wgpu::ShaderSource::Wgsl( + std::borrow::Cow::Borrowed(include_str!( + "shader/gradient.wgsl" + )), + ), + }); + + let pipeline = device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::triangle::gradient pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::<Vertex2D>() + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + ), + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[triangle::fragment_target(format)], + }), + primitive: triangle::primitive_state(), + depth_stencil: None, + multisample: triangle::multisample_state(antialiasing), + multiview: None, + }, + ); + + Self { + pipeline, + vertices, + uniforms, + storage, + color_stop_offset: 0, + color_stops_pending_write: Storage { + color_stops: vec![], + }, + bind_group_layout, + bind_group, + } + } + + pub fn bind_group( + device: &wgpu::Device, + uniform_buffer: &wgpu::Buffer, + storage_buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::gradient bind group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: uniform_buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: storage_buffer.as_entire_binding(), + }, + ], + }) + } + } +} diff --git a/wgpu/src/triangle/gradient.rs b/wgpu/src/triangle/gradient.rs deleted file mode 100644 index b06cbac6..00000000 --- a/wgpu/src/triangle/gradient.rs +++ /dev/null @@ -1,268 +0,0 @@ -use crate::buffer::dynamic; -use crate::settings; -use crate::triangle; -use encase::ShaderType; -use glam::{IVec4, Vec4}; -use iced_graphics::gradient::Gradient; -use iced_graphics::Transformation; - -pub struct Pipeline { - pipeline: wgpu::RenderPipeline, - pub(super) uniform_buffer: dynamic::Buffer<Uniforms>, - pub(super) storage_buffer: dynamic::Buffer<Storage>, - color_stop_offset: i32, - //Need to store these and then write them all at once - //or else they will be padded to 256 and cause gaps in the storage buffer - color_stops_pending_write: Storage, - bind_group_layout: wgpu::BindGroupLayout, - bind_group: wgpu::BindGroup, -} - -#[derive(Debug, ShaderType)] -pub(super) struct Uniforms { - transform: glam::Mat4, - //xy = start, zw = end - direction: Vec4, - //x = start stop, y = end stop, zw = padding - stop_range: IVec4, -} - -#[derive(Debug, ShaderType)] -pub(super) struct ColorStop { - color: Vec4, - offset: f32, -} - -#[derive(ShaderType)] -pub(super) struct Storage { - #[size(runtime)] - pub color_stops: Vec<ColorStop>, -} - -impl Pipeline { - /// Creates a new [GradientPipeline] using `gradient.wgsl` shader. - pub(super) fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, - ) -> Self { - let uniform_buffer = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::gradient uniforms", - ); - - //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static - // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work - let storage_buffer = dynamic::Buffer::storage( - device, - "iced_wgpu::triangle::gradient storage", - ); - - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::triangle::gradient bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(Uniforms::min_size()), - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { - read_only: true, - }, - has_dynamic_offset: false, - min_binding_size: Some(Storage::min_size()), - }, - count: None, - }, - ], - }); - - let bind_group = Pipeline::bind_group( - device, - uniform_buffer.raw(), - storage_buffer.raw(), - &bind_group_layout, - ); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::triangle::gradient pipeline layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some( - "iced_wgpu::triangle::gradient create shader module", - ), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("../shader/gradient.wgsl"), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::gradient pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[triangle::vertex_buffer_layout()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[triangle::fragment_target(format)], - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - }); - - Self { - pipeline, - uniform_buffer, - storage_buffer, - color_stop_offset: 0, - color_stops_pending_write: Storage { - color_stops: vec![], - }, - bind_group_layout, - bind_group, - } - } - - /// Pushes a new gradient uniform to the CPU buffer. - pub fn push(&mut self, transform: Transformation, gradient: &Gradient) { - match gradient { - Gradient::Linear(linear) => { - let start_offset = self.color_stop_offset; - let end_offset = - (linear.color_stops.len() as i32) + start_offset - 1; - - self.uniform_buffer.push(&Uniforms { - transform: transform.into(), - direction: Vec4::new( - linear.start.x, - linear.start.y, - linear.end.x, - linear.end.y, - ), - stop_range: IVec4::new(start_offset, end_offset, 0, 0), - }); - - self.color_stop_offset = end_offset + 1; - - let stops: Vec<ColorStop> = linear - .color_stops - .iter() - .map(|stop| { - let [r, g, b, a] = stop.color.into_linear(); - - ColorStop { - offset: stop.offset, - color: Vec4::new(r, g, b, a), - } - }) - .collect(); - - self.color_stops_pending_write.color_stops.extend(stops); - } - } - } - - fn bind_group( - device: &wgpu::Device, - uniform_buffer: &wgpu::Buffer, - storage_buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::gradient bind group"), - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: uniform_buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: storage_buffer.as_entire_binding(), - }, - ], - }) - } - - /// Writes the contents of the gradient CPU buffer to the GPU buffer, resizing the GPU buffer - /// beforehand if necessary. - pub fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - ) { - //first write the pending color stops to the CPU buffer - self.storage_buffer.push(&self.color_stops_pending_write); - - //resize buffers if needed - let uniforms_resized = self.uniform_buffer.resize(device); - let storage_resized = self.storage_buffer.resize(device); - - if uniforms_resized || storage_resized { - //recreate bind groups if any buffers were resized - self.bind_group = Pipeline::bind_group( - device, - self.uniform_buffer.raw(), - self.storage_buffer.raw(), - &self.bind_group_layout, - ); - } - - //write to GPU - self.uniform_buffer.write(device, staging_belt, encoder); - self.storage_buffer.write(device, staging_belt, encoder); - - //cleanup - self.color_stop_offset = 0; - self.color_stops_pending_write.color_stops.clear(); - } - - pub fn set_render_pass_pipeline<'a>( - &'a self, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - render_pass.set_pipeline(&self.pipeline); - } - - /// Configures the current render pass to draw the gradient at its offset stored in the - /// [DynamicBuffer] at [index]. - pub fn configure_render_pass<'a>( - &'a self, - render_pass: &mut wgpu::RenderPass<'a>, - count: usize, - ) { - render_pass.set_bind_group( - 0, - &self.bind_group, - &[self.uniform_buffer.offset_at_index(count)], - ) - } -} diff --git a/wgpu/src/triangle/solid.rs b/wgpu/src/triangle/solid.rs deleted file mode 100644 index 2e1052f2..00000000 --- a/wgpu/src/triangle/solid.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::buffer::dynamic; -use crate::triangle; -use crate::{settings, Color}; -use encase::ShaderType; -use glam::Vec4; -use iced_graphics::Transformation; - -pub struct Pipeline { - pipeline: wgpu::RenderPipeline, - pub(super) buffer: dynamic::Buffer<Uniforms>, - bind_group_layout: wgpu::BindGroupLayout, - bind_group: wgpu::BindGroup, -} - -#[derive(Debug, Clone, Copy, ShaderType)] -pub(super) struct Uniforms { - transform: glam::Mat4, - color: Vec4, -} - -impl Uniforms { - pub fn new(transform: Transformation, color: Color) -> Self { - let [r, g, b, a] = color.into_linear(); - - Self { - transform: transform.into(), - color: Vec4::new(r, g, b, a), - } - } -} - -impl Pipeline { - /// Creates a new [SolidPipeline] using `solid.wgsl` shader. - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option<settings::Antialiasing>, - ) -> Self { - let buffer = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::solid uniforms", - ); - - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::triangle::solid bind group layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(Uniforms::min_size()), - }, - count: None, - }], - }); - - let bind_group = - Pipeline::bind_group(device, buffer.raw(), &bind_group_layout); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::triangle::solid pipeline layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::triangle::solid create shader module"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("../shader/solid.wgsl"), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::solid pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[triangle::vertex_buffer_layout()], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[triangle::fragment_target(format)], - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - }); - - Self { - pipeline, - buffer, - bind_group_layout, - bind_group, - } - } - - fn bind_group( - device: &wgpu::Device, - buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::solid bind group"), - layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }), - }], - }) - } - - /// Pushes a new solid uniform to the CPU buffer. - pub fn push(&mut self, transform: Transformation, color: &Color) { - self.buffer.push(&Uniforms::new(transform, *color)); - } - - /// Writes the contents of the solid CPU buffer to the GPU buffer, resizing the GPU buffer - /// beforehand if necessary. - pub fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - ) { - let uniforms_resized = self.buffer.resize(device); - - if uniforms_resized { - self.bind_group = Pipeline::bind_group( - device, - self.buffer.raw(), - &self.bind_group_layout, - ) - } - - self.buffer.write(device, staging_belt, encoder); - } - - pub fn set_render_pass_pipeline<'a>( - &'a self, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - render_pass.set_pipeline(&self.pipeline); - } - - /// Configures the current render pass to draw the solid at its offset stored in the - /// [DynamicBuffer] at [index]. - pub fn configure_render_pass<'a>( - &'a self, - render_pass: &mut wgpu::RenderPass<'a>, - count: usize, - ) { - render_pass.set_bind_group( - 0, - &self.bind_group, - &[self.buffer.offset_at_index(count)], - ) - } -} diff --git a/winit/Cargo.toml b/winit/Cargo.toml index d3ed949f..a3ac3ddd 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_winit" -version = "0.4.0" +version = "0.5.1" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2021" description = "A winit runtime for Iced" @@ -26,15 +26,15 @@ git = "https://github.com/iced-rs/winit.git" rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" [dependencies.iced_native] -version = "0.5" +version = "0.6" path = "../native" [dependencies.iced_graphics] -version = "0.3" +version = "0.4" path = "../graphics" [dependencies.iced_futures] -version = "0.4" +version = "0.5" path = "../futures" [target.'cfg(target_os = "windows")'.dependencies.winapi] diff --git a/winit/src/application.rs b/winit/src/application.rs index 939a50c9..1706d2e9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -58,10 +58,10 @@ where /// title of your application when necessary. fn title(&self) -> String; - /// Returns the current [`Theme`] of the [`Application`]. + /// Returns the current `Theme` of the [`Application`]. fn theme(&self) -> <Self::Renderer as crate::Renderer>::Theme; - /// Returns the [`Style`] variation of the [`Theme`]. + /// Returns the `Style` variation of the `Theme`. fn style( &self, ) -> <<Self::Renderer as crate::Renderer>::Theme as StyleSheet>::Style { @@ -137,6 +137,9 @@ where runtime.enter(|| A::new(flags)) }; + #[cfg(target_arch = "wasm32")] + let target = settings.window.platform_specific.target.clone(); + let builder = settings.window.into_builder( &application.title(), event_loop.primary_monitor(), @@ -159,9 +162,20 @@ where let document = window.document().unwrap(); let body = document.body().unwrap(); - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); + let target = target.and_then(|target| { + body.query_selector(&format!("#{}", target)) + .ok() + .unwrap_or(None) + }); + + let _ = match target { + Some(node) => node + .replace_child(&canvas, &node) + .expect(&format!("Could not replace #{}", node.id())), + None => body + .append_child(&canvas) + .expect("Append canvas to HTML body"), + }; } let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index 9c539548..8d6a1df1 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -32,7 +32,7 @@ where let title = application.title(); let scale_factor = application.scale_factor(); let theme = application.theme(); - let appearance = theme.appearance(application.style()); + let appearance = theme.appearance(&application.style()); let viewport = { let physical_size = window.inner_size(); @@ -210,6 +210,6 @@ where // Update theme and appearance self.theme = application.theme(); - self.appearance = self.theme.appearance(application.style()); + self.appearance = self.theme.appearance(&application.style()); } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index ba5b0002..0707aed5 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -1,7 +1,7 @@ //! Convert [`winit`] types into [`iced_native`] types, and viceversa. //! //! [`winit`]: https://github.com/rust-windowing/winit -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native use crate::keyboard; use crate::mouse; use crate::touch; @@ -218,7 +218,7 @@ pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode { /// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native pub fn mouse_interaction( interaction: mouse::Interaction, ) -> winit::window::CursorIcon { @@ -242,7 +242,7 @@ pub fn mouse_interaction( /// Converts a `MouseButton` from [`winit`] to an [`iced_native`] mouse button. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { match mouse_button { winit::event::MouseButton::Left => mouse::Button::Left, @@ -258,7 +258,7 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button { /// modifiers state. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native pub fn modifiers( modifiers: winit::event::ModifiersState, ) -> keyboard::Modifiers { @@ -285,7 +285,7 @@ pub fn cursor_position( /// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native pub fn touch_event( touch: winit::event::Touch, scale_factor: f64, @@ -316,7 +316,7 @@ pub fn touch_event( /// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code. /// /// [`winit`]: https://github.com/rust-windowing/winit -/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native pub fn key_code( virtual_keycode: winit::event::VirtualKeyCode, ) -> keyboard::KeyCode { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index edba887b..bb3a3d5b 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -11,7 +11,7 @@ //! Additionally, a [`conversion`] module is available for users that decide to //! implement a custom event loop. //! -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native //! [`winit`]: https://github.com/rust-windowing/winit //! [`conversion`]: crate::conversion #![doc( diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 6387454b..9bbdef5c 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -7,7 +7,15 @@ mod platform; #[path = "settings/macos.rs"] mod platform; -#[cfg(not(any(target_os = "windows", target_os = "macos")))] +#[cfg(target_arch = "wasm32")] +#[path = "settings/wasm.rs"] +mod platform; + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_arch = "wasm32" +)))] #[path = "settings/other.rs"] mod platform; @@ -27,7 +35,7 @@ pub struct Settings<Flags> { /// communicate with it through the windowing system. pub id: Option<String>, - /// The [`Window`] settings + /// The [`Window`] settings. pub window: Window, /// The data needed to initialize an [`Application`]. diff --git a/winit/src/settings/macos.rs b/winit/src/settings/macos.rs index ad4c8cae..f86e63ad 100644 --- a/winit/src/settings/macos.rs +++ b/winit/src/settings/macos.rs @@ -1,4 +1,3 @@ -#![cfg(target_os = "macos")] //! Platform specific settings for macOS. /// The platform specific window settings of an application. diff --git a/winit/src/settings/wasm.rs b/winit/src/settings/wasm.rs new file mode 100644 index 00000000..8e0f1bbc --- /dev/null +++ b/winit/src/settings/wasm.rs @@ -0,0 +1,11 @@ +//! Platform specific settings for WebAssembly. + +/// The platform specific window settings of an application. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct PlatformSpecific { + /// The identifier of a DOM element that will be replaced with the + /// application. + /// + /// If set to `None`, the application will be appended to the HTML body. + pub target: Option<String>, +} diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs index 9bef1eaf..ff03a9c5 100644 --- a/winit/src/settings/windows.rs +++ b/winit/src/settings/windows.rs @@ -1,4 +1,3 @@ -#![cfg(target_os = "windows")] //! Platform specific settings for Windows. /// The platform specific window settings of an application. |