Merge branch 'master' into feat/multi-window-support
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["canvas", "tokio", "debug"]
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl Application for Arc {
|
|||
(
|
||||
Arc {
|
||||
start: Instant::now(),
|
||||
cache: Default::default(),
|
||||
cache: Cache::default(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["canvas"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced.workspace = true
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
};
|
||||
|
||||
|
|
|
|||
10
examples/combo_box/Cargo.toml
Normal file
|
|
@ -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"]
|
||||
18
examples/combo_box/README.md
Normal file
|
|
@ -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
|
||||
BIN
examples/combo_box/combobox.gif
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
142
examples/combo_box/src/main.rs
Normal file
|
|
@ -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",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["debug", "lazy"]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["advanced"]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ mod quad {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
17
examples/custom_shader/Cargo.toml
Normal file
|
|
@ -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"
|
||||
163
examples/custom_shader/src/main.rs
Normal file
|
|
@ -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()
|
||||
}
|
||||
186
examples/custom_shader/src/scene.rs
Normal file
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
53
examples/custom_shader/src/scene/camera.rs
Normal file
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
619
examples/custom_shader/src/scene/pipeline.rs
Normal file
|
|
@ -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()
|
||||
}
|
||||
41
examples/custom_shader/src/scene/pipeline/buffer.rs
Normal file
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
326
examples/custom_shader/src/scene/pipeline/cube.rs
Normal file
|
|
@ -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),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
23
examples/custom_shader/src/scene/pipeline/uniforms.rs
Normal file
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
31
examples/custom_shader/src/scene/pipeline/vertex.rs
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
123
examples/custom_shader/src/shaders/cubes.wgsl
Normal file
|
|
@ -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;
|
||||
}
|
||||
48
examples/custom_shader/src/shaders/depth.wgsl
Normal file
|
|
@ -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);
|
||||
}
|
||||
BIN
examples/custom_shader/textures/ice_cube_normal_map.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
examples/custom_shader/textures/skybox/neg_x.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
examples/custom_shader/textures/skybox/neg_y.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
examples/custom_shader/textures/skybox/neg_z.jpg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
examples/custom_shader/textures/skybox/pos_x.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
examples/custom_shader/textures/skybox/pos_y.jpg
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
examples/custom_shader/textures/skybox/pos_z.jpg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["advanced"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ mod circle {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["tokio"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["tokio"]
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11"
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ impl Download {
|
|||
| State::Errored { .. } => {
|
||||
self.state = State::Downloading { progress: 0.0 };
|
||||
}
|
||||
_ => {}
|
||||
State::Downloading { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
15
examples/editor/Cargo.toml
Normal file
|
|
@ -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"
|
||||
BIN
examples/editor/fonts/icons.ttf
Normal file
312
examples/editor/src/main.rs
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["debug"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced.workspace = true
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["advanced"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ mod rainbow {
|
|||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut widget::Tree,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
|
|
|
|||
8
examples/gradient/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "gradient"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
99
examples/gradient/src/main.rs
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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"] }
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Controls {
|
|||
pub fn new() -> Controls {
|
||||
Controls {
|
||||
background_color: Color::BLACK,
|
||||
text: Default::default(),
|
||||
text: String::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["debug", "lazy"]
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["advanced"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["advanced"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug", "lazy"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["debug", "lazy"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["debug"]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced.workspace = true
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["qr_code"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["qr_code"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../.." }
|
||||
iced.workspace = true
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["smol"] }
|
||||
iced.workspace = true
|
||||
iced.features = ["smol"]
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||