commit
4769272122
88 changed files with 2881 additions and 1698 deletions
3
.github/workflows/integration.yml
vendored
3
.github/workflows/integration.yml
vendored
|
|
@ -24,6 +24,3 @@ jobs:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose --all --all-features
|
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
|
|
||||||
|
|
|
||||||
18
Cargo.toml
18
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "iced"
|
name = "iced"
|
||||||
version = "0.1.0-alpha"
|
version = "0.1.0-alpha.1"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A cross-platform GUI library inspired by Elm"
|
description = "A cross-platform GUI library inspired by Elm"
|
||||||
|
|
@ -19,5 +19,19 @@ members = [
|
||||||
"core",
|
"core",
|
||||||
"native",
|
"native",
|
||||||
"web",
|
"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"
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,9 @@ __view logic__:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use iced::{Button, Column, Text};
|
use iced::{Button, Column, Text};
|
||||||
use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own!
|
|
||||||
|
|
||||||
impl Counter {
|
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
|
// We use a column: a simple vertical layout
|
||||||
Column::new()
|
Column::new()
|
||||||
.push(
|
.push(
|
||||||
|
|
|
||||||
7
core/src/background.rs
Normal file
7
core/src/background.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
use crate::Color;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Background {
|
||||||
|
Color(Color),
|
||||||
|
// TODO: Add gradient and image variants
|
||||||
|
}
|
||||||
|
|
@ -16,4 +16,31 @@ impl Color {
|
||||||
b: 0.0,
|
b: 0.0,
|
||||||
a: 1.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,
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
|
||||||
mod align;
|
mod align;
|
||||||
|
mod background;
|
||||||
mod color;
|
mod color;
|
||||||
mod justify;
|
mod justify;
|
||||||
mod length;
|
mod length;
|
||||||
|
|
@ -9,6 +10,7 @@ mod rectangle;
|
||||||
mod vector;
|
mod vector;
|
||||||
|
|
||||||
pub use align::Align;
|
pub use align::Align;
|
||||||
|
pub use background::Background;
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
pub use justify::Justify;
|
pub use justify::Justify;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
|
|
|
||||||
|
|
@ -5,68 +5,58 @@
|
||||||
//! [`Button`]: struct.Button.html
|
//! [`Button`]: struct.Button.html
|
||||||
//! [`State`]: struct.State.html
|
//! [`State`]: struct.State.html
|
||||||
|
|
||||||
use crate::{Align, Length};
|
use crate::{Align, Background, Length};
|
||||||
|
|
||||||
/// A generic widget that produces a message when clicked.
|
/// A generic widget that produces a message when clicked.
|
||||||
///
|
pub struct Button<'a, Message, Element> {
|
||||||
/// # 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);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// 
|
|
||||||
pub struct Button<'a, Message> {
|
|
||||||
/// The current state of the button
|
/// The current state of the button
|
||||||
pub state: &'a mut State,
|
pub state: &'a mut State,
|
||||||
|
|
||||||
/// The label of the button
|
pub content: Element,
|
||||||
pub label: String,
|
|
||||||
|
|
||||||
/// The message to produce when the button is pressed
|
/// The message to produce when the button is pressed
|
||||||
pub on_press: Option<Message>,
|
pub on_press: Option<Message>,
|
||||||
|
|
||||||
pub class: Class,
|
|
||||||
|
|
||||||
pub width: Length,
|
pub width: Length,
|
||||||
|
|
||||||
|
pub padding: u16,
|
||||||
|
|
||||||
|
pub background: Option<Background>,
|
||||||
|
|
||||||
|
pub border_radius: u16,
|
||||||
|
|
||||||
pub align_self: Option<Align>,
|
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
|
where
|
||||||
Message: std::fmt::Debug,
|
Message: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Button")
|
f.debug_struct("Button")
|
||||||
.field("state", &self.state)
|
.field("state", &self.state)
|
||||||
.field("label", &self.label)
|
|
||||||
.field("on_press", &self.on_press)
|
.field("on_press", &self.on_press)
|
||||||
.finish()
|
.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.
|
/// Creates a new [`Button`] with some local [`State`] and the given label.
|
||||||
///
|
///
|
||||||
/// [`Button`]: struct.Button.html
|
/// [`Button`]: struct.Button.html
|
||||||
/// [`State`]: struct.State.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 {
|
Button {
|
||||||
state,
|
state,
|
||||||
label: String::from(label),
|
content: content.into(),
|
||||||
on_press: None,
|
on_press: None,
|
||||||
class: Class::Primary,
|
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
|
padding: 0,
|
||||||
|
background: None,
|
||||||
|
border_radius: 0,
|
||||||
align_self: None,
|
align_self: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +69,21 @@ impl<'a, Message> Button<'a, Message> {
|
||||||
self
|
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.
|
/// Sets the alignment of the [`Button`] itself.
|
||||||
///
|
///
|
||||||
/// This is useful if you want to override the default alignment given by
|
/// This is useful if you want to override the default alignment given by
|
||||||
|
|
@ -90,16 +95,6 @@ impl<'a, Message> Button<'a, Message> {
|
||||||
self
|
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.
|
/// Sets the message that will be produced when the [`Button`] is pressed.
|
||||||
///
|
///
|
||||||
/// [`Button`]: struct.Button.html
|
/// [`Button`]: struct.Button.html
|
||||||
|
|
@ -133,26 +128,3 @@ impl State {
|
||||||
self.is_pressed
|
self.is_pressed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of a [`Button`].
|
|
||||||
///
|
|
||||||
/// 
|
|
||||||
///
|
|
||||||
/// [`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,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ use crate::{Align, Length, Rectangle};
|
||||||
/// ```
|
/// ```
|
||||||
/// use iced_core::Image;
|
/// use iced_core::Image;
|
||||||
///
|
///
|
||||||
/// # let my_handle = String::from("some_handle");
|
/// let image = Image::new("resources/ferris.png");
|
||||||
/// let image = Image::new(my_handle);
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Image<I> {
|
#[derive(Debug)]
|
||||||
/// The image handle
|
pub struct Image {
|
||||||
pub handle: I,
|
/// The image path
|
||||||
|
pub path: String,
|
||||||
|
|
||||||
/// The part of the image to show
|
/// The part of the image to show
|
||||||
pub clip: Option<Rectangle<u16>>,
|
pub clip: Option<Rectangle<u16>>,
|
||||||
|
|
@ -28,23 +28,13 @@ pub struct Image<I> {
|
||||||
pub align_self: Option<Align>,
|
pub align_self: Option<Align>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I> std::fmt::Debug for Image<I> {
|
impl Image {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
/// Creates a new [`Image`] with the given path.
|
||||||
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.
|
|
||||||
///
|
///
|
||||||
/// [`Image`]: struct.Image.html
|
/// [`Image`]: struct.Image.html
|
||||||
pub fn new(handle: I) -> Self {
|
pub fn new<T: Into<String>>(path: T) -> Self {
|
||||||
Image {
|
Image {
|
||||||
handle,
|
path: path.into(),
|
||||||
clip: None,
|
clip: None,
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
height: Length::Shrink,
|
height: Length::Shrink,
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,64 @@
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
__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].
|
you want to learn about a specific release, check out [the release list].
|
||||||
|
|
||||||
[the release list]: https://github.com/hecrj/iced/releases
|
[the release list]: https://github.com/hecrj/iced/releases
|
||||||
|
|
||||||
|
|
||||||
## [Tour](tour)
|
## [Tour](tour.rs)
|
||||||
|
A simple UI tour showcasing different widgets that can be built using Iced.
|
||||||
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 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]
|
[![Tour - Iced][gui_gif]][gui_gfycat]
|
||||||
|
|
||||||
[`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_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif
|
||||||
[gui_gfycat]: https://gfycat.com/veneratedsouraurochs
|
[gui_gfycat]: https://gfycat.com/veneratedsouraurochs
|
||||||
|
|
||||||
|
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 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).
|
||||||
|
|
||||||
|
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__.
|
||||||
|
|
||||||
|
[`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
|
||||||
|
|
||||||
|
#### Running the native version
|
||||||
|
Use [Cargo](https://doc.rust-lang.org/cargo/reference/manifest.html#examples)
|
||||||
|
to run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --example tour
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
|
```
|
||||||
|
cd examples
|
||||||
|
cargo build --example tour --target wasm32-unknown-unknown
|
||||||
|
wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, serve the `examples` directory using an HTTP server and access the
|
||||||
|
`tour.html` file.
|
||||||
|
|
||||||
|
[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen
|
||||||
|
|
||||||
|
|
||||||
## [Coffee]
|
## [Coffee]
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
|
@ -6,8 +6,9 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import init from "./pkg/iced_tour.js";
|
import init from "./tour/tour.js";
|
||||||
init("./pkg/iced_tour_bg.wasm");
|
|
||||||
|
init('./tour/tour_bg.wasm');
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
use crate::widget::{
|
use iced::{
|
||||||
button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color,
|
button, slider, text::HorizontalAlignment, Align, Application, Background,
|
||||||
Column, Element, Image, Length, Radio, Row, Slider, Text,
|
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 {
|
pub struct Tour {
|
||||||
steps: Steps,
|
steps: Steps,
|
||||||
back_button: button::State,
|
back_button: button::State,
|
||||||
|
|
@ -19,8 +28,12 @@ impl Tour {
|
||||||
debug: false,
|
debug: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, event: Message) {
|
impl Application for Tour {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn update(&mut self, event: Message) {
|
||||||
match event {
|
match event {
|
||||||
Message::BackPressed => {
|
Message::BackPressed => {
|
||||||
self.steps.go_back();
|
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 {
|
let Tour {
|
||||||
steps,
|
steps,
|
||||||
back_button,
|
back_button,
|
||||||
|
|
@ -46,9 +59,8 @@ impl Tour {
|
||||||
|
|
||||||
if steps.has_previous() {
|
if steps.has_previous() {
|
||||||
controls = controls.push(
|
controls = controls.push(
|
||||||
Button::new(back_button, "Back")
|
secondary_button(back_button, "Back")
|
||||||
.on_press(Message::BackPressed)
|
.on_press(Message::BackPressed),
|
||||||
.class(button::Class::Secondary),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,22 +68,32 @@ impl Tour {
|
||||||
|
|
||||||
if steps.can_continue() {
|
if steps.can_continue() {
|
||||||
controls = controls.push(
|
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()
|
let element: Element<_> = Column::new()
|
||||||
.max_width(Length::Units(500))
|
.max_width(Length::Units(540))
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
|
.padding(20)
|
||||||
.push(steps.view(self.debug).map(Message::StepMessage))
|
.push(steps.view(self.debug).map(Message::StepMessage))
|
||||||
.push(controls)
|
.push(controls)
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
if self.debug {
|
let element = if self.debug {
|
||||||
element.explain(Color::BLACK)
|
element.explain(Color::BLACK)
|
||||||
} else {
|
} else {
|
||||||
element
|
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.",
|
that can be easily implemented on top of Iced.",
|
||||||
))
|
))
|
||||||
.push(Text::new(
|
.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.",
|
simplicity and type-safety. It is heavily inspired by Elm.",
|
||||||
))
|
))
|
||||||
.push(Text::new(
|
.push(Text::new(
|
||||||
|
|
@ -294,9 +316,9 @@ impl<'a> Step {
|
||||||
2D game engine for Rust.",
|
2D game engine for Rust.",
|
||||||
))
|
))
|
||||||
.push(Text::new(
|
.push(Text::new(
|
||||||
"Iced does not provide a built-in renderer. On native \
|
"On native platforms, Iced provides by default a renderer \
|
||||||
platforms, this example runs on a fairly simple renderer \
|
built on top of wgpu, a graphics library supporting Vulkan, \
|
||||||
built on top of ggez, another game library.",
|
Metal, DX11, and DX12.",
|
||||||
))
|
))
|
||||||
.push(Text::new(
|
.push(Text::new(
|
||||||
"Additionally, this tour can also run on WebAssembly thanks \
|
"Additionally, this tour can also run on WebAssembly thanks \
|
||||||
|
|
@ -304,7 +326,7 @@ impl<'a> Step {
|
||||||
))
|
))
|
||||||
.push(Text::new(
|
.push(Text::new(
|
||||||
"You will need to interact with the UI in order to reach the \
|
"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")
|
Self::container("Image")
|
||||||
.push(Text::new("An image that tries to keep its aspect ratio."))
|
.push(Text::new("An image that tries to keep its aspect ratio."))
|
||||||
.push(
|
.push(
|
||||||
Image::new("resources/ferris.png")
|
// This should go away once we unify resource loading on native
|
||||||
.width(Length::Units(width))
|
// platforms
|
||||||
.align_self(Align::Center),
|
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(
|
.push(Slider::new(
|
||||||
slider,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Language {
|
pub enum Language {
|
||||||
Rust,
|
Rust,
|
||||||
|
|
@ -565,3 +634,16 @@ pub enum Layout {
|
||||||
Row,
|
Row,
|
||||||
Column,
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
mod renderer;
|
|
||||||
mod widget;
|
|
||||||
|
|
||||||
pub use renderer::Cache as ImageCache;
|
|
||||||
pub use renderer::Renderer;
|
|
||||||
pub use widget::*;
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>>;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub use iced_web::*;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub use crate::iced_ggez::*;
|
|
||||||
|
|
@ -7,13 +7,8 @@ description = "A renderer-agnostic library for native GUIs"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/hecrj/iced"
|
repository = "https://github.com/hecrj/iced"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = ["winit"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core = { version = "0.1.0-alpha", path = "../core" }
|
iced_core = { version = "0.1.0-alpha", path = "../core" }
|
||||||
stretch = "0.2"
|
stretch = "0.2"
|
||||||
twox-hash = "1.5"
|
twox-hash = "1.5"
|
||||||
|
raw-window-handle = "0.1"
|
||||||
# Enable to obtain conversion traits
|
|
||||||
winit = { version = "0.20.0-alpha3", optional = true }
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use stretch::{geometry, result};
|
use stretch::{geometry, result};
|
||||||
|
|
||||||
use crate::{
|
use crate::{renderer, Color, Event, Hasher, Layout, Node, Point, Widget};
|
||||||
renderer, Color, Event, Hasher, Layout, MouseCursor, Node, Point, Widget,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A generic [`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`].
|
/// Create a new [`Element`] containing the given [`Widget`].
|
||||||
///
|
///
|
||||||
/// [`Element`]: struct.Element.html
|
/// [`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`].
|
/// Applies a transformation to the produced message of the [`Element`].
|
||||||
///
|
///
|
||||||
/// This method is useful when you want to decouple different parts of your
|
/// 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 {
|
/// # mod counter {
|
||||||
/// # use iced_native::{button, Button};
|
/// # use iced_native::{text, Text};
|
||||||
/// #
|
/// #
|
||||||
/// # #[derive(Debug, Clone, Copy)]
|
/// # #[derive(Debug, Clone, Copy)]
|
||||||
/// # pub enum Message {}
|
/// # pub enum Message {}
|
||||||
/// # pub struct Counter(button::State);
|
/// # pub struct Counter;
|
||||||
/// #
|
/// #
|
||||||
/// # impl Counter {
|
/// # impl Counter {
|
||||||
/// # pub fn view(&mut self) -> Button<Message> {
|
/// # pub fn view(&mut self) -> Text {
|
||||||
/// # Button::new(&mut self.0, "_")
|
/// # Text::new("")
|
||||||
/// # }
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
/// # mod iced_wgpu {
|
/// # mod iced_wgpu {
|
||||||
/// # use iced_native::{
|
/// # use iced_native::{
|
||||||
/// # button, Button, MouseCursor, Node, Point, Rectangle, Style, Layout
|
/// # text, row, Text, Node, Point, Rectangle, Style, Layout, Row
|
||||||
/// # };
|
/// # };
|
||||||
/// # pub struct Renderer;
|
/// # pub struct Renderer;
|
||||||
/// #
|
/// #
|
||||||
/// # impl button::Renderer for Renderer {
|
/// # impl iced_native::Renderer for Renderer { type Output = (); }
|
||||||
/// # fn node<Message>(&self, _button: &Button<'_, Message>) -> Node {
|
/// #
|
||||||
|
/// # impl iced_native::row::Renderer for Renderer {
|
||||||
|
/// # fn draw<Message>(
|
||||||
|
/// # &mut self,
|
||||||
|
/// # _column: &Row<'_, Message, Self>,
|
||||||
|
/// # _layout: Layout<'_>,
|
||||||
|
/// # _cursor_position: Point,
|
||||||
|
/// # ) {}
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl text::Renderer for Renderer {
|
||||||
|
/// # fn node(&self, _text: &Text) -> Node {
|
||||||
/// # Node::new(Style::default())
|
/// # Node::new(Style::default())
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
/// # fn draw<Message>(
|
/// # fn draw(
|
||||||
/// # &mut self,
|
/// # &mut self,
|
||||||
/// # _button: &Button<'_, Message>,
|
/// # _text: &Text,
|
||||||
/// # _layout: Layout<'_>,
|
/// # _layout: Layout<'_>,
|
||||||
/// # _cursor_position: Point,
|
/// # ) {}
|
||||||
/// # ) -> MouseCursor {
|
|
||||||
/// # MouseCursor::OutOfBounds
|
|
||||||
/// # }
|
|
||||||
/// # }
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
|
|
@ -225,10 +247,7 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_layout(
|
pub(crate) fn compute_layout(&self, renderer: &Renderer) -> result::Layout {
|
||||||
&self,
|
|
||||||
renderer: &mut Renderer,
|
|
||||||
) -> result::Layout {
|
|
||||||
let node = self.widget.node(renderer);
|
let node = self.widget.node(renderer);
|
||||||
|
|
||||||
node.0.compute_layout(geometry::Size::undefined()).unwrap()
|
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>
|
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
|
||||||
where
|
where
|
||||||
A: Copy,
|
A: Copy,
|
||||||
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn node(&self, renderer: &mut Renderer) -> Node {
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
self.widget.node(renderer)
|
self.widget.node(renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,7 +320,7 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
self.widget.draw(renderer, layout, cursor_position)
|
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>,
|
element: Element<'a, Message, Renderer>,
|
||||||
color: Color,
|
color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer>
|
impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: renderer::Debugger,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Explain")
|
f.debug_struct("Explain")
|
||||||
|
|
@ -327,7 +347,7 @@ where
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
|
impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: renderer::Debugger,
|
Renderer: crate::Renderer,
|
||||||
{
|
{
|
||||||
fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
|
fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
|
||||||
Explain { element, color }
|
Explain { element, color }
|
||||||
|
|
@ -337,9 +357,9 @@ where
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Explain<'a, Message, Renderer>
|
for Explain<'a, Message, Renderer>
|
||||||
where
|
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)
|
self.element.widget.node(renderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,10 +380,13 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
renderer.explain(&layout, self.color);
|
renderer.explain(
|
||||||
|
self.element.widget.as_ref(),
|
||||||
self.element.widget.draw(renderer, layout, cursor_position)
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
self.color,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
/// The state of a button.
|
/// 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)]
|
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum ButtonState {
|
pub enum ButtonState {
|
||||||
/// The button is pressed.
|
/// The button is pressed.
|
||||||
|
|
@ -12,13 +7,3 @@ pub enum ButtonState {
|
||||||
/// The button is __not__ pressed.
|
/// The button is __not__ pressed.
|
||||||
Released,
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,6 @@
|
||||||
///
|
///
|
||||||
/// This is mostly the `KeyCode` type found in [`winit`].
|
/// 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/
|
/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
|
||||||
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
|
|
@ -199,176 +196,3 @@ pub enum KeyCode {
|
||||||
Paste,
|
Paste,
|
||||||
Cut,
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
/// The button of a mouse.
|
/// 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)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum Button {
|
pub enum Button {
|
||||||
/// The left mouse button.
|
/// The left mouse button.
|
||||||
|
|
@ -18,15 +13,3 @@ pub enum Button {
|
||||||
/// Some other button.
|
/// Some other button.
|
||||||
Other(u8),
|
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -77,28 +77,29 @@
|
||||||
//! #
|
//! #
|
||||||
//! # mod iced_wgpu {
|
//! # mod iced_wgpu {
|
||||||
//! # use iced_native::{
|
//! # use iced_native::{
|
||||||
//! # button, text, Button, Text,
|
//! # button, text, Button, Text, Node, Point, Rectangle, Style, Color, Layout
|
||||||
//! # MouseCursor, Node, Point, Rectangle, Style, Color, Layout
|
|
||||||
//! # };
|
//! # };
|
||||||
//! #
|
//! #
|
||||||
//! # pub struct Renderer {}
|
//! # pub struct Renderer {}
|
||||||
//! #
|
//! #
|
||||||
|
//! # impl iced_native::Renderer for Renderer {
|
||||||
|
//! # type Output = ();
|
||||||
|
//! # }
|
||||||
|
//! #
|
||||||
//! # impl button::Renderer for Renderer {
|
//! # impl button::Renderer for Renderer {
|
||||||
//! # fn node<Message>(
|
//! # fn node<Message>(
|
||||||
//! # &self,
|
//! # &self,
|
||||||
//! # _button: &Button<'_, Message>
|
//! # _button: &Button<'_, Message, Self>
|
||||||
//! # ) -> Node {
|
//! # ) -> Node {
|
||||||
//! # Node::new(Style::default())
|
//! # Node::new(Style::default())
|
||||||
//! # }
|
//! # }
|
||||||
//! #
|
//! #
|
||||||
//! # fn draw<Message>(
|
//! # fn draw<Message>(
|
||||||
//! # &mut self,
|
//! # &mut self,
|
||||||
//! # _button: &Button<'_, Message>,
|
//! # _button: &Button<'_, Message, Self>,
|
||||||
//! # _layout: Layout<'_>,
|
//! # _layout: Layout<'_>,
|
||||||
//! # _cursor_position: Point,
|
//! # _cursor_position: Point,
|
||||||
//! # ) -> MouseCursor {
|
//! # ) {}
|
||||||
//! # MouseCursor::OutOfBounds
|
|
||||||
//! # }
|
|
||||||
//! # }
|
//! # }
|
||||||
//! #
|
//! #
|
||||||
//! # impl text::Renderer for Renderer {
|
//! # impl text::Renderer for Renderer {
|
||||||
|
|
@ -124,7 +125,7 @@
|
||||||
//! .push(
|
//! .push(
|
||||||
//! // The increment button. We tell it to produce an
|
//! // The increment button. We tell it to produce an
|
||||||
//! // `IncrementPressed` message when pressed
|
//! // `IncrementPressed` message when pressed
|
||||||
//! Button::new(&mut self.increment_button, "+")
|
//! Button::new(&mut self.increment_button, Text::new("+"))
|
||||||
//! .on_press(Message::IncrementPressed),
|
//! .on_press(Message::IncrementPressed),
|
||||||
//! )
|
//! )
|
||||||
//! .push(
|
//! .push(
|
||||||
|
|
@ -134,7 +135,7 @@
|
||||||
//! .push(
|
//! .push(
|
||||||
//! // The decrement button. We tell it to produce a
|
//! // The decrement button. We tell it to produce a
|
||||||
//! // `DecrementPressed` message when pressed
|
//! // `DecrementPressed` message when pressed
|
||||||
//! Button::new(&mut self.decrement_button, "-")
|
//! Button::new(&mut self.decrement_button, Text::new("-"))
|
||||||
//! .on_press(Message::DecrementPressed),
|
//! .on_press(Message::DecrementPressed),
|
||||||
//! )
|
//! )
|
||||||
//! }
|
//! }
|
||||||
|
|
@ -192,7 +193,7 @@
|
||||||
//! [documentation]: https://docs.rs/iced
|
//! [documentation]: https://docs.rs/iced
|
||||||
//! [examples]: https://github.com/hecrj/iced/tree/master/examples
|
//! [examples]: https://github.com/hecrj/iced/tree/master/examples
|
||||||
//! [`UserInterface`]: struct.UserInterface.html
|
//! [`UserInterface`]: struct.UserInterface.html
|
||||||
#![deny(missing_docs)]
|
//#![deny(missing_docs)]
|
||||||
#![deny(missing_debug_implementations)]
|
#![deny(missing_debug_implementations)]
|
||||||
#![deny(unused_results)]
|
#![deny(unused_results)]
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
@ -212,7 +213,9 @@ mod user_interface;
|
||||||
|
|
||||||
pub(crate) use iced_core::Vector;
|
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)]
|
#[doc(no_inline)]
|
||||||
pub use stretch::{geometry::Size, number::Number};
|
pub use stretch::{geometry::Size, number::Number};
|
||||||
|
|
@ -223,6 +226,7 @@ pub use hasher::Hasher;
|
||||||
pub use layout::Layout;
|
pub use layout::Layout;
|
||||||
pub use mouse_cursor::MouseCursor;
|
pub use mouse_cursor::MouseCursor;
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
|
pub use renderer::Renderer;
|
||||||
pub use style::Style;
|
pub use style::Style;
|
||||||
pub use user_interface::{Cache, UserInterface};
|
pub use user_interface::{Cache, UserInterface};
|
||||||
pub use widget::*;
|
pub use widget::*;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/// The state of the mouse cursor.
|
/// The state of the mouse cursor.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)]
|
||||||
pub enum MouseCursor {
|
pub enum MouseCursor {
|
||||||
/// The cursor is out of the bounds of the user interface.
|
/// The cursor is out of the bounds of the user interface.
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
|
|
@ -19,17 +19,3 @@ pub enum MouseCursor {
|
||||||
/// The cursor is grabbing a widget.
|
/// The cursor is grabbing a widget.
|
||||||
Grabbing,
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
//! Write your own renderer.
|
//! Write your own renderer.
|
||||||
//!
|
//!
|
||||||
//! There is not a common entrypoint or trait for a __renderer__ in Iced.
|
//! You will need to implement the `Renderer` trait first. It simply contains
|
||||||
//! Instead, every [`Widget`] constrains its generic `Renderer` type as
|
//! an `Output` associated type.
|
||||||
//! necessary.
|
//!
|
||||||
|
//! 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
|
//! This approach is flexible and composable. For instance, the
|
||||||
//! [`Text`] widget only needs a [`text::Renderer`] while a [`Checkbox`] widget
|
//! [`Text`] widget only needs a [`text::Renderer`] while a [`Checkbox`] widget
|
||||||
|
|
@ -17,22 +19,13 @@
|
||||||
//! [`text::Renderer`]: ../widget/text/trait.Renderer.html
|
//! [`text::Renderer`]: ../widget/text/trait.Renderer.html
|
||||||
//! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html
|
//! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html
|
||||||
//! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html
|
//! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html
|
||||||
use crate::{Color, Layout};
|
|
||||||
|
|
||||||
/// A renderer able to graphically explain a [`Layout`].
|
mod debugger;
|
||||||
///
|
mod windowed;
|
||||||
/// [`Layout`]: ../struct.Layout.html
|
|
||||||
pub trait Debugger {
|
pub use debugger::Debugger;
|
||||||
/// Explains the [`Layout`] of an [`Element`] for debugging purposes.
|
pub use windowed::Windowed;
|
||||||
///
|
|
||||||
/// This will be called when [`Element::explain`] has been used. It should
|
pub trait Renderer {
|
||||||
/// _explain_ the given [`Layout`] graphically.
|
type Output;
|
||||||
///
|
|
||||||
/// 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);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
native/src/renderer/debugger.rs
Normal file
25
native/src/renderer/debugger.rs
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
17
native/src/renderer/windowed.rs
Normal file
17
native/src/renderer/windowed.rs
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -74,12 +74,12 @@ impl Style {
|
||||||
self
|
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.0.align_items = into_align_items(align);
|
||||||
self
|
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.0.justify_content = into_justify_content(justify);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 std::hash::Hasher;
|
||||||
use stretch::result;
|
use stretch::{geometry, result};
|
||||||
|
|
||||||
/// A set of interactive graphical elements with a specific [`Layout`].
|
/// A set of interactive graphical elements with a specific [`Layout`].
|
||||||
///
|
///
|
||||||
|
|
@ -19,7 +19,10 @@ pub struct UserInterface<'a, Message, Renderer> {
|
||||||
cursor_position: Point,
|
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`].
|
/// Builds a user interface for an [`Element`].
|
||||||
///
|
///
|
||||||
/// It is able to avoid expensive computations when using a [`Cache`]
|
/// 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 {
|
/// # impl Renderer {
|
||||||
/// # pub fn new() -> Self { 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;
|
/// # 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>>>(
|
pub fn build<E: Into<Element<'a, Message, Renderer>>>(
|
||||||
root: E,
|
root: E,
|
||||||
cache: Cache,
|
cache: Cache,
|
||||||
renderer: &mut Renderer,
|
renderer: &Renderer,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let root = root.into();
|
let root = root.into();
|
||||||
|
|
||||||
|
|
@ -127,6 +143,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
|
||||||
/// # impl Renderer {
|
/// # impl Renderer {
|
||||||
/// # pub fn new() -> Self { 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;
|
/// # use iced_native::Column;
|
||||||
|
|
@ -212,6 +241,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
|
||||||
/// # impl Renderer {
|
/// # impl Renderer {
|
||||||
/// # pub fn new() -> Self { 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;
|
/// # use iced_native::Column;
|
||||||
|
|
@ -254,7 +296,7 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
|
||||||
/// // Flush rendering operations...
|
/// // Flush rendering operations...
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn draw(&self, renderer: &mut Renderer) -> MouseCursor {
|
pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {
|
||||||
self.root.widget.draw(
|
self.root.widget.draw(
|
||||||
renderer,
|
renderer,
|
||||||
Layout::new(&self.layout),
|
Layout::new(&self.layout),
|
||||||
|
|
@ -295,14 +337,16 @@ impl Cache {
|
||||||
/// [`Cache`]: struct.Cache.html
|
/// [`Cache`]: struct.Cache.html
|
||||||
/// [`UserInterface`]: struct.UserInterface.html
|
/// [`UserInterface`]: struct.UserInterface.html
|
||||||
pub fn new() -> Cache {
|
pub fn new() -> Cache {
|
||||||
let root: Element<'_, (), ()> = Column::new().into();
|
use crate::{Node, Style};
|
||||||
|
|
||||||
let hasher = &mut crate::Hasher::default();
|
let empty_node = Node::new(Style::default());
|
||||||
root.hash_layout(hasher);
|
|
||||||
|
|
||||||
Cache {
|
Cache {
|
||||||
hash: hasher.finish(),
|
hash: 0,
|
||||||
layout: root.compute_layout(&mut ()),
|
layout: empty_node
|
||||||
|
.0
|
||||||
|
.compute_layout(geometry::Size::undefined())
|
||||||
|
.unwrap(),
|
||||||
cursor_position: Point::new(0.0, 0.0),
|
cursor_position: Point::new(0.0, 0.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,12 @@
|
||||||
//!
|
//!
|
||||||
//! [`Widget`]: trait.Widget.html
|
//! [`Widget`]: trait.Widget.html
|
||||||
//! [renderer]: ../renderer/index.html
|
//! [renderer]: ../renderer/index.html
|
||||||
mod column;
|
|
||||||
mod row;
|
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
pub mod checkbox;
|
pub mod checkbox;
|
||||||
|
pub mod column;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod radio;
|
pub mod radio;
|
||||||
|
pub mod row;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
|
@ -47,7 +46,7 @@ pub use slider::Slider;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use text::Text;
|
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.
|
/// A component that displays information and allows interaction.
|
||||||
///
|
///
|
||||||
|
|
@ -56,7 +55,10 @@ use crate::{Event, Hasher, Layout, MouseCursor, Node, Point};
|
||||||
///
|
///
|
||||||
/// [`Widget`]: trait.Widget.html
|
/// [`Widget`]: trait.Widget.html
|
||||||
/// [`Element`]: ../struct.Element.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`].
|
/// Returns the [`Node`] of the [`Widget`].
|
||||||
///
|
///
|
||||||
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
|
/// 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
|
/// [`Node`]: ../struct.Node.html
|
||||||
/// [`Widget`]: trait.Widget.html
|
/// [`Widget`]: trait.Widget.html
|
||||||
/// [`Layout`]: ../struct.Layout.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`.
|
/// Draws the [`Widget`] using the associated `Renderer`.
|
||||||
///
|
///
|
||||||
/// It must return the [`MouseCursor`] state for the [`Widget`].
|
|
||||||
///
|
|
||||||
/// [`Widget`]: trait.Widget.html
|
/// [`Widget`]: trait.Widget.html
|
||||||
/// [`MouseCursor`]: ../enum.MouseCursor.html
|
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor;
|
) -> Renderer::Output;
|
||||||
|
|
||||||
/// Computes the _layout_ hash of the [`Widget`].
|
/// Computes the _layout_ hash of the [`Widget`].
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,21 @@
|
||||||
//! [`Class`]: enum.Class.html
|
//! [`Class`]: enum.Class.html
|
||||||
|
|
||||||
use crate::input::{mouse, ButtonState};
|
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;
|
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
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: self::Renderer,
|
||||||
Message: Copy + std::fmt::Debug,
|
Message: Copy + std::fmt::Debug,
|
||||||
{
|
{
|
||||||
fn node(&self, renderer: &mut Renderer) -> Node {
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
renderer.node(&self)
|
renderer.node(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,14 +67,14 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
renderer.draw(&self, layout, cursor_position)
|
renderer.draw(&self, layout, cursor_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
self.label.hash(state);
|
|
||||||
self.width.hash(state);
|
self.width.hash(state);
|
||||||
self.align_self.hash(state);
|
self.align_self.hash(state);
|
||||||
|
self.content.hash_layout(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,31 +85,33 @@ where
|
||||||
///
|
///
|
||||||
/// [`Button`]: struct.Button.html
|
/// [`Button`]: struct.Button.html
|
||||||
/// [renderer]: ../../renderer/index.html
|
/// [renderer]: ../../renderer/index.html
|
||||||
pub trait Renderer {
|
pub trait Renderer: crate::Renderer + Sized {
|
||||||
/// Creates a [`Node`] for the provided [`Button`].
|
/// Creates a [`Node`] for the provided [`Button`].
|
||||||
///
|
///
|
||||||
/// [`Node`]: ../../struct.Node.html
|
/// [`Node`]: ../../struct.Node.html
|
||||||
/// [`Button`]: struct.Button.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`].
|
/// Draws a [`Button`].
|
||||||
///
|
///
|
||||||
/// [`Button`]: struct.Button.html
|
/// [`Button`]: struct.Button.html
|
||||||
fn draw<Message>(
|
fn draw<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
button: &Button<'_, Message>,
|
button: &Button<'_, Message, Self>,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
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>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: 'static + self::Renderer,
|
||||||
Message: 'static + Copy + std::fmt::Debug,
|
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)
|
Element::new(button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::input::{mouse, ButtonState};
|
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;
|
pub use iced_core::Checkbox;
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: self::Renderer,
|
||||||
{
|
{
|
||||||
fn node(&self, renderer: &mut Renderer) -> Node {
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
renderer.node(&self)
|
renderer.node(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,9 +26,7 @@ where
|
||||||
button: mouse::Button::Left,
|
button: mouse::Button::Left,
|
||||||
state: ButtonState::Pressed,
|
state: ButtonState::Pressed,
|
||||||
}) => {
|
}) => {
|
||||||
let mouse_over = layout
|
let mouse_over = layout.bounds().contains(cursor_position);
|
||||||
.children()
|
|
||||||
.any(|child| child.bounds().contains(cursor_position));
|
|
||||||
|
|
||||||
if mouse_over {
|
if mouse_over {
|
||||||
messages.push((self.on_toggle)(!self.is_checked));
|
messages.push((self.on_toggle)(!self.is_checked));
|
||||||
|
|
@ -43,7 +41,7 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
renderer.draw(&self, layout, cursor_position)
|
renderer.draw(&self, layout, cursor_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,12 +57,12 @@ where
|
||||||
///
|
///
|
||||||
/// [`Checkbox`]: struct.Checkbox.html
|
/// [`Checkbox`]: struct.Checkbox.html
|
||||||
/// [renderer]: ../../renderer/index.html
|
/// [renderer]: ../../renderer/index.html
|
||||||
pub trait Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// Creates a [`Node`] for the provided [`Checkbox`].
|
/// Creates a [`Node`] for the provided [`Checkbox`].
|
||||||
///
|
///
|
||||||
/// [`Node`]: ../../struct.Node.html
|
/// [`Node`]: ../../struct.Node.html
|
||||||
/// [`Checkbox`]: struct.Checkbox.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`].
|
/// Draws a [`Checkbox`].
|
||||||
///
|
///
|
||||||
|
|
@ -80,7 +78,7 @@ pub trait Renderer {
|
||||||
checkbox: &Checkbox<Message>,
|
checkbox: &Checkbox<Message>,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor;
|
) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Checkbox<Message>>
|
impl<'a, Message, Renderer> From<Checkbox<Message>>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{
|
use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget};
|
||||||
Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A container that distributes its contents vertically.
|
/// A container that distributes its contents vertically.
|
||||||
pub type Column<'a, Message, Renderer> =
|
pub type Column<'a, Message, Renderer> =
|
||||||
|
|
@ -10,8 +8,10 @@ pub type Column<'a, Message, Renderer> =
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Column<'a, 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
|
let mut children: Vec<Node> = self
|
||||||
.children
|
.children
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
let mut cursor = MouseCursor::OutOfBounds;
|
renderer.draw(&self, layout, cursor_position)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
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>>
|
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a,
|
Renderer: 'a + self::Renderer,
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
{
|
{
|
||||||
fn from(
|
fn from(
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
//! Display images in your user interface.
|
//! 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;
|
use std::hash::Hash;
|
||||||
|
|
||||||
pub use iced_core::Image;
|
pub use iced_core::Image;
|
||||||
|
|
||||||
impl<I, Message, Renderer> Widget<Message, Renderer> for Image<I>
|
impl<Message, Renderer> Widget<Message, Renderer> for Image
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer<I>,
|
Renderer: self::Renderer,
|
||||||
I: Clone,
|
|
||||||
{
|
{
|
||||||
fn node(&self, renderer: &mut Renderer) -> Node {
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
renderer.node(&self)
|
renderer.node(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,10 +19,8 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
renderer.draw(&self, layout);
|
renderer.draw(&self, layout)
|
||||||
|
|
||||||
MouseCursor::OutOfBounds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -40,27 +37,26 @@ where
|
||||||
///
|
///
|
||||||
/// [`Image`]: struct.Image.html
|
/// [`Image`]: struct.Image.html
|
||||||
/// [renderer]: ../../renderer/index.html
|
/// [renderer]: ../../renderer/index.html
|
||||||
pub trait Renderer<I> {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// Creates a [`Node`] for the provided [`Image`].
|
/// Creates a [`Node`] for the provided [`Image`].
|
||||||
///
|
///
|
||||||
/// You should probably keep the original aspect ratio, if possible.
|
/// You should probably keep the original aspect ratio, if possible.
|
||||||
///
|
///
|
||||||
/// [`Node`]: ../../struct.Node.html
|
/// [`Node`]: ../../struct.Node.html
|
||||||
/// [`Image`]: struct.Image.html
|
/// [`Image`]: struct.Image.html
|
||||||
fn node(&mut self, image: &Image<I>) -> Node;
|
fn node(&self, image: &Image) -> Node;
|
||||||
|
|
||||||
/// Draws an [`Image`].
|
/// Draws an [`Image`].
|
||||||
///
|
///
|
||||||
/// [`Image`]: struct.Image.html
|
/// [`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
|
where
|
||||||
Renderer: self::Renderer<I>,
|
Renderer: self::Renderer,
|
||||||
I: Clone + 'a,
|
|
||||||
{
|
{
|
||||||
fn from(image: Image<I>) -> Element<'a, Message, Renderer> {
|
fn from(image: Image) -> Element<'a, Message, Renderer> {
|
||||||
Element::new(image)
|
Element::new(image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Create choices using radio buttons.
|
//! Create choices using radio buttons.
|
||||||
use crate::input::{mouse, ButtonState};
|
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;
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ where
|
||||||
Renderer: self::Renderer,
|
Renderer: self::Renderer,
|
||||||
Message: Copy + std::fmt::Debug,
|
Message: Copy + std::fmt::Debug,
|
||||||
{
|
{
|
||||||
fn node(&self, renderer: &mut Renderer) -> Node {
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
renderer.node(&self)
|
renderer.node(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
renderer.draw(&self, layout, cursor_position)
|
renderer.draw(&self, layout, cursor_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,12 +56,12 @@ where
|
||||||
///
|
///
|
||||||
/// [`Radio`]: struct.Radio.html
|
/// [`Radio`]: struct.Radio.html
|
||||||
/// [renderer]: ../../renderer/index.html
|
/// [renderer]: ../../renderer/index.html
|
||||||
pub trait Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// Creates a [`Node`] for the provided [`Radio`].
|
/// Creates a [`Node`] for the provided [`Radio`].
|
||||||
///
|
///
|
||||||
/// [`Node`]: ../../struct.Node.html
|
/// [`Node`]: ../../struct.Node.html
|
||||||
/// [`Radio`]: struct.Radio.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.
|
/// Draws a [`Radio`] button.
|
||||||
///
|
///
|
||||||
|
|
@ -77,7 +77,7 @@ pub trait Renderer {
|
||||||
radio: &Radio<Message>,
|
radio: &Radio<Message>,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor;
|
) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Radio<Message>>
|
impl<'a, Message, Renderer> From<Radio<Message>>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{
|
use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget};
|
||||||
Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A container that distributes its contents horizontally.
|
/// A container that distributes its contents horizontally.
|
||||||
pub type Row<'a, Message, Renderer> =
|
pub type Row<'a, Message, Renderer> =
|
||||||
|
|
@ -10,8 +8,10 @@ pub type Row<'a, Message, Renderer> =
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
for Row<'a, 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
|
let mut children: Vec<Node> = self
|
||||||
.children
|
.children
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
let mut cursor = MouseCursor::OutOfBounds;
|
renderer.draw(&self, layout, cursor_position)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
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>>
|
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Renderer>
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: 'a,
|
Renderer: 'a + self::Renderer,
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
{
|
{
|
||||||
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
|
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::input::{mouse, ButtonState};
|
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::*;
|
pub use iced_core::slider::*;
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message>
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: self::Renderer,
|
||||||
{
|
{
|
||||||
fn node(&self, renderer: &mut Renderer) -> Node {
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
renderer.node(&self)
|
renderer.node(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
renderer.draw(&self, layout, cursor_position)
|
renderer.draw(&self, layout, cursor_position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +87,7 @@ where
|
||||||
///
|
///
|
||||||
/// [`Slider`]: struct.Slider.html
|
/// [`Slider`]: struct.Slider.html
|
||||||
/// [renderer]: ../../renderer/index.html
|
/// [renderer]: ../../renderer/index.html
|
||||||
pub trait Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// Creates a [`Node`] for the provided [`Radio`].
|
/// Creates a [`Node`] for the provided [`Radio`].
|
||||||
///
|
///
|
||||||
/// [`Node`]: ../../struct.Node.html
|
/// [`Node`]: ../../struct.Node.html
|
||||||
|
|
@ -111,7 +111,7 @@ pub trait Renderer {
|
||||||
slider: &Slider<'_, Message>,
|
slider: &Slider<'_, Message>,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> MouseCursor;
|
) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> From<Slider<'a, Message>>
|
impl<'a, Message, Renderer> From<Slider<'a, Message>>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! Write some text for your users to read.
|
//! 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;
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Text
|
||||||
where
|
where
|
||||||
Renderer: self::Renderer,
|
Renderer: self::Renderer,
|
||||||
{
|
{
|
||||||
fn node(&self, renderer: &mut Renderer) -> Node {
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
renderer.node(&self)
|
renderer.node(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -18,10 +18,8 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
) -> MouseCursor {
|
) -> Renderer::Output {
|
||||||
renderer.draw(&self, layout);
|
renderer.draw(&self, layout)
|
||||||
|
|
||||||
MouseCursor::OutOfBounds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_layout(&self, state: &mut Hasher) {
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
|
@ -40,7 +38,7 @@ where
|
||||||
/// [`Text`]: struct.Text.html
|
/// [`Text`]: struct.Text.html
|
||||||
/// [renderer]: ../../renderer/index.html
|
/// [renderer]: ../../renderer/index.html
|
||||||
/// [`UserInterface`]: ../../struct.UserInterface.html
|
/// [`UserInterface`]: ../../struct.UserInterface.html
|
||||||
pub trait Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// Creates a [`Node`] with the given [`Style`] for the provided [`Text`]
|
/// Creates a [`Node`] with the given [`Style`] for the provided [`Text`]
|
||||||
/// contents and size.
|
/// contents and size.
|
||||||
///
|
///
|
||||||
|
|
@ -66,7 +64,7 @@ pub trait Renderer {
|
||||||
/// [`Text`]: struct.Text.html
|
/// [`Text`]: struct.Text.html
|
||||||
/// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
|
/// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
|
||||||
/// [`VerticalAlignment`]: enum.VerticalAlignment.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>
|
impl<'a, Message, Renderer> From<Text> for Element<'a, Message, Renderer>
|
||||||
|
|
|
||||||
59
src/lib.rs
59
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/web.rs
Normal file
1
src/web.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub use iced_web::*;
|
||||||
11
src/winit.rs
Normal file
11
src/winit.rs
Normal file
|
|
@ -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>;
|
||||||
|
|
@ -17,8 +17,7 @@ maintenance = { status = "actively-developed" }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core = { version = "0.1.0-alpha", path = "../core" }
|
iced_core = { version = "0.1.0-alpha", path = "../core" }
|
||||||
dodrio = "0.1.0"
|
dodrio = "0.1.0"
|
||||||
futures-preview = "=0.3.0-alpha.18"
|
wasm-bindgen = "0.2.51"
|
||||||
wasm-bindgen = "0.2.50"
|
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.27"
|
version = "0.3.27"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::Application;
|
use crate::Instance;
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ where
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
publish: Rc::new(Box::new(|message, root| {
|
publish: Rc::new(Box::new(|message, root| {
|
||||||
let app = root.unwrap_mut::<Application<Message>>();
|
let app = root.unwrap_mut::<Instance<Message>>();
|
||||||
|
|
||||||
app.update(message)
|
app.update(message)
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
pub fn explain(self, _color: Color) -> Element<'a, Message> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use dodrio::bumpalo;
|
use dodrio::bumpalo;
|
||||||
use futures::Future;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
mod bus;
|
mod bus;
|
||||||
|
|
@ -8,16 +7,13 @@ pub mod widget;
|
||||||
|
|
||||||
pub use bus::Bus;
|
pub use bus::Bus;
|
||||||
pub use element::Element;
|
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 use widget::*;
|
||||||
|
|
||||||
pub trait UserInterface {
|
pub trait Application {
|
||||||
type Message;
|
type Message;
|
||||||
|
|
||||||
fn update(
|
fn update(&mut self, message: Self::Message);
|
||||||
&mut self,
|
|
||||||
message: Self::Message,
|
|
||||||
) -> Option<Box<dyn Future<Output = Self::Message>>>;
|
|
||||||
|
|
||||||
fn view(&mut self) -> Element<Self::Message>;
|
fn view(&mut self) -> Element<Self::Message>;
|
||||||
|
|
||||||
|
|
@ -25,37 +21,34 @@ pub trait UserInterface {
|
||||||
where
|
where
|
||||||
Self: 'static + Sized,
|
Self: 'static + Sized,
|
||||||
{
|
{
|
||||||
|
let app = Instance::new(self);
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
let body = document.body().unwrap();
|
let body = document.body().unwrap();
|
||||||
|
|
||||||
let app = Application::new(self);
|
|
||||||
|
|
||||||
let vdom = dodrio::Vdom::new(&body, app);
|
let vdom = dodrio::Vdom::new(&body, app);
|
||||||
|
|
||||||
vdom.forget();
|
vdom.forget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Application<Message> {
|
struct Instance<Message> {
|
||||||
ui: RefCell<Box<dyn UserInterface<Message = Message>>>,
|
ui: RefCell<Box<dyn Application<Message = Message>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> Application<Message> {
|
impl<Message> Instance<Message> {
|
||||||
fn new(ui: impl UserInterface<Message = Message> + 'static) -> Self {
|
fn new(ui: impl Application<Message = Message> + 'static) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ui: RefCell::new(Box::new(ui)),
|
ui: RefCell::new(Box::new(ui)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) {
|
fn update(&mut self, message: Message) {
|
||||||
let mut ui = self.ui.borrow_mut();
|
self.ui.borrow_mut().update(message);
|
||||||
|
|
||||||
// TODO: Resolve futures and publish resulting messages
|
|
||||||
let _ = ui.update(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message> dodrio::Render for Application<Message>
|
impl<Message> dodrio::Render for Instance<Message>
|
||||||
where
|
where
|
||||||
Message: 'static,
|
Message: 'static,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ use crate::{Bus, Element, Widget};
|
||||||
|
|
||||||
use dodrio::bumpalo;
|
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>
|
impl<'a, Message> Widget<Message> for Button<'a, Message>
|
||||||
where
|
where
|
||||||
|
|
@ -15,9 +18,8 @@ where
|
||||||
) -> dodrio::Node<'b> {
|
) -> dodrio::Node<'b> {
|
||||||
use dodrio::builder::*;
|
use dodrio::builder::*;
|
||||||
|
|
||||||
let label = bumpalo::format!(in bump, "{}", self.label);
|
let mut node =
|
||||||
|
button(bump).children(vec![self.content.node(bump, bus)]);
|
||||||
let mut node = button(bump).children(vec![text(label.into_bump_str())]);
|
|
||||||
|
|
||||||
if let Some(on_press) = self.on_press {
|
if let Some(on_press) = self.on_press {
|
||||||
let event_bus = bus.clone();
|
let event_bus = bus.clone();
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use crate::{Bus, Element, Length, Widget};
|
||||||
|
|
||||||
use dodrio::bumpalo;
|
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>(
|
fn node<'b>(
|
||||||
&self,
|
&self,
|
||||||
bump: &'b bumpalo::Bump,
|
bump: &'b bumpalo::Bump,
|
||||||
|
|
@ -12,7 +12,7 @@ impl<'a, Message> Widget<Message> for Image<'a> {
|
||||||
) -> dodrio::Node<'b> {
|
) -> dodrio::Node<'b> {
|
||||||
use dodrio::builder::*;
|
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());
|
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> {
|
impl<'a, Message> From<Image> for Element<'a, Message> {
|
||||||
fn from(image: Image<'a>) -> Element<'a, Message> {
|
fn from(image: Image) -> Element<'a, Message> {
|
||||||
Element::new(image)
|
Element::new(image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
wgpu/Cargo.toml
Normal file
16
wgpu/Cargo.toml
Normal file
|
|
@ -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"
|
||||||
438
wgpu/src/image.rs
Normal file
438
wgpu/src/image.rs
Normal file
|
|
@ -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],
|
||||||
|
}
|
||||||
12
wgpu/src/lib.rs
Normal file
12
wgpu/src/lib.rs
Normal file
|
|
@ -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};
|
||||||
26
wgpu/src/primitive.rs
Normal file
26
wgpu/src/primitive.rs
Normal file
|
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
275
wgpu/src/quad.rs
Normal file
275
wgpu/src/quad.rs
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
329
wgpu/src/renderer.rs
Normal file
329
wgpu/src/renderer.rs
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
86
wgpu/src/renderer/button.rs
Normal file
86
wgpu/src/renderer/button.rs
Normal file
|
|
@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
106
wgpu/src/renderer/checkbox.rs
Normal file
106
wgpu/src/renderer/checkbox.rs
Normal file
|
|
@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
wgpu/src/renderer/column.rs
Normal file
34
wgpu/src/renderer/column.rs
Normal file
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
wgpu/src/renderer/image.rs
Normal file
34
wgpu/src/renderer/image.rs
Normal file
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
109
wgpu/src/renderer/radio.rs
Normal file
109
wgpu/src/renderer/radio.rs
Normal file
|
|
@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
wgpu/src/renderer/row.rs
Normal file
34
wgpu/src/renderer/row.rs
Normal file
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
128
wgpu/src/renderer/slider.rs
Normal file
128
wgpu/src/renderer/slider.rs
Normal file
|
|
@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
83
wgpu/src/renderer/text.rs
Normal file
83
wgpu/src/renderer/text.rs
Normal file
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
12
wgpu/src/shader/image.frag
Normal file
12
wgpu/src/shader/image.frag
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
BIN
wgpu/src/shader/image.frag.spv
Normal file
BIN
wgpu/src/shader/image.frag.spv
Normal file
Binary file not shown.
24
wgpu/src/shader/image.vert
Normal file
24
wgpu/src/shader/image.vert
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
BIN
wgpu/src/shader/image.vert.spv
Normal file
BIN
wgpu/src/shader/image.vert.spv
Normal file
Binary file not shown.
37
wgpu/src/shader/quad.frag
Normal file
37
wgpu/src/shader/quad.frag
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
BIN
wgpu/src/shader/quad.frag.spv
Normal file
BIN
wgpu/src/shader/quad.frag.spv
Normal file
Binary file not shown.
32
wgpu/src/shader/quad.vert
Normal file
32
wgpu/src/shader/quad.vert
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
BIN
wgpu/src/shader/quad.vert.spv
Normal file
BIN
wgpu/src/shader/quad.vert.spv
Normal file
Binary file not shown.
30
wgpu/src/transformation.rs
Normal file
30
wgpu/src/transformation.rs
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
13
winit/Cargo.toml
Normal file
13
winit/Cargo.toml
Normal file
|
|
@ -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"
|
||||||
185
winit/src/application.rs
Normal file
185
winit/src/application.rs
Normal file
|
|
@ -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()
|
||||||
|
}
|
||||||
199
winit/src/conversion.rs
Normal file
199
winit/src/conversion.rs
Normal file
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
8
winit/src/lib.rs
Normal file
8
winit/src/lib.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
pub use iced_native::*;
|
||||||
|
pub use winit;
|
||||||
|
|
||||||
|
pub mod conversion;
|
||||||
|
|
||||||
|
mod application;
|
||||||
|
|
||||||
|
pub use application::Application;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue