summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md20
-rw-r--r--examples/arc/Cargo.toml3
-rw-r--r--examples/arc/src/main.rs2
-rw-r--r--examples/bezier_tool/Cargo.toml3
-rw-r--r--examples/bezier_tool/README.md4
-rw-r--r--examples/bezier_tool/src/main.rs11
-rw-r--r--examples/checkbox/Cargo.toml2
-rw-r--r--examples/clock/Cargo.toml6
-rw-r--r--examples/clock/src/main.rs4
-rw-r--r--examples/color_palette/Cargo.toml6
-rw-r--r--examples/color_palette/README.md6
-rw-r--r--examples/color_palette/src/main.rs6
-rw-r--r--examples/combo_box/Cargo.toml10
-rw-r--r--examples/combo_box/README.md18
-rw-r--r--examples/combo_box/combobox.gifbin0 -> 1414629 bytes
-rw-r--r--examples/combo_box/src/main.rs142
-rw-r--r--examples/component/Cargo.toml3
-rw-r--r--examples/counter/Cargo.toml6
-rw-r--r--examples/counter/README.md12
-rw-r--r--examples/custom_quad/Cargo.toml3
-rw-r--r--examples/custom_quad/src/main.rs1
-rw-r--r--examples/custom_shader/Cargo.toml17
-rw-r--r--examples/custom_shader/src/main.rs163
-rw-r--r--examples/custom_shader/src/scene.rs186
-rw-r--r--examples/custom_shader/src/scene/camera.rs53
-rw-r--r--examples/custom_shader/src/scene/pipeline.rs619
-rw-r--r--examples/custom_shader/src/scene/pipeline/buffer.rs41
-rw-r--r--examples/custom_shader/src/scene/pipeline/cube.rs326
-rw-r--r--examples/custom_shader/src/scene/pipeline/uniforms.rs23
-rw-r--r--examples/custom_shader/src/scene/pipeline/vertex.rs31
-rw-r--r--examples/custom_shader/src/shaders/cubes.wgsl123
-rw-r--r--examples/custom_shader/src/shaders/depth.wgsl48
-rw-r--r--examples/custom_shader/textures/ice_cube_normal_map.pngbin0 -> 1773656 bytes
-rw-r--r--examples/custom_shader/textures/skybox/neg_x.jpgbin0 -> 7549 bytes
-rw-r--r--examples/custom_shader/textures/skybox/neg_y.jpgbin0 -> 2722 bytes
-rw-r--r--examples/custom_shader/textures/skybox/neg_z.jpgbin0 -> 3986 bytes
-rw-r--r--examples/custom_shader/textures/skybox/pos_x.jpgbin0 -> 5522 bytes
-rw-r--r--examples/custom_shader/textures/skybox/pos_y.jpgbin0 -> 3382 bytes
-rw-r--r--examples/custom_shader/textures/skybox/pos_z.jpgbin0 -> 5205 bytes
-rw-r--r--examples/custom_widget/Cargo.toml3
-rw-r--r--examples/custom_widget/README.md4
-rw-r--r--examples/custom_widget/src/main.rs1
-rw-r--r--examples/download_progress/Cargo.toml3
-rw-r--r--examples/download_progress/README.md4
-rw-r--r--examples/download_progress/src/main.rs2
-rw-r--r--examples/editor/Cargo.toml15
-rw-r--r--examples/editor/fonts/icons.ttfbin0 -> 6352 bytes
-rw-r--r--examples/editor/src/main.rs312
-rw-r--r--examples/events/Cargo.toml3
-rw-r--r--examples/events/README.md6
-rw-r--r--examples/events/src/main.rs10
-rw-r--r--examples/exit/Cargo.toml2
-rw-r--r--examples/game_of_life/Cargo.toml12
-rw-r--r--examples/game_of_life/README.md4
-rw-r--r--examples/game_of_life/src/main.rs30
-rw-r--r--examples/geometry/Cargo.toml3
-rw-r--r--examples/geometry/README.md4
-rw-r--r--examples/geometry/src/main.rs1
-rw-r--r--examples/gradient/Cargo.toml8
-rw-r--r--examples/gradient/src/main.rs99
-rw-r--r--examples/integration/Cargo.toml23
-rw-r--r--examples/integration/README.md4
-rw-r--r--examples/integration/src/controls.rs2
-rw-r--r--examples/integration/src/main.rs29
-rw-r--r--examples/integration/src/scene.rs5
-rw-r--r--examples/lazy/Cargo.toml3
-rw-r--r--examples/lazy/src/main.rs6
-rw-r--r--examples/loading_spinners/Cargo.toml8
-rw-r--r--examples/loading_spinners/README.md6
-rw-r--r--examples/loading_spinners/src/circular.rs2
-rw-r--r--examples/loading_spinners/src/linear.rs2
-rw-r--r--examples/modal/Cargo.toml3
-rw-r--r--examples/modal/src/main.rs37
-rw-r--r--examples/multi_window/src/main.rs13
-rw-r--r--examples/multitouch/Cargo.toml7
-rw-r--r--examples/multitouch/src/main.rs2
-rw-r--r--examples/pane_grid/Cargo.toml3
-rw-r--r--examples/pane_grid/README.md4
-rw-r--r--examples/pane_grid/src/main.rs43
-rw-r--r--examples/pick_list/Cargo.toml3
-rw-r--r--examples/pokedex/Cargo.toml11
-rw-r--r--examples/pokedex/README.md4
-rw-r--r--examples/pokedex/src/main.rs4
-rw-r--r--examples/progress_bar/Cargo.toml2
-rw-r--r--examples/progress_bar/README.md4
-rw-r--r--examples/qr_code/Cargo.toml3
-rw-r--r--examples/qr_code/README.md4
-rw-r--r--examples/screenshot/Cargo.toml12
-rw-r--r--examples/screenshot/src/main.rs45
-rw-r--r--examples/scrollable/Cargo.toml6
-rw-r--r--examples/scrollable/src/main.rs8
-rw-r--r--examples/sierpinski_triangle/Cargo.toml6
-rw-r--r--examples/sierpinski_triangle/README.md4
-rw-r--r--examples/sierpinski_triangle/src/main.rs5
-rw-r--r--examples/slider/Cargo.toml2
-rw-r--r--examples/solar_system/Cargo.toml6
-rw-r--r--examples/solar_system/README.md4
-rw-r--r--examples/solar_system/src/main.rs6
-rw-r--r--examples/stopwatch/Cargo.toml3
-rw-r--r--examples/stopwatch/README.md4
-rw-r--r--examples/stopwatch/src/main.rs20
-rw-r--r--examples/styling/Cargo.toml2
-rw-r--r--examples/styling/README.md4
-rw-r--r--examples/styling/src/main.rs1
-rw-r--r--examples/svg/Cargo.toml3
-rw-r--r--examples/system_information/Cargo.toml6
-rw-r--r--examples/system_information/src/main.rs4
-rw-r--r--examples/toast/Cargo.toml3
-rw-r--r--examples/toast/src/main.rs32
-rw-r--r--examples/todos/Cargo.toml18
-rw-r--r--examples/todos/README.md13
-rw-r--r--examples/todos/src/main.rs62
-rw-r--r--examples/tooltip/Cargo.toml3
-rw-r--r--examples/tooltip/src/main.rs2
-rw-r--r--examples/tour/Cargo.toml14
-rw-r--r--examples/tour/README.md4
-rw-r--r--examples/tour/src/main.rs61
-rw-r--r--examples/url_handler/Cargo.toml2
-rw-r--r--examples/url_handler/src/main.rs13
-rw-r--r--examples/visible_bounds/Cargo.toml12
-rw-r--r--examples/visible_bounds/src/main.rs187
-rw-r--r--examples/websocket/Cargo.toml14
-rw-r--r--examples/websocket/src/echo/server.rs5
123 files changed, 2895 insertions, 362 deletions
diff --git a/examples/README.md b/examples/README.md
index 111e8910..71dad13e 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -10,8 +10,8 @@ A simple UI tour that can run both on native platforms and the web! It showcases
The __[`main`](tour/src/main.rs)__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__.
<div align="center">
- <a href="https://gfycat.com/politeadorableiberianmole">
- <img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
+ <a href="https://iced.rs/examples/tour.mp4">
+ <img src="https://iced.rs/examples/tour.gif">
</a>
</div>
@@ -33,8 +33,8 @@ A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input,
The example code is located in the __[`main`](todos/src/main.rs)__ file.
<div align="center">
- <a href="https://gfycat.com/littlesanehalicore">
- <img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="400px">
+ <a href="https://iced.rs/examples/todos.mp4">
+ <img src="https://iced.rs/examples/todos.gif" height="400px">
</a>
</div>
@@ -53,9 +53,7 @@ It runs a simulation in a background thread while allowing interaction with a `C
The relevant code is located in the __[`main`](game_of_life/src/main.rs)__ file.
<div align="center">
- <a href="https://gfycat.com/briefaccurateaardvark">
- <img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif">
- </a>
+ <img src="https://iced.rs/examples/game_of_life.gif">
</div>
You can run it with `cargo run`:
@@ -72,9 +70,7 @@ An example showcasing custom styling with a light and dark theme.
The example code is located in the __[`main`](styling/src/main.rs)__ file.
<div align="center">
- <a href="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif">
- <img src="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif" height="400px">
- </a>
+ <img src="https://iced.rs/examples/styling.gif">
</div>
You can run it with `cargo run`:
@@ -120,9 +116,7 @@ Since [Iced was born in May 2019], it has been powering the user interfaces in
<div align="center">
- <a href="https://gfycat.com/gloomyweakhammerheadshark">
- <img src="https://thumbs.gfycat.com/GloomyWeakHammerheadshark-small.gif">
- </a>
+ <img src="https://iced.rs/examples/coffee.gif">
</div>
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35
diff --git a/examples/arc/Cargo.toml b/examples/arc/Cargo.toml
index e6e74363..5012ff82 100644
--- a/examples/arc/Cargo.toml
+++ b/examples/arc/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
+iced.workspace = true
+iced.features = ["canvas", "tokio", "debug"]
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs
index df565859..6a68cca1 100644
--- a/examples/arc/src/main.rs
+++ b/examples/arc/src/main.rs
@@ -37,7 +37,7 @@ impl Application for Arc {
(
Arc {
start: Instant::now(),
- cache: Default::default(),
+ cache: Cache::default(),
},
Command::none(),
)
diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml
index 890e3027..b2547ff1 100644
--- a/examples/bezier_tool/Cargo.toml
+++ b/examples/bezier_tool/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas"] }
+iced.workspace = true
+iced.features = ["canvas"]
diff --git a/examples/bezier_tool/README.md b/examples/bezier_tool/README.md
index ebbb12cc..6dc13785 100644
--- a/examples/bezier_tool/README.md
+++ b/examples/bezier_tool/README.md
@@ -5,9 +5,7 @@ A Paint-like tool for drawing Bézier curves using the `Canvas` widget.
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/soulfulinfiniteantbear">
- <img src="https://thumbs.gfycat.com/SoulfulInfiniteAntbear-small.gif">
- </a>
+ <img src="https://iced.rs/examples/bezier_tool.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index 310be28f..56cb23ba 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -81,7 +81,7 @@ mod bezier {
}
pub fn request_redraw(&mut self) {
- self.cache.clear()
+ self.cache.clear();
}
}
@@ -100,12 +100,9 @@ mod bezier {
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (event::Status, Option<Curve>) {
- let cursor_position =
- if let Some(position) = cursor.position_in(bounds) {
- position
- } else {
- return (event::Status::Ignored, None);
- };
+ let Some(cursor_position) = cursor.position_in(bounds) else {
+ return (event::Status::Ignored, None);
+ };
match event {
Event::Mouse(mouse_event) => {
diff --git a/examples/checkbox/Cargo.toml b/examples/checkbox/Cargo.toml
index dde8f910..1e027c4c 100644
--- a/examples/checkbox/Cargo.toml
+++ b/examples/checkbox/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced.workspace = true
diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml
index 5e869eb5..2d3d5908 100644
--- a/examples/clock/Cargo.toml
+++ b/examples/clock/Cargo.toml
@@ -6,5 +6,7 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-time = { version = "0.3.5", features = ["local-offset"] }
+iced.workspace = true
+iced.features = ["canvas", "tokio", "debug"]
+
+time = { version = "0.3", features = ["local-offset"] }
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index fae77bc0..920aa0c5 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -35,7 +35,7 @@ impl Application for Clock {
Clock {
now: time::OffsetDateTime::now_local()
.unwrap_or_else(|_| time::OffsetDateTime::now_utc()),
- clock: Default::default(),
+ clock: Cache::default(),
},
Command::none(),
)
@@ -141,7 +141,7 @@ impl<Message> canvas::Program<Message, Renderer> for Clock {
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.now.second(), 60));
frame.stroke(&long_hand, thin_stroke());
- })
+ });
});
vec![clock]
diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml
index 3be732bb..2da6c6ed 100644
--- a/examples/color_palette/Cargo.toml
+++ b/examples/color_palette/Cargo.toml
@@ -6,5 +6,7 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas", "palette"] }
-palette = "0.7.0"
+iced.workspace = true
+iced.features = ["canvas", "palette"]
+
+palette.workspace = true
diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md
index f90020b1..9c135937 100644
--- a/examples/color_palette/README.md
+++ b/examples/color_palette/README.md
@@ -3,13 +3,11 @@
A color palette generator, based on a user-defined root color.
<div align="center">
- <a href="https://gfycat.com/dirtylonebighornsheep">
- <img src="screenshot.png">
- </a>
+ <img src="screenshot.png">
</div>
You can run it with `cargo run`:
```
-cargo run --package pure_color_palette
+cargo run --package color_palette
```
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index 736a9d53..7dc981d9 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -3,8 +3,8 @@ use iced::mouse;
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider};
use iced::{
- Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
- Size, Vector,
+ Color, Element, Length, Pixels, Point, Rectangle, Renderer, Sandbox,
+ Settings, Size, Vector,
};
use palette::{
self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
@@ -168,7 +168,7 @@ impl Theme {
let mut text = canvas::Text {
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Top,
- size: 15.0,
+ size: Pixels(15.0),
..canvas::Text::default()
};
diff --git a/examples/combo_box/Cargo.toml b/examples/combo_box/Cargo.toml
new file mode 100644
index 00000000..0f5ecf2a
--- /dev/null
+++ b/examples/combo_box/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "combo_box"
+version = "0.1.0"
+authors = ["Joao Freitas <jhff.15@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced.workspace = true
+iced.features = ["debug"]
diff --git a/examples/combo_box/README.md b/examples/combo_box/README.md
new file mode 100644
index 00000000..9cd224ad
--- /dev/null
+++ b/examples/combo_box/README.md
@@ -0,0 +1,18 @@
+## Combo-Box
+
+A dropdown list of searchable and selectable options.
+
+It displays and positions an overlay based on the window position of the widget.
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <img src="combobox.gif">
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package combo_box
+```
+
+[`main`]: src/main.rs
diff --git a/examples/combo_box/combobox.gif b/examples/combo_box/combobox.gif
new file mode 100644
index 00000000..f216c026
--- /dev/null
+++ b/examples/combo_box/combobox.gif
Binary files differ
diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs
new file mode 100644
index 00000000..4f347667
--- /dev/null
+++ b/examples/combo_box/src/main.rs
@@ -0,0 +1,142 @@
+use iced::widget::{
+ column, combo_box, container, scrollable, text, vertical_space,
+};
+use iced::{Alignment, Element, Length, Sandbox, Settings};
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+struct Example {
+ languages: combo_box::State<Language>,
+ selected_language: Option<Language>,
+ text: String,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ Selected(Language),
+ OptionHovered(Language),
+ Closed,
+}
+
+impl Sandbox for Example {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self {
+ languages: combo_box::State::new(Language::ALL.to_vec()),
+ selected_language: None,
+ text: String::new(),
+ }
+ }
+
+ fn title(&self) -> String {
+ String::from("Combo box - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::Selected(language) => {
+ self.selected_language = Some(language);
+ self.text = language.hello().to_string();
+ }
+ Message::OptionHovered(language) => {
+ self.text = language.hello().to_string();
+ }
+ Message::Closed => {
+ self.text = self
+ .selected_language
+ .map(|language| language.hello().to_string())
+ .unwrap_or_default();
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let combo_box = combo_box(
+ &self.languages,
+ "Type a language...",
+ self.selected_language.as_ref(),
+ Message::Selected,
+ )
+ .on_option_hovered(Message::OptionHovered)
+ .on_close(Message::Closed)
+ .width(250);
+
+ let content = column![
+ text(&self.text),
+ "What is your language?",
+ combo_box,
+ vertical_space(150),
+ ]
+ .width(Length::Fill)
+ .align_items(Alignment::Center)
+ .spacing(10);
+
+ container(scrollable(content))
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum Language {
+ Danish,
+ #[default]
+ English,
+ French,
+ German,
+ Italian,
+ Portuguese,
+ Spanish,
+ Other,
+}
+
+impl Language {
+ const ALL: [Language; 8] = [
+ Language::Danish,
+ Language::English,
+ Language::French,
+ Language::German,
+ Language::Italian,
+ Language::Portuguese,
+ Language::Spanish,
+ Language::Other,
+ ];
+
+ fn hello(&self) -> &str {
+ match self {
+ Language::Danish => "Halloy!",
+ Language::English => "Hello!",
+ Language::French => "Salut!",
+ Language::German => "Hallo!",
+ Language::Italian => "Ciao!",
+ Language::Portuguese => "Olá!",
+ Language::Spanish => "¡Hola!",
+ Language::Other => "... hello?",
+ }
+ }
+}
+
+impl std::fmt::Display for Language {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Language::Danish => "Danish",
+ Language::English => "English",
+ Language::French => "French",
+ Language::German => "German",
+ Language::Italian => "Italian",
+ Language::Portuguese => "Portuguese",
+ Language::Spanish => "Spanish",
+ Language::Other => "Some other language",
+ }
+ )
+ }
+}
diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml
index 9db1e6b4..83b7b8a4 100644
--- a/examples/component/Cargo.toml
+++ b/examples/component/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug", "lazy"] }
+iced.workspace = true
+iced.features = ["debug", "lazy"]
diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml
index e31f440f..22f86064 100644
--- a/examples/counter/Cargo.toml
+++ b/examples/counter/Cargo.toml
@@ -6,4 +6,8 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced.workspace = true
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+iced.workspace = true
+iced.features = ["webgl"]
diff --git a/examples/counter/README.md b/examples/counter/README.md
index 4d9fc5b9..18761bba 100644
--- a/examples/counter/README.md
+++ b/examples/counter/README.md
@@ -5,9 +5,7 @@ The classic counter example explained in the [`README`](../../README.md).
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/fairdeadcatbird">
- <img src="https://thumbs.gfycat.com/FairDeadCatbird-small.gif">
- </a>
+ <img src="https://iced.rs/examples/counter.gif">
</div>
You can run it with `cargo run`:
@@ -15,4 +13,12 @@ You can run it with `cargo run`:
cargo run --package counter
```
+The web version can be run with [`trunk`]:
+
+```
+cd examples/counter
+trunk serve
+```
+
[`main`]: src/main.rs
+[`trunk`]: https://trunkrs.dev/
diff --git a/examples/custom_quad/Cargo.toml b/examples/custom_quad/Cargo.toml
index f097c2dd..31b5055d 100644
--- a/examples/custom_quad/Cargo.toml
+++ b/examples/custom_quad/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["advanced"] }
+iced.workspace = true
+iced.features = ["advanced"]
diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs
index 4b300116..13b08250 100644
--- a/examples/custom_quad/src/main.rs
+++ b/examples/custom_quad/src/main.rs
@@ -36,6 +36,7 @@ mod quad {
fn layout(
&self,
+ _tree: &mut widget::Tree,
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
diff --git a/examples/custom_shader/Cargo.toml b/examples/custom_shader/Cargo.toml
new file mode 100644
index 00000000..b602f98d
--- /dev/null
+++ b/examples/custom_shader/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "custom_shader"
+version = "0.1.0"
+authors = ["Bingus <shankern@protonmail.com>"]
+edition = "2021"
+
+[dependencies]
+iced.workspace = true
+iced.features = ["debug", "advanced"]
+
+image.workspace = true
+bytemuck.workspace = true
+
+glam.workspace = true
+glam.features = ["bytemuck"]
+
+rand = "0.8.5"
diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs
new file mode 100644
index 00000000..3bfa3a43
--- /dev/null
+++ b/examples/custom_shader/src/main.rs
@@ -0,0 +1,163 @@
+mod scene;
+
+use scene::Scene;
+
+use iced::executor;
+use iced::time::Instant;
+use iced::widget::shader::wgpu;
+use iced::widget::{checkbox, column, container, row, shader, slider, text};
+use iced::window;
+use iced::{
+ Alignment, Application, Color, Command, Element, Length, Renderer,
+ Subscription, Theme,
+};
+
+fn main() -> iced::Result {
+ IcedCubes::run(iced::Settings::default())
+}
+
+struct IcedCubes {
+ start: Instant,
+ scene: Scene,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ CubeAmountChanged(u32),
+ CubeSizeChanged(f32),
+ Tick(Instant),
+ ShowDepthBuffer(bool),
+ LightColorChanged(Color),
+}
+
+impl Application for IcedCubes {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
+ (
+ Self {
+ start: Instant::now(),
+ scene: Scene::new(),
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ "Iced Cubes".to_string()
+ }
+
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
+ match message {
+ Message::CubeAmountChanged(amount) => {
+ self.scene.change_amount(amount);
+ }
+ Message::CubeSizeChanged(size) => {
+ self.scene.size = size;
+ }
+ Message::Tick(time) => {
+ self.scene.update(time - self.start);
+ }
+ Message::ShowDepthBuffer(show) => {
+ self.scene.show_depth_buffer = show;
+ }
+ Message::LightColorChanged(color) => {
+ self.scene.light_color = color;
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
+ let top_controls = row![
+ control(
+ "Amount",
+ slider(
+ 1..=scene::MAX,
+ self.scene.cubes.len() as u32,
+ Message::CubeAmountChanged
+ )
+ .width(100)
+ ),
+ control(
+ "Size",
+ slider(0.1..=0.25, self.scene.size, Message::CubeSizeChanged)
+ .step(0.01)
+ .width(100),
+ ),
+ checkbox(
+ "Show Depth Buffer",
+ self.scene.show_depth_buffer,
+ Message::ShowDepthBuffer
+ ),
+ ]
+ .spacing(40);
+
+ let bottom_controls = row![
+ control(
+ "R",
+ slider(0.0..=1.0, self.scene.light_color.r, move |r| {
+ Message::LightColorChanged(Color {
+ r,
+ ..self.scene.light_color
+ })
+ })
+ .step(0.01)
+ .width(100)
+ ),
+ control(
+ "G",
+ slider(0.0..=1.0, self.scene.light_color.g, move |g| {
+ Message::LightColorChanged(Color {
+ g,
+ ..self.scene.light_color
+ })
+ })
+ .step(0.01)
+ .width(100)
+ ),
+ control(
+ "B",
+ slider(0.0..=1.0, self.scene.light_color.b, move |b| {
+ Message::LightColorChanged(Color {
+ b,
+ ..self.scene.light_color
+ })
+ })
+ .step(0.01)
+ .width(100)
+ )
+ ]
+ .spacing(40);
+
+ let controls = column![top_controls, bottom_controls,]
+ .spacing(10)
+ .padding(20)
+ .align_items(Alignment::Center);
+
+ let shader =
+ shader(&self.scene).width(Length::Fill).height(Length::Fill);
+
+ container(column![shader, controls].align_items(Alignment::Center))
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ window::frames().map(Message::Tick)
+ }
+}
+
+fn control<'a>(
+ label: &'static str,
+ control: impl Into<Element<'a, Message>>,
+) -> Element<'a, Message> {
+ row![text(label), control.into()].spacing(10).into()
+}
diff --git a/examples/custom_shader/src/scene.rs b/examples/custom_shader/src/scene.rs
new file mode 100644
index 00000000..a35efdd9
--- /dev/null
+++ b/examples/custom_shader/src/scene.rs
@@ -0,0 +1,186 @@
+mod camera;
+mod pipeline;
+
+use camera::Camera;
+use pipeline::Pipeline;
+
+use crate::wgpu;
+use pipeline::cube::{self, Cube};
+
+use iced::mouse;
+use iced::time::Duration;
+use iced::widget::shader;
+use iced::{Color, Rectangle, Size};
+
+use glam::Vec3;
+use rand::Rng;
+use std::cmp::Ordering;
+use std::iter;
+
+pub const MAX: u32 = 500;
+
+#[derive(Clone)]
+pub struct Scene {
+ pub size: f32,
+ pub cubes: Vec<Cube>,
+ pub camera: Camera,
+ pub show_depth_buffer: bool,
+ pub light_color: Color,
+}
+
+impl Scene {
+ pub fn new() -> Self {
+ let mut scene = Self {
+ size: 0.2,
+ cubes: vec![],
+ camera: Camera::default(),
+ show_depth_buffer: false,
+ light_color: Color::WHITE,
+ };
+
+ scene.change_amount(MAX);
+
+ scene
+ }
+
+ pub fn update(&mut self, time: Duration) {
+ for cube in self.cubes.iter_mut() {
+ cube.update(self.size, time.as_secs_f32());
+ }
+ }
+
+ pub fn change_amount(&mut self, amount: u32) {
+ let curr_cubes = self.cubes.len() as u32;
+
+ match amount.cmp(&curr_cubes) {
+ Ordering::Greater => {
+ // spawn
+ let cubes_2_spawn = (amount - curr_cubes) as usize;
+
+ let mut cubes = 0;
+ self.cubes.extend(iter::from_fn(|| {
+ if cubes < cubes_2_spawn {
+ cubes += 1;
+ Some(Cube::new(self.size, rnd_origin()))
+ } else {
+ None
+ }
+ }));
+ }
+ Ordering::Less => {
+ // chop
+ let cubes_2_cut = curr_cubes - amount;
+ let new_len = self.cubes.len() - cubes_2_cut as usize;
+ self.cubes.truncate(new_len);
+ }
+ Ordering::Equal => {}
+ }
+ }
+}
+
+impl<Message> shader::Program<Message> for Scene {
+ type State = ();
+ type Primitive = Primitive;
+
+ fn draw(
+ &self,
+ _state: &Self::State,
+ _cursor: mouse::Cursor,
+ bounds: Rectangle,
+ ) -> Self::Primitive {
+ Primitive::new(
+ &self.cubes,
+ &self.camera,
+ bounds,
+ self.show_depth_buffer,
+ self.light_color,
+ )
+ }
+}
+
+/// A collection of `Cube`s that can be rendered.
+#[derive(Debug)]
+pub struct Primitive {
+ cubes: Vec<cube::Raw>,
+ uniforms: pipeline::Uniforms,
+ show_depth_buffer: bool,
+}
+
+impl Primitive {
+ pub fn new(
+ cubes: &[Cube],
+ camera: &Camera,
+ bounds: Rectangle,
+ show_depth_buffer: bool,
+ light_color: Color,
+ ) -> Self {
+ let uniforms = pipeline::Uniforms::new(camera, bounds, light_color);
+
+ Self {
+ cubes: cubes
+ .iter()
+ .map(cube::Raw::from_cube)
+ .collect::<Vec<cube::Raw>>(),
+ uniforms,
+ show_depth_buffer,
+ }
+ }
+}
+
+impl shader::Primitive for Primitive {
+ fn prepare(
+ &self,
+ format: wgpu::TextureFormat,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ _bounds: Rectangle,
+ target_size: Size<u32>,
+ _scale_factor: f32,
+ storage: &mut shader::Storage,
+ ) {
+ if !storage.has::<Pipeline>() {
+ storage.store(Pipeline::new(device, queue, format, target_size));
+ }
+
+ let pipeline = storage.get_mut::<Pipeline>().unwrap();
+
+ //upload data to GPU
+ pipeline.update(
+ device,
+ queue,
+ target_size,
+ &self.uniforms,
+ self.cubes.len(),
+ &self.cubes,
+ );
+ }
+
+ fn render(
+ &self,
+ storage: &shader::Storage,
+ target: &wgpu::TextureView,
+ _target_size: Size<u32>,
+ viewport: Rectangle<u32>,
+ encoder: &mut wgpu::CommandEncoder,
+ ) {
+ //at this point our pipeline should always be initialized
+ let pipeline = storage.get::<Pipeline>().unwrap();
+
+ //render primitive
+ pipeline.render(
+ target,
+ encoder,
+ viewport,
+ self.cubes.len() as u32,
+ self.show_depth_buffer,
+ );
+ }
+}
+
+fn rnd_origin() -> Vec3 {
+ Vec3::new(
+ rand::thread_rng().gen_range(-4.0..4.0),
+ rand::thread_rng().gen_range(-4.0..4.0),
+ rand::thread_rng().gen_range(-4.0..2.0),
+ )
+}
diff --git a/examples/custom_shader/src/scene/camera.rs b/examples/custom_shader/src/scene/camera.rs
new file mode 100644
index 00000000..2a49c102
--- /dev/null
+++ b/examples/custom_shader/src/scene/camera.rs
@@ -0,0 +1,53 @@
+use glam::{mat4, vec3, vec4};
+use iced::Rectangle;
+
+#[derive(Copy, Clone)]
+pub struct Camera {
+ eye: glam::Vec3,
+ target: glam::Vec3,
+ up: glam::Vec3,
+ fov_y: f32,
+ near: f32,
+ far: f32,
+}
+
+impl Default for Camera {
+ fn default() -> Self {
+ Self {
+ eye: vec3(0.0, 2.0, 3.0),
+ target: glam::Vec3::ZERO,
+ up: glam::Vec3::Y,
+ fov_y: 45.0,
+ near: 0.1,
+ far: 100.0,
+ }
+ }
+}
+
+pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = mat4(
+ vec4(1.0, 0.0, 0.0, 0.0),
+ vec4(0.0, 1.0, 0.0, 0.0),
+ vec4(0.0, 0.0, 0.5, 0.0),
+ vec4(0.0, 0.0, 0.5, 1.0),
+);
+
+impl Camera {
+ pub fn build_view_proj_matrix(&self, bounds: Rectangle) -> glam::Mat4 {
+ //TODO looks distorted without padding; base on surface texture size instead?
+ let aspect_ratio = bounds.width / (bounds.height + 150.0);
+
+ let view = glam::Mat4::look_at_rh(self.eye, self.target, self.up);
+ let proj = glam::Mat4::perspective_rh(
+ self.fov_y,
+ aspect_ratio,
+ self.near,
+ self.far,
+ );
+
+ OPENGL_TO_WGPU_MATRIX * proj * view
+ }
+
+ pub fn position(&self) -> glam::Vec4 {
+ glam::Vec4::from((self.eye, 0.0))
+ }
+}
diff --git a/examples/custom_shader/src/scene/pipeline.rs b/examples/custom_shader/src/scene/pipeline.rs
new file mode 100644
index 00000000..124b421f
--- /dev/null
+++ b/examples/custom_shader/src/scene/pipeline.rs
@@ -0,0 +1,619 @@
+pub mod cube;
+
+mod buffer;
+mod uniforms;
+mod vertex;
+
+pub use uniforms::Uniforms;
+
+use buffer::Buffer;
+use vertex::Vertex;
+
+use crate::wgpu;
+use crate::wgpu::util::DeviceExt;
+
+use iced::{Rectangle, Size};
+
+const SKY_TEXTURE_SIZE: u32 = 128;
+
+pub struct Pipeline {
+ pipeline: wgpu::RenderPipeline,
+ vertices: wgpu::Buffer,
+ cubes: Buffer,
+ uniforms: wgpu::Buffer,
+ uniform_bind_group: wgpu::BindGroup,
+ depth_texture_size: Size<u32>,
+ depth_view: wgpu::TextureView,
+ depth_pipeline: DepthPipeline,
+}
+
+impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ format: wgpu::TextureFormat,
+ target_size: Size<u32>,
+ ) -> Self {
+ //vertices of one cube
+ let vertices =
+ device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some("cubes vertex buffer"),
+ contents: bytemuck::cast_slice(&cube::Raw::vertices()),
+ usage: wgpu::BufferUsages::VERTEX,
+ });
+
+ //cube instance data
+ let cubes_buffer = Buffer::new(
+ device,
+ "cubes instance buffer",
+ std::mem::size_of::<cube::Raw>() as u64,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ //uniforms for all cubes
+ let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("cubes uniform buffer"),
+ size: std::mem::size_of::<Uniforms>() as u64,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ //depth buffer
+ let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("cubes depth texture"),
+ size: wgpu::Extent3d {
+ width: target_size.width,
+ height: target_size.height,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Depth32Float,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT
+ | wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
+ });
+
+ let depth_view =
+ depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ let normal_map_data = load_normal_map_data();
+
+ //normal map
+ let normal_texture = device.create_texture_with_data(
+ queue,
+ &wgpu::TextureDescriptor {
+ label: Some("cubes normal map texture"),
+ size: wgpu::Extent3d {
+ width: 1024,
+ height: 1024,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Rgba8Unorm,
+ usage: wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
+ },
+ &normal_map_data,
+ );
+
+ let normal_view =
+ normal_texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ //skybox texture for reflection/refraction
+ let skybox_data = load_skybox_data();
+
+ let skybox_texture = device.create_texture_with_data(
+ queue,
+ &wgpu::TextureDescriptor {
+ label: Some("cubes skybox texture"),
+ size: wgpu::Extent3d {
+ width: SKY_TEXTURE_SIZE,
+ height: SKY_TEXTURE_SIZE,
+ depth_or_array_layers: 6, //one for each face of the cube
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Rgba8Unorm,
+ usage: wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
+ },
+ &skybox_data,
+ );
+
+ let sky_view =
+ skybox_texture.create_view(&wgpu::TextureViewDescriptor {
+ label: Some("cubes skybox texture view"),
+ dimension: Some(wgpu::TextureViewDimension::Cube),
+ ..Default::default()
+ });
+
+ let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ label: Some("cubes skybox sampler"),
+ address_mode_u: wgpu::AddressMode::ClampToEdge,
+ address_mode_v: wgpu::AddressMode::ClampToEdge,
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
+ mag_filter: wgpu::FilterMode::Linear,
+ min_filter: wgpu::FilterMode::Linear,
+ mipmap_filter: wgpu::FilterMode::Linear,
+ ..Default::default()
+ });
+
+ let uniform_bind_group_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("cubes uniform bind group layout"),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: true,
+ },
+ view_dimension: wgpu::TextureViewDimension::Cube,
+ multisampled: false,
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 2,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(
+ wgpu::SamplerBindingType::Filtering,
+ ),
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 3,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: true,
+ },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ count: None,
+ },
+ ],
+ });
+
+ let uniform_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("cubes uniform bind group"),
+ layout: &uniform_bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: uniforms.as_entire_binding(),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::TextureView(&sky_view),
+ },
+ wgpu::BindGroupEntry {
+ binding: 2,
+ resource: wgpu::BindingResource::Sampler(&sky_sampler),
+ },
+ wgpu::BindGroupEntry {
+ binding: 3,
+ resource: wgpu::BindingResource::TextureView(
+ &normal_view,
+ ),
+ },
+ ],
+ });
+
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("cubes pipeline layout"),
+ bind_group_layouts: &[&uniform_bind_group_layout],
+ push_constant_ranges: &[],
+ });
+
+ let shader =
+ device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("cubes shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("../shaders/cubes.wgsl"),
+ )),
+ });
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("cubes pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[Vertex::desc(), cube::Raw::desc()],
+ },
+ primitive: wgpu::PrimitiveState::default(),
+ depth_stencil: Some(wgpu::DepthStencilState {
+ format: wgpu::TextureFormat::Depth32Float,
+ depth_write_enabled: true,
+ depth_compare: wgpu::CompareFunction::Less,
+ stencil: wgpu::StencilState::default(),
+ bias: wgpu::DepthBiasState::default(),
+ }),
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[Some(wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::One,
+ operation: wgpu::BlendOperation::Max,
+ },
+ }),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ }),
+ multiview: None,
+ });
+
+ let depth_pipeline = DepthPipeline::new(
+ device,
+ format,
+ depth_texture.create_view(&wgpu::TextureViewDescriptor::default()),
+ );
+
+ Self {
+ pipeline,
+ cubes: cubes_buffer,
+ uniforms,
+ uniform_bind_group,
+ vertices,
+ depth_texture_size: target_size,
+ depth_view,
+ depth_pipeline,
+ }
+ }
+
+ fn update_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) {
+ if self.depth_texture_size.height != size.height
+ || self.depth_texture_size.width != size.width
+ {
+ let text = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("cubes depth texture"),
+ size: wgpu::Extent3d {
+ width: size.width,
+ height: size.height,
+ depth_or_array_layers: 1,
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Depth32Float,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT
+ | wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
+ });
+
+ self.depth_view =
+ text.create_view(&wgpu::TextureViewDescriptor::default());
+ self.depth_texture_size = size;
+
+ self.depth_pipeline.update(device, &text);
+ }
+ }
+
+ pub fn update(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ target_size: Size<u32>,
+ uniforms: &Uniforms,
+ num_cubes: usize,
+ cubes: &[cube::Raw],
+ ) {
+ //recreate depth texture if surface texture size has changed
+ self.update_depth_texture(device, target_size);
+
+ // update uniforms
+ queue.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
+
+ //resize cubes vertex buffer if cubes amount changed
+ let new_size = num_cubes * std::mem::size_of::<cube::Raw>();
+ self.cubes.resize(device, new_size as u64);
+
+ //always write new cube data since they are constantly rotating
+ queue.write_buffer(&self.cubes.raw, 0, bytemuck::cast_slice(cubes));
+ }
+
+ pub fn render(
+ &self,
+ target: &wgpu::TextureView,
+ encoder: &mut wgpu::CommandEncoder,
+ viewport: Rectangle<u32>,
+ num_cubes: u32,
+ show_depth: bool,
+ ) {
+ {
+ let mut pass =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("cubes.pipeline.pass"),
+ color_attachments: &[Some(
+ wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store,
+ },
+ },
+ )],
+ depth_stencil_attachment: Some(
+ wgpu::RenderPassDepthStencilAttachment {
+ view: &self.depth_view,
+ depth_ops: Some(wgpu::Operations {
+ load: wgpu::LoadOp::Clear(1.0),
+ store: wgpu::StoreOp::Store,
+ }),
+ stencil_ops: None,
+ },
+ ),
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ });
+
+ pass.set_scissor_rect(
+ viewport.x,
+ viewport.y,
+ viewport.width,
+ viewport.height,
+ );
+ pass.set_pipeline(&self.pipeline);
+ pass.set_bind_group(0, &self.uniform_bind_group, &[]);
+ pass.set_vertex_buffer(0, self.vertices.slice(..));
+ pass.set_vertex_buffer(1, self.cubes.raw.slice(..));
+ pass.draw(0..36, 0..num_cubes);
+ }
+
+ if show_depth {
+ self.depth_pipeline.render(encoder, target, viewport);
+ }
+ }
+}
+
+struct DepthPipeline {
+ pipeline: wgpu::RenderPipeline,
+ bind_group_layout: wgpu::BindGroupLayout,
+ bind_group: wgpu::BindGroup,
+ sampler: wgpu::Sampler,
+ depth_view: wgpu::TextureView,
+}
+
+impl DepthPipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ depth_texture: wgpu::TextureView,
+ ) -> Self {
+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ label: Some("cubes.depth_pipeline.sampler"),
+ ..Default::default()
+ });
+
+ let bind_group_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("cubes.depth_pipeline.bind_group_layout"),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(
+ wgpu::SamplerBindingType::NonFiltering,
+ ),
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: false,
+ },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ count: None,
+ },
+ ],
+ });
+
+ let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("cubes.depth_pipeline.bind_group"),
+ layout: &bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Sampler(&sampler),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::TextureView(
+ &depth_texture,
+ ),
+ },
+ ],
+ });
+
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("cubes.depth_pipeline.layout"),
+ bind_group_layouts: &[&bind_group_layout],
+ push_constant_ranges: &[],
+ });
+
+ let shader =
+ device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("cubes.depth_pipeline.shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("../shaders/depth.wgsl"),
+ )),
+ });
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("cubes.depth_pipeline.pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[],
+ },
+ primitive: wgpu::PrimitiveState::default(),
+ depth_stencil: Some(wgpu::DepthStencilState {
+ format: wgpu::TextureFormat::Depth32Float,
+ depth_write_enabled: false,
+ depth_compare: wgpu::CompareFunction::Less,
+ stencil: wgpu::StencilState::default(),
+ bias: wgpu::DepthBiasState::default(),
+ }),
+ multisample: wgpu::MultisampleState::default(),
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[Some(wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState::REPLACE),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ }),
+ multiview: None,
+ });
+
+ Self {
+ pipeline,
+ bind_group_layout,
+ bind_group,
+ sampler,
+ depth_view: depth_texture,
+ }
+ }
+
+ pub fn update(
+ &mut self,
+ device: &wgpu::Device,
+ depth_texture: &wgpu::Texture,
+ ) {
+ self.depth_view =
+ depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ self.bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("cubes.depth_pipeline.bind_group"),
+ layout: &self.bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Sampler(&self.sampler),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::TextureView(
+ &self.depth_view,
+ ),
+ },
+ ],
+ });
+ }
+
+ pub fn render(
+ &self,
+ encoder: &mut wgpu::CommandEncoder,
+ target: &wgpu::TextureView,
+ viewport: Rectangle<u32>,
+ ) {
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("cubes.pipeline.depth_pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store,
+ },
+ })],
+ depth_stencil_attachment: Some(
+ wgpu::RenderPassDepthStencilAttachment {
+ view: &self.depth_view,
+ depth_ops: None,
+ stencil_ops: None,
+ },
+ ),
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ });
+
+ pass.set_scissor_rect(
+ viewport.x,
+ viewport.y,
+ viewport.width,
+ viewport.height,
+ );
+ pass.set_pipeline(&self.pipeline);
+ pass.set_bind_group(0, &self.bind_group, &[]);
+ pass.draw(0..6, 0..1);
+ }
+}
+
+fn load_skybox_data() -> Vec<u8> {
+ let pos_x: &[u8] = include_bytes!("../../textures/skybox/pos_x.jpg");
+ let neg_x: &[u8] = include_bytes!("../../textures/skybox/neg_x.jpg");
+ let pos_y: &[u8] = include_bytes!("../../textures/skybox/pos_y.jpg");
+ let neg_y: &[u8] = include_bytes!("../../textures/skybox/neg_y.jpg");
+ let pos_z: &[u8] = include_bytes!("../../textures/skybox/pos_z.jpg");
+ let neg_z: &[u8] = include_bytes!("../../textures/skybox/neg_z.jpg");
+
+ let data: [&[u8]; 6] = [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z];
+
+ data.iter().fold(vec![], |mut acc, bytes| {
+ let i = image::load_from_memory_with_format(
+ bytes,
+ image::ImageFormat::Jpeg,
+ )
+ .unwrap()
+ .to_rgba8()
+ .into_raw();
+
+ acc.extend(i);
+ acc
+ })
+}
+
+fn load_normal_map_data() -> Vec<u8> {
+ let bytes: &[u8] = include_bytes!("../../textures/ice_cube_normal_map.png");
+
+ image::load_from_memory_with_format(bytes, image::ImageFormat::Png)
+ .unwrap()
+ .to_rgba8()
+ .into_raw()
+}
diff --git a/examples/custom_shader/src/scene/pipeline/buffer.rs b/examples/custom_shader/src/scene/pipeline/buffer.rs
new file mode 100644
index 00000000..ef4c41c9
--- /dev/null
+++ b/examples/custom_shader/src/scene/pipeline/buffer.rs
@@ -0,0 +1,41 @@
+use crate::wgpu;
+
+// A custom buffer container for dynamic resizing.
+pub struct Buffer {
+ pub raw: wgpu::Buffer,
+ label: &'static str,
+ size: u64,
+ usage: wgpu::BufferUsages,
+}
+
+impl Buffer {
+ pub fn new(
+ device: &wgpu::Device,
+ label: &'static str,
+ size: u64,
+ usage: wgpu::BufferUsages,
+ ) -> Self {
+ Self {
+ raw: device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some(label),
+ size,
+ usage,
+ mapped_at_creation: false,
+ }),
+ label,
+ size,
+ usage,
+ }
+ }
+
+ pub fn resize(&mut self, device: &wgpu::Device, new_size: u64) {
+ if new_size > self.size {
+ self.raw = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some(self.label),
+ size: new_size,
+ usage: self.usage,
+ mapped_at_creation: false,
+ });
+ }
+ }
+}
diff --git a/examples/custom_shader/src/scene/pipeline/cube.rs b/examples/custom_shader/src/scene/pipeline/cube.rs
new file mode 100644
index 00000000..de8bad6c
--- /dev/null
+++ b/examples/custom_shader/src/scene/pipeline/cube.rs
@@ -0,0 +1,326 @@
+use crate::scene::pipeline::Vertex;
+use crate::wgpu;
+
+use glam::{vec2, vec3, Vec3};
+use rand::{thread_rng, Rng};
+
+/// A single instance of a cube.
+#[derive(Debug, Clone)]
+pub struct Cube {
+ pub rotation: glam::Quat,
+ pub position: Vec3,
+ pub size: f32,
+ rotation_dir: f32,
+ rotation_axis: glam::Vec3,
+}
+
+impl Default for Cube {
+ fn default() -> Self {
+ Self {
+ rotation: glam::Quat::IDENTITY,
+ position: glam::Vec3::ZERO,
+ size: 0.1,
+ rotation_dir: 1.0,
+ rotation_axis: glam::Vec3::Y,
+ }
+ }
+}
+
+impl Cube {
+ pub fn new(size: f32, origin: Vec3) -> Self {
+ let rnd = thread_rng().gen_range(0.0..=1.0f32);
+
+ Self {
+ rotation: glam::Quat::IDENTITY,
+ position: origin + Vec3::new(0.1, 0.1, 0.1),
+ size,
+ rotation_dir: if rnd <= 0.5 { -1.0 } else { 1.0 },
+ rotation_axis: if rnd <= 0.33 {
+ glam::Vec3::Y
+ } else if rnd <= 0.66 {
+ glam::Vec3::X
+ } else {
+ glam::Vec3::Z
+ },
+ }
+ }
+
+ pub fn update(&mut self, size: f32, time: f32) {
+ self.rotation = glam::Quat::from_axis_angle(
+ self.rotation_axis,
+ time / 2.0 * self.rotation_dir,
+ );
+ self.size = size;
+ }
+}
+
+#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
+#[repr(C)]
+pub struct Raw {
+ transformation: glam::Mat4,
+ normal: glam::Mat3,
+ _padding: [f32; 3],
+}
+
+impl Raw {
+ const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
+ //cube transformation matrix
+ 4 => Float32x4,
+ 5 => Float32x4,
+ 6 => Float32x4,
+ 7 => Float32x4,
+ //normal rotation matrix
+ 8 => Float32x3,
+ 9 => Float32x3,
+ 10 => Float32x3,
+ ];
+
+ pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
+ step_mode: wgpu::VertexStepMode::Instance,
+ attributes: &Self::ATTRIBS,
+ }
+ }
+}
+
+impl Raw {
+ pub fn from_cube(cube: &Cube) -> Raw {
+ Raw {
+ transformation: glam::Mat4::from_scale_rotation_translation(
+ glam::vec3(cube.size, cube.size, cube.size),
+ cube.rotation,
+ cube.position,
+ ),
+ normal: glam::Mat3::from_quat(cube.rotation),
+ _padding: [0.0; 3],
+ }
+ }
+
+ pub fn vertices() -> [Vertex; 36] {
+ [
+ //face 1
+ Vertex {
+ pos: vec3(-0.5, -0.5, -0.5),
+ normal: vec3(0.0, 0.0, -1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, -0.5),
+ normal: vec3(0.0, 0.0, -1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, -0.5),
+ normal: vec3(0.0, 0.0, -1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, -0.5),
+ normal: vec3(0.0, 0.0, -1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, 0.5, -0.5),
+ normal: vec3(0.0, 0.0, -1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, -0.5, -0.5),
+ normal: vec3(0.0, 0.0, -1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ //face 2
+ Vertex {
+ pos: vec3(-0.5, -0.5, 0.5),
+ normal: vec3(0.0, 0.0, 1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, 0.5),
+ normal: vec3(0.0, 0.0, 1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, 0.5),
+ normal: vec3(0.0, 0.0, 1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, 0.5),
+ normal: vec3(0.0, 0.0, 1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, 0.5, 0.5),
+ normal: vec3(0.0, 0.0, 1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, -0.5, 0.5),
+ normal: vec3(0.0, 0.0, 1.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ //face 3
+ Vertex {
+ pos: vec3(-0.5, 0.5, 0.5),
+ normal: vec3(-1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(0.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, 0.5, -0.5),
+ normal: vec3(-1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(1.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, -0.5, -0.5),
+ normal: vec3(-1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, -0.5, -0.5),
+ normal: vec3(-1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, -0.5, 0.5),
+ normal: vec3(-1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(0.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, 0.5, 0.5),
+ normal: vec3(-1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(0.0, 1.0),
+ },
+ //face 4
+ Vertex {
+ pos: vec3(0.5, 0.5, 0.5),
+ normal: vec3(1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(0.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, -0.5),
+ normal: vec3(1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(1.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, -0.5),
+ normal: vec3(1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, -0.5),
+ normal: vec3(1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, 0.5),
+ normal: vec3(1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(0.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, 0.5),
+ normal: vec3(1.0, 0.0, 0.0),
+ tangent: vec3(0.0, 0.0, -1.0),
+ uv: vec2(0.0, 1.0),
+ },
+ //face 5
+ Vertex {
+ pos: vec3(-0.5, -0.5, -0.5),
+ normal: vec3(0.0, -1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, -0.5),
+ normal: vec3(0.0, -1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, 0.5),
+ normal: vec3(0.0, -1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(0.5, -0.5, 0.5),
+ normal: vec3(0.0, -1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, -0.5, 0.5),
+ normal: vec3(0.0, -1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, -0.5, -0.5),
+ normal: vec3(0.0, -1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ //face 6
+ Vertex {
+ pos: vec3(-0.5, 0.5, -0.5),
+ normal: vec3(0.0, 1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, -0.5),
+ normal: vec3(0.0, 1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 1.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, 0.5),
+ normal: vec3(0.0, 1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(0.5, 0.5, 0.5),
+ normal: vec3(0.0, 1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(1.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, 0.5, 0.5),
+ normal: vec3(0.0, 1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 0.0),
+ },
+ Vertex {
+ pos: vec3(-0.5, 0.5, -0.5),
+ normal: vec3(0.0, 1.0, 0.0),
+ tangent: vec3(1.0, 0.0, 0.0),
+ uv: vec2(0.0, 1.0),
+ },
+ ]
+ }
+}
diff --git a/examples/custom_shader/src/scene/pipeline/uniforms.rs b/examples/custom_shader/src/scene/pipeline/uniforms.rs
new file mode 100644
index 00000000..1eac8292
--- /dev/null
+++ b/examples/custom_shader/src/scene/pipeline/uniforms.rs
@@ -0,0 +1,23 @@
+use crate::scene::Camera;
+
+use iced::{Color, Rectangle};
+
+#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
+#[repr(C)]
+pub struct Uniforms {
+ camera_proj: glam::Mat4,
+ camera_pos: glam::Vec4,
+ light_color: glam::Vec4,
+}
+
+impl Uniforms {
+ pub fn new(camera: &Camera, bounds: Rectangle, light_color: Color) -> Self {
+ let camera_proj = camera.build_view_proj_matrix(bounds);
+
+ Self {
+ camera_proj,
+ camera_pos: camera.position(),
+ light_color: glam::Vec4::from(light_color.into_linear()),
+ }
+ }
+}
diff --git a/examples/custom_shader/src/scene/pipeline/vertex.rs b/examples/custom_shader/src/scene/pipeline/vertex.rs
new file mode 100644
index 00000000..e64cd926
--- /dev/null
+++ b/examples/custom_shader/src/scene/pipeline/vertex.rs
@@ -0,0 +1,31 @@
+use crate::wgpu;
+
+#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+#[repr(C)]
+pub struct Vertex {
+ pub pos: glam::Vec3,
+ pub normal: glam::Vec3,
+ pub tangent: glam::Vec3,
+ pub uv: glam::Vec2,
+}
+
+impl Vertex {
+ const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
+ //position
+ 0 => Float32x3,
+ //normal
+ 1 => Float32x3,
+ //tangent
+ 2 => Float32x3,
+ //uv
+ 3 => Float32x2,
+ ];
+
+ pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &Self::ATTRIBS,
+ }
+ }
+}
diff --git a/examples/custom_shader/src/shaders/cubes.wgsl b/examples/custom_shader/src/shaders/cubes.wgsl
new file mode 100644
index 00000000..cd7f94d8
--- /dev/null
+++ b/examples/custom_shader/src/shaders/cubes.wgsl
@@ -0,0 +1,123 @@
+struct Uniforms {
+ projection: mat4x4<f32>,
+ camera_pos: vec4<f32>,
+ light_color: vec4<f32>,
+}
+
+const LIGHT_POS: vec3<f32> = vec3<f32>(0.0, 3.0, 3.0);
+
+@group(0) @binding(0) var<uniform> uniforms: Uniforms;
+@group(0) @binding(1) var sky_texture: texture_cube<f32>;
+@group(0) @binding(2) var tex_sampler: sampler;
+@group(0) @binding(3) var normal_texture: texture_2d<f32>;
+
+struct Vertex {
+ @location(0) position: vec3<f32>,
+ @location(1) normal: vec3<f32>,
+ @location(2) tangent: vec3<f32>,
+ @location(3) uv: vec2<f32>,
+}
+
+struct Cube {
+ @location(4) matrix_0: vec4<f32>,
+ @location(5) matrix_1: vec4<f32>,
+ @location(6) matrix_2: vec4<f32>,
+ @location(7) matrix_3: vec4<f32>,
+ @location(8) normal_matrix_0: vec3<f32>,
+ @location(9) normal_matrix_1: vec3<f32>,
+ @location(10) normal_matrix_2: vec3<f32>,
+}
+
+struct Output {
+ @builtin(position) clip_pos: vec4<f32>,
+ @location(0) uv: vec2<f32>,
+ @location(1) tangent_pos: vec3<f32>,
+ @location(2) tangent_camera_pos: vec3<f32>,
+ @location(3) tangent_light_pos: vec3<f32>,
+}
+
+@vertex
+fn vs_main(vertex: Vertex, cube: Cube) -> Output {
+ let cube_matrix = mat4x4<f32>(
+ cube.matrix_0,
+ cube.matrix_1,
+ cube.matrix_2,
+ cube.matrix_3,
+ );
+
+ let normal_matrix = mat3x3<f32>(
+ cube.normal_matrix_0,
+ cube.normal_matrix_1,
+ cube.normal_matrix_2,
+ );
+
+ //convert to tangent space to calculate lighting in same coordinate space as normal map sample
+ let tangent = normalize(normal_matrix * vertex.tangent);
+ let normal = normalize(normal_matrix * vertex.normal);
+ let bitangent = cross(tangent, normal);
+
+ //shift everything into tangent space
+ let tbn = transpose(mat3x3<f32>(tangent, bitangent, normal));
+
+ let world_pos = cube_matrix * vec4<f32>(vertex.position, 1.0);
+
+ var out: Output;
+ out.clip_pos = uniforms.projection * world_pos;
+ out.uv = vertex.uv;
+ out.tangent_pos = tbn * world_pos.xyz;
+ out.tangent_camera_pos = tbn * uniforms.camera_pos.xyz;
+ out.tangent_light_pos = tbn * LIGHT_POS;
+
+ return out;
+}
+
+//cube properties
+const CUBE_BASE_COLOR: vec4<f32> = vec4<f32>(0.294118, 0.462745, 0.611765, 0.6);
+const SHINE_DAMPER: f32 = 1.0;
+const REFLECTIVITY: f32 = 0.8;
+const REFRACTION_INDEX: f32 = 1.31;
+
+//fog, for the ~* cinematic effect *~
+const FOG_DENSITY: f32 = 0.15;
+const FOG_GRADIENT: f32 = 8.0;
+const FOG_COLOR: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0);
+
+@fragment
+fn fs_main(in: Output) -> @location(0) vec4<f32> {
+ let to_camera = in.tangent_camera_pos - in.tangent_pos;
+
+ //normal sample from texture
+ var normal = textureSample(normal_texture, tex_sampler, in.uv).xyz;
+ normal = normal * 2.0 - 1.0;
+
+ //diffuse
+ let dir_to_light: vec3<f32> = normalize(in.tangent_light_pos - in.tangent_pos);
+ let brightness = max(dot(normal, dir_to_light), 0.0);
+ let diffuse: vec3<f32> = brightness * uniforms.light_color.xyz;
+
+ //specular
+ let dir_to_camera = normalize(to_camera);
+ let light_dir = -dir_to_light;
+ let reflected_light_dir = reflect(light_dir, normal);
+ let specular_factor = max(dot(reflected_light_dir, dir_to_camera), 0.0);
+ let damped_factor = pow(specular_factor, SHINE_DAMPER);
+ let specular: vec3<f32> = damped_factor * uniforms.light_color.xyz * REFLECTIVITY;
+
+ //fog
+ let distance = length(to_camera);
+ let visibility = clamp(exp(-pow((distance * FOG_DENSITY), FOG_GRADIENT)), 0.0, 1.0);
+
+ //reflection
+ let reflection_dir = reflect(dir_to_camera, normal);
+ let reflection_color = textureSample(sky_texture, tex_sampler, reflection_dir);
+ let refraction_dir = refract(dir_to_camera, normal, REFRACTION_INDEX);
+ let refraction_color = textureSample(sky_texture, tex_sampler, refraction_dir);
+ let final_reflect_color = mix(reflection_color, refraction_color, 0.5);
+
+ //mix it all together!
+ var color = vec4<f32>(CUBE_BASE_COLOR.xyz * diffuse + specular, CUBE_BASE_COLOR.w);
+ color = mix(color, final_reflect_color, 0.8);
+ color = mix(FOG_COLOR, color, visibility);
+
+ return color;
+}
diff --git a/examples/custom_shader/src/shaders/depth.wgsl b/examples/custom_shader/src/shaders/depth.wgsl
new file mode 100644
index 00000000..a3f7e5ec
--- /dev/null
+++ b/examples/custom_shader/src/shaders/depth.wgsl
@@ -0,0 +1,48 @@
+var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(1.0, -1.0),
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>(1.0, 1.0),
+ vec2<f32>(1.0, -1.0)
+);
+
+var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
+ vec2<f32>(0.0, 0.0),
+ vec2<f32>(0.0, 1.0),
+ vec2<f32>(1.0, 1.0),
+ vec2<f32>(0.0, 0.0),
+ vec2<f32>(1.0, 0.0),
+ vec2<f32>(1.0, 1.0)
+);
+
+@group(0) @binding(0) var depth_sampler: sampler;
+@group(0) @binding(1) var depth_texture: texture_2d<f32>;
+
+struct Output {
+ @builtin(position) position: vec4<f32>,
+ @location(0) uv: vec2<f32>,
+}
+
+@vertex
+fn vs_main(@builtin(vertex_index) v_index: u32) -> Output {
+ var out: Output;
+
+ out.position = vec4<f32>(positions[v_index], 0.0, 1.0);
+ out.uv = uvs[v_index];
+
+ return out;
+}
+
+@fragment
+fn fs_main(input: Output) -> @location(0) vec4<f32> {
+ let depth = textureSample(depth_texture, depth_sampler, input.uv).r;
+
+ if (depth > .9999) {
+ discard;
+ }
+
+ let c = 1.0 - depth;
+
+ return vec4<f32>(c, c, c, 1.0);
+}
diff --git a/examples/custom_shader/textures/ice_cube_normal_map.png b/examples/custom_shader/textures/ice_cube_normal_map.png
new file mode 100644
index 00000000..7b4b7228
--- /dev/null
+++ b/examples/custom_shader/textures/ice_cube_normal_map.png
Binary files differ
diff --git a/examples/custom_shader/textures/skybox/neg_x.jpg b/examples/custom_shader/textures/skybox/neg_x.jpg
new file mode 100644
index 00000000..00cc783d
--- /dev/null
+++ b/examples/custom_shader/textures/skybox/neg_x.jpg
Binary files differ
diff --git a/examples/custom_shader/textures/skybox/neg_y.jpg b/examples/custom_shader/textures/skybox/neg_y.jpg
new file mode 100644
index 00000000..548f6445
--- /dev/null
+++ b/examples/custom_shader/textures/skybox/neg_y.jpg
Binary files differ
diff --git a/examples/custom_shader/textures/skybox/neg_z.jpg b/examples/custom_shader/textures/skybox/neg_z.jpg
new file mode 100644
index 00000000..5698512e
--- /dev/null
+++ b/examples/custom_shader/textures/skybox/neg_z.jpg
Binary files differ
diff --git a/examples/custom_shader/textures/skybox/pos_x.jpg b/examples/custom_shader/textures/skybox/pos_x.jpg
new file mode 100644
index 00000000..dddecba7
--- /dev/null
+++ b/examples/custom_shader/textures/skybox/pos_x.jpg
Binary files differ
diff --git a/examples/custom_shader/textures/skybox/pos_y.jpg b/examples/custom_shader/textures/skybox/pos_y.jpg
new file mode 100644
index 00000000..361427fd
--- /dev/null
+++ b/examples/custom_shader/textures/skybox/pos_y.jpg
Binary files differ
diff --git a/examples/custom_shader/textures/skybox/pos_z.jpg b/examples/custom_shader/textures/skybox/pos_z.jpg
new file mode 100644
index 00000000..0085a49e
--- /dev/null
+++ b/examples/custom_shader/textures/skybox/pos_z.jpg
Binary files differ
diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml
index dda0efe8..1e94bc52 100644
--- a/examples/custom_widget/Cargo.toml
+++ b/examples/custom_widget/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["advanced"] }
+iced.workspace = true
+iced.features = ["advanced"]
diff --git a/examples/custom_widget/README.md b/examples/custom_widget/README.md
index 3d6cf902..b80e898f 100644
--- a/examples/custom_widget/README.md
+++ b/examples/custom_widget/README.md
@@ -5,9 +5,7 @@ A demonstration of how to build a custom widget that draws a circle.
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/jealouscornyhomalocephale">
- <img src="https://thumbs.gfycat.com/JealousCornyHomalocephale-small.gif">
- </a>
+ <img src="https://iced.rs/examples/custom_widget.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index 713bc62d..32a14cbe 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -43,6 +43,7 @@ mod circle {
fn layout(
&self,
+ _tree: &mut widget::Tree,
_renderer: &Renderer,
_limits: &layout::Limits,
) -> layout::Node {
diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml
index 212832f4..18a49f66 100644
--- a/examples/download_progress/Cargo.toml
+++ b/examples/download_progress/Cargo.toml
@@ -6,7 +6,8 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["tokio"] }
+iced.workspace = true
+iced.features = ["tokio"]
[dependencies.reqwest]
version = "0.11"
diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md
index 7999ce94..19cb2966 100644
--- a/examples/download_progress/README.md
+++ b/examples/download_progress/README.md
@@ -5,9 +5,7 @@ A basic application that asynchronously downloads multiple dummy files of 100 MB
The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
<div align="center">
- <a href="https://gfycat.com/wildearlyafricanwilddog">
- <img src="https://thumbs.gfycat.com/WildEarlyAfricanwilddog-small.gif">
- </a>
+ <img src="https://iced.rs/examples/download_progress.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs
index 001a1f8f..a2fcb275 100644
--- a/examples/download_progress/src/main.rs
+++ b/examples/download_progress/src/main.rs
@@ -123,7 +123,7 @@ impl Download {
| State::Errored { .. } => {
self.state = State::Downloading { progress: 0.0 };
}
- _ => {}
+ State::Downloading { .. } => {}
}
}
diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml
new file mode 100644
index 00000000..a3f6ea3b
--- /dev/null
+++ b/examples/editor/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "editor"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector@hecrj.dev>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced.workspace = true
+iced.features = ["highlighter", "tokio", "debug"]
+
+tokio.workspace = true
+tokio.features = ["fs"]
+
+rfd = "0.12"
diff --git a/examples/editor/fonts/icons.ttf b/examples/editor/fonts/icons.ttf
new file mode 100644
index 00000000..393c6922
--- /dev/null
+++ b/examples/editor/fonts/icons.ttf
Binary files differ
diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs
new file mode 100644
index 00000000..03d1e283
--- /dev/null
+++ b/examples/editor/src/main.rs
@@ -0,0 +1,312 @@
+use iced::executor;
+use iced::highlighter::{self, Highlighter};
+use iced::keyboard;
+use iced::theme::{self, Theme};
+use iced::widget::{
+ button, column, container, horizontal_space, pick_list, row, text,
+ text_editor, tooltip,
+};
+use iced::{
+ Alignment, Application, Command, Element, Font, Length, Settings,
+ Subscription,
+};
+
+use std::ffi;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+
+pub fn main() -> iced::Result {
+ Editor::run(Settings {
+ fonts: vec![include_bytes!("../fonts/icons.ttf").as_slice().into()],
+ default_font: Font::MONOSPACE,
+ ..Settings::default()
+ })
+}
+
+struct Editor {
+ file: Option<PathBuf>,
+ content: text_editor::Content,
+ theme: highlighter::Theme,
+ is_loading: bool,
+ is_dirty: bool,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ ActionPerformed(text_editor::Action),
+ ThemeSelected(highlighter::Theme),
+ NewFile,
+ OpenFile,
+ FileOpened(Result<(PathBuf, Arc<String>), Error>),
+ SaveFile,
+ FileSaved(Result<PathBuf, Error>),
+}
+
+impl Application for Editor {
+ type Message = Message;
+ type Theme = Theme;
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
+ (
+ Self {
+ file: None,
+ content: text_editor::Content::new(),
+ theme: highlighter::Theme::SolarizedDark,
+ is_loading: true,
+ is_dirty: false,
+ },
+ Command::perform(load_file(default_file()), Message::FileOpened),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Editor - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::ActionPerformed(action) => {
+ self.is_dirty = self.is_dirty || action.is_edit();
+
+ self.content.perform(action);
+
+ Command::none()
+ }
+ Message::ThemeSelected(theme) => {
+ self.theme = theme;
+
+ Command::none()
+ }
+ Message::NewFile => {
+ if !self.is_loading {
+ self.file = None;
+ self.content = text_editor::Content::new();
+ }
+
+ Command::none()
+ }
+ Message::OpenFile => {
+ if self.is_loading {
+ Command::none()
+ } else {
+ self.is_loading = true;
+
+ Command::perform(open_file(), Message::FileOpened)
+ }
+ }
+ Message::FileOpened(result) => {
+ self.is_loading = false;
+ self.is_dirty = false;
+
+ if let Ok((path, contents)) = result {
+ self.file = Some(path);
+ self.content = text_editor::Content::with_text(&contents);
+ }
+
+ Command::none()
+ }
+ Message::SaveFile => {
+ if self.is_loading {
+ Command::none()
+ } else {
+ self.is_loading = true;
+
+ Command::perform(
+ save_file(self.file.clone(), self.content.text()),
+ Message::FileSaved,
+ )
+ }
+ }
+ Message::FileSaved(result) => {
+ self.is_loading = false;
+
+ if let Ok(path) = result {
+ self.file = Some(path);
+ self.is_dirty = false;
+ }
+
+ Command::none()
+ }
+ }
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ keyboard::on_key_press(|key_code, modifiers| match key_code {
+ keyboard::KeyCode::S if modifiers.command() => {
+ Some(Message::SaveFile)
+ }
+ _ => None,
+ })
+ }
+
+ fn view(&self) -> Element<Message> {
+ let controls = row![
+ action(new_icon(), "New file", Some(Message::NewFile)),
+ action(
+ open_icon(),
+ "Open file",
+ (!self.is_loading).then_some(Message::OpenFile)
+ ),
+ action(
+ save_icon(),
+ "Save file",
+ self.is_dirty.then_some(Message::SaveFile)
+ ),
+ horizontal_space(Length::Fill),
+ pick_list(
+ highlighter::Theme::ALL,
+ Some(self.theme),
+ Message::ThemeSelected
+ )
+ .text_size(14)
+ .padding([5, 10])
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let status = row![
+ text(if let Some(path) = &self.file {
+ let path = path.display().to_string();
+
+ if path.len() > 60 {
+ format!("...{}", &path[path.len() - 40..])
+ } else {
+ path
+ }
+ } else {
+ String::from("New file")
+ }),
+ horizontal_space(Length::Fill),
+ text({
+ let (line, column) = self.content.cursor_position();
+
+ format!("{}:{}", line + 1, column + 1)
+ })
+ ]
+ .spacing(10);
+
+ column![
+ controls,
+ text_editor(&self.content)
+ .on_action(Message::ActionPerformed)
+ .highlight::<Highlighter>(
+ highlighter::Settings {
+ theme: self.theme,
+ extension: self
+ .file
+ .as_deref()
+ .and_then(Path::extension)
+ .and_then(ffi::OsStr::to_str)
+ .map(str::to_string)
+ .unwrap_or(String::from("rs")),
+ },
+ |highlight, _theme| highlight.to_format()
+ ),
+ status,
+ ]
+ .spacing(10)
+ .padding(10)
+ .into()
+ }
+
+ fn theme(&self) -> Theme {
+ if self.theme.is_dark() {
+ Theme::Dark
+ } else {
+ Theme::Light
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Error {
+ DialogClosed,
+ IoError(io::ErrorKind),
+}
+
+fn default_file() -> PathBuf {
+ PathBuf::from(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR")))
+}
+
+async fn open_file() -> Result<(PathBuf, Arc<String>), Error> {
+ let picked_file = rfd::AsyncFileDialog::new()
+ .set_title("Open a text file...")
+ .pick_file()
+ .await
+ .ok_or(Error::DialogClosed)?;
+
+ load_file(picked_file.path().to_owned()).await
+}
+
+async fn load_file(path: PathBuf) -> Result<(PathBuf, Arc<String>), Error> {
+ let contents = tokio::fs::read_to_string(&path)
+ .await
+ .map(Arc::new)
+ .map_err(|error| Error::IoError(error.kind()))?;
+
+ Ok((path, contents))
+}
+
+async fn save_file(
+ path: Option<PathBuf>,
+ contents: String,
+) -> Result<PathBuf, Error> {
+ let path = if let Some(path) = path {
+ path
+ } else {
+ rfd::AsyncFileDialog::new()
+ .save_file()
+ .await
+ .as_ref()
+ .map(rfd::FileHandle::path)
+ .map(Path::to_owned)
+ .ok_or(Error::DialogClosed)?
+ };
+
+ tokio::fs::write(&path, contents)
+ .await
+ .map_err(|error| Error::IoError(error.kind()))?;
+
+ Ok(path)
+}
+
+fn action<'a, Message: Clone + 'a>(
+ content: impl Into<Element<'a, Message>>,
+ label: &'a str,
+ on_press: Option<Message>,
+) -> Element<'a, Message> {
+ let action = button(container(content).width(30).center_x());
+
+ if let Some(on_press) = on_press {
+ tooltip(
+ action.on_press(on_press),
+ label,
+ tooltip::Position::FollowCursor,
+ )
+ .style(theme::Container::Box)
+ .into()
+ } else {
+ action.style(theme::Button::Secondary).into()
+ }
+}
+
+fn new_icon<'a, Message>() -> Element<'a, Message> {
+ icon('\u{0e800}')
+}
+
+fn save_icon<'a, Message>() -> Element<'a, Message> {
+ icon('\u{0e801}')
+}
+
+fn open_icon<'a, Message>() -> Element<'a, Message> {
+ icon('\u{0f115}')
+}
+
+fn icon<'a, Message>(codepoint: char) -> Element<'a, Message> {
+ const ICON_FONT: Font = Font::with_name("editor-icons");
+
+ text(codepoint).font(ICON_FONT).into()
+}
diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml
index 15ffc0af..87315a10 100644
--- a/examples/events/Cargo.toml
+++ b/examples/events/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug"] }
+iced.workspace = true
+iced.features = ["debug"]
diff --git a/examples/events/README.md b/examples/events/README.md
index 3c9a1cab..fd7f9b47 100644
--- a/examples/events/README.md
+++ b/examples/events/README.md
@@ -4,12 +4,6 @@ A log of native events displayed using a conditional `Subscription`.
The __[`main`]__ file contains all the code of the example.
-<div align="center">
- <a href="https://gfycat.com/infamousicyermine">
- <img src="https://thumbs.gfycat.com/InfamousIcyErmine-small.gif">
- </a>
-</div>
-
You can run it with `cargo run`:
```
cargo run --package events
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index c3ac6fd1..334b012d 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -1,9 +1,8 @@
use iced::alignment;
+use iced::event::{self, Event};
use iced::executor;
-use iced::subscription;
use iced::widget::{button, checkbox, container, text, Column};
use iced::window;
-use iced::Event;
use iced::{
Alignment, Application, Command, Element, Length, Settings, Subscription,
Theme,
@@ -11,7 +10,10 @@ use iced::{
pub fn main() -> iced::Result {
Events::run(Settings {
- exit_on_close_request: false,
+ window: window::Settings {
+ exit_on_close_request: false,
+ ..window::Settings::default()
+ },
..Settings::default()
})
}
@@ -72,7 +74,7 @@ impl Application for Events {
}
fn subscription(&self) -> Subscription<Message> {
- subscription::events().map(Message::EventOccurred)
+ event::listen().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {
diff --git a/examples/exit/Cargo.toml b/examples/exit/Cargo.toml
index 34d0789a..b06fbadc 100644
--- a/examples/exit/Cargo.toml
+++ b/examples/exit/Cargo.toml
@@ -5,4 +5,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced.workspace = true
diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml
index f0a794fb..7596844c 100644
--- a/examples/game_of_life/Cargo.toml
+++ b/examples/game_of_life/Cargo.toml
@@ -6,8 +6,10 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-tokio = { version = "1.0", features = ["sync"] }
-itertools = "0.9"
-rustc-hash = "1.1"
-env_logger = "0.9"
+iced.workspace = true
+iced.features = ["debug", "canvas", "tokio"]
+
+itertools = "0.12"
+rustc-hash.workspace = true
+tokio = { workspace = true, features = ["sync"] }
+tracing-subscriber = "0.3"
diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md
index aa39201c..60033c1a 100644
--- a/examples/game_of_life/README.md
+++ b/examples/game_of_life/README.md
@@ -7,9 +7,7 @@ It runs a simulation in a background thread while allowing interaction with a `C
The __[`main`]__ file contains the relevant code of the example.
<div align="center">
- <a href="https://gfycat.com/WhichPaltryChick">
- <img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif">
- </a>
+ <img src="https://iced.rs/examples/game_of_life.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index e951d734..96840143 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -18,7 +18,7 @@ use iced::{
use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
- env_logger::builder().format_timestamp(None).init();
+ tracing_subscriber::fmt::init();
GameOfLife::run(Settings {
antialiasing: true,
@@ -406,12 +406,9 @@ mod grid {
*interaction = Interaction::None;
}
- let cursor_position =
- if let Some(position) = cursor.position_in(bounds) {
- position
- } else {
- return (event::Status::Ignored, None);
- };
+ let Some(cursor_position) = cursor.position_in(bounds) else {
+ return (event::Status::Ignored, None);
+ };
let cell = Cell::at(self.project(cursor_position, bounds.size()));
let is_populated = self.state.contains(&cell);
@@ -472,7 +469,7 @@ mod grid {
* (1.0 / self.scaling),
))
}
- _ => None,
+ Interaction::None => None,
};
let event_status = match interaction {
@@ -550,7 +547,7 @@ mod grid {
frame.translate(center);
frame.scale(self.scaling);
frame.translate(self.translation);
- frame.scale(Cell::SIZE as f32);
+ frame.scale(Cell::SIZE);
let region = self.visible_region(frame.size());
@@ -576,7 +573,7 @@ mod grid {
frame.translate(center);
frame.scale(self.scaling);
frame.translate(self.translation);
- frame.scale(Cell::SIZE as f32);
+ frame.scale(Cell::SIZE);
frame.fill_rectangle(
Point::new(cell.j as f32, cell.i as f32),
@@ -591,7 +588,7 @@ mod grid {
let text = Text {
color: Color::WHITE,
- size: 14.0,
+ size: 14.0.into(),
position: Point::new(frame.width(), frame.height()),
horizontal_alignment: alignment::Horizontal::Right,
vertical_alignment: alignment::Vertical::Bottom,
@@ -610,8 +607,7 @@ mod grid {
frame.fill_text(Text {
content: format!(
- "{} cell{} @ {:?} ({})",
- cell_count,
+ "{cell_count} cell{} @ {:?} ({})",
if cell_count == 1 { "" } else { "s" },
self.last_tick_duration,
self.last_queued_ticks
@@ -630,7 +626,7 @@ mod grid {
frame.translate(center);
frame.scale(self.scaling);
frame.translate(self.translation);
- frame.scale(Cell::SIZE as f32);
+ frame.scale(Cell::SIZE);
let region = self.visible_region(frame.size());
let rows = region.rows();
@@ -677,7 +673,7 @@ mod grid {
Interaction::None if cursor.is_over(bounds) => {
mouse::Interaction::Crosshair
}
- _ => mouse::Interaction::default(),
+ Interaction::None => mouse::Interaction::default(),
}
}
}
@@ -793,7 +789,7 @@ mod grid {
}
}
- for (cell, amount) in adjacent_life.iter() {
+ for (cell, amount) in &adjacent_life {
match amount {
2 => {}
3 => {
@@ -834,7 +830,7 @@ mod grid {
}
impl Cell {
- const SIZE: usize = 20;
+ const SIZE: u16 = 20;
fn at(position: Point) -> Cell {
let i = (position.y / Cell::SIZE as f32).ceil() as isize;
diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml
index 6068d651..9606dcb3 100644
--- a/examples/geometry/Cargo.toml
+++ b/examples/geometry/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["advanced"] }
+iced.workspace = true
+iced.features = ["advanced"]
diff --git a/examples/geometry/README.md b/examples/geometry/README.md
index 4d5c81cb..16be8d19 100644
--- a/examples/geometry/README.md
+++ b/examples/geometry/README.md
@@ -5,9 +5,7 @@ A custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [
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>
+ <img src="https://iced.rs/examples/geometry.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 3bc7f46b..8ab3b493 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -26,6 +26,7 @@ mod rainbow {
fn layout(
&self,
+ _tree: &mut widget::Tree,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
diff --git a/examples/gradient/Cargo.toml b/examples/gradient/Cargo.toml
new file mode 100644
index 00000000..2dea2c4f
--- /dev/null
+++ b/examples/gradient/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "gradient"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
diff --git a/examples/gradient/src/main.rs b/examples/gradient/src/main.rs
new file mode 100644
index 00000000..1bf5822d
--- /dev/null
+++ b/examples/gradient/src/main.rs
@@ -0,0 +1,99 @@
+use iced::gradient;
+use iced::widget::{column, container, horizontal_space, row, slider, text};
+use iced::{
+ Alignment, Background, Color, Element, Length, Radians, Sandbox, Settings,
+};
+
+pub fn main() -> iced::Result {
+ Gradient::run(Settings::default())
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Gradient {
+ start: Color,
+ end: Color,
+ angle: Radians,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ StartChanged(Color),
+ EndChanged(Color),
+ AngleChanged(Radians),
+}
+
+impl Sandbox for Gradient {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self {
+ start: Color::WHITE,
+ end: Color::new(0.0, 0.0, 1.0, 1.0),
+ angle: Radians(0.0),
+ }
+ }
+
+ fn title(&self) -> String {
+ String::from("Gradient")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::StartChanged(color) => self.start = color,
+ Message::EndChanged(color) => self.end = color,
+ Message::AngleChanged(angle) => self.angle = angle,
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let Self { start, end, angle } = *self;
+
+ let gradient_box = container(horizontal_space(Length::Fill))
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .style(move |_: &_| {
+ let gradient = gradient::Linear::new(angle)
+ .add_stop(0.0, start)
+ .add_stop(1.0, end)
+ .into();
+
+ container::Appearance {
+ background: Some(Background::Gradient(gradient)),
+ ..Default::default()
+ }
+ });
+
+ let angle_picker = row![
+ text("Angle").width(64),
+ slider(Radians::RANGE, self.angle, Message::AngleChanged)
+ .step(0.01)
+ ]
+ .spacing(8)
+ .padding(8)
+ .align_items(Alignment::Center);
+
+ column![
+ color_picker("Start", self.start).map(Message::StartChanged),
+ color_picker("End", self.end).map(Message::EndChanged),
+ angle_picker,
+ gradient_box
+ ]
+ .into()
+ }
+}
+
+fn color_picker(label: &str, color: Color) -> Element<'_, Color> {
+ row![
+ text(label).width(64),
+ slider(0.0..=1.0, color.r, move |r| { Color { r, ..color } })
+ .step(0.01),
+ slider(0.0..=1.0, color.g, move |g| { Color { g, ..color } })
+ .step(0.01),
+ slider(0.0..=1.0, color.b, move |b| { Color { b, ..color } })
+ .step(0.01),
+ ]
+ .spacing(8)
+ .padding(8)
+ .align_items(Alignment::Center)
+ .into()
+}
diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml
index 22914742..a4a961f8 100644
--- a/examples/integration/Cargo.toml
+++ b/examples/integration/Cargo.toml
@@ -6,19 +6,18 @@ edition = "2021"
publish = false
[dependencies]
-iced_winit = { path = "../../winit" }
-iced_wgpu = { path = "../../wgpu" }
-iced_widget = { path = "../../widget" }
-iced_renderer = { path = "../../renderer", features = ["wgpu"] }
-env_logger = "0.10"
+iced_winit.workspace = true
+iced_wgpu.workspace = true
+iced_widget.workspace = true
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+tracing-subscriber = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies]
-console_error_panic_hook = "0.1.7"
-console_log = "0.2.0"
-log = "0.4"
+iced_wgpu.workspace = true
+iced_wgpu.features = ["webgl"]
+
+console_error_panic_hook = "0.1"
+console_log = "1.0"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] }
-# This dependency a little bit quirky, it is deep in the tree and without `js` feature it
-# refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch
-# to make it work
-getrandom = { version = "0.2", features = ["js"] }
diff --git a/examples/integration/README.md b/examples/integration/README.md
index ece9ba1e..996cdc17 100644
--- a/examples/integration/README.md
+++ b/examples/integration/README.md
@@ -5,9 +5,7 @@ A demonstration of how to integrate Iced in an existing [`wgpu`] application.
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/nicemediocrekodiakbear">
- <img src="https://thumbs.gfycat.com/NiceMediocreKodiakbear-small.gif">
- </a>
+ <img src="https://iced.rs/examples/integration.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs
index 14e53ede..4714c397 100644
--- a/examples/integration/src/controls.rs
+++ b/examples/integration/src/controls.rs
@@ -19,7 +19,7 @@ impl Controls {
pub fn new() -> Controls {
Controls {
background_color: Color::BLACK,
- text: Default::default(),
+ text: String::default(),
}
}
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 90beb097..276794c8 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -6,13 +6,17 @@ use scene::Scene;
use iced_wgpu::graphics::Viewport;
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
+use iced_winit::conversion;
+use iced_winit::core::mouse;
use iced_winit::core::renderer;
-use iced_winit::core::{mouse, window};
-use iced_winit::core::{Color, Size};
+use iced_winit::core::window;
+use iced_winit::core::{Color, Font, Pixels, Size};
+use iced_winit::futures;
use iced_winit::runtime::program;
use iced_winit::runtime::Debug;
use iced_winit::style::Theme;
-use iced_winit::{conversion, futures, winit, Clipboard};
+use iced_winit::winit;
+use iced_winit::Clipboard;
use winit::{
event::{Event, ModifiersState, WindowEvent},
@@ -29,7 +33,7 @@ use winit::platform::web::WindowBuilderExtWebSys;
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_arch = "wasm32")]
let canvas_element = {
- console_log::init_with_level(log::Level::Debug)?;
+ console_log::init().expect("Initialize logger");
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
@@ -41,7 +45,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
};
#[cfg(not(target_arch = "wasm32"))]
- env_logger::init();
+ tracing_subscriber::fmt::init();
// Initialize winit
let event_loop = EventLoop::new();
@@ -82,7 +86,6 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
futures::futures::executor::block_on(async {
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
&instance,
- backend,
Some(&surface),
)
.await
@@ -143,12 +146,11 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize iced
let mut debug = Debug::new();
- let mut renderer = Renderer::new(Backend::new(
- &device,
- &queue,
- Settings::default(),
- format,
- ));
+ let mut renderer = Renderer::new(
+ Backend::new(&device, &queue, Settings::default(), format),
+ Font::default(),
+ Pixels(16.0),
+ );
let mut state = program::State::new(
controls,
@@ -257,7 +259,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
{
// We clear the frame
- let mut render_pass = scene.clear(
+ let mut render_pass = Scene::clear(
&view,
&mut encoder,
program.background_color(),
@@ -274,6 +276,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
&queue,
&mut encoder,
None,
+ frame.texture.format(),
&view,
primitive,
&viewport,
diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs
index 90c7efbf..e29558bf 100644
--- a/examples/integration/src/scene.rs
+++ b/examples/integration/src/scene.rs
@@ -16,7 +16,6 @@ impl Scene {
}
pub fn clear<'a>(
- &self,
target: &'a wgpu::TextureView,
encoder: &'a mut wgpu::CommandEncoder,
background_color: Color,
@@ -37,10 +36,12 @@ impl Scene {
a: a as f64,
}
}),
- store: true,
+ store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
})
}
diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml
index e03e89a9..4ccb9584 100644
--- a/examples/lazy/Cargo.toml
+++ b/examples/lazy/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug", "lazy"] }
+iced.workspace = true
+iced.features = ["debug", "lazy"]
diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs
index c6baa6a1..01560598 100644
--- a/examples/lazy/src/main.rs
+++ b/examples/lazy/src/main.rs
@@ -27,7 +27,7 @@ impl Default for App {
.into_iter()
.map(From::from)
.collect(),
- input: Default::default(),
+ input: String::default(),
order: Order::Ascending,
}
}
@@ -46,7 +46,7 @@ enum Color {
}
impl Color {
- const ALL: &[Color] = &[
+ const ALL: &'static [Color] = &[
Color::Black,
Color::Red,
Color::Orange,
@@ -107,7 +107,7 @@ impl From<&str> for Item {
fn from(s: &str) -> Self {
Self {
name: s.to_owned(),
- color: Default::default(),
+ color: Color::default(),
}
}
}
diff --git a/examples/loading_spinners/Cargo.toml b/examples/loading_spinners/Cargo.toml
index ee9a48aa..a32da386 100644
--- a/examples/loading_spinners/Cargo.toml
+++ b/examples/loading_spinners/Cargo.toml
@@ -6,6 +6,8 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["advanced", "canvas"] }
-lyon_algorithms = "1"
-once_cell = "1"
+iced.workspace = true
+iced.features = ["advanced", "canvas"]
+
+lyon_algorithms = "1.0"
+once_cell.workspace = true \ No newline at end of file
diff --git a/examples/loading_spinners/README.md b/examples/loading_spinners/README.md
index 3573c6f6..75b88804 100644
--- a/examples/loading_spinners/README.md
+++ b/examples/loading_spinners/README.md
@@ -2,12 +2,6 @@
Example implementation of animated indeterminate loading spinners.
-<div align="center">
- <a href="https://gfycat.com/importantdevotedhammerheadbird">
- <img src="https://thumbs.gfycat.com/ImportantDevotedHammerheadbird-small.gif">
- </a>
-</div>
-
You can run it with `cargo run`:
```
cargo run --package loading_spinners
diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs
index ff599231..dca8046a 100644
--- a/examples/loading_spinners/src/circular.rs
+++ b/examples/loading_spinners/src/circular.rs
@@ -254,6 +254,7 @@ where
fn layout(
&self,
+ _tree: &mut Tree,
_renderer: &iced::Renderer<Theme>,
limits: &layout::Limits,
) -> layout::Node {
@@ -272,6 +273,7 @@ where
_renderer: &iced::Renderer<Theme>,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
+ _viewport: &Rectangle,
) -> event::Status {
const FRAME_RATE: u64 = 60;
diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs
index 8e07c12b..db10bfba 100644
--- a/examples/loading_spinners/src/linear.rs
+++ b/examples/loading_spinners/src/linear.rs
@@ -175,6 +175,7 @@ where
fn layout(
&self,
+ _tree: &mut Tree,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
@@ -193,6 +194,7 @@ where
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
+ _viewport: &Rectangle,
) -> event::Status {
const FRAME_RATE: u64 = 60;
diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml
index 3ac61e6a..009d9653 100644
--- a/examples/modal/Cargo.toml
+++ b/examples/modal/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["advanced"] }
+iced.workspace = true
+iced.features = ["advanced"]
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
index 7fcbbfe4..acb14372 100644
--- a/examples/modal/src/main.rs
+++ b/examples/modal/src/main.rs
@@ -1,12 +1,14 @@
+use iced::event::{self, Event};
use iced::executor;
use iced::keyboard;
-use iced::subscription::{self, Subscription};
use iced::theme;
use iced::widget::{
self, button, column, container, horizontal_space, pick_list, row, text,
text_input,
};
-use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
+use iced::{
+ Alignment, Application, Command, Element, Length, Settings, Subscription,
+};
use modal::Modal;
use std::fmt;
@@ -49,7 +51,7 @@ impl Application for App {
}
fn subscription(&self) -> Subscription<Self::Message> {
- subscription::events().map(Message::Event)
+ event::listen().map(Message::Event)
}
fn update(&mut self, message: Message) -> Command<Message> {
@@ -203,7 +205,8 @@ enum Plan {
}
impl Plan {
- pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise];
+ pub const ALL: &'static [Self] =
+ &[Self::Basic, Self::Pro, Self::Enterprise];
}
impl fmt::Display for Plan {
@@ -226,7 +229,10 @@ mod modal {
use iced::alignment::Alignment;
use iced::event;
use iced::mouse;
- use iced::{Color, Element, Event, Length, Point, Rectangle, Size};
+ use iced::{
+ BorderRadius, Color, Element, Event, Length, Point, Rectangle, Size,
+ Vector,
+ };
/// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> {
@@ -285,10 +291,15 @@ mod modal {
fn layout(
&self,
+ tree: &mut widget::Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- self.base.as_widget().layout(renderer, limits)
+ self.base.as_widget().layout(
+ &mut tree.children[0],
+ renderer,
+ limits,
+ )
}
fn on_event(
@@ -300,6 +311,7 @@ mod modal {
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
) -> event::Status {
self.base.as_widget_mut().on_event(
&mut state.children[0],
@@ -309,6 +321,7 @@ mod modal {
renderer,
clipboard,
shell,
+ viewport,
)
}
@@ -397,16 +410,21 @@ mod modal {
Message: Clone,
{
fn layout(
- &self,
+ &mut self,
renderer: &Renderer,
_bounds: Size,
position: Point,
+ _translation: Vector,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, self.size)
.width(Length::Fill)
.height(Length::Fill);
- let mut child = self.content.as_widget().layout(renderer, &limits);
+ let mut child = self
+ .content
+ .as_widget()
+ .layout(self.tree, renderer, &limits);
+
child.align(Alignment::Center, Alignment::Center, limits.max());
let mut node = layout::Node::with_children(self.size, vec![child]);
@@ -446,6 +464,7 @@ mod modal {
renderer,
clipboard,
shell,
+ &layout.bounds(),
)
}
@@ -460,7 +479,7 @@ mod modal {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
- border_radius: Default::default(),
+ border_radius: BorderRadius::default(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
diff --git a/examples/multi_window/src/main.rs b/examples/multi_window/src/main.rs
index 51ec3595..7d1f1e91 100644
--- a/examples/multi_window/src/main.rs
+++ b/examples/multi_window/src/main.rs
@@ -1,9 +1,10 @@
+use iced::event;
+use iced::executor;
use iced::multi_window::{self, Application};
use iced::widget::{button, column, container, scrollable, text, text_input};
-use iced::window::{Id, Position};
+use iced::window;
use iced::{
- executor, subscription, window, Alignment, Command, Element, Length,
- Settings, Subscription, Theme,
+ Alignment, Command, Element, Length, Settings, Subscription, Theme,
};
use std::collections::HashMap;
@@ -47,7 +48,7 @@ impl multi_window::Application for Example {
(
Example {
windows: HashMap::from([(window::Id::MAIN, Window::new(1))]),
- next_window_pos: Position::Default,
+ next_window_pos: window::Position::Default,
},
Command::none(),
)
@@ -129,7 +130,7 @@ impl multi_window::Application for Example {
.into()
}
- fn theme(&self, window: Id) -> Self::Theme {
+ fn theme(&self, window: window::Id) -> Self::Theme {
self.windows.get(&window).unwrap().theme.clone()
}
@@ -141,7 +142,7 @@ impl multi_window::Application for Example {
}
fn subscription(&self) -> Subscription<Self::Message> {
- subscription::events_with(|event, _| {
+ event::listen_with(|event, _| {
if let iced::Event::Window(id, window_event) = event {
match window_event {
window::Event::CloseRequested => {
diff --git a/examples/multitouch/Cargo.toml b/examples/multitouch/Cargo.toml
index f7c8c145..e0d14f58 100644
--- a/examples/multitouch/Cargo.toml
+++ b/examples/multitouch/Cargo.toml
@@ -6,7 +6,8 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-tokio = { version = "1.0", features = ["sync"] }
-env_logger = "0.9"
+iced.workspace = true
+iced.features = ["debug", "canvas", "tokio"]
+
+tracing-subscriber = "0.3"
voronator = "0.2"
diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs
index 2830b78d..ba8df7aa 100644
--- a/examples/multitouch/src/main.rs
+++ b/examples/multitouch/src/main.rs
@@ -13,7 +13,7 @@ use iced::{
use std::collections::HashMap;
pub fn main() -> iced::Result {
- env_logger::builder().format_timestamp(None).init();
+ tracing_subscriber::fmt::init();
Multitouch::run(Settings {
antialiasing: true,
diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml
index 4c0bf072..095ecd10 100644
--- a/examples/pane_grid/Cargo.toml
+++ b/examples/pane_grid/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug", "lazy"] }
+iced.workspace = true
+iced.features = ["debug", "lazy"]
diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md
index a4cfcb7d..65357b23 100644
--- a/examples/pane_grid/README.md
+++ b/examples/pane_grid/README.md
@@ -15,9 +15,7 @@ This example showcases the `PaneGrid` widget, which features:
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/frailfreshairedaleterrier">
- <img src="https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif">
- </a>
+ <img src="https://iced.rs/examples/pane_grid.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index 04896e20..aa3149bb 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -1,8 +1,6 @@
use iced::alignment::{self, Alignment};
-use iced::event::{self, Event};
use iced::executor;
use iced::keyboard;
-use iced::subscription;
use iced::theme::{self, Theme};
use iced::widget::pane_grid::{self, PaneGrid};
use iced::widget::{
@@ -63,11 +61,8 @@ impl Application for Example {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::Split(axis, pane) => {
- let result = self.panes.split(
- axis,
- &pane,
- Pane::new(self.panes_created),
- );
+ let result =
+ self.panes.split(axis, pane, Pane::new(self.panes_created));
if let Some((pane, _)) = result {
self.focus = Some(pane);
@@ -79,7 +74,7 @@ impl Application for Example {
if let Some(pane) = self.focus {
let result = self.panes.split(
axis,
- &pane,
+ pane,
Pane::new(self.panes_created),
);
@@ -92,8 +87,7 @@ impl Application for Example {
}
Message::FocusAdjacent(direction) => {
if let Some(pane) = self.focus {
- if let Some(adjacent) =
- self.panes.adjacent(&pane, direction)
+ if let Some(adjacent) = self.panes.adjacent(pane, direction)
{
self.focus = Some(adjacent);
}
@@ -103,37 +97,34 @@ impl Application for Example {
self.focus = Some(pane);
}
Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
- self.panes.resize(&split, ratio);
+ self.panes.resize(split, ratio);
}
Message::Dragged(pane_grid::DragEvent::Dropped {
pane,
target,
}) => {
- self.panes.drop(&pane, target);
+ self.panes.drop(pane, target);
}
Message::Dragged(_) => {}
Message::TogglePin(pane) => {
- if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane)
- {
+ if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(pane) {
*is_pinned = !*is_pinned;
}
}
- Message::Maximize(pane) => self.panes.maximize(&pane),
+ Message::Maximize(pane) => self.panes.maximize(pane),
Message::Restore => {
self.panes.restore();
}
Message::Close(pane) => {
- if let Some((_, sibling)) = self.panes.close(&pane) {
+ if let Some((_, sibling)) = self.panes.close(pane) {
self.focus = Some(sibling);
}
}
Message::CloseFocused => {
if let Some(pane) = self.focus {
- if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane)
- {
+ if let Some(Pane { is_pinned, .. }) = self.panes.get(pane) {
if !is_pinned {
- if let Some((_, sibling)) = self.panes.close(&pane)
- {
+ if let Some((_, sibling)) = self.panes.close(pane) {
self.focus = Some(sibling);
}
}
@@ -146,18 +137,12 @@ impl Application for Example {
}
fn subscription(&self) -> Subscription<Message> {
- subscription::events_with(|event, status| {
- if let event::Status::Captured = status {
+ keyboard::on_key_press(|key_code, modifiers| {
+ if !modifiers.command() {
return None;
}
- match event {
- Event::Keyboard(keyboard::Event::KeyPressed {
- modifiers,
- key_code,
- }) if modifiers.command() => handle_hotkey(key_code),
- _ => None,
- }
+ handle_hotkey(key_code)
})
}
diff --git a/examples/pick_list/Cargo.toml b/examples/pick_list/Cargo.toml
index 4aa4603a..030558e7 100644
--- a/examples/pick_list/Cargo.toml
+++ b/examples/pick_list/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug"] }
+iced.workspace = true
+iced.features = ["debug"]
diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml
index e99fc0c3..bf7e1e35 100644
--- a/examples/pokedex/Cargo.toml
+++ b/examples/pokedex/Cargo.toml
@@ -6,7 +6,9 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["image", "debug", "tokio"] }
+iced.workspace = true
+iced.features = ["image", "debug", "tokio"]
+
serde_json = "1.0"
[dependencies.serde]
@@ -19,5 +21,8 @@ default-features = false
features = ["json", "rustls-tls"]
[dependencies.rand]
-version = "0.7"
-features = ["wasm-bindgen"]
+version = "0.8"
+
+[dependencies.getrandom]
+version = "0.2"
+features = ["js"]
diff --git a/examples/pokedex/README.md b/examples/pokedex/README.md
index 50720f57..8e8562ac 100644
--- a/examples/pokedex/README.md
+++ b/examples/pokedex/README.md
@@ -4,9 +4,7 @@ An application that loads a random Pokédex entry using the [PokéAPI].
All the example code can be found in the __[`main`](src/main.rs)__ file.
<div align="center">
- <a href="https://gfycat.com/aggressivedarkelephantseal-rust-gui">
- <img src="https://thumbs.gfycat.com/AggressiveDarkElephantseal-small.gif" height="400px">
- </a>
+ <img src="https://iced.rs/examples/pokedex.gif">
</div>
You can run it on native platforms with `cargo run`:
diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs
index 1873b674..8b71a269 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -151,9 +151,9 @@ impl Pokemon {
}
let id = {
- let mut rng = rand::rngs::OsRng::default();
+ let mut rng = rand::rngs::OsRng;
- rng.gen_range(0, Pokemon::TOTAL)
+ rng.gen_range(0..Pokemon::TOTAL)
};
let fetch_entry = async {
diff --git a/examples/progress_bar/Cargo.toml b/examples/progress_bar/Cargo.toml
index 383a9bdd..6624ae15 100644
--- a/examples/progress_bar/Cargo.toml
+++ b/examples/progress_bar/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced.workspace = true
diff --git a/examples/progress_bar/README.md b/examples/progress_bar/README.md
index 1e927b3c..a87829c6 100644
--- a/examples/progress_bar/README.md
+++ b/examples/progress_bar/README.md
@@ -5,9 +5,7 @@ A simple progress bar that can be filled by using a slider.
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/importantdevotedhammerheadbird">
- <img src="https://thumbs.gfycat.com/ImportantDevotedHammerheadbird-small.gif">
- </a>
+ <img src="https://iced.rs/examples/progress_bar.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml
index 2f164df6..8f33ea8c 100644
--- a/examples/qr_code/Cargo.toml
+++ b/examples/qr_code/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["qr_code"] }
+iced.workspace = true
+iced.features = ["qr_code"]
diff --git a/examples/qr_code/README.md b/examples/qr_code/README.md
index 2dd89c26..0d1abaa7 100644
--- a/examples/qr_code/README.md
+++ b/examples/qr_code/README.md
@@ -5,9 +5,7 @@ A basic QR code generator that showcases the `QRCode` widget.
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/heavyexhaustedaracari">
- <img src="https://thumbs.gfycat.com/HeavyExhaustedAracari-size_restricted.gif">
- </a>
+ <img src="https://iced.rs/examples/qr_code.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml
index b79300b7..77b108bd 100644
--- a/examples/screenshot/Cargo.toml
+++ b/examples/screenshot/Cargo.toml
@@ -6,6 +6,12 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug", "image", "advanced"] }
-image = { version = "0.24.6", features = ["png"]}
-env_logger = "0.10.0"
+iced.workspace = true
+iced.features = ["debug", "image", "advanced", "tokio"]
+
+image.workspace = true
+image.features = ["png"]
+
+tokio.workspace = true
+
+tracing-subscriber = "0.3" \ No newline at end of file
diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs
index 7658384b..20d34be6 100644
--- a/examples/screenshot/src/main.rs
+++ b/examples/screenshot/src/main.rs
@@ -4,16 +4,15 @@ use iced::widget::{button, column, container, image, row, text, text_input};
use iced::window::screenshot::{self, Screenshot};
use iced::{alignment, window};
use iced::{
- event, executor, keyboard, subscription, Alignment, Application, Command,
- ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription,
- Theme,
+ event, executor, keyboard, Alignment, Application, Command, ContentFit,
+ Element, Event, Length, Rectangle, Renderer, Subscription, Theme,
};
use ::image as img;
use ::image::ColorType;
fn main() -> iced::Result {
- env_logger::builder().format_timestamp(None).init();
+ tracing_subscriber::fmt::init();
Example::run(iced::Settings::default())
}
@@ -188,8 +187,8 @@ impl Application for Example {
.align_items(Alignment::Center);
if let Some(crop_error) = &self.crop_error {
- crop_controls = crop_controls
- .push(text(format!("Crop error! \n{}", crop_error)));
+ crop_controls =
+ crop_controls.push(text(format!("Crop error! \n{crop_error}")));
}
let mut controls = column![
@@ -225,9 +224,9 @@ impl Application for Example {
if let Some(png_result) = &self.saved_png_path {
let msg = match png_result {
- Ok(path) => format!("Png saved as: {:?}!", path),
+ Ok(path) => format!("Png saved as: {path:?}!"),
Err(msg) => {
- format!("Png could not be saved due to:\n{:?}", msg)
+ format!("Png could not be saved due to:\n{msg:?}")
}
};
@@ -257,7 +256,7 @@ impl Application for Example {
}
fn subscription(&self) -> Subscription<Self::Message> {
- subscription::events_with(|event, status| {
+ event::listen_with(|event, status| {
if let event::Status::Captured = status {
return None;
}
@@ -277,15 +276,20 @@ impl Application for Example {
async fn save_to_png(screenshot: Screenshot) -> Result<String, PngError> {
let path = "screenshot.png".to_string();
- img::save_buffer(
- &path,
- &screenshot.bytes,
- screenshot.size.width,
- screenshot.size.height,
- ColorType::Rgba8,
- )
- .map(|_| path)
- .map_err(|err| PngError(format!("{:?}", err)))
+
+ tokio::task::spawn_blocking(move || {
+ img::save_buffer(
+ &path,
+ &screenshot.bytes,
+ screenshot.size.width,
+ screenshot.size.height,
+ ColorType::Rgba8,
+ )
+ .map(|_| path)
+ .map_err(|err| PngError(format!("{err:?}")))
+ })
+ .await
+ .expect("Blocking task to finish")
}
#[derive(Clone, Debug)]
@@ -297,10 +301,7 @@ fn numeric_input(
) -> Element<'_, Option<u32>> {
text_input(
placeholder,
- &value
- .as_ref()
- .map(ToString::to_string)
- .unwrap_or_else(String::new),
+ &value.as_ref().map(ToString::to_string).unwrap_or_default(),
)
.on_input(move |text| {
if text.is_empty() {
diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml
index e6411e26..f8c735c0 100644
--- a/examples/scrollable/Cargo.toml
+++ b/examples/scrollable/Cargo.toml
@@ -6,5 +6,7 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug"] }
-once_cell = "1.16.0"
+iced.workspace = true
+iced.features = ["debug"]
+
+once_cell.workspace = true
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index 8c08d993..d82ea841 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -389,14 +389,14 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
background: style
.active(&theme::Scrollable::default())
.background,
- border_radius: 0.0.into(),
+ border_radius: 2.0.into(),
border_width: 0.0,
- border_color: Default::default(),
+ border_color: Color::default(),
scroller: Scroller {
color: Color::from_rgb8(250, 85, 134),
- border_radius: 0.0.into(),
+ border_radius: 2.0.into(),
border_width: 0.0,
- border_color: Default::default(),
+ border_color: Color::default(),
},
}
} else {
diff --git a/examples/sierpinski_triangle/Cargo.toml b/examples/sierpinski_triangle/Cargo.toml
index 39d45f64..600a9e06 100644
--- a/examples/sierpinski_triangle/Cargo.toml
+++ b/examples/sierpinski_triangle/Cargo.toml
@@ -6,5 +6,7 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas", "debug"] }
-rand = "0.8.4"
+iced.workspace = true
+iced.features = ["debug", "canvas"]
+
+rand = "0.8"
diff --git a/examples/sierpinski_triangle/README.md b/examples/sierpinski_triangle/README.md
index 9fd18257..8b7676d1 100644
--- a/examples/sierpinski_triangle/README.md
+++ b/examples/sierpinski_triangle/README.md
@@ -5,9 +5,7 @@ A simple [Sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_tr
Left-click add fixed point, right-click remove fixed point.
<div align="center">
- <a href="https://gfycat.com/flippantrectangularechidna">
- <img src="https://thumbs.gfycat.com/FlippantRectangularEchidna-size_restricted.gif">
- </a>
+ <img src="https://iced.rs/examples/sierpinski_triangle.gif">
</div>
You can run with cargo:
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
index 885d3c63..ef935c33 100644
--- a/examples/sierpinski_triangle/src/main.rs
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -108,10 +108,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
- let cursor_position = if let Some(position) = cursor.position_in(bounds)
- {
- position
- } else {
+ let Some(cursor_position) = cursor.position_in(bounds) else {
return (event::Status::Ignored, None);
};
diff --git a/examples/slider/Cargo.toml b/examples/slider/Cargo.toml
index 112d7cff..fad8916e 100644
--- a/examples/slider/Cargo.toml
+++ b/examples/slider/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced.workspace = true
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
index 1a98a87e..ca64da14 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -6,6 +6,8 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-env_logger = "0.10.0"
+iced.workspace = true
+iced.features = ["debug", "canvas", "tokio"]
+
rand = "0.8.3"
+tracing-subscriber = "0.3"
diff --git a/examples/solar_system/README.md b/examples/solar_system/README.md
index acfbc466..81ffd3a5 100644
--- a/examples/solar_system/README.md
+++ b/examples/solar_system/README.md
@@ -5,9 +5,7 @@ An animated solar system drawn using the `Canvas` widget and showcasing how to c
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/selfassuredaromaticdunnart">
- <img src="https://thumbs.gfycat.com/SelfassuredAromaticDunnart-small.gif">
- </a>
+ <img src="https://iced.rs/examples/solar_system.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 58d06206..8295dded 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -23,7 +23,7 @@ use iced::{
use std::time::Instant;
pub fn main() -> iced::Result {
- env_logger::builder().format_timestamp(None).init();
+ tracing_subscriber::fmt::init();
SolarSystem::run(Settings {
antialiasing: true,
@@ -117,8 +117,8 @@ impl State {
let (width, height) = window::Settings::default().size;
State {
- space_cache: Default::default(),
- system_cache: Default::default(),
+ space_cache: canvas::Cache::default(),
+ system_cache: canvas::Cache::default(),
start: now,
now,
stars: Self::generate_stars(width, height),
diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml
index f623feb9..6b1419f6 100644
--- a/examples/stopwatch/Cargo.toml
+++ b/examples/stopwatch/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["smol"] }
+iced.workspace = true
+iced.features = ["smol"]
diff --git a/examples/stopwatch/README.md b/examples/stopwatch/README.md
index 4cf4582e..1cf370bd 100644
--- a/examples/stopwatch/README.md
+++ b/examples/stopwatch/README.md
@@ -5,9 +5,7 @@ A watch with start/stop and reset buttons showcasing how to listen to time.
The __[`main`]__ file contains all the code of the example.
<div align="center">
- <a href="https://gfycat.com/granularenviousgoitered-rust-gui">
- <img src="https://thumbs.gfycat.com/GranularEnviousGoitered-small.gif">
- </a>
+ <img src="https://iced.rs/examples/stopwatch.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs
index 9581a3ce..0b0f0607 100644
--- a/examples/stopwatch/src/main.rs
+++ b/examples/stopwatch/src/main.rs
@@ -1,5 +1,6 @@
use iced::alignment;
use iced::executor;
+use iced::keyboard;
use iced::theme::{self, Theme};
use iced::time;
use iced::widget::{button, column, container, row, text};
@@ -77,12 +78,25 @@ impl Application for Stopwatch {
}
fn subscription(&self) -> Subscription<Message> {
- match self.state {
+ let tick = match self.state {
State::Idle => Subscription::none(),
State::Ticking { .. } => {
time::every(Duration::from_millis(10)).map(Message::Tick)
}
+ };
+
+ fn handle_hotkey(
+ key_code: keyboard::KeyCode,
+ _modifiers: keyboard::Modifiers,
+ ) -> Option<Message> {
+ match key_code {
+ keyboard::KeyCode::Space => Some(Message::Toggle),
+ keyboard::KeyCode::R => Some(Message::Reset),
+ _ => None,
+ }
}
+
+ Subscription::batch(vec![tick, keyboard::on_key_press(handle_hotkey)])
}
fn view(&self) -> Element<Message> {
@@ -134,4 +148,8 @@ impl Application for Stopwatch {
.center_y()
.into()
}
+
+ fn theme(&self) -> Theme {
+ Theme::Dark
+ }
}
diff --git a/examples/styling/Cargo.toml b/examples/styling/Cargo.toml
index f771708c..c8a90258 100644
--- a/examples/styling/Cargo.toml
+++ b/examples/styling/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced.workspace = true
diff --git a/examples/styling/README.md b/examples/styling/README.md
index 6c198a54..fd12300d 100644
--- a/examples/styling/README.md
+++ b/examples/styling/README.md
@@ -4,9 +4,7 @@ An example showcasing custom styling with a light and dark theme.
All the example code is located in the __[`main`](src/main.rs)__ file.
<div align="center">
- <a href="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif">
- <img src="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif" height="400px">
- </a>
+ <img src="https://iced.rs/examples/styling.gif">
</div>
You can run it with `cargo run`:
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index f8a4c80a..51538ec2 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -108,6 +108,7 @@ impl Sandbox for Styling {
column!["Scroll me!", vertical_space(800), "You did it!"]
.width(Length::Fill),
)
+ .width(Length::Fill)
.height(100);
let checkbox = checkbox(
diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml
index f5a6eaa2..78208fb0 100644
--- a/examples/svg/Cargo.toml
+++ b/examples/svg/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["svg"] }
+iced.workspace = true
+iced.features = ["svg"]
diff --git a/examples/system_information/Cargo.toml b/examples/system_information/Cargo.toml
index 7d1e4b94..41903122 100644
--- a/examples/system_information/Cargo.toml
+++ b/examples/system_information/Cargo.toml
@@ -6,5 +6,7 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["system"] }
-bytesize = { version = "1.1.0" }
+iced.workspace = true
+iced.features = ["system"]
+
+bytesize = "1.1"
diff --git a/examples/system_information/src/main.rs b/examples/system_information/src/main.rs
index 633b6e2b..507431ee 100644
--- a/examples/system_information/src/main.rs
+++ b/examples/system_information/src/main.rs
@@ -105,8 +105,8 @@ impl Application for Example {
ByteSize::kb(information.memory_total).to_string();
let memory_total = text(format!(
- "Memory (total): {} kb ({})",
- information.memory_total, memory_readable
+ "Memory (total): {} kb ({memory_readable})",
+ information.memory_total,
));
let memory_text = if let Some(memory_used) =
diff --git a/examples/toast/Cargo.toml b/examples/toast/Cargo.toml
index f703572c..113313e2 100644
--- a/examples/toast/Cargo.toml
+++ b/examples/toast/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["advanced"] }
+iced.workspace = true
+iced.features = ["advanced"]
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index e28c4236..31b6f191 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -1,10 +1,12 @@
+use iced::event::{self, Event};
use iced::executor;
use iced::keyboard;
-use iced::subscription::{self, Subscription};
use iced::widget::{
self, button, column, container, pick_list, row, slider, text, text_input,
};
-use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
+use iced::{
+ Alignment, Application, Command, Element, Length, Settings, Subscription,
+};
use toast::{Status, Toast};
@@ -57,7 +59,7 @@ impl Application for App {
}
fn subscription(&self) -> Subscription<Self::Message> {
- subscription::events().map(Message::Event)
+ event::listen().map(Message::Event)
}
fn update(&mut self, message: Message) -> Command<Message> {
@@ -208,7 +210,7 @@ mod toast {
}
impl Status {
- pub const ALL: &[Self] =
+ pub const ALL: &'static [Self] =
&[Self::Primary, Self::Secondary, Self::Success, Self::Danger];
}
@@ -326,10 +328,15 @@ mod toast {
fn layout(
&self,
+ tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- self.content.as_widget().layout(renderer, limits)
+ self.content.as_widget().layout(
+ &mut tree.children[0],
+ renderer,
+ limits,
+ )
}
fn tag(&self) -> widget::tree::Tag {
@@ -381,7 +388,7 @@ mod toast {
renderer: &Renderer,
operation: &mut dyn Operation<Message>,
) {
- operation.container(None, &mut |operation| {
+ operation.container(None, layout.bounds(), &mut |operation| {
self.content.as_widget().operate(
&mut state.children[0],
layout,
@@ -400,6 +407,7 @@ mod toast {
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
) -> event::Status {
self.content.as_widget_mut().on_event(
&mut state.children[0],
@@ -409,6 +417,7 @@ mod toast {
renderer,
clipboard,
shell,
+ viewport,
)
}
@@ -498,10 +507,11 @@ mod toast {
for Overlay<'a, 'b, Message>
{
fn layout(
- &self,
+ &mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
+ _translation: Vector,
) -> layout::Node {
let limits = layout::Limits::new(Size::ZERO, bounds)
.width(Length::Fill)
@@ -515,6 +525,7 @@ mod toast {
10.0,
Alignment::End,
self.toasts,
+ self.state,
)
.translate(Vector::new(position.x, position.y))
}
@@ -561,6 +572,8 @@ mod toast {
}
}
+ let viewport = layout.bounds();
+
self.toasts
.iter_mut()
.zip(self.state.iter_mut())
@@ -578,6 +591,7 @@ mod toast {
renderer,
clipboard,
&mut local_shell,
+ &viewport,
);
if !local_shell.is_empty() {
@@ -619,7 +633,7 @@ mod toast {
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
- operation.container(None, &mut |operation| {
+ operation.container(None, layout.bounds(), &mut |operation| {
self.toasts
.iter()
.zip(self.state.iter_mut())
@@ -628,7 +642,7 @@ mod toast {
child
.as_widget()
.operate(state, layout, renderer, operation);
- })
+ });
});
}
diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml
index 7ad4d558..3c62bfbc 100644
--- a/examples/todos/Cargo.toml
+++ b/examples/todos/Cargo.toml
@@ -6,18 +6,26 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["async-std", "debug"] }
+iced.workspace = true
+iced.features = ["async-std", "debug"]
+
+once_cell.workspace = true
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
-once_cell = "1.15"
+uuid = { version = "1.0", features = ["v4", "fast-rng", "serde"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-async-std = "1.0"
+async-std.workspace = true
directories-next = "2.0"
+tracing-subscriber = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies]
-web-sys = { version = "0.3", features = ["Window", "Storage"] }
-wasm-timer = "0.2"
+iced.workspace = true
+iced.features = ["debug", "webgl"]
+
+uuid = { version = "1.0", features = ["js"] }
+web-sys = { workspace = true, features = ["Window", "Storage"] }
+wasm-timer.workspace = true
[package.metadata.deb]
assets = [
diff --git a/examples/todos/README.md b/examples/todos/README.md
index 9c2598b9..5e42f166 100644
--- a/examples/todos/README.md
+++ b/examples/todos/README.md
@@ -5,8 +5,8 @@ A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input,
All the example code is located in the __[`main`]__ file.
<div align="center">
- <a href="https://gfycat.com/littlesanehalicore">
- <img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="400px">
+ <a href="https://iced.rs/examples/todos.mp4">
+ <img src="https://iced.rs/examples/todos.gif">
</a>
</div>
@@ -14,7 +14,14 @@ You can run the native version with `cargo run`:
```
cargo run --package todos
```
-We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_!
+
+The web version can be run with [`trunk`]:
+
+```
+cd examples/todos
+trunk serve
+```
[`main`]: src/main.rs
[TodoMVC]: http://todomvc.com/
+[`trunk`]: https://trunkrs.dev/
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 04c8f618..a7ba69b9 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -1,12 +1,10 @@
use iced::alignment::{self, Alignment};
-use iced::event::{self, Event};
use iced::font::{self, Font};
-use iced::keyboard::{self, KeyCode, Modifiers};
-use iced::subscription;
+use iced::keyboard;
use iced::theme::{self, Theme};
use iced::widget::{
- self, button, checkbox, column, container, row, scrollable, text,
- text_input, Text,
+ self, button, checkbox, column, container, keyed_column, row, scrollable,
+ text, text_input, Text,
};
use iced::window;
use iced::{Application, Element};
@@ -14,10 +12,14 @@ use iced::{Color, Command, Length, Settings, Subscription};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
+use uuid::Uuid;
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
pub fn main() -> iced::Result {
+ #[cfg(not(target_arch = "wasm32"))]
+ tracing_subscriber::fmt::init();
+
Todos::run(Settings {
window: window::Settings {
size: (500, 800),
@@ -222,17 +224,19 @@ impl Application for Todos {
tasks.iter().filter(|task| filter.matches(task));
let tasks: Element<_> = if filtered_tasks.count() > 0 {
- column(
+ keyed_column(
tasks
.iter()
.enumerate()
.filter(|(_, task)| filter.matches(task))
.map(|(i, task)| {
- task.view(i).map(move |message| {
- Message::TaskMessage(i, message)
- })
- })
- .collect(),
+ (
+ task.id,
+ task.view(i).map(move |message| {
+ Message::TaskMessage(i, message)
+ }),
+ )
+ }),
)
.spacing(10)
.into()
@@ -262,39 +266,27 @@ impl Application for Todos {
}
fn subscription(&self) -> Subscription<Message> {
- subscription::events_with(|event, status| match (event, status) {
- (
- Event::Keyboard(keyboard::Event::KeyPressed {
- key_code: keyboard::KeyCode::Tab,
- modifiers,
- ..
- }),
- event::Status::Ignored,
- ) => Some(Message::TabPressed {
- shift: modifiers.shift(),
- }),
- (
- Event::Keyboard(keyboard::Event::KeyPressed {
- key_code,
- modifiers: Modifiers::SHIFT,
+ keyboard::on_key_press(|key_code, modifiers| {
+ match (key_code, modifiers) {
+ (keyboard::KeyCode::Tab, _) => Some(Message::TabPressed {
+ shift: modifiers.shift(),
}),
- event::Status::Ignored,
- ) => match key_code {
- KeyCode::Up => {
+ (keyboard::KeyCode::Up, keyboard::Modifiers::SHIFT) => {
Some(Message::ToggleFullscreen(window::Mode::Fullscreen))
}
- KeyCode::Down => {
+ (keyboard::KeyCode::Down, keyboard::Modifiers::SHIFT) => {
Some(Message::ToggleFullscreen(window::Mode::Windowed))
}
_ => None,
- },
- _ => None,
+ }
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Task {
+ #[serde(default = "Uuid::new_v4")]
+ id: Uuid,
description: String,
completed: bool,
@@ -330,6 +322,7 @@ impl Task {
fn new(description: String) -> Self {
Task {
+ id: Uuid::new_v4(),
description,
completed: false,
state: TaskState::Idle,
@@ -422,8 +415,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
row![
text(format!(
- "{} {} left",
- tasks_left,
+ "{tasks_left} {} left",
if tasks_left == 1 { "task" } else { "tasks" }
))
.width(Length::Fill),
@@ -451,7 +443,7 @@ pub enum Filter {
}
impl Filter {
- fn matches(&self, task: &Task) -> bool {
+ fn matches(self, task: &Task) -> bool {
match self {
Filter::All => true,
Filter::Active => !task.completed,
diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml
index 25840fb4..57bb0dcb 100644
--- a/examples/tooltip/Cargo.toml
+++ b/examples/tooltip/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["debug"] }
+iced.workspace = true
+iced.features = ["debug"]
diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs
index 35b862a8..a904cce0 100644
--- a/examples/tooltip/src/main.rs
+++ b/examples/tooltip/src/main.rs
@@ -40,7 +40,7 @@ impl Sandbox for Example {
Position::Right => Position::FollowCursor,
};
- self.position = position
+ self.position = position;
}
}
}
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
index 48471f2d..9e984ad1 100644
--- a/examples/tour/Cargo.toml
+++ b/examples/tour/Cargo.toml
@@ -6,5 +6,15 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["image", "debug"] }
-env_logger = "0.10.0"
+iced.workspace = true
+iced.features = ["image", "debug"]
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+tracing-subscriber = "0.3"
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+iced.workspace = true
+iced.features = ["image", "debug", "webgl"]
+
+console_error_panic_hook = "0.1"
+console_log = "1.0"
diff --git a/examples/tour/README.md b/examples/tour/README.md
index 731e7e66..1c01236b 100644
--- a/examples/tour/README.md
+++ b/examples/tour/README.md
@@ -5,8 +5,8 @@ A simple UI tour that can run both on native platforms and the web! It showcases
The __[`main`]__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__.
<div align="center">
- <a href="https://gfycat.com/politeadorableiberianmole">
- <img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
+ <a href="https://iced.rs/examples/tour.mp4">
+ <img src="https://iced.rs/examples/tour.gif">
</a>
</div>
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index 13bcd5ff..7003d8ae 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -1,14 +1,21 @@
-use iced::alignment;
+use iced::alignment::{self, Alignment};
use iced::theme;
use iced::widget::{
checkbox, column, container, horizontal_space, image, radio, row,
scrollable, slider, text, text_input, toggler, vertical_space,
};
use iced::widget::{Button, Column, Container, Slider};
-use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
+use iced::{Color, Element, Font, Length, Pixels, Renderer, Sandbox, Settings};
pub fn main() -> iced::Result {
- env_logger::init();
+ #[cfg(target_arch = "wasm32")]
+ {
+ console_log::init().expect("Initialize logger");
+ std::panic::set_hook(Box::new(console_error_panic_hook::hook));
+ }
+
+ #[cfg(not(target_arch = "wasm32"))]
+ tracing_subscriber::fmt::init();
Tour::run(Settings::default())
}
@@ -119,7 +126,10 @@ impl Steps {
Step::Toggler {
can_continue: false,
},
- Step::Image { width: 300 },
+ Step::Image {
+ width: 300,
+ filter_method: image::FilterMethod::Linear,
+ },
Step::Scrollable,
Step::TextInput {
value: String::new(),
@@ -188,6 +198,7 @@ enum Step {
},
Image {
width: u16,
+ filter_method: image::FilterMethod,
},
Scrollable,
TextInput {
@@ -208,6 +219,7 @@ pub enum StepMessage {
TextColorChanged(Color),
LanguageSelected(Language),
ImageWidthChanged(u16),
+ ImageUseNearestToggled(bool),
InputChanged(String),
ToggleSecureInput(bool),
ToggleTextInputIcon(bool),
@@ -258,6 +270,15 @@ impl<'a> Step {
*width = new_width;
}
}
+ StepMessage::ImageUseNearestToggled(use_nearest) => {
+ if let Step::Image { filter_method, .. } = self {
+ *filter_method = if use_nearest {
+ image::FilterMethod::Nearest
+ } else {
+ image::FilterMethod::Linear
+ };
+ }
+ }
StepMessage::InputChanged(new_value) => {
if let Step::TextInput { value, .. } = self {
*value = new_value;
@@ -278,7 +299,7 @@ impl<'a> Step {
is_showing_icon, ..
} = self
{
- *is_showing_icon = toggle
+ *is_showing_icon = toggle;
}
}
};
@@ -323,7 +344,10 @@ impl<'a> Step {
Step::Toggler { can_continue } => Self::toggler(*can_continue),
Step::Slider { value } => Self::slider(*value),
Step::Text { size, color } => Self::text(*size, *color),
- Step::Image { width } => Self::image(*width),
+ Step::Image {
+ width,
+ filter_method,
+ } => Self::image(*width, *filter_method),
Step::RowsAndColumns { layout, spacing } => {
Self::rows_and_columns(*layout, *spacing)
}
@@ -475,7 +499,7 @@ impl<'a> Step {
column(
Language::all()
.iter()
- .cloned()
+ .copied()
.map(|language| {
radio(
language,
@@ -518,16 +542,25 @@ impl<'a> Step {
)
}
- fn image(width: u16) -> Column<'a, StepMessage> {
+ fn image(
+ width: u16,
+ filter_method: image::FilterMethod,
+ ) -> Column<'a, StepMessage> {
Self::container("Image")
.push("An image that tries to keep its aspect ratio.")
- .push(ferris(width))
+ .push(ferris(width, filter_method))
.push(slider(100..=500, width, StepMessage::ImageWidthChanged))
.push(
text(format!("Width: {width} px"))
.width(Length::Fill)
.horizontal_alignment(alignment::Horizontal::Center),
)
+ .push(checkbox(
+ "Use nearest interpolation",
+ filter_method == image::FilterMethod::Nearest,
+ StepMessage::ImageUseNearestToggled,
+ ))
+ .align_items(Alignment::Center)
}
fn scrollable() -> Column<'a, StepMessage> {
@@ -548,7 +581,7 @@ impl<'a> Step {
.horizontal_alignment(alignment::Horizontal::Center),
)
.push(vertical_space(4096))
- .push(ferris(300))
+ .push(ferris(300, image::FilterMethod::Linear))
.push(
text("You made it!")
.width(Length::Fill)
@@ -571,7 +604,7 @@ impl<'a> Step {
text_input = text_input.icon(text_input::Icon {
font: Font::default(),
code_point: '🚀',
- size: Some(28.0),
+ size: Some(Pixels(28.0)),
spacing: 10.0,
side: text_input::Side::Right,
});
@@ -639,7 +672,10 @@ impl<'a> Step {
}
}
-fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
+fn ferris<'a>(
+ width: u16,
+ filter_method: image::FilterMethod,
+) -> Container<'a, StepMessage> {
container(
// This should go away once we unify resource loading on native
// platforms
@@ -648,6 +684,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
} else {
image(format!("{}/images/ferris.png", env!("CARGO_MANIFEST_DIR")))
}
+ .filter_method(filter_method)
.width(width),
)
.width(Length::Fill)
diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml
index 4dcff92d..7bb9914b 100644
--- a/examples/url_handler/Cargo.toml
+++ b/examples/url_handler/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced.workspace = true
diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs
index f63fa06a..bf570123 100644
--- a/examples/url_handler/src/main.rs
+++ b/examples/url_handler/src/main.rs
@@ -1,6 +1,5 @@
-use iced::event::{Event, MacOS, PlatformSpecific};
+use iced::event::{self, Event};
use iced::executor;
-use iced::subscription;
use iced::widget::{container, text};
use iced::{
Application, Command, Element, Length, Settings, Subscription, Theme,
@@ -37,9 +36,11 @@ impl Application for App {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::EventOccurred(event) => {
- if let Event::PlatformSpecific(PlatformSpecific::MacOS(
- MacOS::ReceivedUrl(url),
- )) = event
+ if let Event::PlatformSpecific(
+ event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
+ url,
+ )),
+ ) = event
{
self.url = Some(url);
}
@@ -50,7 +51,7 @@ impl Application for App {
}
fn subscription(&self) -> Subscription<Message> {
- subscription::events().map(Message::EventOccurred)
+ event::listen().map(Message::EventOccurred)
}
fn view(&self) -> Element<Message> {
diff --git a/examples/visible_bounds/Cargo.toml b/examples/visible_bounds/Cargo.toml
new file mode 100644
index 00000000..37594b84
--- /dev/null
+++ b/examples/visible_bounds/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "visible_bounds"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced.workspace = true
+iced.features = ["debug"]
+
+once_cell.workspace = true
diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs
new file mode 100644
index 00000000..fdf1e0f9
--- /dev/null
+++ b/examples/visible_bounds/src/main.rs
@@ -0,0 +1,187 @@
+use iced::event::{self, Event};
+use iced::executor;
+use iced::mouse;
+use iced::theme::{self, Theme};
+use iced::widget::{
+ column, container, horizontal_space, row, scrollable, text, vertical_space,
+};
+use iced::window;
+use iced::{
+ Alignment, Application, Color, Command, Element, Font, Length, Point,
+ Rectangle, Settings, Subscription,
+};
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+struct Example {
+ mouse_position: Option<Point>,
+ outer_bounds: Option<Rectangle>,
+ inner_bounds: Option<Rectangle>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ MouseMoved(Point),
+ WindowResized,
+ Scrolled(scrollable::Viewport),
+ OuterBoundsFetched(Option<Rectangle>),
+ InnerBoundsFetched(Option<Rectangle>),
+}
+
+impl Application for Example {
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+ type Executor = executor::Default;
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
+ (
+ Self {
+ mouse_position: None,
+ outer_bounds: None,
+ inner_bounds: None,
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Visible bounds - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::MouseMoved(position) => {
+ self.mouse_position = Some(position);
+
+ Command::none()
+ }
+ Message::Scrolled(_) | Message::WindowResized => {
+ Command::batch(vec![
+ container::visible_bounds(OUTER_CONTAINER.clone())
+ .map(Message::OuterBoundsFetched),
+ container::visible_bounds(INNER_CONTAINER.clone())
+ .map(Message::InnerBoundsFetched),
+ ])
+ }
+ Message::OuterBoundsFetched(outer_bounds) => {
+ self.outer_bounds = outer_bounds;
+
+ Command::none()
+ }
+ Message::InnerBoundsFetched(inner_bounds) => {
+ self.inner_bounds = inner_bounds;
+
+ Command::none()
+ }
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let data_row = |label, value, color| {
+ row![
+ text(label),
+ horizontal_space(Length::Fill),
+ text(value).font(Font::MONOSPACE).size(14).style(color),
+ ]
+ .height(40)
+ .align_items(Alignment::Center)
+ };
+
+ let view_bounds = |label, bounds: Option<Rectangle>| {
+ data_row(
+ label,
+ match bounds {
+ Some(bounds) => format!("{bounds:?}"),
+ None => "not visible".to_string(),
+ },
+ if bounds
+ .zip(self.mouse_position)
+ .map(|(bounds, mouse_position)| {
+ bounds.contains(mouse_position)
+ })
+ .unwrap_or_default()
+ {
+ Color {
+ g: 1.0,
+ ..Color::BLACK
+ }
+ .into()
+ } else {
+ theme::Text::Default
+ },
+ )
+ };
+
+ column![
+ data_row(
+ "Mouse position",
+ match self.mouse_position {
+ Some(Point { x, y }) => format!("({x}, {y})"),
+ None => "unknown".to_string(),
+ },
+ theme::Text::Default,
+ ),
+ view_bounds("Outer container", self.outer_bounds),
+ view_bounds("Inner container", self.inner_bounds),
+ scrollable(
+ column![
+ text("Scroll me!"),
+ vertical_space(400),
+ container(text("I am the outer container!"))
+ .id(OUTER_CONTAINER.clone())
+ .padding(40)
+ .style(theme::Container::Box),
+ vertical_space(400),
+ scrollable(
+ column![
+ text("Scroll me!"),
+ vertical_space(400),
+ container(text("I am the inner container!"))
+ .id(INNER_CONTAINER.clone())
+ .padding(40)
+ .style(theme::Container::Box),
+ vertical_space(400)
+ ]
+ .padding(20)
+ )
+ .on_scroll(Message::Scrolled)
+ .width(Length::Fill)
+ .height(300),
+ ]
+ .padding(20)
+ )
+ .on_scroll(Message::Scrolled)
+ .width(Length::Fill)
+ .height(300),
+ ]
+ .spacing(10)
+ .padding(20)
+ .into()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ event::listen_with(|event, _| match event {
+ Event::Mouse(mouse::Event::CursorMoved { position }) => {
+ Some(Message::MouseMoved(position))
+ }
+ Event::Window(_, window::Event::Resized { .. }) => {
+ Some(Message::WindowResized)
+ }
+ _ => None,
+ })
+ }
+
+ fn theme(&self) -> Theme {
+ Theme::Dark
+ }
+}
+
+use once_cell::sync::Lazy;
+
+static OUTER_CONTAINER: Lazy<container::Id> =
+ Lazy::new(|| container::Id::new("outer"));
+static INNER_CONTAINER: Lazy<container::Id> =
+ Lazy::new(|| container::Id::new("inner"));
diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml
index 03b240c6..2756e8e0 100644
--- a/examples/websocket/Cargo.toml
+++ b/examples/websocket/Cargo.toml
@@ -6,16 +6,16 @@ edition = "2021"
publish = false
[dependencies]
-iced = { path = "../..", features = ["tokio", "debug"] }
-once_cell = "1.15"
+iced.workspace = true
+iced.features = ["debug", "tokio"]
+
+once_cell.workspace = true
+warp = "0.3"
[dependencies.async-tungstenite]
-version = "0.16"
+version = "0.23"
features = ["tokio-rustls-webpki-roots"]
[dependencies.tokio]
-version = "1"
+workspace = true
features = ["time"]
-
-[dependencies.warp]
-version = "0.3"
diff --git a/examples/websocket/src/echo/server.rs b/examples/websocket/src/echo/server.rs
index 168a635e..a696a7a4 100644
--- a/examples/websocket/src/echo/server.rs
+++ b/examples/websocket/src/echo/server.rs
@@ -47,10 +47,7 @@ async fn user_connected(ws: WebSocket) {
});
while let Some(result) = user_ws_rx.next().await {
- let msg = match result {
- Ok(msg) => msg,
- Err(_) => break,
- };
+ let Ok(msg) = result else { break };
let _ = tx.send(msg).await;
}