summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/integration.yml3
-rw-r--r--Cargo.toml18
-rw-r--r--README.md3
-rw-r--r--core/src/background.rs7
-rw-r--r--core/src/color.rs27
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/widget/button.rs96
-rw-r--r--core/src/widget/image.rs28
-rw-r--r--examples/README.md84
-rw-r--r--examples/resources/Roboto-LICENSE (renamed from examples/tour/resources/Roboto-LICENSE)0
-rw-r--r--examples/resources/Roboto-Regular.ttf (renamed from examples/tour/resources/Roboto-Regular.ttf)bin171272 -> 171272 bytes
-rw-r--r--examples/resources/ferris.png (renamed from examples/tour/resources/ferris.png)bin33061 -> 33061 bytes
-rw-r--r--examples/resources/ui.png (renamed from examples/tour/resources/ui.png)bin16691 -> 16691 bytes
-rw-r--r--examples/tour.html (renamed from examples/tour/index.html)5
-rw-r--r--examples/tour.rs (renamed from examples/tour/src/tour.rs)122
-rw-r--r--examples/tour/Cargo.toml33
-rw-r--r--examples/tour/README.md66
-rw-r--r--examples/tour/src/iced_ggez.rs6
-rw-r--r--examples/tour/src/iced_ggez/renderer.rs77
-rw-r--r--examples/tour/src/iced_ggez/renderer/button.rs154
-rw-r--r--examples/tour/src/iced_ggez/renderer/checkbox.rs94
-rw-r--r--examples/tour/src/iced_ggez/renderer/debugger.rs32
-rw-r--r--examples/tour/src/iced_ggez/renderer/image.rs76
-rw-r--r--examples/tour/src/iced_ggez/renderer/radio.rs92
-rw-r--r--examples/tour/src/iced_ggez/renderer/slider.rs93
-rw-r--r--examples/tour/src/iced_ggez/renderer/text.rs110
-rw-r--r--examples/tour/src/iced_ggez/widget.rs12
-rw-r--r--examples/tour/src/lib.rs11
-rw-r--r--examples/tour/src/main.rs191
-rw-r--r--examples/tour/src/web.rs33
-rw-r--r--examples/tour/src/widget.rs5
-rw-r--r--native/Cargo.toml7
-rw-r--r--native/src/element.rs85
-rw-r--r--native/src/input/button_state.rs15
-rw-r--r--native/src/input/keyboard/key_code.rs176
-rw-r--r--native/src/input/mouse/button.rs17
-rw-r--r--native/src/lib.rs26
-rw-r--r--native/src/mouse_cursor.rs16
-rw-r--r--native/src/renderer.rs33
-rw-r--r--native/src/renderer/debugger.rs25
-rw-r--r--native/src/renderer/windowed.rs17
-rw-r--r--native/src/style.rs4
-rw-r--r--native/src/user_interface.rs64
-rw-r--r--native/src/widget.rs19
-rw-r--r--native/src/widget/button.rs32
-rw-r--r--native/src/widget/checkbox.rs16
-rw-r--r--native/src/widget/column.rs36
-rw-r--r--native/src/widget/image.rs28
-rw-r--r--native/src/widget/radio.rs12
-rw-r--r--native/src/widget/row.rs36
-rw-r--r--native/src/widget/slider.rs10
-rw-r--r--native/src/widget/text.rs14
-rw-r--r--src/lib.rs59
-rw-r--r--src/web.rs1
-rw-r--r--src/winit.rs11
-rw-r--r--web/Cargo.toml3
-rw-r--r--web/src/bus.rs4
-rw-r--r--web/src/element.rs8
-rw-r--r--web/src/lib.rs31
-rw-r--r--web/src/widget/button.rs10
-rw-r--r--web/src/widget/image.rs10
-rw-r--r--wgpu/Cargo.toml16
-rw-r--r--wgpu/src/image.rs438
-rw-r--r--wgpu/src/lib.rs12
-rw-r--r--wgpu/src/primitive.rs26
-rw-r--r--wgpu/src/quad.rs275
-rw-r--r--wgpu/src/renderer.rs329
-rw-r--r--wgpu/src/renderer/button.rs86
-rw-r--r--wgpu/src/renderer/checkbox.rs106
-rw-r--r--wgpu/src/renderer/column.rs34
-rw-r--r--wgpu/src/renderer/image.rs34
-rw-r--r--wgpu/src/renderer/radio.rs109
-rw-r--r--wgpu/src/renderer/row.rs34
-rw-r--r--wgpu/src/renderer/slider.rs128
-rw-r--r--wgpu/src/renderer/text.rs83
-rw-r--r--wgpu/src/shader/image.frag12
-rw-r--r--wgpu/src/shader/image.frag.spvbin0 -> 684 bytes
-rw-r--r--wgpu/src/shader/image.vert24
-rw-r--r--wgpu/src/shader/image.vert.spvbin0 -> 2136 bytes
-rw-r--r--wgpu/src/shader/quad.frag37
-rw-r--r--wgpu/src/shader/quad.frag.spvbin0 -> 3212 bytes
-rw-r--r--wgpu/src/shader/quad.vert32
-rw-r--r--wgpu/src/shader/quad.vert.spvbin0 -> 2544 bytes
-rw-r--r--wgpu/src/transformation.rs30
-rw-r--r--winit/Cargo.toml13
-rw-r--r--winit/src/application.rs185
-rw-r--r--winit/src/conversion.rs199
-rw-r--r--winit/src/lib.rs8
88 files changed, 2869 insertions, 1686 deletions
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index eb54d170..54ca2e33 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -24,6 +24,3 @@ jobs:
- uses: actions/checkout@master
- name: Run tests
run: cargo test --verbose --all --all-features
- - name: Build tour for WebAssembly
- if: matrix.targets == 'wasm32-unknown-unknown'
- run: cargo build --verbose --package iced_tour --lib --target wasm32-unknown-unknown
diff --git a/Cargo.toml b/Cargo.toml
index 0975aeeb..e8b53066 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced"
-version = "0.1.0-alpha"
+version = "0.1.0-alpha.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A cross-platform GUI library inspired by Elm"
@@ -19,5 +19,19 @@ members = [
"core",
"native",
"web",
- "examples/tour",
+ "wgpu",
+ "winit",
]
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+iced_winit = { version = "0.1.0-alpha", path = "winit" }
+iced_wgpu = { version = "0.1.0-alpha", path = "wgpu" }
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+iced_web = { version = "0.1.0-alpha", path = "web" }
+
+[dev-dependencies]
+env_logger = "0.7"
+
+[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
+wasm-bindgen = "0.2.51"
diff --git a/README.md b/README.md
index f7035fb4..fafa7b15 100644
--- a/README.md
+++ b/README.md
@@ -87,10 +87,9 @@ __view logic__:
```rust
use iced::{Button, Column, Text};
-use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own!
impl Counter {
- pub fn view(&mut self) -> Column<Message, Renderer> {
+ pub fn view(&mut self) -> Column<Message> {
// We use a column: a simple vertical layout
Column::new()
.push(
diff --git a/core/src/background.rs b/core/src/background.rs
new file mode 100644
index 00000000..59b67a2c
--- /dev/null
+++ b/core/src/background.rs
@@ -0,0 +1,7 @@
+use crate::Color;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Background {
+ Color(Color),
+ // TODO: Add gradient and image variants
+}
diff --git a/core/src/color.rs b/core/src/color.rs
index 5cc3a084..79910dd8 100644
--- a/core/src/color.rs
+++ b/core/src/color.rs
@@ -16,4 +16,31 @@ impl Color {
b: 0.0,
a: 1.0,
};
+
+ /// The white color.
+ pub const WHITE: Color = Color {
+ r: 1.0,
+ g: 1.0,
+ b: 1.0,
+ a: 1.0,
+ };
+
+ pub fn into_linear(self) -> [f32; 4] {
+ // As described in:
+ // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
+ fn linear_component(u: f32) -> f32 {
+ if u < 0.04045 {
+ u / 12.92
+ } else {
+ ((u + 0.055) / 1.055).powf(2.4)
+ }
+ }
+
+ [
+ linear_component(self.r),
+ linear_component(self.g),
+ linear_component(self.b),
+ self.a,
+ ]
+ }
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 1f43b2b7..877a8f85 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -1,6 +1,7 @@
pub mod widget;
mod align;
+mod background;
mod color;
mod justify;
mod length;
@@ -9,6 +10,7 @@ mod rectangle;
mod vector;
pub use align::Align;
+pub use background::Background;
pub use color::Color;
pub use justify::Justify;
pub use length::Length;
diff --git a/core/src/widget/button.rs b/core/src/widget/button.rs
index b98bb443..a57f2dd8 100644
--- a/core/src/widget/button.rs
+++ b/core/src/widget/button.rs
@@ -5,68 +5,58 @@
//! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html
-use crate::{Align, Length};
+use crate::{Align, Background, Length};
/// A generic widget that produces a message when clicked.
-///
-/// # Example
-///
-/// ```
-/// use iced_core::{button, Button};
-///
-/// pub enum Message {
-/// ButtonClicked,
-/// }
-///
-/// let state = &mut button::State::new();
-///
-/// Button::new(state, "Click me!")
-/// .on_press(Message::ButtonClicked);
-/// ```
-///
-/// ![Button drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button.png?raw=true)
-pub struct Button<'a, Message> {
+pub struct Button<'a, Message, Element> {
/// The current state of the button
pub state: &'a mut State,
- /// The label of the button
- pub label: String,
+ pub content: Element,
/// The message to produce when the button is pressed
pub on_press: Option<Message>,
- pub class: Class,
-
pub width: Length,
+ pub padding: u16,
+
+ pub background: Option<Background>,
+
+ pub border_radius: u16,
+
pub align_self: Option<Align>,
}
-impl<'a, Message> std::fmt::Debug for Button<'a, Message>
+impl<'a, Message, Element> std::fmt::Debug for Button<'a, Message, Element>
where
Message: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Button")
.field("state", &self.state)
- .field("label", &self.label)
.field("on_press", &self.on_press)
.finish()
}
}
-impl<'a, Message> Button<'a, Message> {
+impl<'a, Message, Element> Button<'a, Message, Element> {
/// Creates a new [`Button`] with some local [`State`] and the given label.
///
/// [`Button`]: struct.Button.html
/// [`State`]: struct.State.html
- pub fn new(state: &'a mut State, label: &str) -> Self {
+ pub fn new<E>(state: &'a mut State, content: E) -> Self
+ where
+ E: Into<Element>,
+ {
Button {
state,
- label: String::from(label),
+ content: content.into(),
on_press: None,
- class: Class::Primary,
width: Length::Shrink,
+ padding: 0,
+ background: None,
+ border_radius: 0,
align_self: None,
}
}
@@ -79,6 +69,21 @@ impl<'a, Message> Button<'a, Message> {
self
}
+ pub fn padding(mut self, padding: u16) -> Self {
+ self.padding = padding;
+ self
+ }
+
+ pub fn background(mut self, background: Background) -> Self {
+ self.background = Some(background);
+ self
+ }
+
+ pub fn border_radius(mut self, border_radius: u16) -> Self {
+ self.border_radius = border_radius;
+ self
+ }
+
/// Sets the alignment of the [`Button`] itself.
///
/// This is useful if you want to override the default alignment given by
@@ -90,16 +95,6 @@ impl<'a, Message> Button<'a, Message> {
self
}
- /// Sets the [`Class`] of the [`Button`].
- ///
- ///
- /// [`Button`]: struct.Button.html
- /// [`Class`]: enum.Class.html
- pub fn class(mut self, class: Class) -> Self {
- self.class = class;
- self
- }
-
/// Sets the message that will be produced when the [`Button`] is pressed.
///
/// [`Button`]: struct.Button.html
@@ -133,26 +128,3 @@ impl State {
self.is_pressed
}
}
-
-/// The type of a [`Button`].
-///
-/// ![Different buttons drawn by the built-in renderer in Coffee](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button_classes.png?raw=true)
-///
-/// [`Button`]: struct.Button.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Class {
- /// The [`Button`] performs the main action.
- ///
- /// [`Button`]: struct.Button.html
- Primary,
-
- /// The [`Button`] performs an alternative action.
- ///
- /// [`Button`]: struct.Button.html
- Secondary,
-
- /// The [`Button`] performs a productive action.
- ///
- /// [`Button`]: struct.Button.html
- Positive,
-}
diff --git a/core/src/widget/image.rs b/core/src/widget/image.rs
index 110ba99a..6e410dce 100644
--- a/core/src/widget/image.rs
+++ b/core/src/widget/image.rs
@@ -9,12 +9,12 @@ use crate::{Align, Length, Rectangle};
/// ```
/// use iced_core::Image;
///
-/// # let my_handle = String::from("some_handle");
-/// let image = Image::new(my_handle);
+/// let image = Image::new("resources/ferris.png");
/// ```
-pub struct Image<I> {
- /// The image handle
- pub handle: I,
+#[derive(Debug)]
+pub struct Image {
+ /// The image path
+ pub path: String,
/// The part of the image to show
pub clip: Option<Rectangle<u16>>,
@@ -28,23 +28,13 @@ pub struct Image<I> {
pub align_self: Option<Align>,
}
-impl<I> std::fmt::Debug for Image<I> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("Image")
- .field("clip", &self.clip)
- .field("width", &self.width)
- .field("height", &self.height)
- .finish()
- }
-}
-
-impl<I> Image<I> {
- /// Creates a new [`Image`] with given image handle.
+impl Image {
+ /// Creates a new [`Image`] with the given path.
///
/// [`Image`]: struct.Image.html
- pub fn new(handle: I) -> Self {
+ pub fn new<T: Into<String>>(path: T) -> Self {
Image {
- handle,
+ path: path.into(),
clip: None,
width: Length::Shrink,
height: Length::Shrink,
diff --git a/examples/README.md b/examples/README.md
index 4e83faf1..0a06a012 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,77 +1,63 @@
# Examples
-
__Iced moves fast and the `master` branch can contain breaking changes!__ If
you want to learn about a specific release, check out [the release list].
[the release list]: https://github.com/hecrj/iced/releases
-## [Tour](tour)
-
-A simple UI tour showcasing different widgets that can be built using Iced. It
-also shows how the library can be integrated into an existing system.
+## [Tour](tour.rs)
+A simple UI tour showcasing different widgets that can be built using Iced.
The example can run both on native and web platforms, using the same GUI code!
-The native renderer of the example is built on top of [`ggez`], a game library
-for Rust. Currently, it is using a [personal fork] to [add a `FontCache` type]
-and [fix some issues with HiDPI].
+[![Tour - Iced][gui_gif]][gui_gfycat]
-The web version uses `iced_web` directly. This crate is still a work in
-progress. In particular, the styling of elements is not finished yet
-(text color, alignment, sizing, etc).
+[gui_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif
+[gui_gfycat]: https://gfycat.com/veneratedsouraurochs
-The implementation consists of different modules:
- - __[`tour`]__ contains the actual cross-platform GUI code: __state__,
- __messages__, __update logic__ and __view logic__.
- - __[`iced_ggez`]__ implements a simple renderer for each of the used widgets
- on top of the graphics module of [`ggez`].
- - __[`widget`]__ conditionally re-exposes the correct platform widgets based
- on the target architecture.
- - __[`main`]__ integrates Iced with [`ggez`] and connects the [`tour`] with
- the native [`renderer`].
- - __[`lib`]__ exposes the [`tour`] types and conditionally implements the
- WebAssembly entrypoint in the [`web`] module.
+On native, the example uses:
+ - [`iced_winit`], as a bridge between [`iced_native`] and [`winit`].
+ - [`iced_wgpu`], a WIP Iced renderer built on top of [`wgpu`] and supporting
+ Vulkan, Metal, D3D11, and D3D12 (OpenGL and WebGL soon!).
-The conditional compilation awkwardness from targetting both native and web
-platforms should be handled seamlessly by the `iced` crate in the near future!
+The web version uses [`iced_web`], which is still a work in progress. In
+particular, the styling of elements is not finished yet (text color, alignment,
+sizing, etc).
-If you want to run it as a native app:
+The __[`tour`]__ 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__.
-```
-cd examples/tour
-cargo run
-```
+[`tour`]: tour.rs
+[`iced_winit`]: ../winit
+[`iced_native`]: ../native
+[`iced_wgpu`]: ../wgpu
+[`iced_web`]: ../web
+[`winit`]: https://github.com/rust-windowing/winit
+[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
-If you want to run it on web, you will need [`wasm-pack`]:
+#### Running the native version
+Use [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples)
+to run the example:
```
-cd examples/tour
-wasm-pack build --target web
+cargo run --example tour
```
-Then, simply serve the directory with any HTTP server. For instance:
+#### Running the web version
+Build using the `wasm32-unknown-unknown` target and use the [`wasm-bindgen`] CLI
+to generate appropriate bindings in a `tour` directory.
```
-python3 -m http.server
+cd examples
+cargo build --example tour --target wasm32-unknown-unknown
+wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web
```
-[![Tour - Iced][gui_gif]][gui_gfycat]
+Finally, serve the `examples` directory using an HTTP server and access the
+`tour.html` file.
-[`ggez`]: https://github.com/ggez/ggez
-[`tour`]: tour/src/tour.rs
-[`iced_ggez`]: tour/src/iced_ggez
-[`renderer`]: src/iced_ggez/renderer
-[`widget`]: tour/src/widget.rs
-[`main`]: tour/src/main.rs
-[`lib`]: tour/src/lib.rs
-[`web`]: tour/src/web.rs
-[`wasm-pack`]: https://rustwasm.github.io/wasm-pack/installer/
-[personal fork]: https://github.com/hecrj/ggez
-[add a `FontCache` type]: https://github.com/ggez/ggez/pull/679
-[fix some issues with HiDPI]: https://github.com/hecrj/ggez/commit/dfe2fd2423c51a6daf42c75f66dfaeaacd439fb1
-[gui_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif
-[gui_gfycat]: https://gfycat.com/veneratedsouraurochs
+[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen
## [Coffee]
diff --git a/examples/tour/resources/Roboto-LICENSE b/examples/resources/Roboto-LICENSE
index 75b52484..75b52484 100644
--- a/examples/tour/resources/Roboto-LICENSE
+++ b/examples/resources/Roboto-LICENSE
diff --git a/examples/tour/resources/Roboto-Regular.ttf b/examples/resources/Roboto-Regular.ttf
index 2b6392ff..2b6392ff 100644
--- a/examples/tour/resources/Roboto-Regular.ttf
+++ b/examples/resources/Roboto-Regular.ttf
Binary files differ
diff --git a/examples/tour/resources/ferris.png b/examples/resources/ferris.png
index ebce1a14..ebce1a14 100644
--- a/examples/tour/resources/ferris.png
+++ b/examples/resources/ferris.png
Binary files differ
diff --git a/examples/tour/resources/ui.png b/examples/resources/ui.png
index 4fd3beb3..4fd3beb3 100644
--- a/examples/tour/resources/ui.png
+++ b/examples/resources/ui.png
Binary files differ
diff --git a/examples/tour/index.html b/examples/tour.html
index b17ac4a2..35360e59 100644
--- a/examples/tour/index.html
+++ b/examples/tour.html
@@ -6,8 +6,9 @@
</head>
<body>
<script type="module">
- import init from "./pkg/iced_tour.js";
- init("./pkg/iced_tour_bg.wasm");
+ import init from "./tour/tour.js";
+
+ init('./tour/tour_bg.wasm');
</script>
</body>
</html>
diff --git a/examples/tour/src/tour.rs b/examples/tour.rs
index 60a251d2..59a8c525 100644
--- a/examples/tour/src/tour.rs
+++ b/examples/tour.rs
@@ -1,8 +1,17 @@
-use crate::widget::{
- button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color,
- Column, Element, Image, Length, Radio, Row, Slider, Text,
+use iced::{
+ button, slider, text::HorizontalAlignment, Align, Application, Background,
+ Button, Checkbox, Color, Column, Element, Image, Justify, Length, Radio,
+ Row, Slider, Text,
};
+pub fn main() {
+ env_logger::init();
+
+ let tour = Tour::new();
+
+ tour.run();
+}
+
pub struct Tour {
steps: Steps,
back_button: button::State,
@@ -19,8 +28,12 @@ impl Tour {
debug: false,
}
}
+}
+
+impl Application for Tour {
+ type Message = Message;
- pub fn update(&mut self, event: Message) {
+ fn update(&mut self, event: Message) {
match event {
Message::BackPressed => {
self.steps.go_back();
@@ -34,7 +47,7 @@ impl Tour {
}
}
- pub fn view(&mut self) -> Element<Message> {
+ fn view(&mut self) -> Element<Message> {
let Tour {
steps,
back_button,
@@ -46,9 +59,8 @@ impl Tour {
if steps.has_previous() {
controls = controls.push(
- Button::new(back_button, "Back")
- .on_press(Message::BackPressed)
- .class(button::Class::Secondary),
+ secondary_button(back_button, "Back")
+ .on_press(Message::BackPressed),
);
}
@@ -56,22 +68,32 @@ impl Tour {
if steps.can_continue() {
controls = controls.push(
- Button::new(next_button, "Next").on_press(Message::NextPressed),
+ primary_button(next_button, "Next")
+ .on_press(Message::NextPressed),
);
}
let element: Element<_> = Column::new()
- .max_width(Length::Units(500))
+ .max_width(Length::Units(540))
.spacing(20)
+ .padding(20)
.push(steps.view(self.debug).map(Message::StepMessage))
.push(controls)
.into();
- if self.debug {
+ let element = if self.debug {
element.explain(Color::BLACK)
} else {
element
- }
+ };
+
+ Column::new()
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .align_items(Align::Center)
+ .justify_content(Justify::Center)
+ .push(element)
+ .into()
}
}
@@ -286,7 +308,7 @@ impl<'a> Step {
that can be easily implemented on top of Iced.",
))
.push(Text::new(
- "Iced is a renderer-agnostic GUI library for Rust focused on \
+ "Iced is a cross-platform GUI library for Rust focused on \
simplicity and type-safety. It is heavily inspired by Elm.",
))
.push(Text::new(
@@ -294,9 +316,9 @@ impl<'a> Step {
2D game engine for Rust.",
))
.push(Text::new(
- "Iced does not provide a built-in renderer. On native \
- platforms, this example runs on a fairly simple renderer \
- built on top of ggez, another game library.",
+ "On native platforms, Iced provides by default a renderer \
+ built on top of wgpu, a graphics library supporting Vulkan, \
+ Metal, DX11, and DX12.",
))
.push(Text::new(
"Additionally, this tour can also run on WebAssembly thanks \
@@ -304,7 +326,7 @@ impl<'a> Step {
))
.push(Text::new(
"You will need to interact with the UI in order to reach the \
- end of this tour!",
+ end!",
))
}
@@ -481,9 +503,18 @@ impl<'a> Step {
Self::container("Image")
.push(Text::new("An image that tries to keep its aspect ratio."))
.push(
- Image::new("resources/ferris.png")
- .width(Length::Units(width))
- .align_self(Align::Center),
+ // This should go away once we unify resource loading on native
+ // platforms
+ if cfg!(target_arch = "wasm32") {
+ Image::new("resources/ferris.png")
+ } else {
+ Image::new(format!(
+ "{}/examples/resources/ferris.png",
+ env!("CARGO_MANIFEST_DIR")
+ ))
+ }
+ .width(Length::Units(width))
+ .align_self(Align::Center),
)
.push(Slider::new(
slider,
@@ -524,6 +555,44 @@ impl<'a> Step {
}
}
+fn button<'a, Message>(
+ state: &'a mut button::State,
+ label: &str,
+) -> Button<'a, Message> {
+ Button::new(
+ state,
+ Text::new(label)
+ .color(Color::WHITE)
+ .horizontal_alignment(HorizontalAlignment::Center),
+ )
+ .padding(12)
+ .border_radius(12)
+}
+
+fn primary_button<'a, Message>(
+ state: &'a mut button::State,
+ label: &str,
+) -> Button<'a, Message> {
+ button(state, label).background(Background::Color(Color {
+ r: 0.11,
+ g: 0.42,
+ b: 0.87,
+ a: 1.0,
+ }))
+}
+
+fn secondary_button<'a, Message>(
+ state: &'a mut button::State,
+ label: &str,
+) -> Button<'a, Message> {
+ button(state, label).background(Background::Color(Color {
+ r: 0.4,
+ g: 0.4,
+ b: 0.4,
+ a: 1.0,
+ }))
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
Rust,
@@ -565,3 +634,16 @@ pub enum Layout {
Row,
Column,
}
+
+// This should be gracefully handled by Iced in the future. Probably using our
+// own proc macro, or maybe the whole process is streamlined by `wasm-pack` at
+// some point.
+#[cfg(target_arch = "wasm32")]
+mod wasm {
+ use wasm_bindgen::prelude::*;
+
+ #[wasm_bindgen(start)]
+ pub fn run() {
+ super::main()
+ }
+}
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
deleted file mode 100644
index 2c79cbf7..00000000
--- a/examples/tour/Cargo.toml
+++ /dev/null
@@ -1,33 +0,0 @@
-[package]
-name = "iced_tour"
-version = "0.0.0"
-authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
-description = "Tour example for Iced"
-license = "MIT"
-repository = "https://github.com/hecrj/iced"
-edition = "2018"
-publish = false
-
-[lib]
-crate-type = ["cdylib", "rlib"]
-
-[[bin]]
-name = "main"
-path = "src/main.rs"
-
-[dependencies]
-futures-preview = "=0.3.0-alpha.18"
-
-[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-iced_native = { version = "0.1.0-alpha", path = "../../native" }
-# A personal `ggez` fork that introduces a `FontCache` type to measure text
-# efficiently and fixes HiDPI issues.
-ggez = { version = "0.5", git = "https://github.com/hecrj/ggez.git" }
-env_logger = "0.6"
-
-[target.'cfg(target_arch = "wasm32")'.dependencies]
-iced_web = { path = "../../web" }
-wasm-bindgen = "0.2.50"
-log = "0.4"
-console_error_panic_hook = "0.1.6"
-console_log = "0.1.2"
diff --git a/examples/tour/README.md b/examples/tour/README.md
deleted file mode 100644
index 7ef1a212..00000000
--- a/examples/tour/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Tour
-
-A simple UI tour showcasing different widgets that can be built using Iced. It
-also shows how the library can be integrated into an existing system.
-
-The example can run both on native and web platforms, using the same GUI code!
-
-The native renderer of the example is built on top of [`ggez`], a game library
-for Rust. Currently, it is using a [personal fork] to [add a `FontCache` type]
-and [fix some issues with HiDPI].
-
-The web version uses `iced_web` directly. This crate is still a work in
-progress. In particular, the styling of elements is not finished yet
-(text color, alignment, sizing, etc).
-
-The implementation consists of different modules:
- - __[`tour`]__ contains the actual cross-platform GUI code: __state__,
- __messages__, __update logic__ and __view logic__.
- - __[`iced_ggez`]__ implements a simple renderer for each of the used widgets
- on top of the graphics module of [`ggez`].
- - __[`widget`]__ conditionally re-exposes the correct platform widgets based
- on the target architecture.
- - __[`main`]__ integrates Iced with [`ggez`] and connects the [`tour`] with
- the native [`renderer`].
- - __[`lib`]__ exposes the [`tour`] types and conditionally implements the
- WebAssembly entrypoint in the [`web`] module.
-
-The conditional compilation awkwardness from targetting both native and web
-platforms should be handled seamlessly by the `iced` crate in the near future!
-
-If you want to run it as a native app:
-
-```
-cd examples/tour
-cargo run
-```
-
-If you want to run it on web, you will need [`wasm-pack`]:
-
-```
-cd examples/tour
-wasm-pack build --target web
-```
-
-Then, simply serve the directory with any HTTP server. For instance:
-
-```
-python3 -m http.server
-```
-
-[![Tour - Iced][gui_gif]][gui_gfycat]
-
-[`ggez`]: https://github.com/ggez/ggez
-[`tour`]: src/tour.rs
-[`iced_ggez`]: src/iced_ggez
-[`renderer`]: src/iced_ggez/renderer
-[`widget`]: src/widget.rs
-[`main`]: src/main.rs
-[`lib`]: src/lib.rs
-[`web`]: src/web.rs
-[`wasm-pack`]: https://rustwasm.github.io/wasm-pack/installer/
-[personal fork]: https://github.com/hecrj/ggez
-[add a `FontCache` type]: https://github.com/ggez/ggez/pull/679
-[fix some issues with HiDPI]: https://github.com/hecrj/ggez/commit/dfe2fd2423c51a6daf42c75f66dfaeaacd439fb1
-[gui_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif
-[gui_gfycat]: https://gfycat.com/veneratedsouraurochs
diff --git a/examples/tour/src/iced_ggez.rs b/examples/tour/src/iced_ggez.rs
deleted file mode 100644
index 4a9c0ef4..00000000
--- a/examples/tour/src/iced_ggez.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-mod renderer;
-mod widget;
-
-pub use renderer::Cache as ImageCache;
-pub use renderer::Renderer;
-pub use widget::*;
diff --git a/examples/tour/src/iced_ggez/renderer.rs b/examples/tour/src/iced_ggez/renderer.rs
deleted file mode 100644
index c0e6d559..00000000
--- a/examples/tour/src/iced_ggez/renderer.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-mod button;
-mod checkbox;
-mod debugger;
-mod image;
-mod radio;
-mod slider;
-mod text;
-
-use ggez::graphics::{
- self, spritebatch::SpriteBatch, Font, Image, MeshBuilder,
-};
-use ggez::Context;
-
-pub use image::Cache;
-
-pub struct Renderer<'a> {
- pub context: &'a mut Context,
- pub images: &'a mut image::Cache,
- pub sprites: SpriteBatch,
- pub spritesheet: Image,
- pub font: Font,
- font_size: f32,
- debug_mesh: Option<MeshBuilder>,
-}
-
-impl<'a> Renderer<'a> {
- pub fn new(
- context: &'a mut Context,
- images: &'a mut image::Cache,
- spritesheet: Image,
- font: Font,
- ) -> Renderer<'a> {
- Renderer {
- context,
- images,
- sprites: SpriteBatch::new(spritesheet.clone()),
- spritesheet,
- font,
- font_size: 20.0,
- debug_mesh: None,
- }
- }
-
- pub fn flush(&mut self) {
- graphics::draw(
- self.context,
- &self.sprites,
- graphics::DrawParam::default(),
- )
- .expect("Draw sprites");
-
- graphics::draw_queued_text(
- self.context,
- graphics::DrawParam::default(),
- Default::default(),
- graphics::FilterMode::Linear,
- )
- .expect("Draw text");
-
- if let Some(debug_mesh) = self.debug_mesh.take() {
- let mesh =
- debug_mesh.build(self.context).expect("Build debug mesh");
-
- graphics::draw(self.context, &mesh, graphics::DrawParam::default())
- .expect("Draw debug mesh");
- }
- }
-}
-
-pub fn into_color(color: iced_native::Color) -> graphics::Color {
- graphics::Color {
- r: color.r,
- g: color.g,
- b: color.b,
- a: color.a,
- }
-}
diff --git a/examples/tour/src/iced_ggez/renderer/button.rs b/examples/tour/src/iced_ggez/renderer/button.rs
deleted file mode 100644
index 78a5de07..00000000
--- a/examples/tour/src/iced_ggez/renderer/button.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-use super::Renderer;
-use ggez::graphics::{
- self, Align, Color, DrawParam, Rect, Scale, Text, TextFragment, WHITE,
-};
-use iced_native::{button, Button, Layout, Length, MouseCursor, Node, Style};
-
-const LEFT: Rect = Rect {
- x: 0.0,
- y: 34.0,
- w: 6.0,
- h: 49.0,
-};
-
-const BACKGROUND: Rect = Rect {
- x: LEFT.w,
- y: LEFT.y,
- w: 1.0,
- h: LEFT.h,
-};
-
-const RIGHT: Rect = Rect {
- x: LEFT.h - LEFT.w,
- y: LEFT.y,
- w: LEFT.w,
- h: LEFT.h,
-};
-
-impl button::Renderer for Renderer<'_> {
- fn node<Message>(&self, button: &Button<'_, Message>) -> Node {
- let style = Style::default()
- .width(button.width)
- .height(Length::Units(LEFT.h as u16))
- .min_width(Length::Units(100))
- .align_self(button.align_self);
-
- Node::new(style)
- }
-
- fn draw<Message>(
- &mut self,
- button: &Button<'_, Message>,
- layout: Layout<'_>,
- cursor_position: iced_native::Point,
- ) -> MouseCursor {
- let mut bounds = layout.bounds();
- let mouse_over = bounds.contains(cursor_position);
-
- let mut state_offset = 0.0;
-
- if mouse_over {
- if button.state.is_pressed() {
- bounds.y += 4.0;
- state_offset = RIGHT.x + RIGHT.w;
- } else {
- bounds.y -= 1.0;
- }
- }
-
- let class_index = match button.class {
- button::Class::Primary => 0,
- button::Class::Secondary => 1,
- button::Class::Positive => 2,
- };
-
- let width = self.spritesheet.width() as f32;
- let height = self.spritesheet.height() as f32;
-
- self.sprites.add(DrawParam {
- src: Rect {
- x: (LEFT.x + state_offset) / width,
- y: (LEFT.y + class_index as f32 * LEFT.h) / height,
- w: LEFT.w / width,
- h: LEFT.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x,
- y: bounds.y,
- },
- ..DrawParam::default()
- });
-
- self.sprites.add(DrawParam {
- src: Rect {
- x: (BACKGROUND.x + state_offset) / width,
- y: (BACKGROUND.y + class_index as f32 * BACKGROUND.h) / height,
- w: BACKGROUND.w / width,
- h: BACKGROUND.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x + LEFT.w,
- y: bounds.y,
- },
- scale: ggez::mint::Vector2 {
- x: bounds.width - LEFT.w - RIGHT.w,
- y: 1.0,
- },
- ..DrawParam::default()
- });
-
- self.sprites.add(DrawParam {
- src: Rect {
- x: (RIGHT.x + state_offset) / width,
- y: (RIGHT.y + class_index as f32 * RIGHT.h) / height,
- w: RIGHT.w / width,
- h: RIGHT.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x + bounds.width - RIGHT.w,
- y: bounds.y,
- },
- ..DrawParam::default()
- });
-
- let mut text = Text::new(TextFragment {
- text: button.label.clone(),
- font: Some(self.font),
- scale: Some(Scale { x: 20.0, y: 20.0 }),
- ..Default::default()
- });
-
- text.set_bounds(
- ggez::mint::Point2 {
- x: bounds.width,
- y: bounds.height,
- },
- Align::Center,
- );
-
- graphics::queue_text(
- self.context,
- &text,
- ggez::mint::Point2 {
- x: bounds.x,
- y: bounds.y + BACKGROUND.h / 4.0,
- },
- Some(if mouse_over {
- WHITE
- } else {
- Color {
- r: 0.9,
- g: 0.9,
- b: 0.9,
- a: 1.0,
- }
- }),
- );
-
- if mouse_over {
- MouseCursor::Pointer
- } else {
- MouseCursor::OutOfBounds
- }
- }
-}
diff --git a/examples/tour/src/iced_ggez/renderer/checkbox.rs b/examples/tour/src/iced_ggez/renderer/checkbox.rs
deleted file mode 100644
index 807185d9..00000000
--- a/examples/tour/src/iced_ggez/renderer/checkbox.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-use super::Renderer;
-
-use ggez::graphics::{DrawParam, Rect};
-use iced_native::{
- checkbox, text, Align, Checkbox, Column, Layout, Length, MouseCursor, Node,
- Row, Text, Widget,
-};
-
-const SPRITE: Rect = Rect {
- x: 98.0,
- y: 0.0,
- w: 28.0,
- h: 28.0,
-};
-
-impl checkbox::Renderer for Renderer<'_>
-where
- Self: text::Renderer,
-{
- fn node<Message>(&mut self, checkbox: &Checkbox<Message>) -> Node {
- Row::<(), Self>::new()
- .spacing(15)
- .align_items(Align::Center)
- .push(
- Column::new()
- .width(Length::Units(SPRITE.w as u16))
- .height(Length::Units(SPRITE.h as u16)),
- )
- .push(Text::new(&checkbox.label))
- .node(self)
- }
-
- fn draw<Message>(
- &mut self,
- checkbox: &Checkbox<Message>,
- layout: Layout<'_>,
- cursor_position: iced_native::Point,
- ) -> MouseCursor {
- let bounds = layout.bounds();
- let children: Vec<_> = layout.children().collect();
- let text_bounds = children[1].bounds();
-
- let mut text = Text::new(&checkbox.label);
-
- if let Some(label_color) = checkbox.label_color {
- text = text.color(label_color);
- }
-
- text::Renderer::draw(self, &text, children[1]);
-
- let mouse_over = bounds.contains(cursor_position)
- || text_bounds.contains(cursor_position);
-
- let width = self.spritesheet.width() as f32;
- let height = self.spritesheet.height() as f32;
-
- self.sprites.add(DrawParam {
- src: Rect {
- x: (SPRITE.x + (if mouse_over { SPRITE.w } else { 0.0 }))
- / width,
- y: SPRITE.y / height,
- w: SPRITE.w / width,
- h: SPRITE.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x,
- y: bounds.y,
- },
- ..DrawParam::default()
- });
-
- if checkbox.is_checked {
- self.sprites.add(DrawParam {
- src: Rect {
- x: (SPRITE.x + SPRITE.w * 2.0) / width,
- y: SPRITE.y / height,
- w: SPRITE.w / width,
- h: SPRITE.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x,
- y: bounds.y,
- },
- ..DrawParam::default()
- });
- }
-
- if mouse_over {
- MouseCursor::Pointer
- } else {
- MouseCursor::OutOfBounds
- }
- }
-}
diff --git a/examples/tour/src/iced_ggez/renderer/debugger.rs b/examples/tour/src/iced_ggez/renderer/debugger.rs
deleted file mode 100644
index ffb658af..00000000
--- a/examples/tour/src/iced_ggez/renderer/debugger.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use super::{into_color, Renderer};
-use ggez::graphics::{DrawMode, MeshBuilder, Rect};
-
-impl iced_native::renderer::Debugger for Renderer<'_> {
- fn explain(
- &mut self,
- layout: &iced_native::Layout<'_>,
- color: iced_native::Color,
- ) {
- let bounds = layout.bounds();
-
- let mut debug_mesh =
- self.debug_mesh.take().unwrap_or(MeshBuilder::new());
-
- debug_mesh.rectangle(
- DrawMode::stroke(1.0),
- Rect {
- x: bounds.x,
- y: bounds.y,
- w: bounds.width,
- h: bounds.height,
- },
- into_color(color),
- );
-
- self.debug_mesh = Some(debug_mesh);
-
- for child in layout.children() {
- self.explain(&child, color);
- }
- }
-}
diff --git a/examples/tour/src/iced_ggez/renderer/image.rs b/examples/tour/src/iced_ggez/renderer/image.rs
deleted file mode 100644
index b12b65c3..00000000
--- a/examples/tour/src/iced_ggez/renderer/image.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use super::Renderer;
-
-use ggez::{graphics, nalgebra};
-use iced_native::{image, Image, Layout, Length, Style};
-
-pub struct Cache {
- images: std::collections::HashMap<String, graphics::Image>,
-}
-
-impl Cache {
- pub fn new() -> Self {
- Self {
- images: std::collections::HashMap::new(),
- }
- }
-
- fn get<'a>(
- &mut self,
- name: &'a str,
- context: &mut ggez::Context,
- ) -> graphics::Image {
- if let Some(image) = self.images.get(name) {
- return image.clone();
- }
-
- let mut image = graphics::Image::new(context, &format!("/{}", name))
- .expect("Load ferris image");
-
- image.set_filter(graphics::FilterMode::Linear);
-
- self.images.insert(name.to_string(), image.clone());
-
- image
- }
-}
-
-impl<'a> image::Renderer<&'a str> for Renderer<'_> {
- fn node(&mut self, image: &Image<&'a str>) -> iced_native::Node {
- let ggez_image = self.images.get(image.handle, self.context);
-
- let aspect_ratio =
- ggez_image.width() as f32 / ggez_image.height() as f32;
-
- let mut style = Style::default().align_self(image.align_self);
-
- style = match (image.width, image.height) {
- (Length::Units(width), _) => style.width(image.width).height(
- Length::Units((width as f32 / aspect_ratio).round() as u16),
- ),
- (_, _) => style
- .width(Length::Units(ggez_image.width()))
- .height(Length::Units(ggez_image.height())),
- };
-
- iced_native::Node::new(style)
- }
-
- fn draw(&mut self, image: &Image<&'a str>, layout: Layout<'_>) {
- let image = self.images.get(image.handle, self.context);
- let bounds = layout.bounds();
-
- // We should probably use batches to draw images efficiently and keep
- // draw side-effect free, but this is good enough for the example.
- graphics::draw(
- self.context,
- &image,
- graphics::DrawParam::new()
- .dest(nalgebra::Point2::new(bounds.x, bounds.y))
- .scale(nalgebra::Vector2::new(
- bounds.width / image.width() as f32,
- bounds.height / image.height() as f32,
- )),
- )
- .expect("Draw image");
- }
-}
diff --git a/examples/tour/src/iced_ggez/renderer/radio.rs b/examples/tour/src/iced_ggez/renderer/radio.rs
deleted file mode 100644
index dbd29ecd..00000000
--- a/examples/tour/src/iced_ggez/renderer/radio.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use super::Renderer;
-
-use ggez::graphics::{DrawParam, Rect};
-use iced_native::{
- radio, text, Align, Column, Layout, Length, MouseCursor, Node, Point,
- Radio, Row, Text, Widget,
-};
-
-const SPRITE: Rect = Rect {
- x: 98.0,
- y: 28.0,
- w: 28.0,
- h: 28.0,
-};
-
-impl radio::Renderer for Renderer<'_>
-where
- Self: text::Renderer,
-{
- fn node<Message>(&mut self, radio: &Radio<Message>) -> Node {
- Row::<(), Self>::new()
- .spacing(15)
- .align_items(Align::Center)
- .push(
- Column::new()
- .width(Length::Units(SPRITE.w as u16))
- .height(Length::Units(SPRITE.h as u16)),
- )
- .push(Text::new(&radio.label))
- .node(self)
- }
-
- fn draw<Message>(
- &mut self,
- radio: &Radio<Message>,
- layout: Layout<'_>,
- cursor_position: Point,
- ) -> MouseCursor {
- let children: Vec<_> = layout.children().collect();
-
- let mut text = Text::new(&radio.label);
-
- if let Some(label_color) = radio.label_color {
- text = text.color(label_color);
- }
-
- text::Renderer::draw(self, &text, children[1]);
-
- let bounds = layout.bounds();
- let mouse_over = bounds.contains(cursor_position);
-
- let width = self.spritesheet.width() as f32;
- let height = self.spritesheet.height() as f32;
-
- self.sprites.add(DrawParam {
- src: Rect {
- x: (SPRITE.x + (if mouse_over { SPRITE.w } else { 0.0 }))
- / width,
- y: SPRITE.y / height,
- w: SPRITE.w / width,
- h: SPRITE.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x,
- y: bounds.y,
- },
- ..DrawParam::default()
- });
-
- if radio.is_selected {
- self.sprites.add(DrawParam {
- src: Rect {
- x: (SPRITE.x + SPRITE.w * 2.0) / width,
- y: SPRITE.y / height,
- w: SPRITE.w / width,
- h: SPRITE.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x,
- y: bounds.y,
- },
- ..DrawParam::default()
- });
- }
-
- if mouse_over {
- MouseCursor::Pointer
- } else {
- MouseCursor::OutOfBounds
- }
- }
-}
diff --git a/examples/tour/src/iced_ggez/renderer/slider.rs b/examples/tour/src/iced_ggez/renderer/slider.rs
deleted file mode 100644
index 60c40c55..00000000
--- a/examples/tour/src/iced_ggez/renderer/slider.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-use super::Renderer;
-
-use ggez::graphics::{DrawParam, Rect};
-use iced_native::{
- slider, Layout, Length, MouseCursor, Node, Point, Slider, Style,
-};
-
-const RAIL: Rect = Rect {
- x: 98.0,
- y: 56.0,
- w: 1.0,
- h: 4.0,
-};
-
-const MARKER: Rect = Rect {
- x: RAIL.x + 28.0,
- y: RAIL.y,
- w: 16.0,
- h: 24.0,
-};
-
-impl slider::Renderer for Renderer<'_> {
- fn node<Message>(&self, slider: &Slider<'_, Message>) -> Node {
- let style = Style::default()
- .width(slider.width)
- .height(Length::Units(25))
- .min_width(Length::Units(100));
-
- Node::new(style)
- }
-
- fn draw<Message>(
- &mut self,
- slider: &Slider<'_, Message>,
- layout: Layout<'_>,
- cursor_position: Point,
- ) -> MouseCursor {
- let bounds = layout.bounds();
- let width = self.spritesheet.width() as f32;
- let height = self.spritesheet.height() as f32;
-
- self.sprites.add(DrawParam {
- src: Rect {
- x: RAIL.x / width,
- y: RAIL.y / height,
- w: RAIL.w / width,
- h: RAIL.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x + MARKER.w as f32 / 2.0,
- y: bounds.y + 12.5,
- },
- scale: ggez::mint::Vector2 {
- x: bounds.width - MARKER.w as f32,
- y: 1.0,
- },
- ..DrawParam::default()
- });
-
- let (range_start, range_end) = slider.range.clone().into_inner();
-
- let marker_offset = (bounds.width - MARKER.w as f32)
- * ((slider.value - range_start)
- / (range_end - range_start).max(1.0));
-
- let mouse_over = bounds.contains(cursor_position);
- let is_active = slider.state.is_dragging() || mouse_over;
-
- self.sprites.add(DrawParam {
- src: Rect {
- x: (MARKER.x + (if is_active { MARKER.w } else { 0.0 }))
- / width,
- y: MARKER.y / height,
- w: MARKER.w / width,
- h: MARKER.h / height,
- },
- dest: ggez::mint::Point2 {
- x: bounds.x + marker_offset.round(),
- y: bounds.y
- + (if slider.state.is_dragging() { 2.0 } else { 0.0 }),
- },
- ..DrawParam::default()
- });
-
- if slider.state.is_dragging() {
- MouseCursor::Grabbing
- } else if mouse_over {
- MouseCursor::Grab
- } else {
- MouseCursor::OutOfBounds
- }
- }
-}
diff --git a/examples/tour/src/iced_ggez/renderer/text.rs b/examples/tour/src/iced_ggez/renderer/text.rs
deleted file mode 100644
index b51cc220..00000000
--- a/examples/tour/src/iced_ggez/renderer/text.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-use super::{into_color, Renderer};
-use ggez::graphics::{self, mint, Align, Scale, Text, TextFragment};
-
-use iced_native::{text, Layout, Node, Style};
-use std::cell::RefCell;
-use std::f32;
-
-impl text::Renderer for Renderer<'_> {
- fn node(&self, text: &iced_native::Text) -> Node {
- let font = self.font;
- let font_cache = graphics::font_cache(self.context);
- let content = String::from(&text.content);
-
- // TODO: Investigate why stretch tries to measure this MANY times
- // with every ancestor's bounds.
- // Bug? Using the library wrong? I should probably open an issue on
- // the stretch repository.
- // I noticed that the first measure is the one that matters in
- // practice. Here, we use a RefCell to store the cached measurement.
- let measure = RefCell::new(None);
- let size = text.size.map(f32::from).unwrap_or(self.font_size);
-
- let style = Style::default().width(text.width);
-
- iced_native::Node::with_measure(style, move |bounds| {
- let mut measure = measure.borrow_mut();
-
- if measure.is_none() {
- let bounds = (
- match bounds.width {
- iced_native::Number::Undefined => f32::INFINITY,
- iced_native::Number::Defined(w) => w,
- },
- match bounds.height {
- iced_native::Number::Undefined => f32::INFINITY,
- iced_native::Number::Defined(h) => h,
- },
- );
-
- let mut text = Text::new(TextFragment {
- text: content.clone(),
- font: Some(font),
- scale: Some(Scale { x: size, y: size }),
- ..Default::default()
- });
-
- text.set_bounds(
- mint::Point2 {
- x: bounds.0,
- y: bounds.1,
- },
- Align::Left,
- );
-
- let (width, height) = font_cache.dimensions(&text);
-
- let size = iced_native::Size {
- width: width as f32,
- height: height as f32,
- };
-
- // If the text has no width boundary we avoid caching as the
- // layout engine may just be measuring text in a row.
- if bounds.0 == f32::INFINITY {
- return size;
- } else {
- *measure = Some(size);
- }
- }
-
- measure.unwrap()
- })
- }
-
- fn draw(&mut self, text: &iced_native::Text, layout: Layout<'_>) {
- let size = text.size.map(f32::from).unwrap_or(self.font_size);
- let bounds = layout.bounds();
-
- let mut ggez_text = Text::new(TextFragment {
- text: text.content.clone(),
- font: Some(self.font),
- scale: Some(Scale { x: size, y: size }),
- ..Default::default()
- });
-
- ggez_text.set_bounds(
- mint::Point2 {
- x: bounds.width,
- y: bounds.height,
- },
- match text.horizontal_alignment {
- text::HorizontalAlignment::Left => graphics::Align::Left,
- text::HorizontalAlignment::Center => graphics::Align::Center,
- text::HorizontalAlignment::Right => graphics::Align::Right,
- },
- );
-
- graphics::queue_text(
- self.context,
- &ggez_text,
- mint::Point2 {
- x: bounds.x,
- y: bounds.y,
- },
- text.color
- .or(Some(iced_native::Color::BLACK))
- .map(into_color),
- );
- }
-}
diff --git a/examples/tour/src/iced_ggez/widget.rs b/examples/tour/src/iced_ggez/widget.rs
deleted file mode 100644
index 948f9fc6..00000000
--- a/examples/tour/src/iced_ggez/widget.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use super::Renderer;
-
-pub use iced_native::{
- button, slider, text, Align, Button, Checkbox, Color, Length, Radio,
- Slider, Text,
-};
-
-pub type Image<'a> = iced_native::Image<&'a str>;
-
-pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer<'a>>;
-pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer<'a>>;
-pub type Element<'a, Message> = iced_native::Element<'a, Message, Renderer<'a>>;
diff --git a/examples/tour/src/lib.rs b/examples/tour/src/lib.rs
deleted file mode 100644
index eb41fcd9..00000000
--- a/examples/tour/src/lib.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-pub mod tour;
-
-pub use tour::{Message, Tour};
-
-mod widget;
-
-#[cfg(target_arch = "wasm32")]
-mod web;
-
-#[cfg(not(target_arch = "wasm32"))]
-pub mod iced_ggez;
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
deleted file mode 100644
index a34d3298..00000000
--- a/examples/tour/src/main.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-use iced_tour::{iced_ggez, Tour};
-
-use ggez;
-use ggez::event;
-use ggez::filesystem;
-use ggez::graphics;
-use ggez::input::mouse;
-
-pub fn main() -> ggez::GameResult {
- env_logger::init();
-
- let (context, event_loop) = {
- &mut ggez::ContextBuilder::new("iced", "ggez")
- .window_mode(ggez::conf::WindowMode {
- width: 1280.0,
- height: 1024.0,
- resizable: true,
- ..ggez::conf::WindowMode::default()
- })
- .build()?
- };
-
- filesystem::mount(
- context,
- std::path::Path::new(env!("CARGO_MANIFEST_DIR")),
- true,
- );
-
- let state = &mut Game::new(context)?;
-
- event::run(context, event_loop, state)
-}
-
-struct Game {
- spritesheet: graphics::Image,
- font: graphics::Font,
- images: iced_ggez::ImageCache,
- tour: Tour,
-
- events: Vec<iced_native::Event>,
- cache: Option<iced_native::Cache>,
-}
-
-impl Game {
- fn new(context: &mut ggez::Context) -> ggez::GameResult<Game> {
- graphics::set_default_filter(context, graphics::FilterMode::Nearest);
-
- Ok(Game {
- spritesheet: graphics::Image::new(context, "/resources/ui.png")
- .unwrap(),
- font: graphics::Font::new(context, "/resources/Roboto-Regular.ttf")
- .unwrap(),
- images: iced_ggez::ImageCache::new(),
- tour: Tour::new(),
-
- events: Vec::new(),
- cache: Some(iced_native::Cache::default()),
- })
- }
-}
-
-impl event::EventHandler for Game {
- fn update(&mut self, _ctx: &mut ggez::Context) -> ggez::GameResult {
- Ok(())
- }
-
- fn mouse_button_down_event(
- &mut self,
- _context: &mut ggez::Context,
- _button: mouse::MouseButton,
- _x: f32,
- _y: f32,
- ) {
- self.events.push(iced_native::Event::Mouse(
- iced_native::input::mouse::Event::Input {
- state: iced_native::input::ButtonState::Pressed,
- button: iced_native::input::mouse::Button::Left, // TODO: Map `button`
- },
- ));
- }
-
- fn mouse_button_up_event(
- &mut self,
- _context: &mut ggez::Context,
- _button: mouse::MouseButton,
- _x: f32,
- _y: f32,
- ) {
- self.events.push(iced_native::Event::Mouse(
- iced_native::input::mouse::Event::Input {
- state: iced_native::input::ButtonState::Released,
- button: iced_native::input::mouse::Button::Left, // TODO: Map `button`
- },
- ));
- }
-
- fn mouse_motion_event(
- &mut self,
- _context: &mut ggez::Context,
- x: f32,
- y: f32,
- _dx: f32,
- _dy: f32,
- ) {
- self.events.push(iced_native::Event::Mouse(
- iced_native::input::mouse::Event::CursorMoved { x, y },
- ));
- }
-
- fn resize_event(
- &mut self,
- context: &mut ggez::Context,
- width: f32,
- height: f32,
- ) {
- graphics::set_screen_coordinates(
- context,
- graphics::Rect {
- x: 0.0,
- y: 0.0,
- w: width,
- h: height,
- },
- )
- .expect("Set screen coordinates");
- }
-
- fn draw(&mut self, context: &mut ggez::Context) -> ggez::GameResult {
- graphics::clear(context, graphics::WHITE);
-
- let screen = graphics::screen_coordinates(context);
-
- let (messages, cursor) = {
- let view = self.tour.view();
-
- let content = iced_ggez::Column::new()
- .width(iced_native::Length::Units(screen.w as u16))
- .height(iced_native::Length::Units(screen.h as u16))
- .padding(20)
- .align_items(iced_native::Align::Center)
- .justify_content(iced_native::Justify::Center)
- .push(view);
-
- let renderer = &mut iced_ggez::Renderer::new(
- context,
- &mut self.images,
- self.spritesheet.clone(),
- self.font,
- );
-
- let mut ui = iced_native::UserInterface::build(
- content,
- self.cache.take().unwrap(),
- renderer,
- );
-
- let messages = ui.update(self.events.drain(..));
- let cursor = ui.draw(renderer);
-
- self.cache = Some(ui.into_cache());
-
- renderer.flush();
-
- (messages, cursor)
- };
-
- for message in messages {
- self.tour.update(message);
- }
-
- let cursor_type = into_cursor_type(cursor);
-
- if mouse::cursor_type(context) != cursor_type {
- mouse::set_cursor_type(context, cursor_type);
- }
-
- graphics::present(context)?;
- Ok(())
- }
-}
-
-fn into_cursor_type(cursor: iced_native::MouseCursor) -> mouse::MouseCursor {
- match cursor {
- iced_native::MouseCursor::OutOfBounds => mouse::MouseCursor::Default,
- iced_native::MouseCursor::Idle => mouse::MouseCursor::Default,
- iced_native::MouseCursor::Pointer => mouse::MouseCursor::Hand,
- iced_native::MouseCursor::Working => mouse::MouseCursor::Progress,
- iced_native::MouseCursor::Grab => mouse::MouseCursor::Grab,
- iced_native::MouseCursor::Grabbing => mouse::MouseCursor::Grabbing,
- }
-}
diff --git a/examples/tour/src/web.rs b/examples/tour/src/web.rs
deleted file mode 100644
index a0a3060f..00000000
--- a/examples/tour/src/web.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use futures::Future;
-use iced_web::UserInterface;
-use wasm_bindgen::prelude::*;
-
-use crate::tour::{self, Tour};
-
-#[wasm_bindgen(start)]
-pub fn run() {
- console_error_panic_hook::set_once();
- console_log::init_with_level(log::Level::Trace)
- .expect("Initialize logging");
-
- let tour = Tour::new();
-
- tour.run();
-}
-
-impl iced_web::UserInterface for Tour {
- type Message = tour::Message;
-
- fn update(
- &mut self,
- message: tour::Message,
- ) -> Option<Box<dyn Future<Output = tour::Message>>> {
- self.update(message);
-
- None
- }
-
- fn view(&mut self) -> iced_web::Element<tour::Message> {
- self.view()
- }
-}
diff --git a/examples/tour/src/widget.rs b/examples/tour/src/widget.rs
deleted file mode 100644
index 9c2c4d5b..00000000
--- a/examples/tour/src/widget.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-#[cfg(target_arch = "wasm32")]
-pub use iced_web::*;
-
-#[cfg(not(target_arch = "wasm32"))]
-pub use crate::iced_ggez::*;
diff --git a/native/Cargo.toml b/native/Cargo.toml
index 5f7e5e41..8cabe94c 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -7,13 +7,8 @@ description = "A renderer-agnostic library for native GUIs"
license = "MIT"
repository = "https://github.com/hecrj/iced"
-[package.metadata.docs.rs]
-features = ["winit"]
-
[dependencies]
iced_core = { version = "0.1.0-alpha", path = "../core" }
stretch = "0.2"
twox-hash = "1.5"
-
-# Enable to obtain conversion traits
-winit = { version = "0.20.0-alpha3", optional = true }
+raw-window-handle = "0.1"
diff --git a/native/src/element.rs b/native/src/element.rs
index dd5ce621..bbedd942 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -1,8 +1,6 @@
use stretch::{geometry, result};
-use crate::{
- renderer, Color, Event, Hasher, Layout, MouseCursor, Node, Point, Widget,
-};
+use crate::{renderer, Color, Event, Hasher, Layout, Node, Point, Widget};
/// A generic [`Widget`].
///
@@ -27,7 +25,10 @@ impl<'a, Message, Renderer> std::fmt::Debug for Element<'a, Message, Renderer> {
}
}
-impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
+impl<'a, Message, Renderer> Element<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+{
/// Create a new [`Element`] containing the given [`Widget`].
///
/// [`Element`]: struct.Element.html
@@ -40,6 +41,19 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
}
}
+ pub fn node(&self, renderer: &Renderer) -> Node {
+ self.widget.node(renderer)
+ }
+
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Renderer::Output {
+ self.widget.draw(renderer, layout, cursor_position)
+ }
+
/// Applies a transformation to the produced message of the [`Element`].
///
/// This method is useful when you want to decouple different parts of your
@@ -87,38 +101,46 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
///
/// ```
/// # mod counter {
- /// # use iced_native::{button, Button};
+ /// # use iced_native::{text, Text};
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
- /// # pub struct Counter(button::State);
+ /// # pub struct Counter;
/// #
/// # impl Counter {
- /// # pub fn view(&mut self) -> Button<Message> {
- /// # Button::new(&mut self.0, "_")
+ /// # pub fn view(&mut self) -> Text {
+ /// # Text::new("")
/// # }
/// # }
/// # }
/// #
/// # mod iced_wgpu {
/// # use iced_native::{
- /// # button, Button, MouseCursor, Node, Point, Rectangle, Style, Layout
+ /// # text, row, Text, Node, Point, Rectangle, Style, Layout, Row
/// # };
/// # pub struct Renderer;
/// #
- /// # impl button::Renderer for Renderer {
- /// # fn node<Message>(&self, _button: &Button<'_, Message>) -> Node {
- /// # Node::new(Style::default())
- /// # }
+ /// # impl iced_native::Renderer for Renderer { type Output = (); }
/// #
+ /// # impl iced_native::row::Renderer for Renderer {
/// # fn draw<Message>(
/// # &mut self,
- /// # _button: &Button<'_, Message>,
+ /// # _column: &Row<'_, Message, Self>,
/// # _layout: Layout<'_>,
/// # _cursor_position: Point,
- /// # ) -> MouseCursor {
- /// # MouseCursor::OutOfBounds
+ /// # ) {}
+ /// # }
+ /// #
+ /// # impl text::Renderer for Renderer {
+ /// # fn node(&self, _text: &Text) -> Node {
+ /// # Node::new(Style::default())
/// # }
+ /// #
+ /// # fn draw(
+ /// # &mut self,
+ /// # _text: &Text,
+ /// # _layout: Layout<'_>,
+ /// # ) {}
/// # }
/// # }
/// #
@@ -225,10 +247,7 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
}
}
- pub(crate) fn compute_layout(
- &self,
- renderer: &mut Renderer,
- ) -> result::Layout {
+ pub(crate) fn compute_layout(&self, renderer: &Renderer) -> result::Layout {
let node = self.widget.node(renderer);
node.0.compute_layout(geometry::Size::undefined()).unwrap()
@@ -268,8 +287,9 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
where
A: Copy,
+ Renderer: crate::Renderer,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
self.widget.node(renderer)
}
@@ -300,7 +320,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
+ ) -> Renderer::Output {
self.widget.draw(renderer, layout, cursor_position)
}
@@ -309,14 +329,14 @@ where
}
}
-struct Explain<'a, Message, Renderer: renderer::Debugger> {
+struct Explain<'a, Message, Renderer: crate::Renderer> {
element: Element<'a, Message, Renderer>,
color: Color,
}
impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer>
where
- Renderer: renderer::Debugger,
+ Renderer: crate::Renderer,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Explain")
@@ -327,7 +347,7 @@ where
impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
where
- Renderer: renderer::Debugger,
+ Renderer: crate::Renderer,
{
fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
Explain { element, color }
@@ -337,9 +357,9 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer>
where
- Renderer: renderer::Debugger,
+ Renderer: crate::Renderer + renderer::Debugger,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
self.element.widget.node(renderer)
}
@@ -360,10 +380,13 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
- renderer.explain(&layout, self.color);
-
- self.element.widget.draw(renderer, layout, cursor_position)
+ ) -> Renderer::Output {
+ renderer.explain(
+ self.element.widget.as_ref(),
+ layout,
+ cursor_position,
+ self.color,
+ )
}
fn hash_layout(&self, state: &mut Hasher) {
diff --git a/native/src/input/button_state.rs b/native/src/input/button_state.rs
index e9dc05d7..988043ba 100644
--- a/native/src/input/button_state.rs
+++ b/native/src/input/button_state.rs
@@ -1,9 +1,4 @@
/// The state of a button.
-///
-/// If you are using [`winit`], consider enabling the `winit` feature to get
-/// conversion implementations for free!
-///
-/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
pub enum ButtonState {
/// The button is pressed.
@@ -12,13 +7,3 @@ pub enum ButtonState {
/// The button is __not__ pressed.
Released,
}
-
-#[cfg(feature = "winit")]
-impl From<winit::event::ElementState> for ButtonState {
- fn from(element_state: winit::event::ElementState) -> Self {
- match element_state {
- winit::event::ElementState::Pressed => ButtonState::Pressed,
- winit::event::ElementState::Released => ButtonState::Released,
- }
- }
-}
diff --git a/native/src/input/keyboard/key_code.rs b/native/src/input/keyboard/key_code.rs
index 207ddeac..26020a57 100644
--- a/native/src/input/keyboard/key_code.rs
+++ b/native/src/input/keyboard/key_code.rs
@@ -2,9 +2,6 @@
///
/// This is mostly the `KeyCode` type found in [`winit`].
///
-/// If you are using [`winit`], consider enabling the `winit` feature to get
-/// conversion implementations for free!
-///
/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
@@ -199,176 +196,3 @@ pub enum KeyCode {
Paste,
Cut,
}
-
-#[cfg(feature = "winit")]
-impl From<winit::event::VirtualKeyCode> for KeyCode {
- fn from(virtual_keycode: winit::event::VirtualKeyCode) -> Self {
- match virtual_keycode {
- winit::event::VirtualKeyCode::Key1 => KeyCode::Key1,
- winit::event::VirtualKeyCode::Key2 => KeyCode::Key2,
- winit::event::VirtualKeyCode::Key3 => KeyCode::Key3,
- winit::event::VirtualKeyCode::Key4 => KeyCode::Key4,
- winit::event::VirtualKeyCode::Key5 => KeyCode::Key5,
- winit::event::VirtualKeyCode::Key6 => KeyCode::Key6,
- winit::event::VirtualKeyCode::Key7 => KeyCode::Key7,
- winit::event::VirtualKeyCode::Key8 => KeyCode::Key8,
- winit::event::VirtualKeyCode::Key9 => KeyCode::Key9,
- winit::event::VirtualKeyCode::Key0 => KeyCode::Key0,
- winit::event::VirtualKeyCode::A => KeyCode::A,
- winit::event::VirtualKeyCode::B => KeyCode::B,
- winit::event::VirtualKeyCode::C => KeyCode::C,
- winit::event::VirtualKeyCode::D => KeyCode::D,
- winit::event::VirtualKeyCode::E => KeyCode::E,
- winit::event::VirtualKeyCode::F => KeyCode::F,
- winit::event::VirtualKeyCode::G => KeyCode::G,
- winit::event::VirtualKeyCode::H => KeyCode::H,
- winit::event::VirtualKeyCode::I => KeyCode::I,
- winit::event::VirtualKeyCode::J => KeyCode::J,
- winit::event::VirtualKeyCode::K => KeyCode::K,
- winit::event::VirtualKeyCode::L => KeyCode::L,
- winit::event::VirtualKeyCode::M => KeyCode::M,
- winit::event::VirtualKeyCode::N => KeyCode::N,
- winit::event::VirtualKeyCode::O => KeyCode::O,
- winit::event::VirtualKeyCode::P => KeyCode::P,
- winit::event::VirtualKeyCode::Q => KeyCode::Q,
- winit::event::VirtualKeyCode::R => KeyCode::R,
- winit::event::VirtualKeyCode::S => KeyCode::S,
- winit::event::VirtualKeyCode::T => KeyCode::T,
- winit::event::VirtualKeyCode::U => KeyCode::U,
- winit::event::VirtualKeyCode::V => KeyCode::V,
- winit::event::VirtualKeyCode::W => KeyCode::W,
- winit::event::VirtualKeyCode::X => KeyCode::X,
- winit::event::VirtualKeyCode::Y => KeyCode::Y,
- winit::event::VirtualKeyCode::Z => KeyCode::Z,
- winit::event::VirtualKeyCode::Escape => KeyCode::Escape,
- winit::event::VirtualKeyCode::F1 => KeyCode::F1,
- winit::event::VirtualKeyCode::F2 => KeyCode::F2,
- winit::event::VirtualKeyCode::F3 => KeyCode::F3,
- winit::event::VirtualKeyCode::F4 => KeyCode::F4,
- winit::event::VirtualKeyCode::F5 => KeyCode::F5,
- winit::event::VirtualKeyCode::F6 => KeyCode::F6,
- winit::event::VirtualKeyCode::F7 => KeyCode::F7,
- winit::event::VirtualKeyCode::F8 => KeyCode::F8,
- winit::event::VirtualKeyCode::F9 => KeyCode::F9,
- winit::event::VirtualKeyCode::F10 => KeyCode::F10,
- winit::event::VirtualKeyCode::F11 => KeyCode::F11,
- winit::event::VirtualKeyCode::F12 => KeyCode::F12,
- winit::event::VirtualKeyCode::F13 => KeyCode::F13,
- winit::event::VirtualKeyCode::F14 => KeyCode::F14,
- winit::event::VirtualKeyCode::F15 => KeyCode::F15,
- winit::event::VirtualKeyCode::F16 => KeyCode::F16,
- winit::event::VirtualKeyCode::F17 => KeyCode::F17,
- winit::event::VirtualKeyCode::F18 => KeyCode::F18,
- winit::event::VirtualKeyCode::F19 => KeyCode::F19,
- winit::event::VirtualKeyCode::F20 => KeyCode::F20,
- winit::event::VirtualKeyCode::F21 => KeyCode::F21,
- winit::event::VirtualKeyCode::F22 => KeyCode::F22,
- winit::event::VirtualKeyCode::F23 => KeyCode::F23,
- winit::event::VirtualKeyCode::F24 => KeyCode::F24,
- winit::event::VirtualKeyCode::Snapshot => KeyCode::Snapshot,
- winit::event::VirtualKeyCode::Scroll => KeyCode::Scroll,
- winit::event::VirtualKeyCode::Pause => KeyCode::Pause,
- winit::event::VirtualKeyCode::Insert => KeyCode::Insert,
- winit::event::VirtualKeyCode::Home => KeyCode::Home,
- winit::event::VirtualKeyCode::Delete => KeyCode::Delete,
- winit::event::VirtualKeyCode::End => KeyCode::End,
- winit::event::VirtualKeyCode::PageDown => KeyCode::PageDown,
- winit::event::VirtualKeyCode::PageUp => KeyCode::PageUp,
- winit::event::VirtualKeyCode::Left => KeyCode::Left,
- winit::event::VirtualKeyCode::Up => KeyCode::Up,
- winit::event::VirtualKeyCode::Right => KeyCode::Right,
- winit::event::VirtualKeyCode::Down => KeyCode::Down,
- winit::event::VirtualKeyCode::Back => KeyCode::Backspace,
- winit::event::VirtualKeyCode::Return => KeyCode::Enter,
- winit::event::VirtualKeyCode::Space => KeyCode::Space,
- winit::event::VirtualKeyCode::Compose => KeyCode::Compose,
- winit::event::VirtualKeyCode::Caret => KeyCode::Caret,
- winit::event::VirtualKeyCode::Numlock => KeyCode::Numlock,
- winit::event::VirtualKeyCode::Numpad0 => KeyCode::Numpad0,
- winit::event::VirtualKeyCode::Numpad1 => KeyCode::Numpad1,
- winit::event::VirtualKeyCode::Numpad2 => KeyCode::Numpad2,
- winit::event::VirtualKeyCode::Numpad3 => KeyCode::Numpad3,
- winit::event::VirtualKeyCode::Numpad4 => KeyCode::Numpad4,
- winit::event::VirtualKeyCode::Numpad5 => KeyCode::Numpad5,
- winit::event::VirtualKeyCode::Numpad6 => KeyCode::Numpad6,
- winit::event::VirtualKeyCode::Numpad7 => KeyCode::Numpad7,
- winit::event::VirtualKeyCode::Numpad8 => KeyCode::Numpad8,
- winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9,
- winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1,
- winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2,
- winit::event::VirtualKeyCode::Add => KeyCode::Add,
- winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe,
- winit::event::VirtualKeyCode::Apps => KeyCode::Apps,
- winit::event::VirtualKeyCode::At => KeyCode::At,
- winit::event::VirtualKeyCode::Ax => KeyCode::Ax,
- winit::event::VirtualKeyCode::Backslash => KeyCode::Backslash,
- winit::event::VirtualKeyCode::Calculator => KeyCode::Calculator,
- winit::event::VirtualKeyCode::Capital => KeyCode::Capital,
- winit::event::VirtualKeyCode::Colon => KeyCode::Colon,
- winit::event::VirtualKeyCode::Comma => KeyCode::Comma,
- winit::event::VirtualKeyCode::Convert => KeyCode::Convert,
- winit::event::VirtualKeyCode::Decimal => KeyCode::Decimal,
- winit::event::VirtualKeyCode::Divide => KeyCode::Divide,
- winit::event::VirtualKeyCode::Equals => KeyCode::Equals,
- winit::event::VirtualKeyCode::Grave => KeyCode::Grave,
- winit::event::VirtualKeyCode::Kana => KeyCode::Kana,
- winit::event::VirtualKeyCode::Kanji => KeyCode::Kanji,
- winit::event::VirtualKeyCode::LAlt => KeyCode::LAlt,
- winit::event::VirtualKeyCode::LBracket => KeyCode::LBracket,
- winit::event::VirtualKeyCode::LControl => KeyCode::LControl,
- winit::event::VirtualKeyCode::LShift => KeyCode::LShift,
- winit::event::VirtualKeyCode::LWin => KeyCode::LWin,
- winit::event::VirtualKeyCode::Mail => KeyCode::Mail,
- winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect,
- winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop,
- winit::event::VirtualKeyCode::Minus => KeyCode::Minus,
- winit::event::VirtualKeyCode::Multiply => KeyCode::Multiply,
- winit::event::VirtualKeyCode::Mute => KeyCode::Mute,
- winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer,
- winit::event::VirtualKeyCode::NavigateForward => {
- KeyCode::NavigateForward
- }
- winit::event::VirtualKeyCode::NavigateBackward => {
- KeyCode::NavigateBackward
- }
- winit::event::VirtualKeyCode::NextTrack => KeyCode::NextTrack,
- winit::event::VirtualKeyCode::NoConvert => KeyCode::NoConvert,
- winit::event::VirtualKeyCode::NumpadComma => KeyCode::NumpadComma,
- winit::event::VirtualKeyCode::NumpadEnter => KeyCode::NumpadEnter,
- winit::event::VirtualKeyCode::NumpadEquals => KeyCode::NumpadEquals,
- winit::event::VirtualKeyCode::OEM102 => KeyCode::OEM102,
- winit::event::VirtualKeyCode::Period => KeyCode::Period,
- winit::event::VirtualKeyCode::PlayPause => KeyCode::PlayPause,
- winit::event::VirtualKeyCode::Power => KeyCode::Power,
- winit::event::VirtualKeyCode::PrevTrack => KeyCode::PrevTrack,
- winit::event::VirtualKeyCode::RAlt => KeyCode::RAlt,
- winit::event::VirtualKeyCode::RBracket => KeyCode::RBracket,
- winit::event::VirtualKeyCode::RControl => KeyCode::RControl,
- winit::event::VirtualKeyCode::RShift => KeyCode::RShift,
- winit::event::VirtualKeyCode::RWin => KeyCode::RWin,
- winit::event::VirtualKeyCode::Semicolon => KeyCode::Semicolon,
- winit::event::VirtualKeyCode::Slash => KeyCode::Slash,
- winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep,
- winit::event::VirtualKeyCode::Stop => KeyCode::Stop,
- winit::event::VirtualKeyCode::Subtract => KeyCode::Subtract,
- winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq,
- winit::event::VirtualKeyCode::Tab => KeyCode::Tab,
- winit::event::VirtualKeyCode::Underline => KeyCode::Underline,
- winit::event::VirtualKeyCode::Unlabeled => KeyCode::Unlabeled,
- winit::event::VirtualKeyCode::VolumeDown => KeyCode::VolumeDown,
- winit::event::VirtualKeyCode::VolumeUp => KeyCode::VolumeUp,
- winit::event::VirtualKeyCode::Wake => KeyCode::Wake,
- winit::event::VirtualKeyCode::WebBack => KeyCode::WebBack,
- winit::event::VirtualKeyCode::WebFavorites => KeyCode::WebFavorites,
- winit::event::VirtualKeyCode::WebForward => KeyCode::WebForward,
- winit::event::VirtualKeyCode::WebHome => KeyCode::WebHome,
- winit::event::VirtualKeyCode::WebRefresh => KeyCode::WebRefresh,
- winit::event::VirtualKeyCode::WebSearch => KeyCode::WebSearch,
- winit::event::VirtualKeyCode::WebStop => KeyCode::WebStop,
- winit::event::VirtualKeyCode::Yen => KeyCode::Yen,
- winit::event::VirtualKeyCode::Copy => KeyCode::Copy,
- winit::event::VirtualKeyCode::Paste => KeyCode::Paste,
- winit::event::VirtualKeyCode::Cut => KeyCode::Cut,
- }
- }
-}
diff --git a/native/src/input/mouse/button.rs b/native/src/input/mouse/button.rs
index 6320d701..aeb8a55d 100644
--- a/native/src/input/mouse/button.rs
+++ b/native/src/input/mouse/button.rs
@@ -1,9 +1,4 @@
/// The button of a mouse.
-///
-/// If you are using [`winit`], consider enabling the `winit` feature to get
-/// conversion implementations for free!
-///
-/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum Button {
/// The left mouse button.
@@ -18,15 +13,3 @@ pub enum Button {
/// Some other button.
Other(u8),
}
-
-#[cfg(feature = "winit")]
-impl From<winit::event::MouseButton> for super::Button {
- fn from(mouse_button: winit::event::MouseButton) -> Self {
- match mouse_button {
- winit::event::MouseButton::Left => Button::Left,
- winit::event::MouseButton::Right => Button::Right,
- winit::event::MouseButton::Middle => Button::Middle,
- winit::event::MouseButton::Other(other) => Button::Other(other),
- }
- }
-}
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 39da4943..fa72a553 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -77,28 +77,29 @@
//! #
//! # mod iced_wgpu {
//! # use iced_native::{
-//! # button, text, Button, Text,
-//! # MouseCursor, Node, Point, Rectangle, Style, Color, Layout
+//! # button, text, Button, Text, Node, Point, Rectangle, Style, Color, Layout
//! # };
//! #
//! # pub struct Renderer {}
//! #
+//! # impl iced_native::Renderer for Renderer {
+//! # type Output = ();
+//! # }
+//! #
//! # impl button::Renderer for Renderer {
//! # fn node<Message>(
//! # &self,
-//! # _button: &Button<'_, Message>
+//! # _button: &Button<'_, Message, Self>
//! # ) -> Node {
//! # Node::new(Style::default())
//! # }
//! #
//! # fn draw<Message>(
//! # &mut self,
-//! # _button: &Button<'_, Message>,
+//! # _button: &Button<'_, Message, Self>,
//! # _layout: Layout<'_>,
//! # _cursor_position: Point,
-//! # ) -> MouseCursor {
-//! # MouseCursor::OutOfBounds
-//! # }
+//! # ) {}
//! # }
//! #
//! # impl text::Renderer for Renderer {
@@ -124,7 +125,7 @@
//! .push(
//! // The increment button. We tell it to produce an
//! // `IncrementPressed` message when pressed
-//! Button::new(&mut self.increment_button, "+")
+//! Button::new(&mut self.increment_button, Text::new("+"))
//! .on_press(Message::IncrementPressed),
//! )
//! .push(
@@ -134,7 +135,7 @@
//! .push(
//! // The decrement button. We tell it to produce a
//! // `DecrementPressed` message when pressed
-//! Button::new(&mut self.decrement_button, "-")
+//! Button::new(&mut self.decrement_button, Text::new("-"))
//! .on_press(Message::DecrementPressed),
//! )
//! }
@@ -192,7 +193,7 @@
//! [documentation]: https://docs.rs/iced
//! [examples]: https://github.com/hecrj/iced/tree/master/examples
//! [`UserInterface`]: struct.UserInterface.html
-#![deny(missing_docs)]
+//#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
#![deny(unsafe_code)]
@@ -212,7 +213,9 @@ mod user_interface;
pub(crate) use iced_core::Vector;
-pub use iced_core::{Align, Color, Justify, Length, Point, Rectangle};
+pub use iced_core::{
+ Align, Background, Color, Justify, Length, Point, Rectangle,
+};
#[doc(no_inline)]
pub use stretch::{geometry::Size, number::Number};
@@ -223,6 +226,7 @@ pub use hasher::Hasher;
pub use layout::Layout;
pub use mouse_cursor::MouseCursor;
pub use node::Node;
+pub use renderer::Renderer;
pub use style::Style;
pub use user_interface::{Cache, UserInterface};
pub use widget::*;
diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs
index 4ef6361a..291e57a6 100644
--- a/native/src/mouse_cursor.rs
+++ b/native/src/mouse_cursor.rs
@@ -1,5 +1,5 @@
/// The state of the mouse cursor.
-#[derive(Debug, Eq, PartialEq, Clone, Copy)]
+#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)]
pub enum MouseCursor {
/// The cursor is out of the bounds of the user interface.
OutOfBounds,
@@ -19,17 +19,3 @@ pub enum MouseCursor {
/// The cursor is grabbing a widget.
Grabbing,
}
-
-#[cfg(feature = "winit")]
-impl From<MouseCursor> for winit::window::CursorIcon {
- fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon {
- match mouse_cursor {
- MouseCursor::OutOfBounds => winit::window::CursorIcon::Default,
- MouseCursor::Idle => winit::window::CursorIcon::Default,
- MouseCursor::Pointer => winit::window::CursorIcon::Hand,
- MouseCursor::Working => winit::window::CursorIcon::Progress,
- MouseCursor::Grab => winit::window::CursorIcon::Grab,
- MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
- }
- }
-}
diff --git a/native/src/renderer.rs b/native/src/renderer.rs
index 2244f00b..afe1b09a 100644
--- a/native/src/renderer.rs
+++ b/native/src/renderer.rs
@@ -1,8 +1,10 @@
//! Write your own renderer.
//!
-//! There is not a common entrypoint or trait for a __renderer__ in Iced.
-//! Instead, every [`Widget`] constrains its generic `Renderer` type as
-//! necessary.
+//! You will need to implement the `Renderer` trait first. It simply contains
+//! an `Output` associated type.
+//!
+//! There is no common trait to draw all the widgets. Instead, every [`Widget`]
+//! constrains its generic `Renderer` type as necessary.
//!
//! This approach is flexible and composable. For instance, the
//! [`Text`] widget only needs a [`text::Renderer`] while a [`Checkbox`] widget
@@ -17,22 +19,13 @@
//! [`text::Renderer`]: ../widget/text/trait.Renderer.html
//! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html
//! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html
-use crate::{Color, Layout};
-/// A renderer able to graphically explain a [`Layout`].
-///
-/// [`Layout`]: ../struct.Layout.html
-pub trait Debugger {
- /// Explains the [`Layout`] of an [`Element`] for debugging purposes.
- ///
- /// This will be called when [`Element::explain`] has been used. It should
- /// _explain_ the given [`Layout`] graphically.
- ///
- /// A common approach consists in recursively rendering the bounds of the
- /// [`Layout`] and its children.
- ///
- /// [`Layout`]: struct.Layout.html
- /// [`Element`]: struct.Element.html
- /// [`Element::explain`]: struct.Element.html#method.explain
- fn explain(&mut self, layout: &Layout<'_>, color: Color);
+mod debugger;
+mod windowed;
+
+pub use debugger::Debugger;
+pub use windowed::Windowed;
+
+pub trait Renderer {
+ type Output;
}
diff --git a/native/src/renderer/debugger.rs b/native/src/renderer/debugger.rs
new file mode 100644
index 00000000..4cc50661
--- /dev/null
+++ b/native/src/renderer/debugger.rs
@@ -0,0 +1,25 @@
+use crate::{Color, Layout, Point, Widget};
+
+/// A renderer able to graphically explain a [`Layout`].
+///
+/// [`Layout`]: ../struct.Layout.html
+pub trait Debugger: super::Renderer {
+ /// Explains the [`Layout`] of an [`Element`] for debugging purposes.
+ ///
+ /// This will be called when [`Element::explain`] has been used. It should
+ /// _explain_ the given [`Layout`] graphically.
+ ///
+ /// A common approach consists in recursively rendering the bounds of the
+ /// [`Layout`] and its children.
+ ///
+ /// [`Layout`]: struct.Layout.html
+ /// [`Element`]: struct.Element.html
+ /// [`Element::explain`]: struct.Element.html#method.explain
+ fn explain<Message>(
+ &mut self,
+ widget: &dyn Widget<Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ color: Color,
+ ) -> Self::Output;
+}
diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs
new file mode 100644
index 00000000..bcf37964
--- /dev/null
+++ b/native/src/renderer/windowed.rs
@@ -0,0 +1,17 @@
+use crate::MouseCursor;
+
+use raw_window_handle::HasRawWindowHandle;
+
+pub trait Windowed: super::Renderer {
+ type Target;
+
+ fn new<W: HasRawWindowHandle>(window: &W) -> Self;
+
+ fn target(&self, width: u16, height: u16) -> Self::Target;
+
+ fn draw(
+ &mut self,
+ output: &Self::Output,
+ target: &mut Self::Target,
+ ) -> MouseCursor;
+}
diff --git a/native/src/style.rs b/native/src/style.rs
index b1c49fd4..70a7ff74 100644
--- a/native/src/style.rs
+++ b/native/src/style.rs
@@ -74,12 +74,12 @@ impl Style {
self
}
- pub(crate) fn align_items(mut self, align: Align) -> Self {
+ pub fn align_items(mut self, align: Align) -> Self {
self.0.align_items = into_align_items(align);
self
}
- pub(crate) fn justify_content(mut self, justify: Justify) -> Self {
+ pub fn justify_content(mut self, justify: Justify) -> Self {
self.0.justify_content = into_justify_content(justify);
self
}
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 4bfacb2e..5675076d 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,7 +1,7 @@
-use crate::{input::mouse, Column, Element, Event, Layout, MouseCursor, Point};
+use crate::{input::mouse, Element, Event, Layout, Point};
use std::hash::Hasher;
-use stretch::result;
+use stretch::{geometry, result};
/// A set of interactive graphical elements with a specific [`Layout`].
///
@@ -19,7 +19,10 @@ pub struct UserInterface<'a, Message, Renderer> {
cursor_position: Point,
}
-impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
+impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+{
/// Builds a user interface for an [`Element`].
///
/// It is able to avoid expensive computations when using a [`Cache`]
@@ -44,6 +47,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// # impl Renderer {
/// # pub fn new() -> Self { Renderer }
/// # }
+ /// #
+ /// # impl iced_native::Renderer for Renderer { type Output = (); }
+ /// #
+ /// # impl iced_native::column::Renderer for Renderer {
+ /// # fn draw<Message>(
+ /// # &mut self,
+ /// # _column: &iced_native::Column<'_, Message, Self>,
+ /// # _layout: iced_native::Layout<'_>,
+ /// # _cursor_position: iced_native::Point,
+ /// # ) -> Self::Output {
+ /// # ()
+ /// # }
+ /// # }
/// # }
/// #
/// # use iced_native::Column;
@@ -82,7 +98,7 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
pub fn build<E: Into<Element<'a, Message, Renderer>>>(
root: E,
cache: Cache,
- renderer: &mut Renderer,
+ renderer: &Renderer,
) -> Self {
let root = root.into();
@@ -127,6 +143,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// # impl Renderer {
/// # pub fn new() -> Self { Renderer }
/// # }
+ /// #
+ /// # impl iced_native::Renderer for Renderer { type Output = (); }
+ /// #
+ /// # impl iced_native::column::Renderer for Renderer {
+ /// # fn draw<Message>(
+ /// # &mut self,
+ /// # _column: &iced_native::Column<'_, Message, Self>,
+ /// # _layout: iced_native::Layout<'_>,
+ /// # _cursor_position: iced_native::Point,
+ /// # ) -> Self::Output {
+ /// # ()
+ /// # }
+ /// # }
/// # }
/// #
/// # use iced_native::Column;
@@ -212,6 +241,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// # impl Renderer {
/// # pub fn new() -> Self { Renderer }
/// # }
+ /// #
+ /// # impl iced_native::Renderer for Renderer { type Output = (); }
+ /// #
+ /// # impl iced_native::column::Renderer for Renderer {
+ /// # fn draw<Message>(
+ /// # &mut self,
+ /// # _column: &iced_native::Column<'_, Message, Self>,
+ /// # _layout: iced_native::Layout<'_>,
+ /// # _cursor_position: iced_native::Point,
+ /// # ) -> Self::Output {
+ /// # ()
+ /// # }
+ /// # }
/// # }
/// #
/// # use iced_native::Column;
@@ -254,7 +296,7 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// // Flush rendering operations...
/// }
/// ```
- pub fn draw(&self, renderer: &mut Renderer) -> MouseCursor {
+ pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {
self.root.widget.draw(
renderer,
Layout::new(&self.layout),
@@ -295,14 +337,16 @@ impl Cache {
/// [`Cache`]: struct.Cache.html
/// [`UserInterface`]: struct.UserInterface.html
pub fn new() -> Cache {
- let root: Element<'_, (), ()> = Column::new().into();
+ use crate::{Node, Style};
- let hasher = &mut crate::Hasher::default();
- root.hash_layout(hasher);
+ let empty_node = Node::new(Style::default());
Cache {
- hash: hasher.finish(),
- layout: root.compute_layout(&mut ()),
+ hash: 0,
+ layout: empty_node
+ .0
+ .compute_layout(geometry::Size::undefined())
+ .unwrap(),
cursor_position: Point::new(0.0, 0.0),
}
}
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 9b770454..bcef2665 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -20,13 +20,12 @@
//!
//! [`Widget`]: trait.Widget.html
//! [renderer]: ../renderer/index.html
-mod column;
-mod row;
-
pub mod button;
pub mod checkbox;
+pub mod column;
pub mod image;
pub mod radio;
+pub mod row;
pub mod slider;
pub mod text;
@@ -47,7 +46,7 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text::Text;
-use crate::{Event, Hasher, Layout, MouseCursor, Node, Point};
+use crate::{Event, Hasher, Layout, Node, Point};
/// A component that displays information and allows interaction.
///
@@ -56,7 +55,10 @@ use crate::{Event, Hasher, Layout, MouseCursor, Node, Point};
///
/// [`Widget`]: trait.Widget.html
/// [`Element`]: ../struct.Element.html
-pub trait Widget<Message, Renderer>: std::fmt::Debug {
+pub trait Widget<Message, Renderer>: std::fmt::Debug
+where
+ Renderer: crate::Renderer,
+{
/// Returns the [`Node`] of the [`Widget`].
///
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
@@ -65,20 +67,17 @@ pub trait Widget<Message, Renderer>: std::fmt::Debug {
/// [`Node`]: ../struct.Node.html
/// [`Widget`]: trait.Widget.html
/// [`Layout`]: ../struct.Layout.html
- fn node(&self, renderer: &mut Renderer) -> Node;
+ fn node(&self, renderer: &Renderer) -> Node;
/// Draws the [`Widget`] using the associated `Renderer`.
///
- /// It must return the [`MouseCursor`] state for the [`Widget`].
- ///
/// [`Widget`]: trait.Widget.html
- /// [`MouseCursor`]: ../enum.MouseCursor.html
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor;
+ ) -> Renderer::Output;
/// Computes the _layout_ hash of the [`Widget`].
///
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 7b5c4a86..4ab59f7f 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -7,17 +7,21 @@
//! [`Class`]: enum.Class.html
use crate::input::{mouse, ButtonState};
-use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
+use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
-pub use iced_core::button::*;
+pub use iced_core::button::State;
-impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message>
+pub type Button<'a, Message, Renderer> =
+ iced_core::Button<'a, Message, Element<'a, Message, Renderer>>;
+
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Button<'a, Message, Renderer>
where
Renderer: self::Renderer,
Message: Copy + std::fmt::Debug,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@@ -63,14 +67,14 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
+ ) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
- self.label.hash(state);
self.width.hash(state);
self.align_self.hash(state);
+ self.content.hash_layout(state);
}
}
@@ -81,31 +85,33 @@ where
///
/// [`Button`]: struct.Button.html
/// [renderer]: ../../renderer/index.html
-pub trait Renderer {
+pub trait Renderer: crate::Renderer + Sized {
/// Creates a [`Node`] for the provided [`Button`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Button`]: struct.Button.html
- fn node<Message>(&self, button: &Button<'_, Message>) -> Node;
+ fn node<Message>(&self, button: &Button<'_, Message, Self>) -> Node;
/// Draws a [`Button`].
///
/// [`Button`]: struct.Button.html
fn draw<Message>(
&mut self,
- button: &Button<'_, Message>,
+ button: &Button<'_, Message, Self>,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor;
+ ) -> Self::Output;
}
-impl<'a, Message, Renderer> From<Button<'a, Message>>
+impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: 'static + self::Renderer,
Message: 'static + Copy + std::fmt::Debug,
{
- fn from(button: Button<'a, Message>) -> Element<'a, Message, Renderer> {
+ fn from(
+ button: Button<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
Element::new(button)
}
}
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 3e307f64..5393417e 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -2,7 +2,7 @@
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
-use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
+use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
pub use iced_core::Checkbox;
@@ -10,7 +10,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message>
where
Renderer: self::Renderer,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@@ -26,9 +26,7 @@ where
button: mouse::Button::Left,
state: ButtonState::Pressed,
}) => {
- let mouse_over = layout
- .children()
- .any(|child| child.bounds().contains(cursor_position));
+ let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
messages.push((self.on_toggle)(!self.is_checked));
@@ -43,7 +41,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
+ ) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
@@ -59,12 +57,12 @@ where
///
/// [`Checkbox`]: struct.Checkbox.html
/// [renderer]: ../../renderer/index.html
-pub trait Renderer {
+pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Checkbox`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Checkbox`]: struct.Checkbox.html
- fn node<Message>(&mut self, checkbox: &Checkbox<Message>) -> Node;
+ fn node<Message>(&self, checkbox: &Checkbox<Message>) -> Node;
/// Draws a [`Checkbox`].
///
@@ -80,7 +78,7 @@ pub trait Renderer {
checkbox: &Checkbox<Message>,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor;
+ ) -> Self::Output;
}
impl<'a, Message, Renderer> From<Checkbox<Message>>
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 9da2e161..7995cf5d 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -1,8 +1,6 @@
use std::hash::Hash;
-use crate::{
- Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget,
-};
+use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget};
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Renderer> =
@@ -10,8 +8,10 @@ pub type Column<'a, Message, Renderer> =
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
let mut children: Vec<Node> = self
.children
.iter()
@@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
- let mut cursor = MouseCursor::OutOfBounds;
-
- self.children.iter().zip(layout.children()).for_each(
- |(child, layout)| {
- let new_cursor =
- child.widget.draw(renderer, layout, cursor_position);
-
- if new_cursor != MouseCursor::OutOfBounds {
- cursor = new_cursor;
- }
- },
- );
-
- cursor
+ ) -> Renderer::Output {
+ renderer.draw(&self, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -104,10 +91,19 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
}
}
+pub trait Renderer: crate::Renderer + Sized {
+ fn draw<Message>(
+ &mut self,
+ row: &Column<'_, Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output;
+}
+
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a,
+ Renderer: 'a + self::Renderer,
Message: 'static,
{
fn from(
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 81f99acb..6255a7b5 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -1,17 +1,16 @@
//! Display images in your user interface.
-use crate::{Element, Hasher, Layout, MouseCursor, Node, Point, Widget};
+use crate::{Element, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
pub use iced_core::Image;
-impl<I, Message, Renderer> Widget<Message, Renderer> for Image<I>
+impl<Message, Renderer> Widget<Message, Renderer> for Image
where
- Renderer: self::Renderer<I>,
- I: Clone,
+ Renderer: self::Renderer,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@@ -20,10 +19,8 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
_cursor_position: Point,
- ) -> MouseCursor {
- renderer.draw(&self, layout);
-
- MouseCursor::OutOfBounds
+ ) -> Renderer::Output {
+ renderer.draw(&self, layout)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -40,27 +37,26 @@ where
///
/// [`Image`]: struct.Image.html
/// [renderer]: ../../renderer/index.html
-pub trait Renderer<I> {
+pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Image`].
///
/// You should probably keep the original aspect ratio, if possible.
///
/// [`Node`]: ../../struct.Node.html
/// [`Image`]: struct.Image.html
- fn node(&mut self, image: &Image<I>) -> Node;
+ fn node(&self, image: &Image) -> Node;
/// Draws an [`Image`].
///
/// [`Image`]: struct.Image.html
- fn draw(&mut self, image: &Image<I>, layout: Layout<'_>);
+ fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output;
}
-impl<'a, I, Message, Renderer> From<Image<I>> for Element<'a, Message, Renderer>
+impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer<I>,
- I: Clone + 'a,
+ Renderer: self::Renderer,
{
- fn from(image: Image<I>) -> Element<'a, Message, Renderer> {
+ fn from(image: Image) -> Element<'a, Message, Renderer> {
Element::new(image)
}
}
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 33d42e61..27b8f8a8 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,6 +1,6 @@
//! Create choices using radio buttons.
use crate::input::{mouse, ButtonState};
-use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
+use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
@@ -11,7 +11,7 @@ where
Renderer: self::Renderer,
Message: Copy + std::fmt::Debug,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@@ -40,7 +40,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
+ ) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
@@ -56,12 +56,12 @@ where
///
/// [`Radio`]: struct.Radio.html
/// [renderer]: ../../renderer/index.html
-pub trait Renderer {
+pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Radio`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Radio`]: struct.Radio.html
- fn node<Message>(&mut self, radio: &Radio<Message>) -> Node;
+ fn node<Message>(&self, radio: &Radio<Message>) -> Node;
/// Draws a [`Radio`] button.
///
@@ -77,7 +77,7 @@ pub trait Renderer {
radio: &Radio<Message>,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor;
+ ) -> Self::Output;
}
impl<'a, Message, Renderer> From<Radio<Message>>
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 3cd451b7..5ec27159 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,8 +1,6 @@
use std::hash::Hash;
-use crate::{
- Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget,
-};
+use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget};
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Renderer> =
@@ -10,8 +8,10 @@ pub type Row<'a, Message, Renderer> =
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
let mut children: Vec<Node> = self
.children
.iter()
@@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
- let mut cursor = MouseCursor::OutOfBounds;
-
- self.children.iter().zip(layout.children()).for_each(
- |(child, layout)| {
- let new_cursor =
- child.widget.draw(renderer, layout, cursor_position);
-
- if new_cursor != MouseCursor::OutOfBounds {
- cursor = new_cursor;
- }
- },
- );
-
- cursor
+ ) -> Renderer::Output {
+ renderer.draw(&self, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -105,10 +92,19 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
}
}
+pub trait Renderer: crate::Renderer + Sized {
+ fn draw<Message>(
+ &mut self,
+ row: &Row<'_, Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output;
+}
+
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a,
+ Renderer: 'a + self::Renderer,
Message: 'static,
{
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 481296bd..d643d902 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -7,7 +7,7 @@
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
-use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
+use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
pub use iced_core::slider::*;
@@ -15,7 +15,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message>
where
Renderer: self::Renderer,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@@ -71,7 +71,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor {
+ ) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
@@ -87,7 +87,7 @@ where
///
/// [`Slider`]: struct.Slider.html
/// [renderer]: ../../renderer/index.html
-pub trait Renderer {
+pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Radio`].
///
/// [`Node`]: ../../struct.Node.html
@@ -111,7 +111,7 @@ pub trait Renderer {
slider: &Slider<'_, Message>,
layout: Layout<'_>,
cursor_position: Point,
- ) -> MouseCursor;
+ ) -> Self::Output;
}
impl<'a, Message, Renderer> From<Slider<'a, Message>>
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 5ca6ebf3..e389e1d9 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -1,5 +1,5 @@
//! Write some text for your users to read.
-use crate::{Element, Hasher, Layout, MouseCursor, Node, Point, Widget};
+use crate::{Element, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
@@ -9,7 +9,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Text
where
Renderer: self::Renderer,
{
- fn node(&self, renderer: &mut Renderer) -> Node {
+ fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@@ -18,10 +18,8 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
_cursor_position: Point,
- ) -> MouseCursor {
- renderer.draw(&self, layout);
-
- MouseCursor::OutOfBounds
+ ) -> Renderer::Output {
+ renderer.draw(&self, layout)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -40,7 +38,7 @@ where
/// [`Text`]: struct.Text.html
/// [renderer]: ../../renderer/index.html
/// [`UserInterface`]: ../../struct.UserInterface.html
-pub trait Renderer {
+pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] with the given [`Style`] for the provided [`Text`]
/// contents and size.
///
@@ -66,7 +64,7 @@ pub trait Renderer {
/// [`Text`]: struct.Text.html
/// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
/// [`VerticalAlignment`]: enum.VerticalAlignment.html
- fn draw(&mut self, text: &Text, layout: Layout<'_>);
+ fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output;
}
impl<'a, Message, Renderer> From<Text> for Element<'a, Message, Renderer>
diff --git a/src/lib.rs b/src/lib.rs
index e69de29b..1bcdada2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -0,0 +1,59 @@
+#[cfg_attr(target_arch = "wasm32", path = "web.rs")]
+#[cfg_attr(not(target_arch = "wasm32"), path = "winit.rs")]
+mod platform;
+
+pub use platform::*;
+
+pub trait Application {
+ type Message;
+
+ fn update(&mut self, message: Self::Message);
+
+ fn view(&mut self) -> Element<Self::Message>;
+
+ fn run(self)
+ where
+ Self: 'static + Sized,
+ {
+ #[cfg(not(target_arch = "wasm32"))]
+ iced_winit::Application::run(Instance(self));
+
+ #[cfg(target_arch = "wasm32")]
+ iced_web::Application::run(Instance(self));
+ }
+}
+
+struct Instance<A: Application>(A);
+
+#[cfg(not(target_arch = "wasm32"))]
+impl<A> iced_winit::Application for Instance<A>
+where
+ A: Application,
+{
+ type Renderer = Renderer;
+ type Message = A::Message;
+
+ fn update(&mut self, message: Self::Message) {
+ self.0.update(message);
+ }
+
+ fn view(&mut self) -> Element<Self::Message> {
+ self.0.view()
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+impl<A> iced_web::Application for Instance<A>
+where
+ A: Application,
+{
+ type Message = A::Message;
+
+ fn update(&mut self, message: Self::Message) {
+ self.0.update(message);
+ }
+
+ fn view(&mut self) -> Element<Self::Message> {
+ self.0.view()
+ }
+}
diff --git a/src/web.rs b/src/web.rs
new file mode 100644
index 00000000..31f1a6fc
--- /dev/null
+++ b/src/web.rs
@@ -0,0 +1 @@
+pub use iced_web::*;
diff --git a/src/winit.rs b/src/winit.rs
new file mode 100644
index 00000000..64e301f4
--- /dev/null
+++ b/src/winit.rs
@@ -0,0 +1,11 @@
+pub use iced_wgpu::{Primitive, Renderer};
+
+pub use iced_winit::{
+ button, slider, text, winit, Align, Background, Checkbox, Color, Image,
+ Justify, Length, Radio, Slider, Text,
+};
+
+pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>;
+pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>;
+pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>;
+pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>;
diff --git a/web/Cargo.toml b/web/Cargo.toml
index d5a987b0..473bde17 100644
--- a/web/Cargo.toml
+++ b/web/Cargo.toml
@@ -17,8 +17,7 @@ maintenance = { status = "actively-developed" }
[dependencies]
iced_core = { version = "0.1.0-alpha", path = "../core" }
dodrio = "0.1.0"
-futures-preview = "=0.3.0-alpha.18"
-wasm-bindgen = "0.2.50"
+wasm-bindgen = "0.2.51"
[dependencies.web-sys]
version = "0.3.27"
diff --git a/web/src/bus.rs b/web/src/bus.rs
index d76466f5..b4fd67c7 100644
--- a/web/src/bus.rs
+++ b/web/src/bus.rs
@@ -1,4 +1,4 @@
-use crate::Application;
+use crate::Instance;
use std::rc::Rc;
@@ -14,7 +14,7 @@ where
pub fn new() -> Self {
Self {
publish: Rc::new(Box::new(|message, root| {
- let app = root.unwrap_mut::<Application<Message>>();
+ let app = root.unwrap_mut::<Instance<Message>>();
app.update(message)
})),
diff --git a/web/src/element.rs b/web/src/element.rs
index 8270d8db..a2b78c69 100644
--- a/web/src/element.rs
+++ b/web/src/element.rs
@@ -14,6 +14,14 @@ impl<'a, Message> Element<'a, Message> {
}
}
+ pub fn node<'b>(
+ &self,
+ bump: &'b bumpalo::Bump,
+ bus: &Bus<Message>,
+ ) -> dodrio::Node<'b> {
+ self.widget.node(bump, bus)
+ }
+
pub fn explain(self, _color: Color) -> Element<'a, Message> {
self
}
diff --git a/web/src/lib.rs b/web/src/lib.rs
index caf17df5..559a5af0 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -1,5 +1,4 @@
use dodrio::bumpalo;
-use futures::Future;
use std::cell::RefCell;
mod bus;
@@ -8,16 +7,13 @@ pub mod widget;
pub use bus::Bus;
pub use element::Element;
-pub use iced_core::{Align, Color, Justify, Length};
+pub use iced_core::{Align, Background, Color, Justify, Length};
pub use widget::*;
-pub trait UserInterface {
+pub trait Application {
type Message;
- fn update(
- &mut self,
- message: Self::Message,
- ) -> Option<Box<dyn Future<Output = Self::Message>>>;
+ fn update(&mut self, message: Self::Message);
fn view(&mut self) -> Element<Self::Message>;
@@ -25,37 +21,34 @@ pub trait UserInterface {
where
Self: 'static + Sized,
{
+ let app = Instance::new(self);
+
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
-
- let app = Application::new(self);
-
let vdom = dodrio::Vdom::new(&body, app);
+
vdom.forget();
}
}
-struct Application<Message> {
- ui: RefCell<Box<dyn UserInterface<Message = Message>>>,
+struct Instance<Message> {
+ ui: RefCell<Box<dyn Application<Message = Message>>>,
}
-impl<Message> Application<Message> {
- fn new(ui: impl UserInterface<Message = Message> + 'static) -> Self {
+impl<Message> Instance<Message> {
+ fn new(ui: impl Application<Message = Message> + 'static) -> Self {
Self {
ui: RefCell::new(Box::new(ui)),
}
}
fn update(&mut self, message: Message) {
- let mut ui = self.ui.borrow_mut();
-
- // TODO: Resolve futures and publish resulting messages
- let _ = ui.update(message);
+ self.ui.borrow_mut().update(message);
}
}
-impl<Message> dodrio::Render for Application<Message>
+impl<Message> dodrio::Render for Instance<Message>
where
Message: 'static,
{
diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs
index 23a4165a..257034a7 100644
--- a/web/src/widget/button.rs
+++ b/web/src/widget/button.rs
@@ -2,7 +2,10 @@ use crate::{Bus, Element, Widget};
use dodrio::bumpalo;
-pub use iced_core::button::*;
+pub use iced_core::button::State;
+
+pub type Button<'a, Message> =
+ iced_core::Button<'a, Message, Element<'a, Message>>;
impl<'a, Message> Widget<Message> for Button<'a, Message>
where
@@ -15,9 +18,8 @@ where
) -> dodrio::Node<'b> {
use dodrio::builder::*;
- let label = bumpalo::format!(in bump, "{}", self.label);
-
- let mut node = button(bump).children(vec![text(label.into_bump_str())]);
+ let mut node =
+ button(bump).children(vec![self.content.node(bump, bus)]);
if let Some(on_press) = self.on_press {
let event_bus = bus.clone();
diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs
index fd4ff0df..bd3e5daf 100644
--- a/web/src/widget/image.rs
+++ b/web/src/widget/image.rs
@@ -2,9 +2,9 @@ use crate::{Bus, Element, Length, Widget};
use dodrio::bumpalo;
-pub type Image<'a> = iced_core::Image<&'a str>;
+pub use iced_core::Image;
-impl<'a, Message> Widget<Message> for Image<'a> {
+impl<Message> Widget<Message> for Image {
fn node<'b>(
&self,
bump: &'b bumpalo::Bump,
@@ -12,7 +12,7 @@ impl<'a, Message> Widget<Message> for Image<'a> {
) -> dodrio::Node<'b> {
use dodrio::builder::*;
- let src = bumpalo::format!(in bump, "{}", self.handle);
+ let src = bumpalo::format!(in bump, "{}", self.path);
let mut image = img(bump).attr("src", src.into_bump_str());
@@ -35,8 +35,8 @@ impl<'a, Message> Widget<Message> for Image<'a> {
}
}
-impl<'a, Message> From<Image<'a>> for Element<'a, Message> {
- fn from(image: Image<'a>) -> Element<'a, Message> {
+impl<'a, Message> From<Image> for Element<'a, Message> {
+ fn from(image: Image) -> Element<'a, Message> {
Element::new(image)
}
}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
new file mode 100644
index 00000000..cac5e113
--- /dev/null
+++ b/wgpu/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "iced_wgpu"
+version = "0.1.0-alpha"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+description = "A wgpu renderer for Iced"
+license = "MIT"
+repository = "https://github.com/hecrj/iced"
+
+[dependencies]
+iced_native = { version = "0.1.0-alpha", path = "../native" }
+wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "cb25914b95b58fee0dc139b400867e7a731d98f4" }
+wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "48daa98f5f785963838b4345e86ac40eac095ba9" }
+raw-window-handle = "0.1"
+image = "0.22"
+log = "0.4"
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
new file mode 100644
index 00000000..c883eaa8
--- /dev/null
+++ b/wgpu/src/image.rs
@@ -0,0 +1,438 @@
+use crate::Transformation;
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::mem;
+use std::rc::Rc;
+
+pub struct Pipeline {
+ cache: RefCell<HashMap<String, Memory>>,
+
+ pipeline: wgpu::RenderPipeline,
+ transform: wgpu::Buffer,
+ vertices: wgpu::Buffer,
+ indices: wgpu::Buffer,
+ instances: wgpu::Buffer,
+ constants: wgpu::BindGroup,
+ texture_layout: wgpu::BindGroupLayout,
+}
+
+impl Pipeline {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ 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,
+ lod_min_clamp: -100.0,
+ lod_max_clamp: 100.0,
+ compare_function: wgpu::CompareFunction::Always,
+ });
+
+ let constant_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ bindings: &[
+ wgpu::BindGroupLayoutBinding {
+ binding: 0,
+ visibility: wgpu::ShaderStage::VERTEX,
+ ty: wgpu::BindingType::UniformBuffer { dynamic: false },
+ },
+ wgpu::BindGroupLayoutBinding {
+ binding: 1,
+ visibility: wgpu::ShaderStage::FRAGMENT,
+ ty: wgpu::BindingType::Sampler,
+ },
+ ],
+ });
+
+ let matrix: [f32; 16] = Transformation::identity().into();
+
+ let transform_buffer = device
+ .create_buffer_mapped(
+ 16,
+ wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
+ )
+ .fill_from_slice(&matrix[..]);
+
+ let constant_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &constant_layout,
+ bindings: &[
+ wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer {
+ buffer: &transform_buffer,
+ range: 0..64,
+ },
+ },
+ wgpu::Binding {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(&sampler),
+ },
+ ],
+ });
+
+ let texture_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ bindings: &[wgpu::BindGroupLayoutBinding {
+ binding: 0,
+ visibility: wgpu::ShaderStage::FRAGMENT,
+ ty: wgpu::BindingType::SampledTexture {
+ multisampled: false,
+ dimension: wgpu::TextureViewDimension::D2,
+ },
+ }],
+ });
+
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ bind_group_layouts: &[&constant_layout, &texture_layout],
+ });
+
+ let vs = include_bytes!("shader/image.vert.spv");
+ let vs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&vs[..]))
+ .expect("Read image vertex shader as SPIR-V"),
+ );
+
+ let fs = include_bytes!("shader/image.frag.spv");
+ let fs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&fs[..]))
+ .expect("Read image fragment shader as SPIR-V"),
+ );
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ layout: &layout,
+ vertex_stage: wgpu::ProgrammableStageDescriptor {
+ module: &vs_module,
+ entry_point: "main",
+ },
+ fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
+ module: &fs_module,
+ entry_point: "main",
+ }),
+ rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+ front_face: wgpu::FrontFace::Cw,
+ cull_mode: wgpu::CullMode::None,
+ depth_bias: 0,
+ depth_bias_slope_scale: 0.0,
+ depth_bias_clamp: 0.0,
+ }),
+ primitive_topology: wgpu::PrimitiveTopology::TriangleList,
+ color_states: &[wgpu::ColorStateDescriptor {
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ color_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
+ depth_stencil_state: None,
+ index_format: wgpu::IndexFormat::Uint16,
+ vertex_buffers: &[
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Vertex>() as u64,
+ step_mode: wgpu::InputStepMode::Vertex,
+ attributes: &[wgpu::VertexAttributeDescriptor {
+ shader_location: 0,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ }],
+ },
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Instance>() as u64,
+ step_mode: wgpu::InputStepMode::Instance,
+ attributes: &[
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 1,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 2,
+ format: wgpu::VertexFormat::Float2,
+ offset: 4 * 2,
+ },
+ ],
+ },
+ ],
+ sample_count: 1,
+ sample_mask: !0,
+ alpha_to_coverage_enabled: false,
+ });
+
+ let vertices = device
+ .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX)
+ .fill_from_slice(&QUAD_VERTS);
+
+ let indices = device
+ .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX)
+ .fill_from_slice(&QUAD_INDICES);
+
+ let instances = device.create_buffer(&wgpu::BufferDescriptor {
+ size: mem::size_of::<Instance>() as u64,
+ usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
+ });
+
+ Pipeline {
+ cache: RefCell::new(HashMap::new()),
+
+ pipeline,
+ transform: transform_buffer,
+ vertices,
+ indices,
+ instances,
+ constants: constant_bind_group,
+ texture_layout,
+ }
+ }
+
+ pub fn dimensions(&self, path: &str) -> (u32, u32) {
+ self.load(path);
+
+ self.cache.borrow().get(path).unwrap().dimensions()
+ }
+
+ fn load(&self, path: &str) {
+ if !self.cache.borrow().contains_key(path) {
+ let image = image::open(path).expect("Load image").to_bgra();
+
+ self.cache
+ .borrow_mut()
+ .insert(path.to_string(), Memory::Host { image });
+ }
+ }
+
+ pub fn draw(
+ &mut self,
+ device: &mut wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ instances: &[Image],
+ transformation: Transformation,
+ target: &wgpu::TextureView,
+ ) {
+ let matrix: [f32; 16] = transformation.into();
+
+ let transform_buffer = device
+ .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&matrix[..]);
+
+ encoder.copy_buffer_to_buffer(
+ &transform_buffer,
+ 0,
+ &self.transform,
+ 0,
+ 16 * 4,
+ );
+
+ // TODO: Batch draw calls using a texture atlas
+ // Guillotière[1] by @nical can help us a lot here.
+ //
+ // [1]: https://github.com/nical/guillotiere
+ for image in instances {
+ self.load(&image.path);
+
+ let texture = self
+ .cache
+ .borrow_mut()
+ .get_mut(&image.path)
+ .unwrap()
+ .upload(device, encoder, &self.texture_layout);
+
+ let instance_buffer = device
+ .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&[Instance {
+ _position: image.position,
+ _scale: image.scale,
+ }]);
+
+ encoder.copy_buffer_to_buffer(
+ &instance_buffer,
+ 0,
+ &self.instances,
+ 0,
+ mem::size_of::<Image>() as u64,
+ );
+
+ {
+ let mut render_pass =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[
+ wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: target,
+ resolve_target: None,
+ load_op: wgpu::LoadOp::Load,
+ store_op: wgpu::StoreOp::Store,
+ clear_color: wgpu::Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.0,
+ },
+ },
+ ],
+ depth_stencil_attachment: None,
+ });
+
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_bind_group(1, &texture, &[]);
+ render_pass.set_index_buffer(&self.indices, 0);
+ render_pass.set_vertex_buffers(
+ 0,
+ &[(&self.vertices, 0), (&self.instances, 0)],
+ );
+
+ render_pass.draw_indexed(
+ 0..QUAD_INDICES.len() as u32,
+ 0,
+ 0..1 as u32,
+ );
+ }
+ }
+ }
+}
+
+enum Memory {
+ Host {
+ image: image::ImageBuffer<image::Bgra<u8>, Vec<u8>>,
+ },
+ Device {
+ bind_group: Rc<wgpu::BindGroup>,
+ width: u32,
+ height: u32,
+ },
+}
+
+impl Memory {
+ fn dimensions(&self) -> (u32, u32) {
+ match self {
+ Memory::Host { image } => image.dimensions(),
+ Memory::Device { width, height, .. } => (*width, *height),
+ }
+ }
+
+ fn upload(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ texture_layout: &wgpu::BindGroupLayout,
+ ) -> Rc<wgpu::BindGroup> {
+ match self {
+ Memory::Host { image } => {
+ let (width, height) = image.dimensions();
+
+ let extent = wgpu::Extent3d {
+ width,
+ height,
+ depth: 1,
+ };
+
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ size: extent,
+ array_layer_count: 1,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::SAMPLED,
+ });
+
+ let slice = image.clone().into_raw();
+
+ let temp_buf = device
+ .create_buffer_mapped(
+ slice.len(),
+ wgpu::BufferUsage::COPY_SRC,
+ )
+ .fill_from_slice(&slice[..]);
+
+ encoder.copy_buffer_to_texture(
+ wgpu::BufferCopyView {
+ buffer: &temp_buf,
+ offset: 0,
+ row_pitch: 4 * width as u32,
+ image_height: height as u32,
+ },
+ wgpu::TextureCopyView {
+ texture: &texture,
+ array_layer: 0,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ extent,
+ );
+
+ let bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: texture_layout,
+ bindings: &[wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(
+ &texture.create_default_view(),
+ ),
+ }],
+ });
+
+ let bind_group = Rc::new(bind_group);
+
+ *self = Memory::Device {
+ bind_group: bind_group.clone(),
+ width,
+ height,
+ };
+
+ bind_group
+ }
+ Memory::Device { bind_group, .. } => bind_group.clone(),
+ }
+ }
+}
+
+pub struct Image {
+ pub path: String,
+ pub position: [f32; 2],
+ pub scale: [f32; 2],
+}
+
+#[derive(Clone, Copy)]
+pub struct Vertex {
+ _position: [f32; 2],
+}
+
+const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+
+const QUAD_VERTS: [Vertex; 4] = [
+ Vertex {
+ _position: [0.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 1.0],
+ },
+ Vertex {
+ _position: [0.0, 1.0],
+ },
+];
+
+#[derive(Clone, Copy)]
+struct Instance {
+ _position: [f32; 2],
+ _scale: [f32; 2],
+}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
new file mode 100644
index 00000000..01dc4c20
--- /dev/null
+++ b/wgpu/src/lib.rs
@@ -0,0 +1,12 @@
+mod image;
+mod primitive;
+mod quad;
+mod renderer;
+mod transformation;
+
+pub(crate) use crate::image::Image;
+pub(crate) use quad::Quad;
+pub(crate) use transformation::Transformation;
+
+pub use primitive::Primitive;
+pub use renderer::{Renderer, Target};
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
new file mode 100644
index 00000000..0b9e2c41
--- /dev/null
+++ b/wgpu/src/primitive.rs
@@ -0,0 +1,26 @@
+use iced_native::{text, Background, Color, Rectangle};
+
+#[derive(Debug, Clone)]
+pub enum Primitive {
+ None,
+ Group {
+ primitives: Vec<Primitive>,
+ },
+ Text {
+ content: String,
+ bounds: Rectangle,
+ color: Color,
+ size: f32,
+ horizontal_alignment: text::HorizontalAlignment,
+ vertical_alignment: text::VerticalAlignment,
+ },
+ Quad {
+ bounds: Rectangle,
+ background: Background,
+ border_radius: u16,
+ },
+ Image {
+ path: String,
+ bounds: Rectangle,
+ },
+}
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
new file mode 100644
index 00000000..adb294f0
--- /dev/null
+++ b/wgpu/src/quad.rs
@@ -0,0 +1,275 @@
+use crate::Transformation;
+
+use std::mem;
+
+pub struct Pipeline {
+ pipeline: wgpu::RenderPipeline,
+ constants: wgpu::BindGroup,
+ transform: wgpu::Buffer,
+ vertices: wgpu::Buffer,
+ indices: wgpu::Buffer,
+ instances: wgpu::Buffer,
+}
+
+impl Pipeline {
+ pub fn new(device: &mut wgpu::Device) -> Pipeline {
+ let constant_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ bindings: &[wgpu::BindGroupLayoutBinding {
+ binding: 0,
+ visibility: wgpu::ShaderStage::VERTEX,
+ ty: wgpu::BindingType::UniformBuffer { dynamic: false },
+ }],
+ });
+
+ let matrix: [f32; 16] = Transformation::identity().into();
+
+ let transform = device
+ .create_buffer_mapped(
+ 16,
+ wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
+ )
+ .fill_from_slice(&matrix[..]);
+
+ let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &constant_layout,
+ bindings: &[wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer {
+ buffer: &transform,
+ range: 0..64,
+ },
+ }],
+ });
+
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ bind_group_layouts: &[&constant_layout],
+ });
+
+ let vs = include_bytes!("shader/quad.vert.spv");
+ let vs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&vs[..]))
+ .expect("Read quad vertex shader as SPIR-V"),
+ );
+
+ let fs = include_bytes!("shader/quad.frag.spv");
+ let fs_module = device.create_shader_module(
+ &wgpu::read_spirv(std::io::Cursor::new(&fs[..]))
+ .expect("Read quad fragment shader as SPIR-V"),
+ );
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ layout: &layout,
+ vertex_stage: wgpu::ProgrammableStageDescriptor {
+ module: &vs_module,
+ entry_point: "main",
+ },
+ fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
+ module: &fs_module,
+ entry_point: "main",
+ }),
+ rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+ front_face: wgpu::FrontFace::Cw,
+ cull_mode: wgpu::CullMode::None,
+ depth_bias: 0,
+ depth_bias_slope_scale: 0.0,
+ depth_bias_clamp: 0.0,
+ }),
+ primitive_topology: wgpu::PrimitiveTopology::TriangleList,
+ color_states: &[wgpu::ColorStateDescriptor {
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ color_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha_blend: wgpu::BlendDescriptor {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
+ depth_stencil_state: None,
+ index_format: wgpu::IndexFormat::Uint16,
+ vertex_buffers: &[
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Vertex>() as u64,
+ step_mode: wgpu::InputStepMode::Vertex,
+ attributes: &[wgpu::VertexAttributeDescriptor {
+ shader_location: 0,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ }],
+ },
+ wgpu::VertexBufferDescriptor {
+ stride: mem::size_of::<Quad>() as u64,
+ step_mode: wgpu::InputStepMode::Instance,
+ attributes: &[
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 1,
+ format: wgpu::VertexFormat::Float2,
+ offset: 0,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 2,
+ format: wgpu::VertexFormat::Float2,
+ offset: 4 * 2,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 3,
+ format: wgpu::VertexFormat::Float4,
+ offset: 4 * (2 + 2),
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 4,
+ format: wgpu::VertexFormat::Uint,
+ offset: 4 * (2 + 2 + 4),
+ },
+ ],
+ },
+ ],
+ sample_count: 1,
+ sample_mask: !0,
+ alpha_to_coverage_enabled: false,
+ });
+
+ let vertices = device
+ .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX)
+ .fill_from_slice(&QUAD_VERTS);
+
+ let indices = device
+ .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX)
+ .fill_from_slice(&QUAD_INDICES);
+
+ let instances = device.create_buffer(&wgpu::BufferDescriptor {
+ size: mem::size_of::<Quad>() as u64 * Quad::MAX as u64,
+ usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
+ });
+
+ Pipeline {
+ pipeline,
+ constants,
+ transform,
+ vertices,
+ indices,
+ instances,
+ }
+ }
+
+ pub fn draw(
+ &mut self,
+ device: &mut wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ instances: &[Quad],
+ transformation: Transformation,
+ target: &wgpu::TextureView,
+ ) {
+ let matrix: [f32; 16] = transformation.into();
+
+ let transform_buffer = device
+ .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&matrix[..]);
+
+ encoder.copy_buffer_to_buffer(
+ &transform_buffer,
+ 0,
+ &self.transform,
+ 0,
+ 16 * 4,
+ );
+
+ let mut i = 0;
+ let total = instances.len();
+
+ while i < total {
+ let end = (i + Quad::MAX).min(total);
+ let amount = end - i;
+
+ let instance_buffer = device
+ .create_buffer_mapped(amount, wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&instances[i..end]);
+
+ encoder.copy_buffer_to_buffer(
+ &instance_buffer,
+ 0,
+ &self.instances,
+ 0,
+ (mem::size_of::<Quad>() * amount) as u64,
+ );
+
+ {
+ let mut render_pass =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[
+ wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: target,
+ resolve_target: None,
+ load_op: wgpu::LoadOp::Load,
+ store_op: wgpu::StoreOp::Store,
+ clear_color: wgpu::Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.0,
+ },
+ },
+ ],
+ depth_stencil_attachment: None,
+ });
+
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_index_buffer(&self.indices, 0);
+ render_pass.set_vertex_buffers(
+ 0,
+ &[(&self.vertices, 0), (&self.instances, 0)],
+ );
+
+ render_pass.draw_indexed(
+ 0..QUAD_INDICES.len() as u32,
+ 0,
+ 0..amount as u32,
+ );
+ }
+
+ i += Quad::MAX;
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct Vertex {
+ _position: [f32; 2],
+}
+
+const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+
+const QUAD_VERTS: [Vertex; 4] = [
+ Vertex {
+ _position: [0.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 0.0],
+ },
+ Vertex {
+ _position: [1.0, 1.0],
+ },
+ Vertex {
+ _position: [0.0, 1.0],
+ },
+];
+
+#[derive(Debug, Clone, Copy)]
+pub struct Quad {
+ pub position: [f32; 2],
+ pub scale: [f32; 2],
+ pub color: [f32; 4],
+ pub border_radius: u32,
+}
+
+impl Quad {
+ const MAX: usize = 100_000;
+}
diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs
new file mode 100644
index 00000000..ab6f744f
--- /dev/null
+++ b/wgpu/src/renderer.rs
@@ -0,0 +1,329 @@
+use crate::{quad, Image, Primitive, Quad, Transformation};
+use iced_native::{
+ renderer::Debugger, renderer::Windowed, Background, Color, Layout,
+ MouseCursor, Point, Widget,
+};
+
+use raw_window_handle::HasRawWindowHandle;
+use wgpu::{
+ Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor,
+ Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, Surface,
+ SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage,
+};
+use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, Section};
+
+use std::{cell::RefCell, rc::Rc};
+
+mod button;
+mod checkbox;
+mod column;
+mod image;
+mod radio;
+mod row;
+mod slider;
+mod text;
+
+pub struct Renderer {
+ surface: Surface,
+ adapter: Adapter,
+ device: Device,
+ queue: Queue,
+ quad_pipeline: quad::Pipeline,
+ image_pipeline: crate::image::Pipeline,
+
+ quads: Vec<Quad>,
+ images: Vec<Image>,
+ glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>,
+}
+
+pub struct Target {
+ width: u16,
+ height: u16,
+ transformation: Transformation,
+ swap_chain: SwapChain,
+}
+
+impl Renderer {
+ fn new<W: HasRawWindowHandle>(window: &W) -> Self {
+ let adapter = Adapter::request(&RequestAdapterOptions {
+ power_preference: PowerPreference::LowPower,
+ backends: BackendBit::all(),
+ })
+ .expect("Request adapter");
+
+ let (mut device, queue) = adapter.request_device(&DeviceDescriptor {
+ extensions: Extensions {
+ anisotropic_filtering: false,
+ },
+ limits: Limits { max_bind_groups: 1 },
+ });
+
+ let surface = Surface::create(window);
+
+ // TODO: Think about font loading strategy
+ // Loading system fonts with fallback may be a good idea
+ let font: &[u8] =
+ include_bytes!("../../examples/resources/Roboto-Regular.ttf");
+
+ let glyph_brush = GlyphBrushBuilder::using_font_bytes(font)
+ .build(&mut device, TextureFormat::Bgra8UnormSrgb);
+
+ let quad_pipeline = quad::Pipeline::new(&mut device);
+ let image_pipeline = crate::image::Pipeline::new(&mut device);
+
+ Self {
+ surface,
+ adapter,
+ device,
+ queue,
+ quad_pipeline,
+ image_pipeline,
+
+ quads: Vec::new(),
+ images: Vec::new(),
+ glyph_brush: Rc::new(RefCell::new(glyph_brush)),
+ }
+ }
+
+ fn target(&self, width: u16, height: u16) -> Target {
+ Target {
+ width,
+ height,
+ transformation: Transformation::orthographic(width, height),
+ swap_chain: self.device.create_swap_chain(
+ &self.surface,
+ &SwapChainDescriptor {
+ usage: TextureUsage::OUTPUT_ATTACHMENT,
+ format: TextureFormat::Bgra8UnormSrgb,
+ width: u32::from(width),
+ height: u32::from(height),
+ present_mode: wgpu::PresentMode::Vsync,
+ },
+ ),
+ }
+ }
+
+ fn draw(
+ &mut self,
+ (primitive, mouse_cursor): &(Primitive, MouseCursor),
+ target: &mut Target,
+ ) -> MouseCursor {
+ log::debug!("Drawing");
+
+ let frame = target.swap_chain.get_next_texture();
+
+ let mut encoder = self
+ .device
+ .create_command_encoder(&CommandEncoderDescriptor { todo: 0 });
+
+ let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: &frame.view,
+ resolve_target: None,
+ load_op: wgpu::LoadOp::Clear,
+ store_op: wgpu::StoreOp::Store,
+ clear_color: wgpu::Color {
+ r: 1.0,
+ g: 1.0,
+ b: 1.0,
+ a: 1.0,
+ },
+ }],
+ depth_stencil_attachment: None,
+ });
+
+ self.draw_primitive(primitive);
+
+ self.quad_pipeline.draw(
+ &mut self.device,
+ &mut encoder,
+ &self.quads,
+ target.transformation,
+ &frame.view,
+ );
+
+ self.quads.clear();
+
+ self.image_pipeline.draw(
+ &mut self.device,
+ &mut encoder,
+ &self.images,
+ target.transformation,
+ &frame.view,
+ );
+
+ self.images.clear();
+
+ self.glyph_brush
+ .borrow_mut()
+ .draw_queued(
+ &mut self.device,
+ &mut encoder,
+ &frame.view,
+ u32::from(target.width),
+ u32::from(target.height),
+ )
+ .expect("Draw text");
+
+ self.queue.submit(&[encoder.finish()]);
+
+ *mouse_cursor
+ }
+
+ fn draw_primitive(&mut self, primitive: &Primitive) {
+ match primitive {
+ Primitive::None => {}
+ Primitive::Group { primitives } => {
+ // TODO: Inspect a bit and regroup (?)
+ for primitive in primitives {
+ self.draw_primitive(primitive)
+ }
+ }
+ Primitive::Text {
+ content,
+ bounds,
+ size,
+ color,
+ horizontal_alignment,
+ vertical_alignment,
+ } => {
+ let x = match horizontal_alignment {
+ iced_native::text::HorizontalAlignment::Left => bounds.x,
+ iced_native::text::HorizontalAlignment::Center => {
+ bounds.x + bounds.width / 2.0
+ }
+ iced_native::text::HorizontalAlignment::Right => {
+ bounds.x + bounds.width
+ }
+ };
+
+ let y = match vertical_alignment {
+ iced_native::text::VerticalAlignment::Top => bounds.y,
+ iced_native::text::VerticalAlignment::Center => {
+ bounds.y + bounds.height / 2.0
+ }
+ iced_native::text::VerticalAlignment::Bottom => {
+ bounds.y + bounds.height
+ }
+ };
+
+ self.glyph_brush.borrow_mut().queue(Section {
+ text: &content,
+ screen_position: (x, y),
+ bounds: (bounds.width, bounds.height),
+ scale: wgpu_glyph::Scale { x: *size, y: *size },
+ color: color.into_linear(),
+ layout: wgpu_glyph::Layout::default()
+ .h_align(match horizontal_alignment {
+ iced_native::text::HorizontalAlignment::Left => {
+ wgpu_glyph::HorizontalAlign::Left
+ }
+ iced_native::text::HorizontalAlignment::Center => {
+ wgpu_glyph::HorizontalAlign::Center
+ }
+ iced_native::text::HorizontalAlignment::Right => {
+ wgpu_glyph::HorizontalAlign::Right
+ }
+ })
+ .v_align(match vertical_alignment {
+ iced_native::text::VerticalAlignment::Top => {
+ wgpu_glyph::VerticalAlign::Top
+ }
+ iced_native::text::VerticalAlignment::Center => {
+ wgpu_glyph::VerticalAlign::Center
+ }
+ iced_native::text::VerticalAlignment::Bottom => {
+ wgpu_glyph::VerticalAlign::Bottom
+ }
+ }),
+ ..Default::default()
+ })
+ }
+ Primitive::Quad {
+ bounds,
+ background,
+ border_radius,
+ } => {
+ self.quads.push(Quad {
+ position: [bounds.x, bounds.y],
+ scale: [bounds.width, bounds.height],
+ color: match background {
+ Background::Color(color) => color.into_linear(),
+ },
+ border_radius: u32::from(*border_radius),
+ });
+ }
+ Primitive::Image { path, bounds } => {
+ self.images.push(Image {
+ path: path.clone(),
+ position: [bounds.x, bounds.y],
+ scale: [bounds.width, bounds.height],
+ });
+ }
+ }
+ }
+}
+
+impl iced_native::Renderer for Renderer {
+ type Output = (Primitive, MouseCursor);
+}
+
+impl Windowed for Renderer {
+ type Target = Target;
+
+ fn new<W: HasRawWindowHandle>(window: &W) -> Self {
+ Self::new(window)
+ }
+
+ fn target(&self, width: u16, height: u16) -> Target {
+ self.target(width, height)
+ }
+
+ fn draw(
+ &mut self,
+ output: &Self::Output,
+ target: &mut Target,
+ ) -> MouseCursor {
+ self.draw(output, target)
+ }
+}
+
+impl Debugger for Renderer {
+ fn explain<Message>(
+ &mut self,
+ widget: &dyn Widget<Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ color: Color,
+ ) -> Self::Output {
+ let mut primitives = Vec::new();
+ let (primitive, cursor) = widget.draw(self, layout, cursor_position);
+
+ explain_layout(layout, color, &mut primitives);
+ primitives.push(primitive);
+
+ (Primitive::Group { primitives }, cursor)
+ }
+}
+
+fn explain_layout(
+ layout: Layout,
+ color: Color,
+ primitives: &mut Vec<Primitive>,
+) {
+ // TODO: Draw borders instead
+ primitives.push(Primitive::Quad {
+ bounds: layout.bounds(),
+ background: Background::Color(Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.05,
+ }),
+ border_radius: 0,
+ });
+
+ for child in layout.children() {
+ explain_layout(child, color, primitives);
+ }
+}
diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs
new file mode 100644
index 00000000..ad2186d6
--- /dev/null
+++ b/wgpu/src/renderer/button.rs
@@ -0,0 +1,86 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ button, Align, Background, Button, Color, Layout, Length, MouseCursor,
+ Node, Point, Rectangle, Style,
+};
+
+impl button::Renderer for Renderer {
+ fn node<Message>(&self, button: &Button<Message, Self>) -> Node {
+ let style = Style::default()
+ .width(button.width)
+ .padding(button.padding)
+ .min_width(Length::Units(100))
+ .align_self(button.align_self)
+ .align_items(Align::Stretch);
+
+ Node::with_children(style, vec![button.content.node(self)])
+ }
+
+ fn draw<Message>(
+ &mut self,
+ button: &Button<Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+
+ let (content, _) = button.content.draw(
+ self,
+ layout.children().next().unwrap(),
+ cursor_position,
+ );
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ // TODO: Render proper shadows
+ // TODO: Make hovering and pressed styles configurable
+ let shadow_offset = if is_mouse_over {
+ if button.state.is_pressed {
+ 0.0
+ } else {
+ 2.0
+ }
+ } else {
+ 1.0
+ };
+
+ (
+ Primitive::Group {
+ primitives: vec![
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x + 1.0,
+ y: bounds.y + shadow_offset,
+ ..bounds
+ },
+ background: Background::Color(Color {
+ r: 0.0,
+ b: 0.0,
+ g: 0.0,
+ a: 0.5,
+ }),
+ border_radius: button.border_radius,
+ },
+ Primitive::Quad {
+ bounds,
+ background: button.background.unwrap_or(
+ Background::Color(Color {
+ r: 0.8,
+ b: 0.8,
+ g: 0.8,
+ a: 1.0,
+ }),
+ ),
+ border_radius: button.border_radius,
+ },
+ content,
+ ],
+ },
+ if is_mouse_over {
+ MouseCursor::Pointer
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs
new file mode 100644
index 00000000..fd3f08b1
--- /dev/null
+++ b/wgpu/src/renderer/checkbox.rs
@@ -0,0 +1,106 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ checkbox, text, text::HorizontalAlignment, text::VerticalAlignment, Align,
+ Background, Checkbox, Color, Column, Layout, Length, MouseCursor, Node,
+ Point, Rectangle, Row, Text, Widget,
+};
+
+const SIZE: f32 = 28.0;
+
+impl checkbox::Renderer for Renderer {
+ fn node<Message>(&self, checkbox: &Checkbox<Message>) -> Node {
+ Row::<(), Self>::new()
+ .spacing(15)
+ .align_items(Align::Center)
+ .push(
+ Column::new()
+ .width(Length::Units(SIZE as u16))
+ .height(Length::Units(SIZE as u16)),
+ )
+ .push(Text::new(&checkbox.label))
+ .node(self)
+ }
+
+ fn draw<Message>(
+ &mut self,
+ checkbox: &Checkbox<Message>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ let checkbox_layout = children.next().unwrap();
+ let label_layout = children.next().unwrap();
+ let checkbox_bounds = checkbox_layout.bounds();
+
+ let (label, _) = text::Renderer::draw(
+ self,
+ &Text::new(&checkbox.label),
+ label_layout,
+ );
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let (checkbox_border, checkbox_box) = (
+ Primitive::Quad {
+ bounds: checkbox_bounds,
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: 6,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: checkbox_bounds.x + 1.0,
+ y: checkbox_bounds.y + 1.0,
+ width: checkbox_bounds.width - 2.0,
+ height: checkbox_bounds.height - 2.0,
+ },
+ background: Background::Color(if is_mouse_over {
+ Color {
+ r: 0.90,
+ g: 0.90,
+ b: 0.90,
+ a: 1.0,
+ }
+ } else {
+ Color {
+ r: 0.95,
+ g: 0.95,
+ b: 0.95,
+ a: 1.0,
+ }
+ }),
+ border_radius: 6,
+ },
+ );
+
+ (
+ Primitive::Group {
+ primitives: if checkbox.is_checked {
+ // TODO: Draw an actual icon
+ let (check, _) = text::Renderer::draw(
+ self,
+ &Text::new("X")
+ .horizontal_alignment(HorizontalAlignment::Center)
+ .vertical_alignment(VerticalAlignment::Center),
+ checkbox_layout,
+ );
+
+ vec![checkbox_border, checkbox_box, check, label]
+ } else {
+ vec![checkbox_border, checkbox_box, label]
+ },
+ },
+ if is_mouse_over {
+ MouseCursor::Pointer
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/column.rs b/wgpu/src/renderer/column.rs
new file mode 100644
index 00000000..cac6da77
--- /dev/null
+++ b/wgpu/src/renderer/column.rs
@@ -0,0 +1,34 @@
+use crate::{Primitive, Renderer};
+use iced_native::{column, Column, Layout, MouseCursor, Point};
+
+impl column::Renderer for Renderer {
+ fn draw<Message>(
+ &mut self,
+ column: &Column<'_, Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let mut mouse_cursor = MouseCursor::OutOfBounds;
+
+ (
+ Primitive::Group {
+ primitives: column
+ .children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ let (primitive, new_mouse_cursor) =
+ child.draw(self, layout, cursor_position);
+
+ if new_mouse_cursor > mouse_cursor {
+ mouse_cursor = new_mouse_cursor;
+ }
+
+ primitive
+ })
+ .collect(),
+ },
+ mouse_cursor,
+ )
+ }
+}
diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs
new file mode 100644
index 00000000..0e312706
--- /dev/null
+++ b/wgpu/src/renderer/image.rs
@@ -0,0 +1,34 @@
+use crate::{Primitive, Renderer};
+use iced_native::{image, Image, Layout, Length, MouseCursor, Node, Style};
+
+impl image::Renderer for Renderer {
+ fn node(&self, image: &Image) -> Node {
+ let (width, height) = self.image_pipeline.dimensions(&image.path);
+
+ let aspect_ratio = width as f32 / height as f32;
+
+ let mut style = Style::default().align_self(image.align_self);
+
+ // TODO: Deal with additional cases
+ style = match (image.width, image.height) {
+ (Length::Units(width), _) => style.width(image.width).height(
+ Length::Units((width as f32 / aspect_ratio).round() as u16),
+ ),
+ (_, _) => style
+ .width(Length::Units(width as u16))
+ .height(Length::Units(height as u16)),
+ };
+
+ Node::new(style)
+ }
+
+ fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output {
+ (
+ Primitive::Image {
+ path: image.path.clone(),
+ bounds: layout.bounds(),
+ },
+ MouseCursor::OutOfBounds,
+ )
+ }
+}
diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs
new file mode 100644
index 00000000..97b4f70e
--- /dev/null
+++ b/wgpu/src/renderer/radio.rs
@@ -0,0 +1,109 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ radio, text, Align, Background, Color, Column, Layout, Length, MouseCursor,
+ Node, Point, Radio, Rectangle, Row, Text, Widget,
+};
+
+const SIZE: f32 = 28.0;
+const DOT_SIZE: f32 = SIZE / 2.0;
+
+impl radio::Renderer for Renderer {
+ fn node<Message>(&self, radio: &Radio<Message>) -> Node {
+ Row::<(), Self>::new()
+ .spacing(15)
+ .align_items(Align::Center)
+ .push(
+ Column::new()
+ .width(Length::Units(SIZE as u16))
+ .height(Length::Units(SIZE as u16)),
+ )
+ .push(Text::new(&radio.label))
+ .node(self)
+ }
+
+ fn draw<Message>(
+ &mut self,
+ radio: &Radio<Message>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ let radio_bounds = children.next().unwrap().bounds();
+ let label_layout = children.next().unwrap();
+
+ let (label, _) =
+ text::Renderer::draw(self, &Text::new(&radio.label), label_layout);
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let (radio_border, radio_box) = (
+ Primitive::Quad {
+ bounds: radio_bounds,
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: (SIZE / 2.0) as u16,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: radio_bounds.x + 1.0,
+ y: radio_bounds.y + 1.0,
+ width: radio_bounds.width - 2.0,
+ height: radio_bounds.height - 2.0,
+ },
+ background: Background::Color(if is_mouse_over {
+ Color {
+ r: 0.90,
+ g: 0.90,
+ b: 0.90,
+ a: 1.0,
+ }
+ } else {
+ Color {
+ r: 0.95,
+ g: 0.95,
+ b: 0.95,
+ a: 1.0,
+ }
+ }),
+ border_radius: (SIZE / 2.0 - 1.0) as u16,
+ },
+ );
+
+ (
+ Primitive::Group {
+ primitives: if radio.is_selected {
+ let radio_circle = Primitive::Quad {
+ bounds: Rectangle {
+ x: radio_bounds.x + DOT_SIZE / 2.0,
+ y: radio_bounds.y + DOT_SIZE / 2.0,
+ width: radio_bounds.width - DOT_SIZE,
+ height: radio_bounds.height - DOT_SIZE,
+ },
+ background: Background::Color(Color {
+ r: 0.30,
+ g: 0.30,
+ b: 0.30,
+ a: 1.0,
+ }),
+ border_radius: (DOT_SIZE / 2.0) as u16,
+ };
+
+ vec![radio_border, radio_box, radio_circle, label]
+ } else {
+ vec![radio_border, radio_box, label]
+ },
+ },
+ if is_mouse_over {
+ MouseCursor::Pointer
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/row.rs b/wgpu/src/renderer/row.rs
new file mode 100644
index 00000000..bbfef9a1
--- /dev/null
+++ b/wgpu/src/renderer/row.rs
@@ -0,0 +1,34 @@
+use crate::{Primitive, Renderer};
+use iced_native::{row, Layout, MouseCursor, Point, Row};
+
+impl row::Renderer for Renderer {
+ fn draw<Message>(
+ &mut self,
+ row: &Row<'_, Message, Self>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let mut mouse_cursor = MouseCursor::OutOfBounds;
+
+ (
+ Primitive::Group {
+ primitives: row
+ .children
+ .iter()
+ .zip(layout.children())
+ .map(|(child, layout)| {
+ let (primitive, new_mouse_cursor) =
+ child.draw(self, layout, cursor_position);
+
+ if new_mouse_cursor > mouse_cursor {
+ mouse_cursor = new_mouse_cursor;
+ }
+
+ primitive
+ })
+ .collect(),
+ },
+ mouse_cursor,
+ )
+ }
+}
diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs
new file mode 100644
index 00000000..4ae3abc4
--- /dev/null
+++ b/wgpu/src/renderer/slider.rs
@@ -0,0 +1,128 @@
+use crate::{Primitive, Renderer};
+use iced_native::{
+ slider, Background, Color, Layout, Length, MouseCursor, Node, Point,
+ Rectangle, Slider, Style,
+};
+
+const HANDLE_WIDTH: f32 = 8.0;
+const HANDLE_HEIGHT: f32 = 22.0;
+
+impl slider::Renderer for Renderer {
+ fn node<Message>(&self, slider: &Slider<Message>) -> Node {
+ let style = Style::default()
+ .width(slider.width)
+ .height(Length::Units(HANDLE_HEIGHT as u16))
+ .min_width(Length::Units(100));
+
+ Node::new(style)
+ }
+
+ fn draw<Message>(
+ &mut self,
+ slider: &Slider<Message>,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Self::Output {
+ let bounds = layout.bounds();
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ let rail_y = bounds.y + (bounds.height / 2.0).round();
+
+ let (rail_top, rail_bottom) = (
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y,
+ width: bounds.width,
+ height: 2.0,
+ },
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: 0,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y + 2.0,
+ width: bounds.width,
+ height: 2.0,
+ },
+ background: Background::Color(Color::WHITE),
+ border_radius: 0,
+ },
+ );
+
+ let (range_start, range_end) = slider.range.clone().into_inner();
+
+ let handle_offset = (bounds.width - HANDLE_WIDTH)
+ * ((slider.value - range_start)
+ / (range_end - range_start).max(1.0));
+
+ let (handle_border, handle) = (
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x + handle_offset.round() - 1.0,
+ y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0,
+ width: HANDLE_WIDTH + 2.0,
+ height: HANDLE_HEIGHT + 2.0,
+ },
+ background: Background::Color(Color {
+ r: 0.6,
+ g: 0.6,
+ b: 0.6,
+ a: 1.0,
+ }),
+ border_radius: 5,
+ },
+ Primitive::Quad {
+ bounds: Rectangle {
+ x: bounds.x + handle_offset.round(),
+ y: rail_y - HANDLE_HEIGHT / 2.0,
+ width: HANDLE_WIDTH,
+ height: HANDLE_HEIGHT,
+ },
+ background: Background::Color(if slider.state.is_dragging() {
+ Color {
+ r: 0.85,
+ g: 0.85,
+ b: 0.85,
+ a: 1.0,
+ }
+ } else if is_mouse_over {
+ Color {
+ r: 0.9,
+ g: 0.9,
+ b: 0.9,
+ a: 1.0,
+ }
+ } else {
+ Color {
+ r: 0.95,
+ g: 0.95,
+ b: 0.95,
+ a: 1.0,
+ }
+ }),
+ border_radius: 4,
+ },
+ );
+
+ (
+ Primitive::Group {
+ primitives: vec![rail_top, rail_bottom, handle_border, handle],
+ },
+ if slider.state.is_dragging() {
+ MouseCursor::Grabbing
+ } else if is_mouse_over {
+ MouseCursor::Grab
+ } else {
+ MouseCursor::OutOfBounds
+ },
+ )
+ }
+}
diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs
new file mode 100644
index 00000000..8fbade4e
--- /dev/null
+++ b/wgpu/src/renderer/text.rs
@@ -0,0 +1,83 @@
+use crate::{Primitive, Renderer};
+use iced_native::{text, Color, Layout, MouseCursor, Node, Style, Text};
+
+use wgpu_glyph::{GlyphCruncher, Section};
+
+use std::cell::RefCell;
+use std::f32;
+
+impl text::Renderer for Renderer {
+ fn node(&self, text: &Text) -> Node {
+ let glyph_brush = self.glyph_brush.clone();
+ let content = text.content.clone();
+
+ // TODO: Investigate why stretch tries to measure this MANY times
+ // with every ancestor's bounds.
+ // Bug? Using the library wrong? I should probably open an issue on
+ // the stretch repository.
+ // I noticed that the first measure is the one that matters in
+ // practice. Here, we use a RefCell to store the cached measurement.
+ let measure = RefCell::new(None);
+ let size = text.size.map(f32::from).unwrap_or(20.0);
+
+ let style = Style::default().width(text.width);
+
+ iced_native::Node::with_measure(style, move |bounds| {
+ let mut measure = measure.borrow_mut();
+
+ if measure.is_none() {
+ let bounds = (
+ match bounds.width {
+ iced_native::Number::Undefined => f32::INFINITY,
+ iced_native::Number::Defined(w) => w,
+ },
+ match bounds.height {
+ iced_native::Number::Undefined => f32::INFINITY,
+ iced_native::Number::Defined(h) => h,
+ },
+ );
+
+ let text = Section {
+ text: &content,
+ scale: wgpu_glyph::Scale { x: size, y: size },
+ bounds,
+ ..Default::default()
+ };
+
+ let (width, height) = if let Some(bounds) =
+ glyph_brush.borrow_mut().glyph_bounds(&text)
+ {
+ (bounds.width(), bounds.height())
+ } else {
+ (0.0, 0.0)
+ };
+
+ let size = iced_native::Size { width, height };
+
+ // If the text has no width boundary we avoid caching as the
+ // layout engine may just be measuring text in a row.
+ if bounds.0 == f32::INFINITY {
+ return size;
+ } else {
+ *measure = Some(size);
+ }
+ }
+
+ measure.unwrap()
+ })
+ }
+
+ fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output {
+ (
+ Primitive::Text {
+ content: text.content.clone(),
+ size: f32::from(text.size.unwrap_or(20)),
+ bounds: layout.bounds(),
+ color: text.color.unwrap_or(Color::BLACK),
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ },
+ MouseCursor::OutOfBounds,
+ )
+ }
+}
diff --git a/wgpu/src/shader/image.frag b/wgpu/src/shader/image.frag
new file mode 100644
index 00000000..e35e455a
--- /dev/null
+++ b/wgpu/src/shader/image.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout(location = 0) in vec2 v_Uv;
+
+layout(set = 0, binding = 1) uniform sampler u_Sampler;
+layout(set = 1, binding = 0) uniform texture2D u_Texture;
+
+layout(location = 0) out vec4 o_Color;
+
+void main() {
+ o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv);
+}
diff --git a/wgpu/src/shader/image.frag.spv b/wgpu/src/shader/image.frag.spv
new file mode 100644
index 00000000..ebee82ac
--- /dev/null
+++ b/wgpu/src/shader/image.frag.spv
Binary files differ
diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert
new file mode 100644
index 00000000..688c2311
--- /dev/null
+++ b/wgpu/src/shader/image.vert
@@ -0,0 +1,24 @@
+#version 450
+
+layout(location = 0) in vec2 v_Pos;
+layout(location = 1) in vec2 i_Pos;
+layout(location = 2) in vec2 i_Scale;
+
+layout (set = 0, binding = 0) uniform Globals {
+ mat4 u_Transform;
+};
+
+layout(location = 0) out vec2 o_Uv;
+
+void main() {
+ o_Uv = v_Pos;
+
+ mat4 i_Transform = mat4(
+ vec4(i_Scale.x, 0.0, 0.0, 0.0),
+ vec4(0.0, i_Scale.y, 0.0, 0.0),
+ vec4(0.0, 0.0, 1.0, 0.0),
+ vec4(i_Pos, 0.0, 1.0)
+ );
+
+ gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
+}
diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv
new file mode 100644
index 00000000..9ba702bc
--- /dev/null
+++ b/wgpu/src/shader/image.vert.spv
Binary files differ
diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag
new file mode 100644
index 00000000..849f581e
--- /dev/null
+++ b/wgpu/src/shader/quad.frag
@@ -0,0 +1,37 @@
+#version 450
+
+layout(location = 0) in vec4 v_Color;
+layout(location = 1) in vec2 v_Pos;
+layout(location = 2) in vec2 v_Scale;
+layout(location = 3) in flat uint v_BorderRadius;
+
+layout(location = 0) out vec4 o_Color;
+
+float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, float s)
+{
+ vec2 inner_size = size - vec2(radius, radius) * 2.0;
+ vec2 top_left = position + vec2(radius, radius);
+ vec2 bottom_right = top_left + inner_size;
+
+ vec2 top_left_distance = top_left - frag_coord;
+ vec2 bottom_right_distance = frag_coord - bottom_right;
+
+ vec2 distance = vec2(
+ max(max(top_left_distance.x, bottom_right_distance.x), 0),
+ max(max(top_left_distance.y, bottom_right_distance.y), 0)
+ );
+
+ float d = sqrt(distance.x * distance.x + distance.y * distance.y);
+
+ return 1.0 - smoothstep(radius - s, radius + s, d);
+}
+
+void main() {
+ float radius_alpha = 1.0;
+
+ if(v_BorderRadius > 0.0) {
+ radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 0.5);
+ }
+
+ o_Color = vec4(v_Color.xyz, v_Color.w * radius_alpha);
+}
diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv
new file mode 100644
index 00000000..71b91b44
--- /dev/null
+++ b/wgpu/src/shader/quad.frag.spv
Binary files differ
diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert
new file mode 100644
index 00000000..b7c5cf3e
--- /dev/null
+++ b/wgpu/src/shader/quad.vert
@@ -0,0 +1,32 @@
+#version 450
+
+layout(location = 0) in vec2 v_Pos;
+layout(location = 1) in vec2 i_Pos;
+layout(location = 2) in vec2 i_Scale;
+layout(location = 3) in vec4 i_Color;
+layout(location = 4) in uint i_BorderRadius;
+
+layout (set = 0, binding = 0) uniform Globals {
+ mat4 u_Transform;
+};
+
+layout(location = 0) out vec4 o_Color;
+layout(location = 1) out vec2 o_Pos;
+layout(location = 2) out vec2 o_Scale;
+layout(location = 3) out uint o_BorderRadius;
+
+void main() {
+ mat4 i_Transform = mat4(
+ vec4(i_Scale.x, 0.0, 0.0, 0.0),
+ vec4(0.0, i_Scale.y, 0.0, 0.0),
+ vec4(0.0, 0.0, 1.0, 0.0),
+ vec4(i_Pos, 0.0, 1.0)
+ );
+
+ o_Color = i_Color;
+ o_Pos = i_Pos;
+ o_Scale = i_Scale;
+ o_BorderRadius = i_BorderRadius;
+
+ gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
+}
diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv
new file mode 100644
index 00000000..f62a160c
--- /dev/null
+++ b/wgpu/src/shader/quad.vert.spv
Binary files differ
diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs
new file mode 100644
index 00000000..1101e135
--- /dev/null
+++ b/wgpu/src/transformation.rs
@@ -0,0 +1,30 @@
+#[derive(Debug, Clone, Copy)]
+pub struct Transformation([f32; 16]);
+
+impl Transformation {
+ #[rustfmt::skip]
+ pub fn identity() -> Self {
+ Transformation([
+ 1.0, 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0,
+ ])
+ }
+
+ #[rustfmt::skip]
+ pub fn orthographic(width: u16, height: u16) -> Self {
+ Transformation([
+ 2.0 / width as f32, 0.0, 0.0, 0.0,
+ 0.0, 2.0 / height as f32, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ -1.0, -1.0, 0.0, 1.0,
+ ])
+ }
+}
+
+impl From<Transformation> for [f32; 16] {
+ fn from(transformation: Transformation) -> [f32; 16] {
+ transformation.0
+ }
+}
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
new file mode 100644
index 00000000..fa5d6adf
--- /dev/null
+++ b/winit/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "iced_winit"
+version = "0.1.0-alpha"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+description = "A winit runtime for Iced"
+license = "MIT"
+repository = "https://github.com/hecrj/iced"
+
+[dependencies]
+iced_native = { version = "0.1.0-alpha", path = "../native" }
+winit = { version = "0.20.0-alpha3", git = "https://github.com/hecrj/winit", branch = "redraw-requested-2.0" }
+log = "0.4"
diff --git a/winit/src/application.rs b/winit/src/application.rs
new file mode 100644
index 00000000..418ee3c4
--- /dev/null
+++ b/winit/src/application.rs
@@ -0,0 +1,185 @@
+use crate::{
+ column, conversion, input::mouse, renderer::Windowed, Cache, Column,
+ Element, Event, Length, MouseCursor, UserInterface,
+};
+
+pub trait Application {
+ type Renderer: Windowed + column::Renderer;
+
+ type Message;
+
+ fn update(&mut self, message: Self::Message);
+
+ fn view(&mut self) -> Element<Self::Message, Self::Renderer>;
+
+ fn run(mut self)
+ where
+ Self: 'static + Sized,
+ {
+ use winit::{
+ event::{self, WindowEvent},
+ event_loop::{ControlFlow, EventLoop},
+ window::WindowBuilder,
+ };
+
+ let event_loop = EventLoop::new();
+
+ // TODO: Ask for window settings and configure this properly
+ let window = WindowBuilder::new()
+ .with_inner_size(winit::dpi::LogicalSize {
+ width: 1280.0,
+ height: 1024.0,
+ })
+ .build(&event_loop)
+ .expect("Open window");
+
+ let mut size: Size = window
+ .inner_size()
+ .to_physical(window.hidpi_factor())
+ .into();
+ let mut new_size: Option<Size> = None;
+
+ let mut renderer = Self::Renderer::new(&window);
+ let mut target = renderer.target(size.width, size.height);
+
+ let user_interface = UserInterface::build(
+ document(&mut self, size),
+ Cache::default(),
+ &renderer,
+ );
+
+ let mut primitive = user_interface.draw(&mut renderer);
+ let mut cache = Some(user_interface.into_cache());
+ let mut events = Vec::new();
+ let mut mouse_cursor = MouseCursor::OutOfBounds;
+
+ window.request_redraw();
+
+ event_loop.run(move |event, _, control_flow| match event {
+ event::Event::MainEventsCleared => {
+ // TODO: We should be able to keep a user interface alive
+ // between events once we remove state references.
+ //
+ // This will allow us to rebuild it only when a message is
+ // handled.
+ let mut user_interface = UserInterface::build(
+ document(&mut self, size),
+ cache.take().unwrap(),
+ &renderer,
+ );
+
+ let messages = user_interface.update(events.drain(..));
+
+ if messages.is_empty() {
+ primitive = user_interface.draw(&mut renderer);
+
+ cache = Some(user_interface.into_cache());
+ } else {
+ // When there are messages, we are forced to rebuild twice
+ // for now :^)
+ let temp_cache = user_interface.into_cache();
+
+ for message in messages {
+ log::debug!("Updating");
+
+ self.update(message);
+ }
+
+ let user_interface = UserInterface::build(
+ document(&mut self, size),
+ temp_cache,
+ &renderer,
+ );
+
+ primitive = user_interface.draw(&mut renderer);
+
+ cache = Some(user_interface.into_cache());
+ }
+
+ window.request_redraw();
+ }
+ event::Event::RedrawRequested(_) => {
+ if let Some(new_size) = new_size.take() {
+ target = renderer.target(new_size.width, new_size.height);
+ size = new_size;
+ }
+
+ let new_mouse_cursor = renderer.draw(&primitive, &mut target);
+
+ if new_mouse_cursor != mouse_cursor {
+ window.set_cursor_icon(conversion::mouse_cursor(
+ new_mouse_cursor,
+ ));
+
+ mouse_cursor = new_mouse_cursor;
+ }
+
+ // TODO: Handle animations!
+ // Maybe we can use `ControlFlow::WaitUntil` for this.
+ }
+ event::Event::WindowEvent {
+ event: window_event,
+ ..
+ } => match window_event {
+ WindowEvent::CursorMoved { position, .. } => {
+ let physical_position =
+ position.to_physical(window.hidpi_factor());
+
+ events.push(Event::Mouse(mouse::Event::CursorMoved {
+ x: physical_position.x as f32,
+ y: physical_position.y as f32,
+ }));
+ }
+ WindowEvent::MouseInput { button, state, .. } => {
+ events.push(Event::Mouse(mouse::Event::Input {
+ button: conversion::mouse_button(button),
+ state: conversion::button_state(state),
+ }));
+ }
+ WindowEvent::CloseRequested => {
+ *control_flow = ControlFlow::Exit;
+ }
+ WindowEvent::Resized(size) => {
+ new_size =
+ Some(size.to_physical(window.hidpi_factor()).into());
+
+ log::debug!("Resized: {:?}", new_size);
+ }
+ _ => {}
+ },
+ _ => {
+ *control_flow = ControlFlow::Wait;
+ }
+ })
+ }
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+struct Size {
+ width: u16,
+ height: u16,
+}
+
+impl From<winit::dpi::PhysicalSize> for Size {
+ fn from(physical_size: winit::dpi::PhysicalSize) -> Self {
+ Self {
+ width: physical_size.width.round() as u16,
+ height: physical_size.height.round() as u16,
+ }
+ }
+}
+
+fn document<Application>(
+ application: &mut Application,
+ size: Size,
+) -> Element<Application::Message, Application::Renderer>
+where
+ Application: self::Application,
+ Application::Message: 'static,
+{
+ Column::new()
+ .width(Length::Units(size.width))
+ .height(Length::Units(size.height))
+ .push(application.view())
+ .into()
+}
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
new file mode 100644
index 00000000..bb0d252e
--- /dev/null
+++ b/winit/src/conversion.rs
@@ -0,0 +1,199 @@
+use crate::input::{keyboard::KeyCode, mouse, ButtonState};
+use crate::MouseCursor;
+
+pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon {
+ match mouse_cursor {
+ MouseCursor::OutOfBounds => winit::window::CursorIcon::Default,
+ MouseCursor::Idle => winit::window::CursorIcon::Default,
+ MouseCursor::Pointer => winit::window::CursorIcon::Hand,
+ MouseCursor::Working => winit::window::CursorIcon::Progress,
+ MouseCursor::Grab => winit::window::CursorIcon::Grab,
+ MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
+ }
+}
+
+pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
+ match mouse_button {
+ winit::event::MouseButton::Left => mouse::Button::Left,
+ winit::event::MouseButton::Right => mouse::Button::Right,
+ winit::event::MouseButton::Middle => mouse::Button::Middle,
+ winit::event::MouseButton::Other(other) => mouse::Button::Other(other),
+ }
+}
+
+pub fn button_state(element_state: winit::event::ElementState) -> ButtonState {
+ match element_state {
+ winit::event::ElementState::Pressed => ButtonState::Pressed,
+ winit::event::ElementState::Released => ButtonState::Released,
+ }
+}
+
+pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
+ match virtual_keycode {
+ winit::event::VirtualKeyCode::Key1 => KeyCode::Key1,
+ winit::event::VirtualKeyCode::Key2 => KeyCode::Key2,
+ winit::event::VirtualKeyCode::Key3 => KeyCode::Key3,
+ winit::event::VirtualKeyCode::Key4 => KeyCode::Key4,
+ winit::event::VirtualKeyCode::Key5 => KeyCode::Key5,
+ winit::event::VirtualKeyCode::Key6 => KeyCode::Key6,
+ winit::event::VirtualKeyCode::Key7 => KeyCode::Key7,
+ winit::event::VirtualKeyCode::Key8 => KeyCode::Key8,
+ winit::event::VirtualKeyCode::Key9 => KeyCode::Key9,
+ winit::event::VirtualKeyCode::Key0 => KeyCode::Key0,
+ winit::event::VirtualKeyCode::A => KeyCode::A,
+ winit::event::VirtualKeyCode::B => KeyCode::B,
+ winit::event::VirtualKeyCode::C => KeyCode::C,
+ winit::event::VirtualKeyCode::D => KeyCode::D,
+ winit::event::VirtualKeyCode::E => KeyCode::E,
+ winit::event::VirtualKeyCode::F => KeyCode::F,
+ winit::event::VirtualKeyCode::G => KeyCode::G,
+ winit::event::VirtualKeyCode::H => KeyCode::H,
+ winit::event::VirtualKeyCode::I => KeyCode::I,
+ winit::event::VirtualKeyCode::J => KeyCode::J,
+ winit::event::VirtualKeyCode::K => KeyCode::K,
+ winit::event::VirtualKeyCode::L => KeyCode::L,
+ winit::event::VirtualKeyCode::M => KeyCode::M,
+ winit::event::VirtualKeyCode::N => KeyCode::N,
+ winit::event::VirtualKeyCode::O => KeyCode::O,
+ winit::event::VirtualKeyCode::P => KeyCode::P,
+ winit::event::VirtualKeyCode::Q => KeyCode::Q,
+ winit::event::VirtualKeyCode::R => KeyCode::R,
+ winit::event::VirtualKeyCode::S => KeyCode::S,
+ winit::event::VirtualKeyCode::T => KeyCode::T,
+ winit::event::VirtualKeyCode::U => KeyCode::U,
+ winit::event::VirtualKeyCode::V => KeyCode::V,
+ winit::event::VirtualKeyCode::W => KeyCode::W,
+ winit::event::VirtualKeyCode::X => KeyCode::X,
+ winit::event::VirtualKeyCode::Y => KeyCode::Y,
+ winit::event::VirtualKeyCode::Z => KeyCode::Z,
+ winit::event::VirtualKeyCode::Escape => KeyCode::Escape,
+ winit::event::VirtualKeyCode::F1 => KeyCode::F1,
+ winit::event::VirtualKeyCode::F2 => KeyCode::F2,
+ winit::event::VirtualKeyCode::F3 => KeyCode::F3,
+ winit::event::VirtualKeyCode::F4 => KeyCode::F4,
+ winit::event::VirtualKeyCode::F5 => KeyCode::F5,
+ winit::event::VirtualKeyCode::F6 => KeyCode::F6,
+ winit::event::VirtualKeyCode::F7 => KeyCode::F7,
+ winit::event::VirtualKeyCode::F8 => KeyCode::F8,
+ winit::event::VirtualKeyCode::F9 => KeyCode::F9,
+ winit::event::VirtualKeyCode::F10 => KeyCode::F10,
+ winit::event::VirtualKeyCode::F11 => KeyCode::F11,
+ winit::event::VirtualKeyCode::F12 => KeyCode::F12,
+ winit::event::VirtualKeyCode::F13 => KeyCode::F13,
+ winit::event::VirtualKeyCode::F14 => KeyCode::F14,
+ winit::event::VirtualKeyCode::F15 => KeyCode::F15,
+ winit::event::VirtualKeyCode::F16 => KeyCode::F16,
+ winit::event::VirtualKeyCode::F17 => KeyCode::F17,
+ winit::event::VirtualKeyCode::F18 => KeyCode::F18,
+ winit::event::VirtualKeyCode::F19 => KeyCode::F19,
+ winit::event::VirtualKeyCode::F20 => KeyCode::F20,
+ winit::event::VirtualKeyCode::F21 => KeyCode::F21,
+ winit::event::VirtualKeyCode::F22 => KeyCode::F22,
+ winit::event::VirtualKeyCode::F23 => KeyCode::F23,
+ winit::event::VirtualKeyCode::F24 => KeyCode::F24,
+ winit::event::VirtualKeyCode::Snapshot => KeyCode::Snapshot,
+ winit::event::VirtualKeyCode::Scroll => KeyCode::Scroll,
+ winit::event::VirtualKeyCode::Pause => KeyCode::Pause,
+ winit::event::VirtualKeyCode::Insert => KeyCode::Insert,
+ winit::event::VirtualKeyCode::Home => KeyCode::Home,
+ winit::event::VirtualKeyCode::Delete => KeyCode::Delete,
+ winit::event::VirtualKeyCode::End => KeyCode::End,
+ winit::event::VirtualKeyCode::PageDown => KeyCode::PageDown,
+ winit::event::VirtualKeyCode::PageUp => KeyCode::PageUp,
+ winit::event::VirtualKeyCode::Left => KeyCode::Left,
+ winit::event::VirtualKeyCode::Up => KeyCode::Up,
+ winit::event::VirtualKeyCode::Right => KeyCode::Right,
+ winit::event::VirtualKeyCode::Down => KeyCode::Down,
+ winit::event::VirtualKeyCode::Back => KeyCode::Backspace,
+ winit::event::VirtualKeyCode::Return => KeyCode::Enter,
+ winit::event::VirtualKeyCode::Space => KeyCode::Space,
+ winit::event::VirtualKeyCode::Compose => KeyCode::Compose,
+ winit::event::VirtualKeyCode::Caret => KeyCode::Caret,
+ winit::event::VirtualKeyCode::Numlock => KeyCode::Numlock,
+ winit::event::VirtualKeyCode::Numpad0 => KeyCode::Numpad0,
+ winit::event::VirtualKeyCode::Numpad1 => KeyCode::Numpad1,
+ winit::event::VirtualKeyCode::Numpad2 => KeyCode::Numpad2,
+ winit::event::VirtualKeyCode::Numpad3 => KeyCode::Numpad3,
+ winit::event::VirtualKeyCode::Numpad4 => KeyCode::Numpad4,
+ winit::event::VirtualKeyCode::Numpad5 => KeyCode::Numpad5,
+ winit::event::VirtualKeyCode::Numpad6 => KeyCode::Numpad6,
+ winit::event::VirtualKeyCode::Numpad7 => KeyCode::Numpad7,
+ winit::event::VirtualKeyCode::Numpad8 => KeyCode::Numpad8,
+ winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9,
+ winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1,
+ winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2,
+ winit::event::VirtualKeyCode::Add => KeyCode::Add,
+ winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe,
+ winit::event::VirtualKeyCode::Apps => KeyCode::Apps,
+ winit::event::VirtualKeyCode::At => KeyCode::At,
+ winit::event::VirtualKeyCode::Ax => KeyCode::Ax,
+ winit::event::VirtualKeyCode::Backslash => KeyCode::Backslash,
+ winit::event::VirtualKeyCode::Calculator => KeyCode::Calculator,
+ winit::event::VirtualKeyCode::Capital => KeyCode::Capital,
+ winit::event::VirtualKeyCode::Colon => KeyCode::Colon,
+ winit::event::VirtualKeyCode::Comma => KeyCode::Comma,
+ winit::event::VirtualKeyCode::Convert => KeyCode::Convert,
+ winit::event::VirtualKeyCode::Decimal => KeyCode::Decimal,
+ winit::event::VirtualKeyCode::Divide => KeyCode::Divide,
+ winit::event::VirtualKeyCode::Equals => KeyCode::Equals,
+ winit::event::VirtualKeyCode::Grave => KeyCode::Grave,
+ winit::event::VirtualKeyCode::Kana => KeyCode::Kana,
+ winit::event::VirtualKeyCode::Kanji => KeyCode::Kanji,
+ winit::event::VirtualKeyCode::LAlt => KeyCode::LAlt,
+ winit::event::VirtualKeyCode::LBracket => KeyCode::LBracket,
+ winit::event::VirtualKeyCode::LControl => KeyCode::LControl,
+ winit::event::VirtualKeyCode::LShift => KeyCode::LShift,
+ winit::event::VirtualKeyCode::LWin => KeyCode::LWin,
+ winit::event::VirtualKeyCode::Mail => KeyCode::Mail,
+ winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect,
+ winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop,
+ winit::event::VirtualKeyCode::Minus => KeyCode::Minus,
+ winit::event::VirtualKeyCode::Multiply => KeyCode::Multiply,
+ winit::event::VirtualKeyCode::Mute => KeyCode::Mute,
+ winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer,
+ winit::event::VirtualKeyCode::NavigateForward => {
+ KeyCode::NavigateForward
+ }
+ winit::event::VirtualKeyCode::NavigateBackward => {
+ KeyCode::NavigateBackward
+ }
+ winit::event::VirtualKeyCode::NextTrack => KeyCode::NextTrack,
+ winit::event::VirtualKeyCode::NoConvert => KeyCode::NoConvert,
+ winit::event::VirtualKeyCode::NumpadComma => KeyCode::NumpadComma,
+ winit::event::VirtualKeyCode::NumpadEnter => KeyCode::NumpadEnter,
+ winit::event::VirtualKeyCode::NumpadEquals => KeyCode::NumpadEquals,
+ winit::event::VirtualKeyCode::OEM102 => KeyCode::OEM102,
+ winit::event::VirtualKeyCode::Period => KeyCode::Period,
+ winit::event::VirtualKeyCode::PlayPause => KeyCode::PlayPause,
+ winit::event::VirtualKeyCode::Power => KeyCode::Power,
+ winit::event::VirtualKeyCode::PrevTrack => KeyCode::PrevTrack,
+ winit::event::VirtualKeyCode::RAlt => KeyCode::RAlt,
+ winit::event::VirtualKeyCode::RBracket => KeyCode::RBracket,
+ winit::event::VirtualKeyCode::RControl => KeyCode::RControl,
+ winit::event::VirtualKeyCode::RShift => KeyCode::RShift,
+ winit::event::VirtualKeyCode::RWin => KeyCode::RWin,
+ winit::event::VirtualKeyCode::Semicolon => KeyCode::Semicolon,
+ winit::event::VirtualKeyCode::Slash => KeyCode::Slash,
+ winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep,
+ winit::event::VirtualKeyCode::Stop => KeyCode::Stop,
+ winit::event::VirtualKeyCode::Subtract => KeyCode::Subtract,
+ winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq,
+ winit::event::VirtualKeyCode::Tab => KeyCode::Tab,
+ winit::event::VirtualKeyCode::Underline => KeyCode::Underline,
+ winit::event::VirtualKeyCode::Unlabeled => KeyCode::Unlabeled,
+ winit::event::VirtualKeyCode::VolumeDown => KeyCode::VolumeDown,
+ winit::event::VirtualKeyCode::VolumeUp => KeyCode::VolumeUp,
+ winit::event::VirtualKeyCode::Wake => KeyCode::Wake,
+ winit::event::VirtualKeyCode::WebBack => KeyCode::WebBack,
+ winit::event::VirtualKeyCode::WebFavorites => KeyCode::WebFavorites,
+ winit::event::VirtualKeyCode::WebForward => KeyCode::WebForward,
+ winit::event::VirtualKeyCode::WebHome => KeyCode::WebHome,
+ winit::event::VirtualKeyCode::WebRefresh => KeyCode::WebRefresh,
+ winit::event::VirtualKeyCode::WebSearch => KeyCode::WebSearch,
+ winit::event::VirtualKeyCode::WebStop => KeyCode::WebStop,
+ winit::event::VirtualKeyCode::Yen => KeyCode::Yen,
+ winit::event::VirtualKeyCode::Copy => KeyCode::Copy,
+ winit::event::VirtualKeyCode::Paste => KeyCode::Paste,
+ winit::event::VirtualKeyCode::Cut => KeyCode::Cut,
+ }
+}
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
new file mode 100644
index 00000000..b08fcb6c
--- /dev/null
+++ b/winit/src/lib.rs
@@ -0,0 +1,8 @@
+pub use iced_native::*;
+pub use winit;
+
+pub mod conversion;
+
+mod application;
+
+pub use application::Application;