summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml14
-rw-r--r--core/src/padding.rs20
-rw-r--r--core/src/size.rs27
-rw-r--r--examples/arc/src/main.rs14
-rw-r--r--examples/cached/Cargo.toml10
-rw-r--r--examples/cached/src/main.rs139
-rw-r--r--examples/clock/src/main.rs54
-rw-r--r--examples/geometry/Cargo.toml11
-rw-r--r--examples/geometry/README.md18
-rw-r--r--examples/geometry/src/main.rs217
-rw-r--r--examples/lazy/Cargo.toml10
-rw-r--r--examples/lazy/src/main.rs139
-rw-r--r--examples/modern_art/Cargo.toml10
-rw-r--r--examples/modern_art/src/main.rs140
-rw-r--r--examples/multitouch/src/main.rs5
-rw-r--r--examples/scrollable/src/main.rs23
-rw-r--r--examples/solar_system/src/main.rs45
-rw-r--r--examples/styling/src/main.rs57
-rw-r--r--glow/Cargo.toml16
-rw-r--r--glow/src/backend.rs35
-rw-r--r--glow/src/image.rs230
-rw-r--r--glow/src/image/storage.rs78
-rw-r--r--glow/src/lib.rs2
-rw-r--r--glow/src/quad/compatibility.rs6
-rw-r--r--glow/src/quad/core.rs6
-rw-r--r--glow/src/shader/common/gradient.frag59
-rw-r--r--glow/src/shader/common/image.frag22
-rw-r--r--glow/src/shader/common/image.vert9
-rw-r--r--glow/src/shader/common/triangle.frag6
-rw-r--r--glow/src/shader/common/triangle.vert8
-rw-r--r--glow/src/triangle.rs226
-rw-r--r--glow/src/triangle/gradient.rs162
-rw-r--r--glow/src/triangle/solid.rs91
-rw-r--r--glow/src/window/compositor.rs7
-rw-r--r--glutin/Cargo.toml1
-rw-r--r--graphics/Cargo.toml40
-rw-r--r--graphics/src/backend.rs4
-rw-r--r--graphics/src/gradient.rs112
-rw-r--r--graphics/src/gradient/linear.rs109
-rw-r--r--graphics/src/image.rs10
-rw-r--r--graphics/src/image/raster.rs (renamed from wgpu/src/image/raster.rs)86
-rw-r--r--graphics/src/image/storage.rs31
-rw-r--r--graphics/src/image/vector.rs (renamed from wgpu/src/image/vector.rs)67
-rw-r--r--graphics/src/layer.rs114
-rw-r--r--graphics/src/layer/image.rs23
-rw-r--r--graphics/src/layer/mesh.rs31
-rw-r--r--graphics/src/layer/quad.rs30
-rw-r--r--graphics/src/layer/text.rs26
-rw-r--r--graphics/src/lib.rs3
-rw-r--r--graphics/src/primitive.rs3
-rw-r--r--graphics/src/renderer.rs4
-rw-r--r--graphics/src/transformation.rs8
-rw-r--r--graphics/src/triangle.rs33
-rw-r--r--graphics/src/widget/canvas.rs6
-rw-r--r--graphics/src/widget/canvas/fill.rs30
-rw-r--r--graphics/src/widget/canvas/frame.rs173
-rw-r--r--graphics/src/widget/canvas/stroke.rs18
-rw-r--r--lazy/src/lazy.rs362
-rw-r--r--lazy/src/lib.rs15
-rw-r--r--native/src/image.rs31
-rw-r--r--native/src/svg.rs9
-rw-r--r--native/src/widget/button.rs9
-rw-r--r--native/src/widget/container.rs8
-rw-r--r--native/src/widget/image.rs4
-rw-r--r--native/src/widget/image/viewer.rs4
-rw-r--r--native/src/widget/svg.rs4
-rw-r--r--native/src/widget/text_input.rs12
-rw-r--r--native/src/window/action.rs20
-rw-r--r--style/src/theme.rs29
-rw-r--r--style/src/theme/palette.rs8
-rw-r--r--wgpu/Cargo.toml48
-rw-r--r--wgpu/src/backend.rs29
-rw-r--r--wgpu/src/buffer.rs3
-rw-r--r--wgpu/src/buffer/dynamic.rs199
-rw-r--r--wgpu/src/buffer/static.rs117
-rw-r--r--wgpu/src/image.rs68
-rw-r--r--wgpu/src/image/atlas.rs205
-rw-r--r--wgpu/src/image/atlas/allocation.rs6
-rw-r--r--wgpu/src/image/atlas/allocator.rs4
-rw-r--r--wgpu/src/image/atlas/entry.rs10
-rw-r--r--wgpu/src/lib.rs3
-rw-r--r--wgpu/src/shader/gradient.wgsl88
-rw-r--r--wgpu/src/shader/solid.wgsl17
-rw-r--r--wgpu/src/triangle.rs514
-rw-r--r--wgpu/src/triangle/gradient.rs268
-rw-r--r--wgpu/src/triangle/solid.rs170
-rw-r--r--winit/src/application.rs32
-rw-r--r--winit/src/settings.rs12
-rw-r--r--winit/src/settings/macos.rs1
-rw-r--r--winit/src/settings/wasm.rs11
-rw-r--r--winit/src/settings/windows.rs1
-rw-r--r--winit/src/window.rs20
92 files changed, 3915 insertions, 1304 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9c6a435a..ff59d7a9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
@@ -69,9 +66,10 @@ members = [
"examples/events",
"examples/exit",
"examples/game_of_life",
- "examples/geometry",
"examples/integration_opengl",
"examples/integration_wgpu",
+ "examples/lazy",
+ "examples/modern_art",
"examples/multitouch",
"examples/pane_grid",
"examples/pick_list",
@@ -103,7 +101,7 @@ iced_glow = { version = "0.3", path = "glow", optional = true }
thiserror = "1.0"
[dependencies.image_rs]
-version = "0.23"
+version = "0.24"
package = "image"
optional = true
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/arc/src/main.rs b/examples/arc/src/main.rs
index 0c619dc9..7b6ea0e1 100644
--- a/examples/arc/src/main.rs
+++ b/examples/arc/src/main.rs
@@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant};
use iced::executor;
use iced::widget::canvas::{
- self, Cache, Canvas, Cursor, Geometry, Path, Stroke,
+ self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
};
use iced::{
Application, Command, Element, Length, Point, Rectangle, Settings,
@@ -52,11 +52,6 @@ impl Application for Arc {
Command::none()
}
- fn subscription(&self) -> Subscription<Message> {
- iced::time::every(std::time::Duration::from_millis(10))
- .map(|_| Message::Tick)
- }
-
fn view(&self) -> Element<Message> {
Canvas::new(self)
.width(Length::Fill)
@@ -67,6 +62,11 @@ impl Application for Arc {
fn theme(&self) -> Theme {
Theme::Dark
}
+
+ fn subscription(&self) -> Subscription<Message> {
+ iced::time::every(std::time::Duration::from_millis(10))
+ .map(|_| Message::Tick)
+ }
}
impl<Message> canvas::Program<Message> for Arc {
@@ -114,7 +114,7 @@ impl<Message> canvas::Program<Message> for Arc {
frame.stroke(
&path,
Stroke {
- color: palette.text,
+ style: stroke::Style::Solid(palette.text),
width: 10.0,
..Stroke::default()
},
diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml
new file mode 100644
index 00000000..2c7edde2
--- /dev/null
+++ b/examples/cached/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "cached"
+version = "0.1.0"
+authors = ["Nick Senger <dev@nsenger.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
+iced_lazy = { path = "../../lazy" }
diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs
new file mode 100644
index 00000000..8845b874
--- /dev/null
+++ b/examples/cached/src/main.rs
@@ -0,0 +1,139 @@
+use iced::theme;
+use iced::widget::{
+ button, column, horizontal_space, row, scrollable, text, text_input,
+};
+use iced::{Element, Length, Sandbox, Settings};
+use iced_lazy::lazy;
+
+use std::collections::HashSet;
+
+pub fn main() -> iced::Result {
+ App::run(Settings::default())
+}
+
+struct App {
+ options: HashSet<String>,
+ input: String,
+ order: Order,
+}
+
+impl Default for App {
+ fn default() -> Self {
+ Self {
+ options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
+ .into_iter()
+ .map(ToString::to_string)
+ .collect(),
+ input: Default::default(),
+ order: Order::Ascending,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ InputChanged(String),
+ ToggleOrder,
+ DeleteOption(String),
+ AddOption(String),
+}
+
+impl Sandbox for App {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Cached - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::InputChanged(input) => {
+ self.input = input;
+ }
+ Message::ToggleOrder => {
+ self.order = match self.order {
+ Order::Ascending => Order::Descending,
+ Order::Descending => Order::Ascending,
+ }
+ }
+ Message::AddOption(option) => {
+ self.options.insert(option);
+ self.input.clear();
+ }
+ Message::DeleteOption(option) => {
+ self.options.remove(&option);
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let options = lazy((&self.order, self.options.len()), || {
+ let mut options: Vec<_> = self.options.iter().collect();
+
+ options.sort_by(|a, b| match self.order {
+ Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
+ Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
+ });
+
+ column(
+ options
+ .into_iter()
+ .map(|option| {
+ row![
+ text(option),
+ horizontal_space(Length::Fill),
+ button("Delete")
+ .on_press(Message::DeleteOption(
+ option.to_string(),
+ ),)
+ .style(theme::Button::Destructive)
+ ]
+ .into()
+ })
+ .collect(),
+ )
+ .spacing(10)
+ });
+
+ column![
+ scrollable(options).height(Length::Fill),
+ row![
+ text_input(
+ "Add a new option",
+ &self.input,
+ Message::InputChanged,
+ )
+ .on_submit(Message::AddOption(self.input.clone())),
+ button(text(format!("Toggle Order ({})", self.order)))
+ .on_press(Message::ToggleOrder)
+ ]
+ .spacing(10)
+ ]
+ .spacing(20)
+ .padding(20)
+ .into()
+ }
+}
+
+#[derive(Debug, Hash)]
+enum Order {
+ Ascending,
+ Descending,
+}
+
+impl std::fmt::Display for Order {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::Ascending => "Ascending",
+ Self::Descending => "Descending",
+ }
+ )
+ }
+}
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index 8818fb54..a389c54f 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,5 +1,7 @@
use iced::executor;
-use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke};
+use iced::widget::canvas::{
+ stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
+};
use iced::widget::{canvas, container};
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
@@ -24,9 +26,9 @@ enum Message {
}
impl Application for Clock {
+ type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
- type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@@ -59,15 +61,6 @@ impl Application for Clock {
Command::none()
}
- fn subscription(&self) -> Subscription<Message> {
- iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
- Message::Tick(
- time::OffsetDateTime::now_local()
- .unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
- )
- })
- }
-
fn view(&self) -> Element<Message> {
let canvas = canvas(self as &Self)
.width(Length::Fill)
@@ -79,6 +72,15 @@ impl Application for Clock {
.padding(20)
.into()
}
+
+ fn subscription(&self) -> Subscription<Message> {
+ iced::time::every(std::time::Duration::from_millis(500)).map(|_| {
+ Message::Tick(
+ time::OffsetDateTime::now_local()
+ .unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
+ )
+ })
+ }
}
impl<Message> canvas::Program<Message> for Clock {
@@ -104,33 +106,41 @@ impl<Message> canvas::Program<Message> for Clock {
let long_hand =
Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius));
- let thin_stroke = Stroke {
- width: radius / 100.0,
- color: Color::WHITE,
- line_cap: LineCap::Round,
- ..Stroke::default()
+ let width = radius / 100.0;
+
+ let thin_stroke = || -> Stroke {
+ Stroke {
+ width,
+ style: stroke::Style::Solid(Color::WHITE),
+ line_cap: LineCap::Round,
+ ..Stroke::default()
+ }
};
- let wide_stroke = Stroke {
- width: thin_stroke.width * 3.0,
- ..thin_stroke
+ let wide_stroke = || -> Stroke {
+ Stroke {
+ width: width * 3.0,
+ style: stroke::Style::Solid(Color::WHITE),
+ line_cap: LineCap::Round,
+ ..Stroke::default()
+ }
};
frame.translate(Vector::new(center.x, center.y));
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.hour(), 12));
- frame.stroke(&short_hand, wide_stroke);
+ frame.stroke(&short_hand, wide_stroke());
});
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.minute(), 60));
- frame.stroke(&long_hand, wide_stroke);
+ frame.stroke(&long_hand, wide_stroke());
});
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.second(), 60));
- frame.stroke(&long_hand, thin_stroke);
+ frame.stroke(&long_hand, thin_stroke());
})
});
diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml
deleted file mode 100644
index 22ede0e0..00000000
--- a/examples/geometry/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[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
deleted file mode 100644
index 4d5c81cb..00000000
--- a/examples/geometry/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-## 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
deleted file mode 100644
index d8b99ab3..00000000
--- a/examples/geometry/src/main.rs
+++ /dev/null
@@ -1,217 +0,0 @@
-//! 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::{Backend, Primitive};
-
- use iced_native::widget::{self, Widget};
- use iced_native::{
- layout, Element, Layout, Length, Point, Rectangle, Size, Vector,
- };
-
- #[derive(Default)]
- pub struct Rainbow;
-
- impl Rainbow {
- pub fn new() -> Self {
- Self
- }
- }
-
- 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, Vertex2D};
- 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::Mesh2D {
- size: b.size(),
- buffers: Mesh2D {
- vertices: vec![
- Vertex2D {
- position: posn_center,
- color: [1.0, 1.0, 1.0, 1.0],
- },
- Vertex2D {
- position: posn_tl,
- color: color_r,
- },
- Vertex2D {
- position: posn_t,
- color: color_o,
- },
- Vertex2D {
- position: posn_tr,
- color: color_y,
- },
- Vertex2D {
- position: posn_r,
- color: color_g,
- },
- Vertex2D {
- position: posn_br,
- color: color_gb,
- },
- Vertex2D {
- position: posn_b,
- color: color_b,
- },
- Vertex2D {
- position: posn_bl,
- color: color_i,
- },
- Vertex2D {
- 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::{Alignment, 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 {
- Example
- }
-
- 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)
- .align_items(Alignment::Start);
-
- 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/lazy/Cargo.toml b/examples/lazy/Cargo.toml
new file mode 100644
index 00000000..79255c25
--- /dev/null
+++ b/examples/lazy/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "lazy"
+version = "0.1.0"
+authors = ["Nick Senger <dev@nsenger.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
+iced_lazy = { path = "../../lazy" }
diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs
new file mode 100644
index 00000000..8845b874
--- /dev/null
+++ b/examples/lazy/src/main.rs
@@ -0,0 +1,139 @@
+use iced::theme;
+use iced::widget::{
+ button, column, horizontal_space, row, scrollable, text, text_input,
+};
+use iced::{Element, Length, Sandbox, Settings};
+use iced_lazy::lazy;
+
+use std::collections::HashSet;
+
+pub fn main() -> iced::Result {
+ App::run(Settings::default())
+}
+
+struct App {
+ options: HashSet<String>,
+ input: String,
+ order: Order,
+}
+
+impl Default for App {
+ fn default() -> Self {
+ Self {
+ options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"]
+ .into_iter()
+ .map(ToString::to_string)
+ .collect(),
+ input: Default::default(),
+ order: Order::Ascending,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ InputChanged(String),
+ ToggleOrder,
+ DeleteOption(String),
+ AddOption(String),
+}
+
+impl Sandbox for App {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Cached - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::InputChanged(input) => {
+ self.input = input;
+ }
+ Message::ToggleOrder => {
+ self.order = match self.order {
+ Order::Ascending => Order::Descending,
+ Order::Descending => Order::Ascending,
+ }
+ }
+ Message::AddOption(option) => {
+ self.options.insert(option);
+ self.input.clear();
+ }
+ Message::DeleteOption(option) => {
+ self.options.remove(&option);
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let options = lazy((&self.order, self.options.len()), || {
+ let mut options: Vec<_> = self.options.iter().collect();
+
+ options.sort_by(|a, b| match self.order {
+ Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()),
+ Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()),
+ });
+
+ column(
+ options
+ .into_iter()
+ .map(|option| {
+ row![
+ text(option),
+ horizontal_space(Length::Fill),
+ button("Delete")
+ .on_press(Message::DeleteOption(
+ option.to_string(),
+ ),)
+ .style(theme::Button::Destructive)
+ ]
+ .into()
+ })
+ .collect(),
+ )
+ .spacing(10)
+ });
+
+ column![
+ scrollable(options).height(Length::Fill),
+ row![
+ text_input(
+ "Add a new option",
+ &self.input,
+ Message::InputChanged,
+ )
+ .on_submit(Message::AddOption(self.input.clone())),
+ button(text(format!("Toggle Order ({})", self.order)))
+ .on_press(Message::ToggleOrder)
+ ]
+ .spacing(10)
+ ]
+ .spacing(20)
+ .padding(20)
+ .into()
+ }
+}
+
+#[derive(Debug, Hash)]
+enum Order {
+ Ascending,
+ Descending,
+}
+
+impl std::fmt::Display for Order {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::Ascending => "Ascending",
+ Self::Descending => "Descending",
+ }
+ )
+ }
+}
diff --git a/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml
new file mode 100644
index 00000000..a48361ae
--- /dev/null
+++ b/examples/modern_art/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "modern_art"
+version = "0.1.0"
+authors = ["Bingus <shankern@protonmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
+rand = "0.8.5"
diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs
new file mode 100644
index 00000000..0dd21c74
--- /dev/null
+++ b/examples/modern_art/src/main.rs
@@ -0,0 +1,140 @@
+use iced::widget::canvas::{
+ self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame,
+ Geometry, Gradient,
+};
+use iced::{
+ executor, Application, Color, Command, Element, Length, Point, Rectangle,
+ Renderer, Settings, Size, Theme,
+};
+use rand::{thread_rng, Rng};
+
+fn main() -> iced::Result {
+ ModernArt::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {}
+
+struct ModernArt {
+ cache: Cache,
+}
+
+impl Application for ModernArt {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
+ (
+ ModernArt {
+ cache: Default::default(),
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Modern Art")
+ }
+
+ fn update(&mut self, _message: Message) -> Command<Message> {
+ Command::none()
+ }
+
+ fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
+ Canvas::new(self)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ }
+}
+
+impl<Message> canvas::Program<Message> for ModernArt {
+ type State = ();
+
+ fn draw(
+ &self,
+ _state: &Self::State,
+ _theme: &Theme,
+ bounds: Rectangle,
+ _cursor: Cursor,
+ ) -> Vec<Geometry> {
+ let geometry = self.cache.draw(bounds.size(), |frame| {
+ let num_squares = thread_rng().gen_range(0..1200);
+
+ let mut i = 0;
+ while i <= num_squares {
+ generate_box(frame, bounds.size());
+ i += 1;
+ }
+ });
+
+ vec![geometry]
+ }
+}
+
+fn random_direction() -> Location {
+ match thread_rng().gen_range(0..8) {
+ 0 => Location::TopLeft,
+ 1 => Location::Top,
+ 2 => Location::TopRight,
+ 3 => Location::Right,
+ 4 => Location::BottomRight,
+ 5 => Location::Bottom,
+ 6 => Location::BottomLeft,
+ 7 => Location::Left,
+ _ => Location::TopLeft,
+ }
+}
+
+fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
+ let solid = rand::random::<bool>();
+
+ let random_color = || -> Color {
+ Color::from_rgb(
+ thread_rng().gen_range(0.0..1.0),
+ thread_rng().gen_range(0.0..1.0),
+ thread_rng().gen_range(0.0..1.0),
+ )
+ };
+
+ let gradient = |top_left: Point, size: Size| -> Gradient {
+ let mut builder = Gradient::linear(Position::Relative {
+ top_left,
+ size,
+ start: random_direction(),
+ end: random_direction(),
+ });
+ let stops = thread_rng().gen_range(1..15u32);
+
+ let mut i = 0;
+ while i <= stops {
+ builder = builder.add_stop(i as f32 / stops as f32, random_color());
+ i += 1;
+ }
+
+ builder.build().unwrap()
+ };
+
+ let top_left = Point::new(
+ thread_rng().gen_range(0.0..bounds.width),
+ thread_rng().gen_range(0.0..bounds.height),
+ );
+
+ let size = Size::new(
+ thread_rng().gen_range(50.0..200.0),
+ thread_rng().gen_range(50.0..200.0),
+ );
+
+ if solid {
+ frame.fill_rectangle(top_left, size, random_color());
+ } else {
+ frame.fill_rectangle(top_left, size, gradient(top_left, size));
+ };
+
+ solid
+}
diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs
index 0345ceb7..f5faae0f 100644
--- a/examples/multitouch/src/main.rs
+++ b/examples/multitouch/src/main.rs
@@ -2,7 +2,8 @@
//! a circle around each fingertip. This only works on touch-enabled
//! computers like Microsoft Surface.
use iced::widget::canvas::event;
-use iced::widget::canvas::{self, Canvas, Cursor, Geometry, Stroke};
+use iced::widget::canvas::stroke::{self, Stroke};
+use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
use iced::{
executor, touch, window, Application, Color, Command, Element, Length,
Point, Rectangle, Settings, Subscription, Theme,
@@ -186,7 +187,7 @@ impl canvas::Program<Message> for State {
frame.stroke(
&path,
Stroke {
- color: Color::BLACK,
+ style: stroke::Style::Solid(Color::BLACK),
width: 3.0,
..Stroke::default()
},
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index b7b3dedc..6eba34e2 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -14,9 +14,15 @@ struct ScrollableDemo {
variants: Vec<Variant>,
}
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum ThemeType {
+ Light,
+ Dark,
+}
+
#[derive(Debug, Clone)]
enum Message {
- ThemeChanged(Theme),
+ ThemeChanged(ThemeType),
ScrollToTop(usize),
ScrollToBottom(usize),
Scrolled(usize, f32),
@@ -45,7 +51,10 @@ impl Application for ScrollableDemo {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::ThemeChanged(theme) => {
- self.theme = theme;
+ self.theme = match theme {
+ ThemeType::Light => Theme::Light,
+ ThemeType::Dark => Theme::Dark,
+ };
Command::none()
}
@@ -78,17 +87,15 @@ impl Application for ScrollableDemo {
}
fn view(&self) -> Element<Message> {
- let ScrollableDemo {
- theme, variants, ..
- } = self;
+ let ScrollableDemo { variants, .. } = self;
- let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
+ let choose_theme = [ThemeType::Light, ThemeType::Dark].iter().fold(
column!["Choose a theme:"].spacing(10),
|column, option| {
column.push(radio(
format!("{:?}", option),
*option,
- Some(*theme),
+ Some(*option),
Message::ThemeChanged,
))
},
@@ -198,7 +205,7 @@ impl Application for ScrollableDemo {
}
fn theme(&self) -> Theme {
- self.theme
+ self.theme.clone()
}
}
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index c59d73a8..56787a99 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -11,7 +11,9 @@ use iced::executor;
use iced::theme::{self, Theme};
use iced::time;
use iced::widget::canvas;
-use iced::widget::canvas::{Cursor, Path, Stroke};
+use iced::widget::canvas::gradient::{self, Gradient};
+use iced::widget::canvas::stroke::{self, Stroke};
+use iced::widget::canvas::{Cursor, Path};
use iced::window;
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings,
@@ -37,9 +39,9 @@ enum Message {
}
impl Application for SolarSystem {
+ type Executor = executor::Default;
type Message = Message;
type Theme = Theme;
- type Executor = executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
@@ -65,10 +67,6 @@ impl Application for SolarSystem {
Command::none()
}
- fn subscription(&self) -> Subscription<Message> {
- time::every(std::time::Duration::from_millis(10)).map(Message::Tick)
- }
-
fn view(&self) -> Element<Message> {
canvas(&self.state)
.width(Length::Fill)
@@ -86,6 +84,10 @@ impl Application for SolarSystem {
text_color: Color::WHITE,
})
}
+
+ fn subscription(&self) -> Subscription<Message> {
+ time::every(time::Duration::from_millis(10)).map(Message::Tick)
+ }
}
#[derive(Debug)]
@@ -178,8 +180,10 @@ impl<Message> canvas::Program<Message> for State {
frame.stroke(
&orbit,
Stroke {
+ style: stroke::Style::Solid(Color::from_rgba8(
+ 0, 153, 255, 0.1,
+ )),
width: 1.0,
- color: Color::from_rgba8(0, 153, 255, 0.1),
line_dash: canvas::LineDash {
offset: 0,
segments: &[3.0, 6.0],
@@ -198,15 +202,18 @@ impl<Message> canvas::Program<Message> for State {
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
- let shadow = Path::rectangle(
- Point::new(0.0, -Self::EARTH_RADIUS),
- Size::new(
- Self::EARTH_RADIUS * 4.0,
- Self::EARTH_RADIUS * 2.0,
- ),
- );
- frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6));
+ let earth_fill =
+ Gradient::linear(gradient::Position::Absolute {
+ start: Point::new(-Self::EARTH_RADIUS, 0.0),
+ end: Point::new(Self::EARTH_RADIUS, 0.0),
+ })
+ .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
+ .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
+ .build()
+ .expect("Build Earth fill gradient");
+
+ frame.fill(&earth, earth_fill);
frame.with_save(|frame| {
frame.rotate(rotation * 10.0);
@@ -215,14 +222,6 @@ impl<Message> canvas::Program<Message> for State {
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
frame.fill(&moon, Color::WHITE);
});
-
- frame.fill(
- &shadow,
- Color {
- a: 0.7,
- ..Color::BLACK
- },
- );
});
});
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index cda53e87..e16860ad 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -1,9 +1,10 @@
+use iced::theme::{self, Theme};
use iced::widget::{
button, checkbox, column, container, horizontal_rule, progress_bar, radio,
row, scrollable, slider, text, text_input, toggler, vertical_rule,
vertical_space,
};
-use iced::{Alignment, Element, Length, Sandbox, Settings, Theme};
+use iced::{Alignment, Color, Element, Length, Sandbox, Settings};
pub fn main() -> iced::Result {
Styling::run(Settings::default())
@@ -18,9 +19,16 @@ struct Styling {
toggler_value: bool,
}
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum ThemeType {
+ Light,
+ Dark,
+ Custom,
+}
+
#[derive(Debug, Clone)]
enum Message {
- ThemeChanged(Theme),
+ ThemeChanged(ThemeType),
InputChanged(String),
ButtonPressed,
SliderChanged(f32),
@@ -41,7 +49,19 @@ impl Sandbox for Styling {
fn update(&mut self, message: Message) {
match message {
- Message::ThemeChanged(theme) => self.theme = theme,
+ Message::ThemeChanged(theme) => {
+ self.theme = match theme {
+ ThemeType::Light => Theme::Light,
+ ThemeType::Dark => Theme::Dark,
+ ThemeType::Custom => Theme::custom(theme::Palette {
+ background: Color::from_rgb(1.0, 0.9, 1.0),
+ text: Color::BLACK,
+ primary: Color::from_rgb(0.5, 0.5, 0.0),
+ success: Color::from_rgb(0.0, 1.0, 0.0),
+ danger: Color::from_rgb(1.0, 0.0, 0.0),
+ }),
+ }
+ }
Message::InputChanged(value) => self.input_value = value,
Message::ButtonPressed => {}
Message::SliderChanged(value) => self.slider_value = value,
@@ -51,17 +71,24 @@ impl Sandbox for Styling {
}
fn view(&self) -> Element<Message> {
- let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
- column![text("Choose a theme:")].spacing(10),
- |column, theme| {
- column.push(radio(
- format!("{:?}", theme),
- *theme,
- Some(self.theme),
- Message::ThemeChanged,
- ))
- },
- );
+ let choose_theme =
+ [ThemeType::Light, ThemeType::Dark, ThemeType::Custom]
+ .iter()
+ .fold(
+ column![text("Choose a theme:")].spacing(10),
+ |column, theme| {
+ column.push(radio(
+ format!("{:?}", theme),
+ *theme,
+ Some(match self.theme {
+ Theme::Light => ThemeType::Light,
+ Theme::Dark => ThemeType::Dark,
+ Theme::Custom { .. } => ThemeType::Custom,
+ }),
+ Message::ThemeChanged,
+ ))
+ },
+ );
let text_input = text_input(
"Type something...",
@@ -132,6 +159,6 @@ impl Sandbox for Styling {
}
fn theme(&self) -> Theme {
- self.theme
+ self.theme.clone()
}
}
diff --git a/glow/Cargo.toml b/glow/Cargo.toml
index 18215e9b..a50fd375 100644
--- a/glow/Cargo.toml
+++ b/glow/Cargo.toml
@@ -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"
diff --git a/glow/src/backend.rs b/glow/src/backend.rs
index 78d4229e..1a41d540 100644
--- a/glow/src/backend.rs
+++ b/glow/src/backend.rs
@@ -1,7 +1,8 @@
-use crate::program;
+#[cfg(any(feature = "image", feature = "svg"))]
+use crate::image;
use crate::quad;
use crate::text;
-use crate::triangle;
+use crate::{program, triangle};
use crate::{Settings, Transformation, Viewport};
use iced_graphics::backend;
@@ -16,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,
@@ -33,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,
@@ -71,6 +78,9 @@ impl Backend {
viewport_size.height,
);
}
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ self.image_pipeline.trim_cache(gl);
}
fn flush(
@@ -105,14 +115,23 @@ impl Backend {
* Transformation::scale(scale_factor, scale_factor);
self.triangle_pipeline.draw(
+ &layer.meshes,
gl,
target_height,
scaled,
scale_factor,
- &layer.meshes,
);
}
+ #[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, scaled, scale_factor, &layer.images);
+ }
+
if !layer.text.is_empty() {
for text in layer.text.iter() {
// Target physical coordinates directly to avoid blurry text
@@ -239,8 +258,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)
}
}
@@ -248,8 +267,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..f906cd4c
--- /dev/null
+++ b/glow/src/image.rs
@@ -0,0 +1,230 @@
+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::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,
+ transformation: Transformation,
+ _scale_factor: f32,
+ images: &[layer::Image],
+ ) {
+ 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));
+ }
+
+ #[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 {
+ 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);
+ }
+ }
+
+ 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..e3690a69 100644
--- a/glow/src/lib.rs
+++ b/glow/src/lib.rs
@@ -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/quad/compatibility.rs b/glow/src/quad/compatibility.rs
index eb3fb7e0..28a5ea7c 100644
--- a/glow/src/quad/compatibility.rs
+++ b/glow/src/quad/compatibility.rs
@@ -70,11 +70,10 @@ impl Pipeline {
unsafe {
gl.use_program(Some(program));
- let matrix: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
- &matrix,
+ Transformation::identity().as_ref(),
);
gl.uniform_1_f32(Some(&scale_location), 1.0);
@@ -139,11 +138,10 @@ impl Pipeline {
if transformation != self.current_transform {
unsafe {
- let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
- &matrix,
+ transformation.as_ref(),
);
self.current_transform = transformation;
diff --git a/glow/src/quad/core.rs b/glow/src/quad/core.rs
index 3e51b1ca..16bec385 100644
--- a/glow/src/quad/core.rs
+++ b/glow/src/quad/core.rs
@@ -65,11 +65,10 @@ impl Pipeline {
unsafe {
gl.use_program(Some(program));
- let matrix: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(&transform_location),
false,
- &matrix,
+ Transformation::identity().as_ref(),
);
gl.uniform_1_f32(Some(&scale_location), 1.0);
@@ -119,11 +118,10 @@ impl Pipeline {
if transformation != self.current_transform {
unsafe {
- let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(&self.transform_location),
false,
- &matrix,
+ transformation.as_ref(),
);
self.current_transform = transformation;
diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag
new file mode 100644
index 00000000..42d0201f
--- /dev/null
+++ b/glow/src/shader/common/gradient.frag
@@ -0,0 +1,59 @@
+#ifdef GL_ES
+ #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
+#endif
+
+in vec2 raw_position;
+
+uniform vec4 gradient_direction;
+uniform uint 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];
+
+//TODO: rewrite without branching to make ALUs happy
+void main() {
+ vec2 start = gradient_direction.xy;
+ vec2 end = gradient_direction.zw;
+ vec2 gradient_vec = vec2(end - start);
+ vec2 current_vec = vec2(raw_position.xy - start);
+ 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);
+
+ float min_offset = color_stops[1].x;
+ float max_offset = color_stops[color_stops_size - 1u].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;
+
+ if (coord_offset <= min_offset) {
+ //current coordinate is before the first defined offset, set it to the start color
+ 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(
+ curr_offset,
+ next_offset,
+ coord_offset
+ ));
+ }
+
+ 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];
+ }
+ }
+}
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/triangle.frag
index e8689f2e..ead40fe5 100644
--- a/glow/src/shader/common/triangle.frag
+++ b/glow/src/shader/common/triangle.frag
@@ -11,8 +11,8 @@ out vec4 fragColor;
#define gl_FragColor fragColor
#endif
-in vec4 v_Color;
+uniform vec4 color;
void main() {
- gl_FragColor = v_Color;
-} \ No newline at end of file
+ fragColor = color;
+}
diff --git a/glow/src/shader/common/triangle.vert b/glow/src/shader/common/triangle.vert
index d0494a5f..fe505997 100644
--- a/glow/src/shader/common/triangle.vert
+++ b/glow/src/shader/common/triangle.vert
@@ -1,11 +1,9 @@
uniform mat4 u_Transform;
in vec2 i_Position;
-in vec4 i_Color;
-
-out vec4 v_Color;
+out vec2 raw_position;
void main() {
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
- v_Color = i_Color;
-} \ No newline at end of file
+ raw_position = i_Position;
+}
diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs
index ae4f83ef..0e27bcf2 100644
--- a/glow/src/triangle.rs
+++ b/glow/src/triangle.rs
@@ -1,66 +1,32 @@
//! Draw meshes of triangles.
-use crate::program::{self, Shader};
+mod gradient;
+mod solid;
+
+use crate::program;
use crate::Transformation;
-use glow::HasContext;
-use iced_graphics::layer;
-use std::marker::PhantomData;
-pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
+use iced_graphics::layer::mesh::{self, Mesh};
+use iced_graphics::triangle::{self, Vertex2D};
-const VERTEX_BUFFER_SIZE: usize = 10_000;
-const INDEX_BUFFER_SIZE: usize = 10_000;
+use glow::HasContext;
+use std::marker::PhantomData;
#[derive(Debug)]
pub(crate) struct Pipeline {
- program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
vertices: Buffer<Vertex2D>,
indices: Buffer<u32>,
- transform_location: <glow::Context as HasContext>::UniformLocation,
- current_transform: Transformation,
+ programs: ProgramList,
}
-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/triangle.vert"),
- );
- let fragment_shader = Shader::fragment(
- gl,
- shader_version,
- include_str!("shader/common/triangle.frag"),
- );
-
- program::create(
- gl,
- &[vertex_shader, fragment_shader],
- &[(0, "i_Position"), (1, "i_Color")],
- )
- };
-
- 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);
- }
+#[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") };
@@ -73,7 +39,7 @@ impl Pipeline {
gl,
glow::ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
- VERTEX_BUFFER_SIZE,
+ std::mem::size_of::<Vertex2D>() as usize,
)
};
@@ -82,7 +48,7 @@ impl Pipeline {
gl,
glow::ELEMENT_ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
- INDEX_BUFFER_SIZE,
+ std::mem::size_of::<u32>() as usize,
)
};
@@ -92,83 +58,63 @@ impl Pipeline {
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);
- }
+ };
- Pipeline {
- program,
+ Self {
vertex_array,
vertices,
indices,
- transform_location,
- current_transform: Transformation::identity(),
+ programs: ProgramList {
+ solid: solid::Program::new(gl, shader_version),
+ gradient: gradient::Program::new(gl, shader_version),
+ },
}
}
pub fn draw(
&mut self,
+ meshes: &[Mesh<'_>],
gl: &glow::Context,
target_height: u32,
transformation: Transformation,
scale_factor: f32,
- meshes: &[layer::Mesh<'_>],
) {
unsafe {
gl.enable(glow::MULTISAMPLE);
gl.enable(glow::SCISSOR_TEST);
- gl.use_program(Some(self.program));
- gl.bind_vertex_array(Some(self.vertex_array));
+ gl.bind_vertex_array(Some(self.vertex_array))
}
- // This looks a bit crazy, but we are just counting how many vertices
- // and indices we will need to handle.
- // TODO: Improve readability
- let (total_vertices, total_indices) = meshes
- .iter()
- .map(|layer::Mesh { buffers, .. }| {
- (buffers.vertices.len(), buffers.indices.len())
- })
- .fold((0, 0), |(total_v, total_i), (v, i)| {
- (total_v + v, total_i + i)
- });
-
- // Then we ensure the current buffers are big enough, resizing if
- // necessary
+ //count the total amount of vertices & indices we need to handle
+ let (total_vertices, total_indices) = 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);
}
// We upload all the vertices and indices upfront
- let mut last_vertex = 0;
- let mut last_index = 0;
+ let mut vertex_offset = 0;
+ let mut index_offset = 0;
- for layer::Mesh { buffers, .. } in meshes {
+ for mesh in meshes {
unsafe {
gl.buffer_sub_data_u8_slice(
glow::ARRAY_BUFFER,
- (last_vertex * std::mem::size_of::<Vertex2D>()) as i32,
- bytemuck::cast_slice(&buffers.vertices),
+ (vertex_offset * std::mem::size_of::<Vertex2D>()) as i32,
+ bytemuck::cast_slice(&mesh.buffers.vertices),
);
gl.buffer_sub_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
- (last_index * std::mem::size_of::<u32>()) as i32,
- bytemuck::cast_slice(&buffers.indices),
+ (index_offset * std::mem::size_of::<u32>()) as i32,
+ bytemuck::cast_slice(&mesh.buffers.indices),
);
- last_vertex += buffers.vertices.len();
- last_index += buffers.indices.len();
+ vertex_offset += mesh.buffers.vertices.len();
+ index_offset += mesh.buffers.indices.len();
}
}
@@ -176,29 +122,13 @@ impl Pipeline {
let mut last_vertex = 0;
let mut last_index = 0;
- for layer::Mesh {
- buffers,
- origin,
- clip_bounds,
- } in meshes
- {
- let transform =
- transformation * Transformation::translate(origin.x, origin.y);
+ for mesh in meshes {
+ let transform = transformation
+ * Transformation::translate(mesh.origin.x, mesh.origin.y);
- let clip_bounds = (*clip_bounds * scale_factor).snap();
+ let clip_bounds = (mesh.clip_bounds * scale_factor).snap();
unsafe {
- if self.current_transform != transform {
- let matrix: [f32; 16] = transform.into();
- gl.uniform_matrix_4_f32_slice(
- Some(&self.transform_location),
- false,
- &matrix,
- );
-
- self.current_transform = transform;
- }
-
gl.scissor(
clip_bounds.x as i32,
(target_height - (clip_bounds.y + clip_bounds.height))
@@ -207,50 +137,74 @@ impl Pipeline {
clip_bounds.height as i32,
);
+ match mesh.style {
+ triangle::Style::Solid(color) => {
+ self.programs.solid.use_program(gl, color, &transform);
+ }
+ triangle::Style::Gradient(gradient) => {
+ self.programs
+ .gradient
+ .use_program(gl, gradient, &transform);
+ }
+ }
+
gl.draw_elements_base_vertex(
glow::TRIANGLES,
- buffers.indices.len() as i32,
+ mesh.buffers.indices.len() as i32,
glow::UNSIGNED_INT,
(last_index * std::mem::size_of::<u32>()) as i32,
last_vertex as i32,
);
- last_vertex += buffers.vertices.len();
- last_index += buffers.indices.len();
+ last_vertex += mesh.buffers.vertices.len();
+ last_index += mesh.buffers.indices.len();
}
}
unsafe {
gl.bind_vertex_array(None);
- gl.use_program(None);
gl.disable(glow::SCISSOR_TEST);
gl.disable(glow::MULTISAMPLE);
}
}
}
-#[repr(C)]
-#[derive(Debug, Clone, Copy)]
-struct Uniforms {
- transform: [f32; 16],
-}
-
-unsafe impl bytemuck::Zeroable for Uniforms {}
-unsafe impl bytemuck::Pod for Uniforms {}
-
-impl Default for Uniforms {
- fn default() -> Self {
- Self {
- transform: *Transformation::identity().as_ref(),
- }
+/// 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")],
+ )
}
}
-impl From<Transformation> for Uniforms {
- fn from(transformation: Transformation) -> Uniforms {
- Self {
- transform: transformation.into(),
- }
+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(),
+ );
}
}
diff --git a/glow/src/triangle/gradient.rs b/glow/src/triangle/gradient.rs
new file mode 100644
index 00000000..5225612e
--- /dev/null
+++ b/glow/src/triangle/gradient.rs
@@ -0,0 +1,162 @@
+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
new file mode 100644
index 00000000..fb3d40c3
--- /dev/null
+++ b/glow/src/triangle/solid.rs
@@ -0,0 +1,91 @@
+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/glow/src/window/compositor.rs b/glow/src/window/compositor.rs
index f6afaa68..20756032 100644
--- a/glow/src/window/compositor.rs
+++ b/glow/src/window/compositor.rs
@@ -26,8 +26,11 @@ impl<Theme> iced_graphics::window::GLCompositor for Compositor<Theme> {
log::info!("{:#?}", settings);
let version = gl.version();
- log::info!("Version: {:?}", version);
- log::info!("Embedded: {}", version.is_embedded);
+ log::info!(
+ "OpenGL version: {:?} (Embedded: {})",
+ version,
+ version.is_embedded
+ );
let renderer = gl.get_parameter_string(glow::RENDERER);
log::info!("Renderer: {}", renderer);
diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml
index d84f9d70..65b16947 100644
--- a/glutin/Cargo.toml
+++ b/glutin/Cargo.toml
@@ -29,6 +29,7 @@ path = "../native"
[dependencies.iced_winit]
version = "0.4"
path = "../winit"
+features = ["application"]
[dependencies.iced_graphics]
version = "0.3"
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index 18fef54f..57079b95 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -11,17 +11,33 @@ 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.10"
+glam = "0.21.3"
+log = "0.4"
raw-window-handle = "0.5"
thiserror = "1.0"
+bitflags = "1.2"
[dependencies.bytemuck]
version = "1.4"
@@ -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
new file mode 100644
index 00000000..64f9e4b3
--- /dev/null
+++ b/graphics/src/gradient.rs
@@ -0,0 +1,112 @@
+//! For creating a Gradient.
+pub mod linear;
+
+pub use linear::Linear;
+
+use crate::{Color, Point, Size};
+
+#[derive(Debug, Clone, PartialEq)]
+/// 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`]
+ /// point.
+ Linear(Linear),
+}
+
+impl Gradient {
+ /// Creates a new linear [`linear::Builder`].
+ pub fn linear(position: impl Into<Position>) -> linear::Builder {
+ linear::Builder::new(position.into())
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+/// A point along the gradient vector where the specified [`color`] is unmixed.
+pub struct ColorStop {
+ /// Offset along the gradient vector.
+ pub offset: f32,
+ /// The color of the gradient at the specified [`offset`].
+ pub color: Color,
+}
+
+#[derive(Debug)]
+/// The position of the gradient within its bounds.
+pub enum Position {
+ /// The gradient will be positioned with respect to two points.
+ Absolute {
+ /// The starting point of the gradient.
+ start: Point,
+ /// The ending point of the gradient.
+ end: Point,
+ },
+ /// The gradient will be positioned relative to the provided bounds.
+ Relative {
+ /// The top left position of the bounds.
+ top_left: Point,
+ /// The width & height of the bounds.
+ size: Size,
+ /// The start [Location] of the gradient.
+ start: Location,
+ /// The end [Location] of the gradient.
+ end: Location,
+ },
+}
+
+impl From<(Point, Point)> for Position {
+ fn from((start, end): (Point, Point)) -> Self {
+ Self::Absolute { start, end }
+ }
+}
+
+#[derive(Debug)]
+/// The location of a relatively-positioned gradient.
+pub enum Location {
+ /// Top left.
+ TopLeft,
+ /// Top.
+ Top,
+ /// Top right.
+ TopRight,
+ /// Right.
+ Right,
+ /// Bottom right.
+ BottomRight,
+ /// Bottom.
+ Bottom,
+ /// Bottom left.
+ BottomLeft,
+ /// Left.
+ Left,
+}
+
+impl Location {
+ fn to_absolute(&self, top_left: Point, size: Size) -> Point {
+ match self {
+ Location::TopLeft => top_left,
+ Location::Top => {
+ Point::new(top_left.x + size.width / 2.0, top_left.y)
+ }
+ Location::TopRight => {
+ Point::new(top_left.x + size.width, top_left.y)
+ }
+ Location::Right => Point::new(
+ top_left.x + size.width,
+ top_left.y + size.height / 2.0,
+ ),
+ Location::BottomRight => {
+ Point::new(top_left.x + size.width, top_left.y + size.height)
+ }
+ Location::Bottom => Point::new(
+ top_left.x + size.width / 2.0,
+ top_left.y + size.height,
+ ),
+ Location::BottomLeft => {
+ Point::new(top_left.x, top_left.y + size.height)
+ }
+ Location::Left => {
+ Point::new(top_left.x, top_left.y + size.height / 2.0)
+ }
+ }
+ }
+}
diff --git a/graphics/src/gradient/linear.rs b/graphics/src/gradient/linear.rs
new file mode 100644
index 00000000..9928c1eb
--- /dev/null
+++ b/graphics/src/gradient/linear.rs
@@ -0,0 +1,109 @@
+//! Linear gradient builder & definition.
+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`].
+#[derive(Debug, Clone, PartialEq)]
+pub struct Linear {
+ /// The point where the linear gradient begins.
+ pub start: Point,
+ /// The point where the linear gradient ends.
+ pub end: Point,
+ /// [`ColorStop`]s along the linear gradient path.
+ pub color_stops: Vec<ColorStop>,
+}
+
+/// A [`Linear`] builder.
+#[derive(Debug)]
+pub struct Builder {
+ start: Point,
+ end: Point,
+ stops: Vec<ColorStop>,
+ error: Option<BuilderError>,
+}
+
+impl Builder {
+ /// Creates a new [`Builder`].
+ pub fn new(position: Position) -> Self {
+ let (start, end) = match position {
+ Position::Absolute { start, end } => (start, end),
+ Position::Relative {
+ top_left,
+ size,
+ start,
+ end,
+ } => (
+ start.to_absolute(top_left, size),
+ end.to_absolute(top_left, size),
+ ),
+ };
+
+ Self {
+ start,
+ end,
+ stops: vec![],
+ error: None,
+ }
+ }
+
+ /// Adds a new stop, defined by an offset and a color, to the gradient.
+ ///
+ /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built.
+ ///
+ /// Note: when using the [`glow`] backend, any color stop added after the 16th
+ /// will not be displayed.
+ ///
+ /// On the [`wgpu`] backend this limitation does not exist (technical limit is 524,288 stops).
+ ///
+ /// [`glow`]: https://docs.rs/iced_glow
+ /// [`wgpu`]: https://docs.rs/iced_wgpu
+ pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
+ if offset.is_finite() && (0.0..=1.0).contains(&offset) {
+ match self.stops.binary_search_by(|stop| {
+ stop.offset.partial_cmp(&offset).unwrap()
+ }) {
+ Ok(_) => {
+ self.error = Some(BuilderError::DuplicateOffset(offset))
+ }
+ Err(index) => {
+ self.stops.insert(index, ColorStop { offset, color });
+ }
+ }
+ } else {
+ self.error = Some(BuilderError::InvalidOffset(offset))
+ };
+
+ self
+ }
+
+ /// Builds the linear [`Gradient`] of this [`Builder`].
+ ///
+ /// Returns `BuilderError` if gradient in invalid.
+ pub fn build(self) -> Result<Gradient, BuilderError> {
+ if self.stops.is_empty() {
+ Err(BuilderError::MissingColorStop)
+ } else if let Some(error) = self.error {
+ Err(error)
+ } else {
+ Ok(Gradient::Linear(Linear {
+ start: self.start,
+ end: self.end,
+ color_stops: self.stops,
+ }))
+ }
+ }
+}
+
+/// An error that happened when building a [`Linear`] gradient.
+#[derive(Debug, thiserror::Error)]
+pub enum BuilderError {
+ #[error("Gradients must contain at least one color stop.")]
+ /// Gradients must contain at least one color stop.
+ MissingColorStop,
+ #[error("Offset {0} must be a unique, finite number.")]
+ /// Offsets in a gradient must all be unique & finite.
+ DuplicateOffset(f32),
+ #[error("Offset {0} must be between 0.0..=1.0.")]
+ /// Offsets in a gradient must be between 0.0..=1.0.
+ InvalidOffset(f32),
+}
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 af545713..e95934b0 100644
--- a/graphics/src/layer.rs
+++ b/graphics/src/layer.rs
@@ -1,15 +1,22 @@
//! Organize rendering primitives into a flattened list of layers.
+mod image;
+mod quad;
+mod text;
+
+pub mod mesh;
+
+pub use image::Image;
+pub use mesh::Mesh;
+pub use quad::Quad;
+pub use text::Text;
+
use crate::alignment;
-use crate::triangle;
use crate::{
Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport,
};
-use iced_native::image;
-use iced_native::svg;
-
/// A group of primitives that should be clipped together.
-#[derive(Debug, Clone)]
+#[derive(Debug)]
pub struct Layer<'a> {
/// The clipping bounds of the [`Layer`].
pub bounds: Rectangle,
@@ -159,7 +166,11 @@ impl<'a> Layer<'a> {
border_color: border_color.into_linear(),
});
}
- Primitive::Mesh2D { buffers, size } => {
+ Primitive::Mesh2D {
+ buffers,
+ size,
+ style,
+ } => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
@@ -173,6 +184,7 @@ impl<'a> Layer<'a> {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
+ style,
});
}
}
@@ -233,93 +245,3 @@ impl<'a> Layer<'a> {
}
}
}
-
-/// A colored rectangle with a border.
-///
-/// This type can be directly uploaded to GPU memory.
-#[derive(Debug, Clone, Copy)]
-#[repr(C)]
-pub struct Quad {
- /// The position of the [`Quad`].
- pub position: [f32; 2],
-
- /// The size of the [`Quad`].
- pub size: [f32; 2],
-
- /// The color of the [`Quad`], in __linear RGB__.
- pub color: [f32; 4],
-
- /// The border color of the [`Quad`], in __linear RGB__.
- pub border_color: [f32; 4],
-
- /// The border radius of the [`Quad`].
- pub border_radius: f32,
-
- /// The border width of the [`Quad`].
- pub border_width: f32,
-}
-
-/// A mesh of triangles.
-#[derive(Debug, Clone, Copy)]
-pub struct Mesh<'a> {
- /// The origin of the vertices of the [`Mesh`].
- pub origin: Point,
-
- /// The vertex and index buffers of the [`Mesh`].
- pub buffers: &'a triangle::Mesh2D,
-
- /// The clipping bounds of the [`Mesh`].
- pub clip_bounds: Rectangle<f32>,
-}
-
-/// A paragraph of text.
-#[derive(Debug, Clone, Copy)]
-pub struct Text<'a> {
- /// The content of the [`Text`].
- pub content: &'a str,
-
- /// The layout bounds of the [`Text`].
- pub bounds: Rectangle,
-
- /// The color of the [`Text`], in __linear RGB_.
- pub color: [f32; 4],
-
- /// The size of the [`Text`].
- pub size: f32,
-
- /// The font of the [`Text`].
- pub font: Font,
-
- /// The horizontal alignment of the [`Text`].
- pub horizontal_alignment: alignment::Horizontal,
-
- /// The vertical alignment of the [`Text`].
- pub vertical_alignment: alignment::Vertical,
-}
-
-/// A raster or vector image.
-#[derive(Debug, Clone)]
-pub enum Image {
- /// A raster image.
- Raster {
- /// The handle of a raster image.
- handle: image::Handle,
-
- /// The bounds of the image.
- bounds: Rectangle,
- },
- /// A vector image.
- Vector {
- /// The handle of a vector image.
- handle: svg::Handle,
-
- /// The bounds of the image.
- bounds: Rectangle,
- },
-}
-
-#[allow(unsafe_code)]
-unsafe impl bytemuck::Zeroable for Quad {}
-
-#[allow(unsafe_code)]
-unsafe impl bytemuck::Pod for Quad {}
diff --git a/graphics/src/layer/image.rs b/graphics/src/layer/image.rs
new file mode 100644
index 00000000..045ec665
--- /dev/null
+++ b/graphics/src/layer/image.rs
@@ -0,0 +1,23 @@
+use crate::Rectangle;
+use iced_native::{image, svg};
+
+/// A raster or vector image.
+#[derive(Debug, Clone)]
+pub enum Image {
+ /// A raster image.
+ Raster {
+ /// The handle of a raster image.
+ handle: image::Handle,
+
+ /// The bounds of the image.
+ bounds: Rectangle,
+ },
+ /// A vector image.
+ Vector {
+ /// The handle of a vector image.
+ handle: svg::Handle,
+
+ /// The bounds of the image.
+ bounds: Rectangle,
+ },
+}
diff --git a/graphics/src/layer/mesh.rs b/graphics/src/layer/mesh.rs
new file mode 100644
index 00000000..979081f1
--- /dev/null
+++ b/graphics/src/layer/mesh.rs
@@ -0,0 +1,31 @@
+//! A collection of triangle primitives.
+use crate::triangle;
+use crate::{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,
+
+ /// The vertex and index buffers of the [`Mesh`].
+ pub buffers: &'a triangle::Mesh2D,
+
+ /// The clipping bounds of the [`Mesh`].
+ pub clip_bounds: Rectangle<f32>,
+
+ /// The shader of the [`Mesh`].
+ pub style: &'a triangle::Style,
+}
+
+/// Returns the number of total vertices & total indices of all [`Mesh`]es.
+pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> (usize, usize) {
+ 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)
+ })
+}
diff --git a/graphics/src/layer/quad.rs b/graphics/src/layer/quad.rs
new file mode 100644
index 00000000..4def8427
--- /dev/null
+++ b/graphics/src/layer/quad.rs
@@ -0,0 +1,30 @@
+/// A colored rectangle with a border.
+///
+/// This type can be directly uploaded to GPU memory.
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub struct Quad {
+ /// The position of the [`Quad`].
+ pub position: [f32; 2],
+
+ /// The size of the [`Quad`].
+ pub size: [f32; 2],
+
+ /// The color of the [`Quad`], in __linear RGB__.
+ pub color: [f32; 4],
+
+ /// The border color of the [`Quad`], in __linear RGB__.
+ pub border_color: [f32; 4],
+
+ /// The border radius of the [`Quad`].
+ pub border_radius: f32,
+
+ /// The border width of the [`Quad`].
+ pub border_width: f32,
+}
+
+#[allow(unsafe_code)]
+unsafe impl bytemuck::Zeroable for Quad {}
+
+#[allow(unsafe_code)]
+unsafe impl bytemuck::Pod for Quad {}
diff --git a/graphics/src/layer/text.rs b/graphics/src/layer/text.rs
new file mode 100644
index 00000000..74f7a676
--- /dev/null
+++ b/graphics/src/layer/text.rs
@@ -0,0 +1,26 @@
+use crate::{alignment, Font, Rectangle};
+
+/// A paragraph of text.
+#[derive(Debug, Clone, Copy)]
+pub struct Text<'a> {
+ /// The content of the [`Text`].
+ pub content: &'a str,
+
+ /// The layout bounds of the [`Text`].
+ pub bounds: Rectangle,
+
+ /// The color of the [`Text`], in __linear RGB_.
+ pub color: [f32; 4],
+
+ /// The size of the [`Text`].
+ pub size: f32,
+
+ /// The font of the [`Text`].
+ pub font: Font,
+
+ /// The horizontal alignment of the [`Text`].
+ pub horizontal_alignment: alignment::Horizontal,
+
+ /// The vertical alignment of the [`Text`].
+ pub vertical_alignment: alignment::Vertical,
+}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index 11082472..d39dd90c 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -29,6 +29,8 @@ mod viewport;
pub mod backend;
pub mod font;
+pub mod gradient;
+pub mod image;
pub mod layer;
pub mod overlay;
pub mod renderer;
@@ -39,6 +41,7 @@ pub mod window;
pub use antialiasing::Antialiasing;
pub use backend::Backend;
pub use error::Error;
+pub use gradient::Gradient;
pub use layer::Layer;
pub use primitive::Primitive;
pub use renderer::Renderer;
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index 5f7a344d..b481ac0b 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -88,6 +88,9 @@ pub enum Primitive {
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
+
+ /// The shader of the mesh
+ style: triangle::Style,
},
/// A cached primitive.
///
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index cdbc4f40..036b398c 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/transformation.rs b/graphics/src/transformation.rs
index 2a19caed..cf0457a4 100644
--- a/graphics/src/transformation.rs
+++ b/graphics/src/transformation.rs
@@ -8,7 +8,7 @@ pub struct Transformation(Mat4);
impl Transformation {
/// Get the identity transformation.
pub fn identity() -> Transformation {
- Transformation(Mat4::identity())
+ Transformation(Mat4::IDENTITY)
}
/// Creates an orthographic projection.
@@ -51,3 +51,9 @@ impl From<Transformation> for [f32; 16] {
*t.as_ref()
}
}
+
+impl From<Transformation> for Mat4 {
+ fn from(transformation: Transformation) -> Self {
+ transformation.0
+ }
+}
diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs
index 05028f51..04ff6d21 100644
--- a/graphics/src/triangle.rs
+++ b/graphics/src/triangle.rs
@@ -1,4 +1,6 @@
//! 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.
@@ -6,20 +8,37 @@ use bytemuck::{Pod, Zeroable};
pub struct Mesh2D {
/// The vertices of the mesh
pub vertices: Vec<Vertex2D>,
-
/// 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.
+ /// Therefore, this list should always have a length that is a multiple of 3.
pub indices: Vec<u32>,
}
-/// A two-dimensional vertex with some color in __linear__ RGBA.
+/// A two-dimensional vertex.
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
#[repr(C)]
pub struct Vertex2D {
- /// The vertex position
+ /// The vertex position in 2D space.
pub position: [f32; 2],
- /// The vertex color in __linear__ RGBA.
- pub color: [f32; 4],
+}
+
+#[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)
+ }
+}
+
+impl From<Gradient> for Style {
+ fn from(gradient: Gradient) -> Self {
+ Self::Gradient(gradient)
+ }
}
diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs
index b4afd998..a14940d9 100644
--- a/graphics/src/widget/canvas.rs
+++ b/graphics/src/widget/canvas.rs
@@ -3,19 +3,19 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
-
pub mod event;
+pub mod fill;
pub mod path;
+pub mod stroke;
mod cache;
mod cursor;
-mod fill;
mod frame;
mod geometry;
mod program;
-mod stroke;
mod text;
+pub use crate::gradient::{self, Gradient};
pub use cache::Cache;
pub use cursor::Cursor;
pub use event::Event;
diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs
index 56495435..c69fc0d7 100644
--- a/graphics/src/widget/canvas/fill.rs
+++ b/graphics/src/widget/canvas/fill.rs
@@ -1,12 +1,15 @@
-use iced_native::Color;
+//! Fill [crate::widget::canvas::Geometry] with a certain style.
+use crate::{Color, Gradient};
+
+pub use crate::triangle::Style;
/// The style used to fill geometry.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone)]
pub struct Fill {
- /// The color used to fill geometry.
+ /// The color or gradient of the fill.
///
- /// By default, it is set to `BLACK`.
- pub color: Color,
+ /// By default, it is set to [`FillStyle::Solid`] `BLACK`.
+ pub style: Style,
/// The fill rule defines how to determine what is inside and what is
/// outside of a shape.
@@ -20,9 +23,9 @@ pub struct Fill {
}
impl Default for Fill {
- fn default() -> Fill {
- Fill {
- color: Color::BLACK,
+ fn default() -> Self {
+ Self {
+ style: Style::Solid(Color::BLACK),
rule: FillRule::NonZero,
}
}
@@ -31,12 +34,21 @@ impl Default for Fill {
impl From<Color> for Fill {
fn from(color: Color) -> Fill {
Fill {
- color,
+ style: Style::Solid(color),
..Fill::default()
}
}
}
+impl From<Gradient> for Fill {
+ fn from(gradient: Gradient) -> Self {
+ Fill {
+ style: Style::Gradient(gradient),
+ ..Default::default()
+ }
+ }
+}
+
/// The fill rule defines how to determine what is inside and what is outside of
/// a shape.
///
diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs
index 516539ca..cf6c6928 100644
--- a/graphics/src/widget/canvas/frame.rs
+++ b/graphics/src/widget/canvas/frame.rs
@@ -1,13 +1,14 @@
-use std::borrow::Cow;
-
-use iced_native::{Point, Rectangle, Size, Vector};
-
+use crate::gradient::Gradient;
use crate::triangle;
-use crate::widget::canvas::path;
-use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text};
+use crate::triangle::Vertex2D;
+use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Text};
use crate::Primitive;
+use iced_native::{Point, Rectangle, Size, Vector};
+
+use lyon::geom::euclid;
use lyon::tessellation;
+use std::borrow::Cow;
/// The frame of a [`Canvas`].
///
@@ -15,13 +16,41 @@ use lyon::tessellation;
#[allow(missing_debug_implementations)]
pub struct Frame {
size: Size,
- buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>,
+ buffers: BufferStack,
primitives: Vec<Primitive>,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
stroke_tessellator: tessellation::StrokeTessellator,
}
+struct BufferStack {
+ stack: Vec<(tessellation::VertexBuffers<Vertex2D, u32>, triangle::Style)>,
+}
+
+impl BufferStack {
+ fn new() -> Self {
+ 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));
+ }
+ };
+
+ tessellation::BuffersBuilder::new(
+ &mut self.stack.last_mut().unwrap().0,
+ Vertex2DBuilder,
+ )
+ }
+}
+
#[derive(Debug)]
struct Transforms {
previous: Vec<Transform>,
@@ -34,6 +63,35 @@ struct Transform {
is_identity: bool,
}
+impl Transform {
+ /// Transforms the given [Point] by the transformation matrix.
+ fn transform_point(&self, point: &mut Point) {
+ let transformed = self
+ .raw
+ .transform_point(euclid::Point2D::new(point.x, point.y));
+ point.x = transformed.x;
+ point.y = transformed.y;
+ }
+
+ fn transform_style(&self, style: triangle::Style) -> triangle::Style {
+ match style {
+ triangle::Style::Solid(color) => triangle::Style::Solid(color),
+ triangle::Style::Gradient(gradient) => {
+ triangle::Style::Gradient(self.transform_gradient(gradient))
+ }
+ }
+ }
+
+ fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
+ let (start, end) = match &mut gradient {
+ Gradient::Linear(linear) => (&mut linear.start, &mut linear.end),
+ };
+ self.transform_point(start);
+ self.transform_point(end);
+ gradient
+ }
+}
+
impl Frame {
/// Creates a new empty [`Frame`] with the given dimensions.
///
@@ -42,7 +100,7 @@ impl Frame {
pub fn new(size: Size) -> Frame {
Frame {
size,
- buffers: lyon::tessellation::VertexBuffers::new(),
+ buffers: BufferStack::new(),
primitives: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
@@ -83,21 +141,20 @@ impl Frame {
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
/// provided style.
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
- let Fill { color, rule } = fill.into();
+ let Fill { style, rule } = fill.into();
- let mut buffers = tessellation::BuffersBuilder::new(
- &mut self.buffers,
- FillVertex(color.into_linear()),
- );
+ let mut buffer = self
+ .buffers
+ .get(self.transforms.current.transform_style(style));
let options =
tessellation::FillOptions::default().with_fill_rule(rule.into());
- let result = if self.transforms.current.is_identity {
+ if self.transforms.current.is_identity {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffers,
+ &mut buffer,
)
} else {
let path = path.transformed(&self.transforms.current.raw);
@@ -105,11 +162,10 @@ impl Frame {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffers,
+ &mut buffer,
)
- };
-
- result.expect("Tessellate path");
+ }
+ .expect("Tessellate path.");
}
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
@@ -120,12 +176,11 @@ impl Frame {
size: Size,
fill: impl Into<Fill>,
) {
- let Fill { color, rule } = fill.into();
+ let Fill { style, rule } = fill.into();
- let mut buffers = tessellation::BuffersBuilder::new(
- &mut self.buffers,
- FillVertex(color.into_linear()),
- );
+ let mut buffer = self
+ .buffers
+ .get(self.transforms.current.transform_style(style));
let top_left =
self.transforms.current.raw.transform_point(
@@ -144,7 +199,7 @@ impl Frame {
.tessellate_rectangle(
&lyon::math::Box2D::new(top_left, top_left + size),
&options,
- &mut buffers,
+ &mut buffer,
)
.expect("Fill rectangle");
}
@@ -154,10 +209,9 @@ impl Frame {
pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
let stroke = stroke.into();
- let mut buffers = tessellation::BuffersBuilder::new(
- &mut self.buffers,
- StrokeVertex(stroke.color.into_linear()),
- );
+ let mut buffer = self
+ .buffers
+ .get(self.transforms.current.transform_style(stroke.style));
let mut options = tessellation::StrokeOptions::default();
options.line_width = stroke.width;
@@ -171,11 +225,11 @@ impl Frame {
Cow::Owned(path::dashed(path, stroke.line_dash))
};
- let result = if self.transforms.current.is_identity {
+ if self.transforms.current.is_identity {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffers,
+ &mut buffer,
)
} else {
let path = path.transformed(&self.transforms.current.raw);
@@ -183,11 +237,10 @@ impl Frame {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffers,
+ &mut buffer,
)
- };
-
- result.expect("Stroke path");
+ }
+ .expect("Stroke path");
}
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
@@ -206,8 +259,6 @@ impl Frame {
///
/// [`Canvas`]: crate::widget::Canvas
pub fn fill_text(&mut self, text: impl Into<Text>) {
- use std::f32;
-
let text = text.into();
let position = if self.transforms.current.is_identity {
@@ -304,7 +355,7 @@ impl Frame {
self.transforms.current.is_identity = false;
}
- /// Applies a rotation to the current transform of the [`Frame`].
+ /// Applies a rotation in radians to the current transform of the [`Frame`].
#[inline]
pub fn rotate(&mut self, angle: f32) {
self.transforms.current.raw = self
@@ -331,52 +382,44 @@ impl Frame {
}
fn into_primitives(mut self) -> Vec<Primitive> {
- if !self.buffers.indices.is_empty() {
- self.primitives.push(Primitive::Mesh2D {
- buffers: triangle::Mesh2D {
- vertices: self.buffers.vertices,
- indices: self.buffers.indices,
- },
- size: self.size,
- });
+ 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,
+ })
+ }
}
self.primitives
}
}
-struct FillVertex([f32; 4]);
+struct Vertex2DBuilder;
-impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D>
- for FillVertex
-{
- fn new_vertex(
- &mut self,
- vertex: lyon::tessellation::FillVertex<'_>,
- ) -> triangle::Vertex2D {
+impl tessellation::FillVertexConstructor<Vertex2D> for Vertex2DBuilder {
+ fn new_vertex(&mut self, vertex: tessellation::FillVertex<'_>) -> Vertex2D {
let position = vertex.position();
- triangle::Vertex2D {
+ Vertex2D {
position: [position.x, position.y],
- color: self.0,
}
}
}
-struct StrokeVertex([f32; 4]);
-
-impl lyon::tessellation::StrokeVertexConstructor<triangle::Vertex2D>
- for StrokeVertex
-{
+impl tessellation::StrokeVertexConstructor<Vertex2D> for Vertex2DBuilder {
fn new_vertex(
&mut self,
- vertex: lyon::tessellation::StrokeVertex<'_, '_>,
- ) -> triangle::Vertex2D {
+ vertex: tessellation::StrokeVertex<'_, '_>,
+ ) -> Vertex2D {
let position = vertex.position();
- triangle::Vertex2D {
+ Vertex2D {
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 6accc2fb..f9b8e447 100644
--- a/graphics/src/widget/canvas/stroke.rs
+++ b/graphics/src/widget/canvas/stroke.rs
@@ -1,10 +1,15 @@
+//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles.
+pub use crate::triangle::Style;
+
use iced_native::Color;
/// The style of a stroke.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone)]
pub struct Stroke<'a> {
- /// The color of the stroke.
- pub color: Color,
+ /// The color or gradient of the stroke.
+ ///
+ /// By default, it is set to [`StrokeStyle::Solid`] `BLACK`.
+ pub style: Style,
/// The distance between the two edges of the stroke.
pub width: f32,
/// The shape to be used at the end of open subpaths when they are stroked.
@@ -19,7 +24,10 @@ pub struct Stroke<'a> {
impl<'a> Stroke<'a> {
/// Sets the color of the [`Stroke`].
pub fn with_color(self, color: Color) -> Self {
- Stroke { color, ..self }
+ Stroke {
+ style: Style::Solid(color),
+ ..self
+ }
}
/// Sets the width of the [`Stroke`].
@@ -41,7 +49,7 @@ impl<'a> Stroke<'a> {
impl<'a> Default for Stroke<'a> {
fn default() -> Self {
Stroke {
- color: Color::BLACK,
+ style: Style::Solid(Color::BLACK),
width: 1.0,
line_cap: LineCap::default(),
line_join: LineJoin::default(),
diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs
new file mode 100644
index 00000000..d61cc77e
--- /dev/null
+++ b/lazy/src/lazy.rs
@@ -0,0 +1,362 @@
+use iced_native::event;
+use iced_native::layout::{self, Layout};
+use iced_native::mouse;
+use iced_native::overlay;
+use iced_native::renderer;
+use iced_native::widget::tree::{self, Tree};
+use iced_native::widget::{self, Widget};
+use iced_native::Element;
+use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size};
+
+use ouroboros::self_referencing;
+use std::cell::{Ref, RefCell, RefMut};
+use std::hash::{Hash, Hasher as H};
+use std::marker::PhantomData;
+use std::rc::Rc;
+
+#[allow(missing_debug_implementations)]
+pub struct Lazy<'a, Message, Renderer, Dependency, View> {
+ dependency: Dependency,
+ view: Box<dyn Fn() -> View + 'a>,
+ element: RefCell<Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>,
+}
+
+impl<'a, Message, Renderer, Dependency, View>
+ Lazy<'a, Message, Renderer, Dependency, View>
+where
+ Dependency: Hash + 'a,
+ View: Into<Element<'static, Message, Renderer>>,
+{
+ pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self {
+ Self {
+ dependency,
+ view: Box::new(view),
+ element: RefCell::new(None),
+ }
+ }
+
+ fn with_element<T>(
+ &self,
+ f: impl FnOnce(Ref<Element<Message, Renderer>>) -> T,
+ ) -> T {
+ f(self.element.borrow().as_ref().unwrap().borrow())
+ }
+
+ fn with_element_mut<T>(
+ &self,
+ f: impl FnOnce(RefMut<Element<Message, Renderer>>) -> T,
+ ) -> T {
+ f(self.element.borrow().as_ref().unwrap().borrow_mut())
+ }
+}
+
+struct Internal<Message, Renderer> {
+ element: Rc<RefCell<Element<'static, Message, Renderer>>>,
+ hash: u64,
+}
+
+impl<'a, Message, Renderer, Dependency, View> Widget<Message, Renderer>
+ for Lazy<'a, Message, Renderer, Dependency, View>
+where
+ View: Into<Element<'static, Message, Renderer>> + 'static,
+ Dependency: Hash + 'a,
+ Message: 'static,
+ Renderer: iced_native::Renderer + 'static,
+{
+ fn tag(&self) -> tree::Tag {
+ struct Tag<T>(T);
+ tree::Tag::of::<Tag<View>>()
+ }
+
+ fn state(&self) -> tree::State {
+ let mut hasher = Hasher::default();
+ self.dependency.hash(&mut hasher);
+ let hash = hasher.finish();
+
+ let element = Rc::new(RefCell::new((self.view)().into()));
+
+ (*self.element.borrow_mut()) = Some(element.clone());
+
+ tree::State::new(Internal { element, hash })
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(
+ self.element.borrow().as_ref().unwrap().borrow().as_widget(),
+ )]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ let current = tree.state.downcast_mut::<Internal<Message, Renderer>>();
+
+ let mut hasher = Hasher::default();
+ self.dependency.hash(&mut hasher);
+ let new_hash = hasher.finish();
+
+ if current.hash != new_hash {
+ current.hash = new_hash;
+
+ let element = (self.view)().into();
+ current.element = Rc::new(RefCell::new(element));
+
+ (*self.element.borrow_mut()) = Some(current.element.clone());
+ tree.diff_children(std::slice::from_ref(
+ &self.element.borrow().as_ref().unwrap().borrow().as_widget(),
+ ));
+ } else {
+ (*self.element.borrow_mut()) = Some(current.element.clone());
+ }
+ }
+
+ fn width(&self) -> Length {
+ self.with_element(|element| element.as_widget().width())
+ }
+
+ fn height(&self) -> Length {
+ self.with_element(|element| element.as_widget().height())
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.with_element(|element| {
+ element.as_widget().layout(renderer, limits)
+ })
+ }
+
+ fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ self.with_element(|element| {
+ element.as_widget().operate(
+ &mut tree.children[0],
+ layout,
+ operation,
+ );
+ });
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: iced_native::Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ self.with_element_mut(|mut element| {
+ element.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ })
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.with_element(|element| {
+ element.as_widget().mouse_interaction(
+ &tree.children[0],
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ })
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.with_element(|element| {
+ element.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor_position,
+ viewport,
+ )
+ })
+ }
+
+ fn overlay<'b>(
+ &'b self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ let overlay = OverlayBuilder {
+ 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)
+ },
+ }
+ .build();
+
+ let has_overlay = overlay.with_overlay(|overlay| {
+ overlay.as_ref().map(overlay::Element::position)
+ });
+
+ has_overlay
+ .map(|position| overlay::Element::new(position, Box::new(overlay)))
+ }
+}
+
+#[self_referencing]
+struct Overlay<'a, 'b, Message, Renderer, Dependency, View> {
+ cached: &'a 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)]
+ #[covariant]
+ overlay: Option<overlay::Element<'this, Message, Renderer>>,
+}
+
+impl<'a, 'b, Message, Renderer, Dependency, View>
+ Overlay<'a, 'b, Message, Renderer, Dependency, View>
+{
+ fn with_overlay_maybe<T>(
+ &self,
+ f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
+ ) -> Option<T> {
+ self.borrow_overlay().as_ref().map(f)
+ }
+
+ fn with_overlay_mut_maybe<T>(
+ &mut self,
+ f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
+ ) -> Option<T> {
+ self.with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ }
+}
+
+impl<'a, 'b, Message, Renderer, Dependency, View>
+ overlay::Overlay<Message, Renderer>
+ for Overlay<'a, 'b, Message, Renderer, Dependency, View>
+where
+ Renderer: iced_native::Renderer,
+{
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ self.with_overlay_maybe(|overlay| {
+ let vector = position - overlay.position();
+
+ overlay.layout(renderer, bounds).translate(vector)
+ })
+ .unwrap_or_default()
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) {
+ let _ = self.with_overlay_maybe(|overlay| {
+ overlay.draw(renderer, theme, style, layout, cursor_position);
+ });
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.with_overlay_maybe(|overlay| {
+ overlay.mouse_interaction(
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ })
+ .unwrap_or_default()
+ }
+
+ fn on_event(
+ &mut self,
+ event: iced_native::Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ self.with_overlay_mut_maybe(|overlay| {
+ overlay.on_event(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ })
+ .unwrap_or(iced_native::event::Status::Ignored)
+ }
+}
+
+impl<'a, Message, Renderer, Dependency, View>
+ From<Lazy<'a, Message, Renderer, Dependency, View>>
+ for Element<'a, Message, Renderer>
+where
+ View: Into<Element<'static, Message, Renderer>> + 'static,
+ Renderer: iced_native::Renderer + 'static,
+ Message: 'static,
+ Dependency: Hash + 'a,
+{
+ fn from(lazy: Lazy<'a, Message, Renderer, Dependency, View>) -> Self {
+ Self::new(lazy)
+ }
+}
diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs
index 3827746c..f49fe4b6 100644
--- a/lazy/src/lib.rs
+++ b/lazy/src/lib.rs
@@ -17,15 +17,30 @@
clippy::type_complexity
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
+mod lazy;
+
pub mod component;
pub mod responsive;
pub use component::Component;
+pub use lazy::Lazy;
pub use responsive::Responsive;
mod cache;
use iced_native::{Element, Size};
+use std::hash::Hash;
+
+pub fn lazy<'a, Message, Renderer, Dependency, View>(
+ dependency: Dependency,
+ view: impl Fn() -> View + 'a,
+) -> Lazy<'a, Message, Renderer, Dependency, View>
+where
+ Dependency: Hash + 'a,
+ View: Into<Element<'static, Message, Renderer>>,
+{
+ Lazy::new(dependency, view)
+}
/// Turns an implementor of [`Component`] into an [`Element`] that can be
/// embedded in any application.
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/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/widget/button.rs b/native/src/widget/button.rs
index 6c0b8f6e..1582188b 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -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/container.rs b/native/src/widget/container.rs
index 2afad3f2..10a80b58 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -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(
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/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_input.rs b/native/src/widget/text_input.rs
index e5213cbe..25a8690e 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -92,7 +92,7 @@ where
is_secure: false,
font: Default::default(),
width: Length::Fill,
- padding: Padding::ZERO,
+ padding: Padding::new(5),
size: None,
on_change: Box::new(on_change),
on_paste: None,
@@ -350,15 +350,19 @@ where
{
let text_size = size.unwrap_or_else(|| renderer.default_size());
- let limits = limits
+ let text_limits = limits
.pad(padding)
.width(width)
.height(Length::Units(text_size));
+ let limits = limits.width(width).height(Length::Shrink);
+
+ let mut text = layout::Node::new(text_limits.resolve(Size::ZERO));
+ let padding = padding.fit(text.size(), limits.max());
+ let size = limits.pad(padding).resolve(text.size()).pad(padding);
- let mut text = layout::Node::new(limits.resolve(Size::ZERO));
text.move_to(Point::new(padding.left.into(), padding.top.into()));
- layout::Node::with_children(text.size().pad(padding), vec![text])
+ layout::Node::with_children(size, vec![text])
}
/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
diff --git a/native/src/window/action.rs b/native/src/window/action.rs
index 73338e22..009dcc27 100644
--- a/native/src/window/action.rs
+++ b/native/src/window/action.rs
@@ -5,6 +5,12 @@ use std::fmt;
/// An operation to be performed on some window.
pub enum Action<T> {
+ /// Moves the window with the left mouse button until the button is
+ /// released.
+ ///
+ /// There’s no guarantee that this will work unless the left mouse
+ /// button was pressed immediately before this function is called.
+ Drag,
/// Resize the window.
Resize {
/// The new logical width of the window
@@ -12,6 +18,10 @@ pub enum Action<T> {
/// The new logical height of the window
height: u32,
},
+ /// Sets the window to maximized or back
+ Maximize(bool),
+ /// Set the window to minimized or back
+ Minimize(bool),
/// Move the window.
///
/// Unsupported on Wayland.
@@ -23,6 +33,8 @@ pub enum Action<T> {
},
/// Set the [`Mode`] of the window.
SetMode(Mode),
+ /// Sets the window to maximized or back
+ ToggleMaximize,
/// Fetch the current [`Mode`] of the window.
FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>),
}
@@ -37,9 +49,13 @@ impl<T> Action<T> {
T: 'static,
{
match self {
+ Self::Drag => Action::Drag,
Self::Resize { width, height } => Action::Resize { width, height },
+ Self::Maximize(bool) => Action::Maximize(bool),
+ Self::Minimize(bool) => Action::Minimize(bool),
Self::Move { x, y } => Action::Move { x, y },
Self::SetMode(mode) => Action::SetMode(mode),
+ Self::ToggleMaximize => Action::ToggleMaximize,
Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))),
}
}
@@ -48,15 +64,19 @@ impl<T> Action<T> {
impl<T> fmt::Debug for Action<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
+ Self::Drag => write!(f, "Action::Drag"),
Self::Resize { width, height } => write!(
f,
"Action::Resize {{ widget: {}, height: {} }}",
width, height
),
+ Self::Maximize(value) => write!(f, "Action::Maximize({})", value),
+ Self::Minimize(value) => write!(f, "Action::Minimize({}", value),
Self::Move { x, y } => {
write!(f, "Action::Move {{ x: {}, y: {} }}", x, y)
}
Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode),
+ Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"),
Self::FetchMode(_) => write!(f, "Action::FetchMode"),
}
}
diff --git a/style/src/theme.rs b/style/src/theme.rs
index ea538c3a..a253e990 100644
--- a/style/src/theme.rs
+++ b/style/src/theme.rs
@@ -1,5 +1,6 @@
pub mod palette;
+use self::palette::Extended;
pub use self::palette::Palette;
use crate::application;
@@ -20,17 +21,23 @@ use crate::toggler;
use iced_core::{Background, Color};
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Theme {
Light,
Dark,
+ Custom(Box<Custom>),
}
impl Theme {
- pub fn palette(self) -> Palette {
+ pub fn custom(palette: Palette) -> Self {
+ Self::Custom(Box::new(Custom::new(palette)))
+ }
+
+ pub fn palette(&self) -> Palette {
match self {
Self::Light => Palette::LIGHT,
Self::Dark => Palette::DARK,
+ Self::Custom(custom) => custom.palette,
}
}
@@ -38,6 +45,7 @@ impl Theme {
match self {
Self::Light => &palette::EXTENDED_LIGHT,
Self::Dark => &palette::EXTENDED_DARK,
+ Self::Custom(custom) => &custom.extended,
}
}
}
@@ -48,6 +56,21 @@ impl Default for Theme {
}
}
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Custom {
+ palette: Palette,
+ extended: Extended,
+}
+
+impl Custom {
+ pub fn new(palette: Palette) -> Self {
+ Self {
+ palette,
+ extended: Extended::generate(palette),
+ }
+ }
+}
+
#[derive(Debug, Clone, Copy)]
pub enum Application {
Default,
@@ -71,7 +94,7 @@ impl application::StyleSheet for Theme {
background_color: palette.background.base.color,
text_color: palette.background.base.text,
},
- Application::Custom(f) => f(*self),
+ Application::Custom(f) => f(self.clone()),
}
}
}
diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs
index 4fb5e4c8..b3a10d28 100644
--- a/style/src/theme/palette.rs
+++ b/style/src/theme/palette.rs
@@ -58,6 +58,7 @@ impl Palette {
};
}
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Extended {
pub background: Background,
pub primary: Primary,
@@ -95,7 +96,7 @@ impl Extended {
}
}
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Pair {
pub color: Color,
pub text: Color,
@@ -110,6 +111,7 @@ impl Pair {
}
}
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Background {
pub base: Pair,
pub weak: Pair,
@@ -129,6 +131,7 @@ impl Background {
}
}
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Primary {
pub base: Pair,
pub weak: Pair,
@@ -148,6 +151,7 @@ impl Primary {
}
}
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Secondary {
pub base: Pair,
pub weak: Pair,
@@ -168,6 +172,7 @@ impl Secondary {
}
}
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Success {
pub base: Pair,
pub weak: Pair,
@@ -187,6 +192,7 @@ impl Success {
}
}
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Danger {
pub base: Pair,
pub weak: Pair,
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 55eec73f..e9509db2 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -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]
@@ -51,23 +50,12 @@ version = "0.3"
path = "../graphics"
features = ["font-fallback", "font-icons"]
-[dependencies.image_rs]
-version = "0.23"
-package = "image"
-default-features = false
-optional = true
+[dependencies.encase]
+version = "0.3.0"
+features = ["glam"]
-[dependencies.resvg]
-version = "0.18"
-optional = true
-
-[dependencies.usvg]
-version = "0.18"
-optional = true
-
-[dependencies.tiny-skia]
-version = "0.6"
-optional = true
+[dependencies.glam]
+version = "0.21.3"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 8c875254..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,
@@ -94,13 +94,12 @@ impl Backend {
staging_belt,
encoder,
frame,
- target_size.width,
- target_size.height,
+ target_size,
);
}
- #[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(
@@ -112,8 +111,7 @@ impl Backend {
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
- target_width: u32,
- target_height: u32,
+ target_size: Size<u32>,
) {
let bounds = (layer.bounds * scale_factor).snap();
@@ -143,15 +141,14 @@ impl Backend {
staging_belt,
encoder,
target,
- target_width,
- target_height,
+ target_size,
scaled,
scale_factor,
&layer.meshes,
);
}
- #[cfg(any(feature = "image_rs", feature = "svg"))]
+ #[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
let scaled = transformation
@@ -297,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)
}
}
@@ -309,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.rs b/wgpu/src/buffer.rs
new file mode 100644
index 00000000..7c092d0b
--- /dev/null
+++ b/wgpu/src/buffer.rs
@@ -0,0 +1,3 @@
+//! Utilities for buffer operations.
+pub mod dynamic;
+pub mod r#static;
diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs
new file mode 100644
index 00000000..c0c48c74
--- /dev/null
+++ b/wgpu/src/buffer/dynamic.rs
@@ -0,0 +1,199 @@
+//! Utilities for uniform buffer operations.
+use encase::private::WriteInto;
+use encase::ShaderType;
+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> {
+ offsets: Vec<wgpu::DynamicOffset>,
+ cpu: Internal,
+ gpu: wgpu::Buffer,
+ label: &'static str,
+ size: u64,
+ _data: PhantomData<T>,
+}
+
+impl<T: ShaderType + WriteInto> Buffer<T> {
+ /// Creates a new dynamic uniform buffer.
+ pub fn uniform(device: &wgpu::Device, label: &'static str) -> Self {
+ Buffer::new(
+ device,
+ Internal::Uniform(encase::DynamicUniformBuffer::new(Vec::new())),
+ label,
+ wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ )
+ }
+
+ /// Creates a new dynamic storage buffer.
+ pub fn storage(device: &wgpu::Device, label: &'static str) -> Self {
+ Buffer::new(
+ device,
+ Internal::Storage(encase::DynamicStorageBuffer::new(Vec::new())),
+ label,
+ wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ )
+ }
+
+ fn new(
+ device: &wgpu::Device,
+ dynamic_buffer_type: Internal,
+ label: &'static str,
+ usage: wgpu::BufferUsages,
+ ) -> Self {
+ let initial_size = u64::from(T::min_size());
+
+ Self {
+ offsets: Vec::new(),
+ cpu: dynamic_buffer_type,
+ gpu: Buffer::<T>::create_gpu_buffer(
+ device,
+ label,
+ usage,
+ initial_size,
+ ),
+ label,
+ size: initial_size,
+ _data: Default::default(),
+ }
+ }
+
+ fn create_gpu_buffer(
+ device: &wgpu::Device,
+ label: &'static str,
+ usage: wgpu::BufferUsages,
+ size: u64,
+ ) -> wgpu::Buffer {
+ device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some(label),
+ size,
+ usage,
+ mapped_at_creation: false,
+ })
+ }
+
+ /// Write a new value to the CPU buffer with proper alignment. Stores the returned offset value
+ /// in the buffer for future use.
+ pub fn push(&mut self, value: &T) {
+ //this write operation on the cpu buffer will adjust for uniform alignment requirements
+ let offset = self.cpu.write(value);
+ self.offsets.push(offset as u32);
+ }
+
+ /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is
+ /// less than the newly computed size from the CPU buffer.
+ ///
+ /// If the gpu buffer is resized, its bind group will need to be recreated!
+ pub fn resize(&mut self, device: &wgpu::Device) -> bool {
+ let new_size = self.cpu.get_ref().len() as u64;
+
+ if self.size < new_size {
+ let usages = match self.cpu {
+ Internal::Uniform(_) => {
+ wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
+ }
+ Internal::Storage(_) => {
+ wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
+ }
+ };
+
+ self.gpu = Buffer::<T>::create_gpu_buffer(
+ device, self.label, usages, new_size,
+ );
+ self.size = new_size;
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Write the contents of this dynamic buffer to the GPU via staging belt command.
+ pub fn write(
+ &mut self,
+ device: &wgpu::Device,
+ staging_belt: &mut wgpu::util::StagingBelt,
+ encoder: &mut wgpu::CommandEncoder,
+ ) {
+ let size = self.cpu.get_ref().len();
+
+ if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) {
+ let mut buffer = staging_belt.write_buffer(
+ encoder,
+ &self.gpu,
+ 0,
+ buffer_size,
+ device,
+ );
+
+ buffer.copy_from_slice(self.cpu.get_ref());
+ }
+ }
+
+ // Gets the aligned offset at the given index from the CPU buffer.
+ pub fn offset_at_index(&self, index: usize) -> wgpu::DynamicOffset {
+ let offset = self
+ .offsets
+ .get(index)
+ .copied()
+ .expect("Index not found in offsets.");
+
+ offset
+ }
+
+ /// Returns a reference to the GPU buffer.
+ pub fn raw(&self) -> &wgpu::Buffer {
+ &self.gpu
+ }
+
+ /// Reset the buffer.
+ pub fn clear(&mut self) {
+ self.offsets.clear();
+ self.cpu.clear();
+ }
+}
+
+// Currently supported dynamic buffers.
+enum Internal {
+ Uniform(encase::DynamicUniformBuffer<Vec<u8>>),
+ Storage(encase::DynamicStorageBuffer<Vec<u8>>),
+}
+
+impl Internal {
+ /// Writes the current value to its CPU buffer with proper alignment.
+ pub(super) fn write<T: ShaderType + WriteInto>(
+ &mut self,
+ value: &T,
+ ) -> wgpu::DynamicOffset {
+ match self {
+ Internal::Uniform(buf) => buf
+ .write(value)
+ .expect("Error when writing to dynamic uniform buffer.")
+ as u32,
+ Internal::Storage(buf) => buf
+ .write(value)
+ .expect("Error when writing to dynamic storage buffer.")
+ as u32,
+ }
+ }
+
+ /// Returns bytearray of aligned CPU buffer.
+ pub(super) fn get_ref(&self) -> &Vec<u8> {
+ match self {
+ Internal::Uniform(buf) => buf.as_ref(),
+ Internal::Storage(buf) => buf.as_ref(),
+ }
+ }
+
+ /// Resets the CPU buffer.
+ pub(super) fn clear(&mut self) {
+ match self {
+ Internal::Uniform(buf) => {
+ buf.as_mut().clear();
+ buf.set_offset(0);
+ }
+ Internal::Storage(buf) => {
+ buf.as_mut().clear();
+ buf.set_offset(0);
+ }
+ }
+ }
+}
diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs
new file mode 100644
index 00000000..cf06790c
--- /dev/null
+++ b/wgpu/src/buffer/static.rs
@@ -0,0 +1,117 @@
+use bytemuck::{Pod, Zeroable};
+use std::marker::PhantomData;
+use std::mem;
+
+//128 triangles/indices
+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> {
+ //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer
+ offsets: Vec<wgpu::BufferAddress>,
+ label: &'static str,
+ usages: wgpu::BufferUsages,
+ gpu: wgpu::Buffer,
+ size: wgpu::BufferAddress,
+ _data: PhantomData<T>,
+}
+
+impl<T: Pod + Zeroable> Buffer<T> {
+ /// Initialize a new static buffer.
+ pub fn new(
+ device: &wgpu::Device,
+ label: &'static str,
+ usages: wgpu::BufferUsages,
+ ) -> Self {
+ let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT;
+
+ Self {
+ offsets: Vec::new(),
+ label,
+ usages,
+ gpu: Self::gpu_buffer(device, label, size, usages),
+ size,
+ _data: PhantomData,
+ }
+ }
+
+ fn gpu_buffer(
+ device: &wgpu::Device,
+ label: &'static str,
+ size: wgpu::BufferAddress,
+ usage: wgpu::BufferUsages,
+ ) -> wgpu::Buffer {
+ device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some(label),
+ size,
+ usage,
+ mapped_at_creation: false,
+ })
+ }
+
+ /// Returns whether or not the buffer needs to be recreated. This can happen whenever mesh data
+ /// changes & a redraw is requested.
+ pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
+ let size = (mem::size_of::<T>() * new_count) as u64;
+
+ if self.size < size {
+ self.offsets.clear();
+ self.size = size;
+ self.gpu = Self::gpu_buffer(device, self.label, size, self.usages);
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Writes the current vertex data to the gpu buffer with a memcpy & stores its offset.
+ ///
+ /// Returns the size of the written bytes.
+ pub fn write(
+ &mut self,
+ device: &wgpu::Device,
+ staging_belt: &mut wgpu::util::StagingBelt,
+ encoder: &mut wgpu::CommandEncoder,
+ offset: u64,
+ content: &[T],
+ ) -> u64 {
+ let bytes = bytemuck::cast_slice(content);
+ let bytes_size = bytes.len() as u64;
+
+ if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) {
+ let mut buffer = staging_belt.write_buffer(
+ encoder,
+ &self.gpu,
+ offset,
+ buffer_size,
+ device,
+ );
+
+ buffer.copy_from_slice(bytes);
+
+ self.offsets.push(offset);
+ }
+
+ bytes_size
+ }
+
+ fn offset_at(&self, index: usize) -> &wgpu::BufferAddress {
+ self.offsets
+ .get(index)
+ .expect("Offset at index does not exist.")
+ }
+
+ /// Returns the slice calculated from the offset stored at the given index.
+ /// e.g. to calculate the slice for the 2nd mesh in the layer, this would be the offset at index
+ /// 1 that we stored earlier when writing.
+ pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> {
+ self.gpu.slice(self.offset_at(index)..)
+ }
+
+ /// Clears any temporary data from the buffer.
+ pub fn clear(&mut self) {
+ self.offsets.clear()
+ }
+}
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 3a98c6bd..dcb699e8 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -43,6 +43,7 @@ pub mod triangle;
pub mod window;
mod backend;
+mod buffer;
mod quad;
mod text;
@@ -55,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/gradient.wgsl b/wgpu/src/shader/gradient.wgsl
new file mode 100644
index 00000000..63825aec
--- /dev/null
+++ b/wgpu/src/shader/gradient.wgsl
@@ -0,0 +1,88 @@
+struct Uniforms {
+ transform: mat4x4<f32>,
+ //xy = start, wz = end
+ position: vec4<f32>,
+ //x = start stop, y = end stop, zw = padding
+ stop_range: vec4<i32>,
+}
+
+struct Stop {
+ color: vec4<f32>,
+ offset: f32,
+};
+
+@group(0) @binding(0)
+var<uniform> uniforms: Uniforms;
+
+@group(0) @binding(1)
+var<storage, read> color_stops: array<Stop>;
+
+struct VertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) raw_position: vec2<f32>
+}
+
+@vertex
+fn vs_main(@location(0) input: vec2<f32>) -> VertexOutput {
+ var output: VertexOutput;
+ output.position = uniforms.transform * vec4<f32>(input.xy, 0.0, 1.0);
+ output.raw_position = input;
+
+ return output;
+}
+
+//TODO: rewrite without branching
+@fragment
+fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
+ let start = uniforms.position.xy;
+ let end = uniforms.position.zw;
+ let start_stop = uniforms.stop_range.x;
+ let end_stop = uniforms.stop_range.y;
+
+ let v1 = end - start;
+ let v2 = input.raw_position.xy - start;
+ let unit = normalize(v1);
+ let offset = dot(unit, v2) / length(v1);
+
+ let min_stop = color_stops[start_stop];
+ let max_stop = color_stops[end_stop];
+
+ var color: vec4<f32>;
+
+ if (offset <= min_stop.offset) {
+ color = min_stop.color;
+ } else if (offset >= max_stop.offset) {
+ color = max_stop.color;
+ } else {
+ var min = min_stop;
+ var max = max_stop;
+ var min_index = start_stop;
+ var max_index = end_stop;
+
+ loop {
+ if (min_index >= max_index - 1) {
+ break;
+ }
+
+ let index = min_index + (max_index - min_index) / 2;
+
+ let stop = color_stops[index];
+
+ if (offset <= stop.offset) {
+ max = stop;
+ max_index = index;
+ } else {
+ min = stop;
+ min_index = index;
+ }
+ }
+
+ color = mix(min.color, max.color, smoothstep(
+ min.offset,
+ max.offset,
+ offset
+ ));
+ }
+
+ return color;
+}
diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl
new file mode 100644
index 00000000..68a8fea3
--- /dev/null
+++ b/wgpu/src/shader/solid.wgsl
@@ -0,0 +1,17 @@
+struct Uniforms {
+ transform: mat4x4<f32>,
+ color: vec4<f32>
+}
+
+@group(0) @binding(0)
+var<uniform> uniforms: Uniforms;
+
+@vertex
+fn vs_main(@location(0) input: vec2<f32>) -> @builtin(position) vec4<f32> {
+ return uniforms.transform * vec4<f32>(input.xy, 0.0, 1.0);
+}
+
+@fragment
+fn fs_main() -> @location(0) vec4<f32> {
+ return uniforms.color;
+}
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index fd06dddf..f9abf2b5 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -1,345 +1,176 @@
//! Draw meshes of triangles.
-use crate::{settings, Transformation};
-use iced_graphics::layer;
-
-use bytemuck::{Pod, Zeroable};
-use std::mem;
+mod gradient;
+mod msaa;
+mod solid;
-pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
+use crate::buffer::r#static::Buffer;
+use crate::settings;
+use crate::Transformation;
-mod msaa;
+use iced_graphics::layer::mesh::{self, Mesh};
+use iced_graphics::triangle::{self, Vertex2D};
+use iced_graphics::Size;
-const UNIFORM_BUFFER_SIZE: usize = 50;
-const VERTEX_BUFFER_SIZE: usize = 10_000;
-const INDEX_BUFFER_SIZE: usize = 10_000;
+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 {
- pipeline: wgpu::RenderPipeline,
blit: Option<msaa::Blit>,
- constants_layout: wgpu::BindGroupLayout,
- constants: wgpu::BindGroup,
- uniforms_buffer: Buffer<Uniforms>,
vertex_buffer: Buffer<Vertex2D>,
index_buffer: Buffer<u32>,
+ index_strides: Vec<u32>,
+ pipelines: PipelineList,
}
-#[derive(Debug)]
-struct Buffer<T> {
- label: &'static str,
- raw: wgpu::Buffer,
- size: usize,
- usage: wgpu::BufferUsages,
- _type: std::marker::PhantomData<T>,
+/// Supported triangle pipelines for different fills.
+pub(crate) struct PipelineList {
+ solid: solid::Pipeline,
+ gradient: gradient::Pipeline,
}
-impl<T> Buffer<T> {
- pub fn new(
- label: &'static str,
- device: &wgpu::Device,
- size: usize,
- usage: wgpu::BufferUsages,
- ) -> Self {
- let raw = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some(label),
- size: (std::mem::size_of::<T>() * size) as u64,
- usage,
- mapped_at_creation: false,
- });
-
- Buffer {
- label,
- raw,
- size,
- usage,
- _type: std::marker::PhantomData,
- }
+impl fmt::Debug for PipelineList {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ f.debug_struct("TrianglePipelines").finish()
}
+}
- pub fn expand(&mut self, device: &wgpu::Device, size: usize) -> bool {
- let needs_resize = self.size < size;
-
- if needs_resize {
- self.raw = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some(self.label),
- size: (std::mem::size_of::<T>() * size) as u64,
- usage: self.usage,
- mapped_at_creation: false,
- });
-
- self.size = size;
- }
+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();
+ }
- needs_resize
+ /// 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);
}
}
impl Pipeline {
+ /// Creates supported pipelines, listed in [TrianglePipelines].
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Pipeline {
- let constants_layout =
- device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("iced_wgpu::triangle uniforms layout"),
- entries: &[wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::VERTEX,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: true,
- min_binding_size: wgpu::BufferSize::new(
- mem::size_of::<Uniforms>() as u64,
- ),
- },
- count: None,
- }],
- });
-
- let constants_buffer = Buffer::new(
- "iced_wgpu::triangle uniforms buffer",
- device,
- UNIFORM_BUFFER_SIZE,
- wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- );
-
- let constant_bind_group =
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle uniforms bind group"),
- layout: &constants_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(
- wgpu::BufferBinding {
- buffer: &constants_buffer.raw,
- offset: 0,
- size: wgpu::BufferSize::new(std::mem::size_of::<
- Uniforms,
- >(
- )
- as u64),
- },
- ),
- }],
- });
-
- let layout =
- device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("iced_wgpu::triangle pipeline layout"),
- push_constant_ranges: &[],
- bind_group_layouts: &[&constants_layout],
- });
-
- let shader =
- device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("iced_wgpu::triangle::shader"),
- source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
- include_str!("shader/triangle.wgsl"),
- )),
- });
-
- let pipeline =
- device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("iced_wgpu::triangle pipeline"),
- layout: Some(&layout),
- vertex: wgpu::VertexState {
- module: &shader,
- entry_point: "vs_main",
- buffers: &[wgpu::VertexBufferLayout {
- array_stride: mem::size_of::<Vertex2D>() 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: &[Some(wgpu::ColorTargetState {
- format,
- blend: Some(wgpu::BlendState::ALPHA_BLENDING),
- write_mask: wgpu::ColorWrites::ALL,
- })],
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleList,
- front_face: wgpu::FrontFace::Cw,
- ..Default::default()
- },
- depth_stencil: None,
- multisample: wgpu::MultisampleState {
- count: antialiasing.map(|a| a.sample_count()).unwrap_or(1),
- mask: !0,
- alpha_to_coverage_enabled: false,
- },
- multiview: None,
- });
-
Pipeline {
- pipeline,
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
- constants_layout,
- constants: constant_bind_group,
- uniforms_buffer: constants_buffer,
vertex_buffer: Buffer::new(
- "iced_wgpu::triangle vertex buffer",
device,
- VERTEX_BUFFER_SIZE,
+ "iced_wgpu::triangle vertex buffer",
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
),
index_buffer: Buffer::new(
- "iced_wgpu::triangle index buffer",
device,
- INDEX_BUFFER_SIZE,
+ "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),
+ },
}
}
+ /// Draws the contents of the current layer's meshes to the [target].
pub fn draw(
&mut self,
device: &wgpu::Device,
staging_belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
- target_width: u32,
- target_height: u32,
+ target_size: Size<u32>,
transformation: Transformation,
scale_factor: f32,
- meshes: &[layer::Mesh<'_>],
+ meshes: &[Mesh<'_>],
) {
- // This looks a bit crazy, but we are just counting how many vertices
- // and indices we will need to handle.
- // TODO: Improve readability
- let (total_vertices, total_indices) = meshes
- .iter()
- .map(|layer::Mesh { buffers, .. }| {
- (buffers.vertices.len(), buffers.indices.len())
- })
- .fold((0, 0), |(total_v, total_i), (v, i)| {
- (total_v + v, total_i + i)
- });
-
- // Then we ensure the current buffers are big enough, resizing if
- // necessary
- let _ = self.vertex_buffer.expand(device, total_vertices);
- let _ = self.index_buffer.expand(device, total_indices);
-
- // If the uniforms buffer is resized, then we need to recreate its
- // bind group.
- if self.uniforms_buffer.expand(device, meshes.len()) {
- self.constants =
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle uniforms buffer"),
- layout: &self.constants_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(
- wgpu::BufferBinding {
- buffer: &self.uniforms_buffer.raw,
- offset: 0,
- size: wgpu::BufferSize::new(
- std::mem::size_of::<Uniforms>() as u64,
- ),
- },
- ),
- }],
- });
- }
+ //count the total amount of vertices & indices we need to handle
+ let (total_vertices, total_indices) = mesh::attribute_count_of(meshes);
- let mut uniforms: Vec<Uniforms> = Vec::with_capacity(meshes.len());
- let mut offsets: Vec<(
- wgpu::BufferAddress,
- wgpu::BufferAddress,
- usize,
- )> = Vec::with_capacity(meshes.len());
- let mut last_vertex = 0;
- let mut last_index = 0;
+ // Then we ensure the current attribute buffers are big enough, resizing if necessary.
- // We upload everything upfront
- for mesh in meshes {
- let transform = (transformation
- * Transformation::translate(mesh.origin.x, mesh.origin.y))
- .into();
-
- let vertices = bytemuck::cast_slice(&mesh.buffers.vertices);
- let indices = bytemuck::cast_slice(&mesh.buffers.indices);
-
- if let (Some(vertices_size), Some(indices_size)) = (
- wgpu::BufferSize::new(vertices.len() as u64),
- wgpu::BufferSize::new(indices.len() as u64),
- ) {
- {
- let mut vertex_buffer = staging_belt.write_buffer(
- encoder,
- &self.vertex_buffer.raw,
- (std::mem::size_of::<Vertex2D>() * last_vertex) as u64,
- vertices_size,
- device,
- );
-
- vertex_buffer.copy_from_slice(vertices);
- }
+ //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);
- {
- let mut index_buffer = staging_belt.write_buffer(
- encoder,
- &self.index_buffer.raw,
- (std::mem::size_of::<u32>() * last_index) as u64,
- indices_size,
- device,
- );
+ //prepare dynamic buffers & data store for writing
+ self.index_strides.clear();
+ self.pipelines.clear();
- index_buffer.copy_from_slice(indices);
- }
-
- uniforms.push(transform);
- offsets.push((
- last_vertex as u64,
- last_index as u64,
- mesh.buffers.indices.len(),
- ));
+ let mut vertex_offset = 0;
+ let mut index_offset = 0;
- last_vertex += mesh.buffers.vertices.len();
- last_index += mesh.buffers.indices.len();
- }
- }
-
- let uniforms = bytemuck::cast_slice(&uniforms);
+ for mesh in meshes {
+ let transform = transformation
+ * Transformation::translate(mesh.origin.x, mesh.origin.y);
- if let Some(uniforms_size) =
- wgpu::BufferSize::new(uniforms.len() as u64)
- {
- let mut uniforms_buffer = staging_belt.write_buffer(
+ //write to both buffers
+ let new_vertex_offset = self.vertex_buffer.write(
+ device,
+ staging_belt,
encoder,
- &self.uniforms_buffer.raw,
- 0,
- uniforms_size,
+ vertex_offset,
+ &mesh.buffers.vertices,
+ );
+
+ let new_index_offset = self.index_buffer.write(
device,
+ staging_belt,
+ encoder,
+ index_offset,
+ &mesh.buffers.indices,
);
- uniforms_buffer.copy_from_slice(uniforms);
+ vertex_offset += new_vertex_offset;
+ index_offset += new_index_offset;
+
+ self.index_strides.push(mesh.buffers.indices.len() as u32);
+
+ //push uniform data to CPU buffers
+ match mesh.style {
+ triangle::Style::Solid(color) => {
+ self.pipelines.solid.push(transform, color);
+ }
+ triangle::Style::Gradient(gradient) => {
+ self.pipelines.gradient.push(transform, gradient);
+ }
+ }
}
+ //write uniform data to GPU
+ self.pipelines.write(device, staging_belt, encoder);
+
+ //configure the render pass now that the data is uploaded to the GPU
{
- let (attachment, resolve_target, load) =
- if let Some(blit) = &mut self.blit {
- let (attachment, resolve_target) =
- blit.targets(device, target_width, target_height);
-
- (
- attachment,
- Some(resolve_target),
- wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
- )
- } else {
- (target, None, wgpu::LoadOp::Load)
- };
+ //configure antialiasing pass
+ let (attachment, resolve_target, load) = if let Some(blit) =
+ &mut self.blit
+ {
+ let (attachment, resolve_target) =
+ blit.targets(device, target_size.width, target_size.height);
+
+ (
+ attachment,
+ Some(resolve_target),
+ wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
+ )
+ } else {
+ (target, None, wgpu::LoadOp::Load)
+ };
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -354,12 +185,12 @@ impl Pipeline {
depth_stencil_attachment: None,
});
- render_pass.set_pipeline(&self.pipeline);
+ let mut num_solids = 0;
+ let mut num_gradients = 0;
+ let mut last_is_solid = None;
- for (i, (vertex_offset, index_offset, indices)) in
- offsets.into_iter().enumerate()
- {
- let clip_bounds = (meshes[i].clip_bounds * scale_factor).snap();
+ for (index, mesh) in meshes.iter().enumerate() {
+ let clip_bounds = (mesh.clip_bounds * scale_factor).snap();
render_pass.set_scissor_rect(
clip_bounds.x,
@@ -368,62 +199,105 @@ impl Pipeline {
clip_bounds.height,
);
- render_pass.set_bind_group(
+ match mesh.style {
+ triangle::Style::Solid(_) => {
+ if !last_is_solid.unwrap_or(false) {
+ self.pipelines
+ .solid
+ .set_render_pass_pipeline(&mut render_pass);
+
+ last_is_solid = Some(true);
+ }
+
+ self.pipelines.solid.configure_render_pass(
+ &mut render_pass,
+ num_solids,
+ );
+
+ num_solids += 1;
+ }
+ triangle::Style::Gradient(_) => {
+ if last_is_solid.unwrap_or(true) {
+ self.pipelines
+ .gradient
+ .set_render_pass_pipeline(&mut render_pass);
+
+ last_is_solid = Some(false);
+ }
+
+ self.pipelines.gradient.configure_render_pass(
+ &mut render_pass,
+ num_gradients,
+ );
+
+ num_gradients += 1;
+ }
+ };
+
+ render_pass.set_vertex_buffer(
0,
- &self.constants,
- &[(std::mem::size_of::<Uniforms>() * i) as u32],
+ self.vertex_buffer.slice_from_index(index),
);
render_pass.set_index_buffer(
- self.index_buffer
- .raw
- .slice(index_offset * mem::size_of::<u32>() as u64..),
+ self.index_buffer.slice_from_index(index),
wgpu::IndexFormat::Uint32,
);
- render_pass.set_vertex_buffer(
+ render_pass.draw_indexed(
+ 0..(self.index_strides[index] as u32),
0,
- self.vertex_buffer.raw.slice(
- vertex_offset * mem::size_of::<Vertex2D>() as u64..,
- ),
+ 0..1,
);
-
- render_pass.draw_indexed(0..indices as u32, 0, 0..1);
}
}
+ self.vertex_buffer.clear();
+ self.index_buffer.clear();
+
if let Some(blit) = &mut self.blit {
blit.draw(encoder, target);
}
}
}
-#[repr(C)]
-#[derive(Debug, Clone, Copy, Zeroable, Pod)]
-struct Uniforms {
- transform: [f32; 16],
- // We need to align this to 256 bytes to please `wgpu`...
- // TODO: Be smarter and stop wasting memory!
- _padding_a: [f32; 32],
- _padding_b: [f32; 16],
+//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,
+ }],
+ }
}
-impl Default for Uniforms {
- fn default() -> Self {
- Self {
- transform: *Transformation::identity().as_ref(),
- _padding_a: [0.0; 32],
- _padding_b: [0.0; 16],
- }
+fn fragment_target(
+ texture_format: wgpu::TextureFormat,
+) -> Option<wgpu::ColorTargetState> {
+ Some(wgpu::ColorTargetState {
+ format: texture_format,
+ blend: Some(wgpu::BlendState::ALPHA_BLENDING),
+ write_mask: wgpu::ColorWrites::ALL,
+ })
+}
+
+fn primitive_state() -> wgpu::PrimitiveState {
+ wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
}
}
-impl From<Transformation> for Uniforms {
- fn from(transformation: Transformation) -> Uniforms {
- Self {
- transform: transformation.into(),
- _padding_a: [0.0; 32],
- _padding_b: [0.0; 16],
- }
+fn multisample_state(
+ antialiasing: Option<settings::Antialiasing>,
+) -> wgpu::MultisampleState {
+ wgpu::MultisampleState {
+ count: antialiasing.map(|a| a.sample_count()).unwrap_or(1),
+ mask: !0,
+ alpha_to_coverage_enabled: false,
}
}
diff --git a/wgpu/src/triangle/gradient.rs b/wgpu/src/triangle/gradient.rs
new file mode 100644
index 00000000..b06cbac6
--- /dev/null
+++ b/wgpu/src/triangle/gradient.rs
@@ -0,0 +1,268 @@
+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
new file mode 100644
index 00000000..2e1052f2
--- /dev/null
+++ b/wgpu/src/triangle/solid.rs
@@ -0,0 +1,170 @@
+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/src/application.rs b/winit/src/application.rs
index 0496aea9..ffaaa8fb 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -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))?;
@@ -615,12 +629,21 @@ pub fn run_command<A, E>(
}
},
command::Action::Window(action) => match action {
+ window::Action::Drag => {
+ let _res = window.drag_window();
+ }
window::Action::Resize { width, height } => {
window.set_inner_size(winit::dpi::LogicalSize {
width,
height,
});
}
+ window::Action::Maximize(value) => {
+ window.set_maximized(value);
+ }
+ window::Action::Minimize(value) => {
+ window.set_minimized(value);
+ }
window::Action::Move { x, y } => {
window.set_outer_position(winit::dpi::LogicalPosition {
x,
@@ -634,6 +657,9 @@ pub fn run_command<A, E>(
mode,
));
}
+ window::Action::ToggleMaximize => {
+ window.set_maximized(!window.is_maximized())
+ }
window::Action::FetchMode(tag) => {
let mode = if window.is_visible().unwrap_or(true) {
conversion::mode(window.fullscreen())
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.
diff --git a/winit/src/window.rs b/winit/src/window.rs
index 265139f7..1e704c5b 100644
--- a/winit/src/window.rs
+++ b/winit/src/window.rs
@@ -4,6 +4,11 @@ use iced_native::window;
pub use window::{Event, Mode};
+/// Begins dragging the window while the left mouse button is held.
+pub fn drag<Message>() -> Command<Message> {
+ Command::single(command::Action::Window(window::Action::Drag))
+}
+
/// Resizes the window to the given logical dimensions.
pub fn resize<Message>(width: u32, height: u32) -> Command<Message> {
Command::single(command::Action::Window(window::Action::Resize {
@@ -12,6 +17,16 @@ pub fn resize<Message>(width: u32, height: u32) -> Command<Message> {
}))
}
+/// Sets the window to maximized or back.
+pub fn maximize<Message>(value: bool) -> Command<Message> {
+ Command::single(command::Action::Window(window::Action::Maximize(value)))
+}
+
+/// Set the window to minimized or back.
+pub fn minimize<Message>(value: bool) -> Command<Message> {
+ Command::single(command::Action::Window(window::Action::Minimize(value)))
+}
+
/// Moves a window to the given logical coordinates.
pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> {
Command::single(command::Action::Window(window::Action::Move { x, y }))
@@ -22,6 +37,11 @@ pub fn set_mode<Message>(mode: Mode) -> Command<Message> {
Command::single(command::Action::Window(window::Action::SetMode(mode)))
}
+/// Sets the window to maximized or back.
+pub fn toggle_maximize<Message>() -> Command<Message> {
+ Command::single(command::Action::Window(window::Action::ToggleMaximize))
+}
+
/// Fetches the current [`Mode`] of the window.
pub fn fetch_mode<Message>(
f: impl FnOnce(Mode) -> Message + 'static,