Merge pull request #22 from hecrj/basic-renderer

Basic `wgpu` renderer
This commit is contained in:
Héctor Ramón 2019-10-23 04:52:51 +02:00 committed by GitHub
commit 4769272122
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 2881 additions and 1698 deletions

View file

@ -24,6 +24,3 @@ jobs:
- uses: actions/checkout@master
- name: Run tests
run: cargo test --verbose --all --all-features
- name: Build tour for WebAssembly
if: matrix.targets == 'wasm32-unknown-unknown'
run: cargo build --verbose --package iced_tour --lib --target wasm32-unknown-unknown

View file

@ -1,6 +1,6 @@
[package]
name = "iced"
version = "0.1.0-alpha"
version = "0.1.0-alpha.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A cross-platform GUI library inspired by Elm"
@ -19,5 +19,19 @@ members = [
"core",
"native",
"web",
"examples/tour",
"wgpu",
"winit",
]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
iced_winit = { version = "0.1.0-alpha", path = "winit" }
iced_wgpu = { version = "0.1.0-alpha", path = "wgpu" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
iced_web = { version = "0.1.0-alpha", path = "web" }
[dev-dependencies]
env_logger = "0.7"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen = "0.2.51"

View file

@ -87,10 +87,9 @@ __view logic__:
```rust
use iced::{Button, Column, Text};
use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own!
impl Counter {
pub fn view(&mut self) -> Column<Message, Renderer> {
pub fn view(&mut self) -> Column<Message> {
// We use a column: a simple vertical layout
Column::new()
.push(

7
core/src/background.rs Normal file
View file

@ -0,0 +1,7 @@
use crate::Color;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Background {
Color(Color),
// TODO: Add gradient and image variants
}

View file

@ -16,4 +16,31 @@ impl Color {
b: 0.0,
a: 1.0,
};
/// The white color.
pub const WHITE: Color = Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub fn into_linear(self) -> [f32; 4] {
// As described in:
// https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
fn linear_component(u: f32) -> f32 {
if u < 0.04045 {
u / 12.92
} else {
((u + 0.055) / 1.055).powf(2.4)
}
}
[
linear_component(self.r),
linear_component(self.g),
linear_component(self.b),
self.a,
]
}
}

View file

@ -1,6 +1,7 @@
pub mod widget;
mod align;
mod background;
mod color;
mod justify;
mod length;
@ -9,6 +10,7 @@ mod rectangle;
mod vector;
pub use align::Align;
pub use background::Background;
pub use color::Color;
pub use justify::Justify;
pub use length::Length;

View file

@ -5,68 +5,58 @@
//! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html
use crate::{Align, Length};
use crate::{Align, Background, Length};
/// A generic widget that produces a message when clicked.
///
/// # Example
///
/// ```
/// use iced_core::{button, Button};
///
/// pub enum Message {
/// ButtonClicked,
/// }
///
/// let state = &mut button::State::new();
///
/// Button::new(state, "Click me!")
/// .on_press(Message::ButtonClicked);
/// ```
///
/// ![Button drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button.png?raw=true)
pub struct Button<'a, Message> {
pub struct Button<'a, Message, Element> {
/// The current state of the button
pub state: &'a mut State,
/// The label of the button
pub label: String,
pub content: Element,
/// The message to produce when the button is pressed
pub on_press: Option<Message>,
pub class: Class,
pub width: Length,
pub padding: u16,
pub background: Option<Background>,
pub border_radius: u16,
pub align_self: Option<Align>,
}
impl<'a, Message> std::fmt::Debug for Button<'a, Message>
impl<'a, Message, Element> std::fmt::Debug for Button<'a, Message, Element>
where
Message: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Button")
.field("state", &self.state)
.field("label", &self.label)
.field("on_press", &self.on_press)
.finish()
}
}
impl<'a, Message> Button<'a, Message> {
impl<'a, Message, Element> Button<'a, Message, Element> {
/// Creates a new [`Button`] with some local [`State`] and the given label.
///
/// [`Button`]: struct.Button.html
/// [`State`]: struct.State.html
pub fn new(state: &'a mut State, label: &str) -> Self {
pub fn new<E>(state: &'a mut State, content: E) -> Self
where
E: Into<Element>,
{
Button {
state,
label: String::from(label),
content: content.into(),
on_press: None,
class: Class::Primary,
width: Length::Shrink,
padding: 0,
background: None,
border_radius: 0,
align_self: None,
}
}
@ -79,6 +69,21 @@ impl<'a, Message> Button<'a, Message> {
self
}
pub fn padding(mut self, padding: u16) -> Self {
self.padding = padding;
self
}
pub fn background(mut self, background: Background) -> Self {
self.background = Some(background);
self
}
pub fn border_radius(mut self, border_radius: u16) -> Self {
self.border_radius = border_radius;
self
}
/// Sets the alignment of the [`Button`] itself.
///
/// This is useful if you want to override the default alignment given by
@ -90,16 +95,6 @@ impl<'a, Message> Button<'a, Message> {
self
}
/// Sets the [`Class`] of the [`Button`].
///
///
/// [`Button`]: struct.Button.html
/// [`Class`]: enum.Class.html
pub fn class(mut self, class: Class) -> Self {
self.class = class;
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
///
/// [`Button`]: struct.Button.html
@ -133,26 +128,3 @@ impl State {
self.is_pressed
}
}
/// The type of a [`Button`].
///
/// ![Different buttons drawn by the built-in renderer in Coffee](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button_classes.png?raw=true)
///
/// [`Button`]: struct.Button.html
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Class {
/// The [`Button`] performs the main action.
///
/// [`Button`]: struct.Button.html
Primary,
/// The [`Button`] performs an alternative action.
///
/// [`Button`]: struct.Button.html
Secondary,
/// The [`Button`] performs a productive action.
///
/// [`Button`]: struct.Button.html
Positive,
}

View file

@ -9,12 +9,12 @@ use crate::{Align, Length, Rectangle};
/// ```
/// use iced_core::Image;
///
/// # let my_handle = String::from("some_handle");
/// let image = Image::new(my_handle);
/// let image = Image::new("resources/ferris.png");
/// ```
pub struct Image<I> {
/// The image handle
pub handle: I,
#[derive(Debug)]
pub struct Image {
/// The image path
pub path: String,
/// The part of the image to show
pub clip: Option<Rectangle<u16>>,
@ -28,23 +28,13 @@ pub struct Image<I> {
pub align_self: Option<Align>,
}
impl<I> std::fmt::Debug for Image<I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Image")
.field("clip", &self.clip)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
impl<I> Image<I> {
/// Creates a new [`Image`] with given image handle.
impl Image {
/// Creates a new [`Image`] with the given path.
///
/// [`Image`]: struct.Image.html
pub fn new(handle: I) -> Self {
pub fn new<T: Into<String>>(path: T) -> Self {
Image {
handle,
path: path.into(),
clip: None,
width: Length::Shrink,
height: Length::Shrink,

View file

@ -1,78 +1,64 @@
# Examples
__Iced moves fast and the `master` branch can contain breaking changes!__ If
you want to learn about a specific release, check out [the release list].
[the release list]: https://github.com/hecrj/iced/releases
## [Tour](tour)
A simple UI tour showcasing different widgets that can be built using Iced. It
also shows how the library can be integrated into an existing system.
## [Tour](tour.rs)
A simple UI tour showcasing different widgets that can be built using Iced.
The example can run both on native and web platforms, using the same GUI code!
The native renderer of the example is built on top of [`ggez`], a game library
for Rust. Currently, it is using a [personal fork] to [add a `FontCache` type]
and [fix some issues with HiDPI].
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`]: tour/src/tour.rs
[`iced_ggez`]: tour/src/iced_ggez
[`renderer`]: src/iced_ggez/renderer
[`widget`]: tour/src/widget.rs
[`main`]: tour/src/main.rs
[`lib`]: tour/src/lib.rs
[`web`]: tour/src/web.rs
[`wasm-pack`]: https://rustwasm.github.io/wasm-pack/installer/
[personal fork]: https://github.com/hecrj/ggez
[add a `FontCache` type]: https://github.com/ggez/ggez/pull/679
[fix some issues with HiDPI]: https://github.com/hecrj/ggez/commit/dfe2fd2423c51a6daf42c75f66dfaeaacd439fb1
[gui_gif]: https://thumbs.gfycat.com/VeneratedSourAurochs-small.gif
[gui_gfycat]: https://gfycat.com/veneratedsouraurochs
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]

View file

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -6,8 +6,9 @@
</head>
<body>
<script type="module">
import init from "./pkg/iced_tour.js";
init("./pkg/iced_tour_bg.wasm");
import init from "./tour/tour.js";
init('./tour/tour_bg.wasm');
</script>
</body>
</html>

View file

@ -1,8 +1,17 @@
use crate::widget::{
button, slider, text::HorizontalAlignment, Align, Button, Checkbox, Color,
Column, Element, Image, Length, Radio, Row, Slider, Text,
use iced::{
button, slider, text::HorizontalAlignment, Align, Application, Background,
Button, Checkbox, Color, Column, Element, Image, Justify, Length, Radio,
Row, Slider, Text,
};
pub fn main() {
env_logger::init();
let tour = Tour::new();
tour.run();
}
pub struct Tour {
steps: Steps,
back_button: button::State,
@ -19,8 +28,12 @@ impl Tour {
debug: false,
}
}
}
pub fn update(&mut self, event: Message) {
impl Application for Tour {
type Message = Message;
fn update(&mut self, event: Message) {
match event {
Message::BackPressed => {
self.steps.go_back();
@ -34,7 +47,7 @@ impl Tour {
}
}
pub fn view(&mut self) -> Element<Message> {
fn view(&mut self) -> Element<Message> {
let Tour {
steps,
back_button,
@ -46,9 +59,8 @@ impl Tour {
if steps.has_previous() {
controls = controls.push(
Button::new(back_button, "Back")
.on_press(Message::BackPressed)
.class(button::Class::Secondary),
secondary_button(back_button, "Back")
.on_press(Message::BackPressed),
);
}
@ -56,22 +68,32 @@ impl Tour {
if steps.can_continue() {
controls = controls.push(
Button::new(next_button, "Next").on_press(Message::NextPressed),
primary_button(next_button, "Next")
.on_press(Message::NextPressed),
);
}
let element: Element<_> = Column::new()
.max_width(Length::Units(500))
.max_width(Length::Units(540))
.spacing(20)
.padding(20)
.push(steps.view(self.debug).map(Message::StepMessage))
.push(controls)
.into();
if self.debug {
let element = if self.debug {
element.explain(Color::BLACK)
} else {
element
}
};
Column::new()
.width(Length::Fill)
.height(Length::Fill)
.align_items(Align::Center)
.justify_content(Justify::Center)
.push(element)
.into()
}
}
@ -286,7 +308,7 @@ impl<'a> Step {
that can be easily implemented on top of Iced.",
))
.push(Text::new(
"Iced is a renderer-agnostic GUI library for Rust focused on \
"Iced is a cross-platform GUI library for Rust focused on \
simplicity and type-safety. It is heavily inspired by Elm.",
))
.push(Text::new(
@ -294,9 +316,9 @@ impl<'a> Step {
2D game engine for Rust.",
))
.push(Text::new(
"Iced does not provide a built-in renderer. On native \
platforms, this example runs on a fairly simple renderer \
built on top of ggez, another game library.",
"On native platforms, Iced provides by default a renderer \
built on top of wgpu, a graphics library supporting Vulkan, \
Metal, DX11, and DX12.",
))
.push(Text::new(
"Additionally, this tour can also run on WebAssembly thanks \
@ -304,7 +326,7 @@ impl<'a> Step {
))
.push(Text::new(
"You will need to interact with the UI in order to reach the \
end of this tour!",
end!",
))
}
@ -481,9 +503,18 @@ impl<'a> Step {
Self::container("Image")
.push(Text::new("An image that tries to keep its aspect ratio."))
.push(
Image::new("resources/ferris.png")
.width(Length::Units(width))
.align_self(Align::Center),
// This should go away once we unify resource loading on native
// platforms
if cfg!(target_arch = "wasm32") {
Image::new("resources/ferris.png")
} else {
Image::new(format!(
"{}/examples/resources/ferris.png",
env!("CARGO_MANIFEST_DIR")
))
}
.width(Length::Units(width))
.align_self(Align::Center),
)
.push(Slider::new(
slider,
@ -524,6 +555,44 @@ impl<'a> Step {
}
}
fn button<'a, Message>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
Button::new(
state,
Text::new(label)
.color(Color::WHITE)
.horizontal_alignment(HorizontalAlignment::Center),
)
.padding(12)
.border_radius(12)
}
fn primary_button<'a, Message>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
button(state, label).background(Background::Color(Color {
r: 0.11,
g: 0.42,
b: 0.87,
a: 1.0,
}))
}
fn secondary_button<'a, Message>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
button(state, label).background(Background::Color(Color {
r: 0.4,
g: 0.4,
b: 0.4,
a: 1.0,
}))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
Rust,
@ -565,3 +634,16 @@ pub enum Layout {
Row,
Column,
}
// This should be gracefully handled by Iced in the future. Probably using our
// own proc macro, or maybe the whole process is streamlined by `wasm-pack` at
// some point.
#[cfg(target_arch = "wasm32")]
mod wasm {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn run() {
super::main()
}
}

View file

@ -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"

View file

@ -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

View file

@ -1,6 +0,0 @@
mod renderer;
mod widget;
pub use renderer::Cache as ImageCache;
pub use renderer::Renderer;
pub use widget::*;

View file

@ -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,
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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);
}
}
}

View file

@ -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");
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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),
);
}
}

View file

@ -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>>;

View file

@ -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;

View file

@ -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,
}
}

View file

@ -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()
}
}

View file

@ -1,5 +0,0 @@
#[cfg(target_arch = "wasm32")]
pub use iced_web::*;
#[cfg(not(target_arch = "wasm32"))]
pub use crate::iced_ggez::*;

View file

@ -7,13 +7,8 @@ description = "A renderer-agnostic library for native GUIs"
license = "MIT"
repository = "https://github.com/hecrj/iced"
[package.metadata.docs.rs]
features = ["winit"]
[dependencies]
iced_core = { version = "0.1.0-alpha", path = "../core" }
stretch = "0.2"
twox-hash = "1.5"
# Enable to obtain conversion traits
winit = { version = "0.20.0-alpha3", optional = true }
raw-window-handle = "0.1"

View file

@ -1,8 +1,6 @@
use stretch::{geometry, result};
use crate::{
renderer, Color, Event, Hasher, Layout, MouseCursor, Node, Point, Widget,
};
use crate::{renderer, Color, Event, Hasher, Layout, Node, Point, Widget};
/// A generic [`Widget`].
///
@ -27,7 +25,10 @@ impl<'a, Message, Renderer> std::fmt::Debug for Element<'a, Message, Renderer> {
}
}
impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
impl<'a, Message, Renderer> Element<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Create a new [`Element`] containing the given [`Widget`].
///
/// [`Element`]: struct.Element.html
@ -40,6 +41,19 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
}
}
pub fn node(&self, renderer: &Renderer) -> Node {
self.widget.node(renderer)
}
pub fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
self.widget.draw(renderer, layout, cursor_position)
}
/// Applies a transformation to the produced message of the [`Element`].
///
/// This method is useful when you want to decouple different parts of your
@ -87,38 +101,46 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
///
/// ```
/// # mod counter {
/// # use iced_native::{button, Button};
/// # use iced_native::{text, Text};
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
/// # pub struct Counter(button::State);
/// # pub struct Counter;
/// #
/// # impl Counter {
/// # pub fn view(&mut self) -> Button<Message> {
/// # Button::new(&mut self.0, "_")
/// # pub fn view(&mut self) -> Text {
/// # Text::new("")
/// # }
/// # }
/// # }
/// #
/// # mod iced_wgpu {
/// # use iced_native::{
/// # button, Button, MouseCursor, Node, Point, Rectangle, Style, Layout
/// # text, row, Text, Node, Point, Rectangle, Style, Layout, Row
/// # };
/// # pub struct Renderer;
/// #
/// # impl button::Renderer for Renderer {
/// # fn node<Message>(&self, _button: &Button<'_, Message>) -> Node {
/// # impl iced_native::Renderer for Renderer { type Output = (); }
/// #
/// # 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())
/// # }
/// #
/// # fn draw<Message>(
/// # fn draw(
/// # &mut self,
/// # _button: &Button<'_, Message>,
/// # _text: &Text,
/// # _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(
&self,
renderer: &mut Renderer,
) -> result::Layout {
pub(crate) fn compute_layout(&self, renderer: &Renderer) -> result::Layout {
let node = self.widget.node(renderer);
node.0.compute_layout(geometry::Size::undefined()).unwrap()
@ -268,8 +287,9 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
where
A: Copy,
Renderer: crate::Renderer,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
self.widget.node(renderer)
}
@ -300,7 +320,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
) -> Renderer::Output {
self.widget.draw(renderer, layout, cursor_position)
}
@ -309,14 +329,14 @@ where
}
}
struct Explain<'a, Message, Renderer: renderer::Debugger> {
struct Explain<'a, Message, Renderer: crate::Renderer> {
element: Element<'a, Message, Renderer>,
color: Color,
}
impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer>
where
Renderer: renderer::Debugger,
Renderer: crate::Renderer,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Explain")
@ -327,7 +347,7 @@ where
impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
where
Renderer: renderer::Debugger,
Renderer: crate::Renderer,
{
fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
Explain { element, color }
@ -337,9 +357,9 @@ where
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer>
where
Renderer: renderer::Debugger,
Renderer: crate::Renderer + renderer::Debugger,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
self.element.widget.node(renderer)
}
@ -360,10 +380,13 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
renderer.explain(&layout, self.color);
self.element.widget.draw(renderer, layout, cursor_position)
) -> Renderer::Output {
renderer.explain(
self.element.widget.as_ref(),
layout,
cursor_position,
self.color,
)
}
fn hash_layout(&self, state: &mut Hasher) {

View file

@ -1,9 +1,4 @@
/// The state of a button.
///
/// If you are using [`winit`], consider enabling the `winit` feature to get
/// conversion implementations for free!
///
/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
pub enum ButtonState {
/// The button is pressed.
@ -12,13 +7,3 @@ pub enum ButtonState {
/// The button is __not__ pressed.
Released,
}
#[cfg(feature = "winit")]
impl From<winit::event::ElementState> for ButtonState {
fn from(element_state: winit::event::ElementState) -> Self {
match element_state {
winit::event::ElementState::Pressed => ButtonState::Pressed,
winit::event::ElementState::Released => ButtonState::Released,
}
}
}

View file

@ -2,9 +2,6 @@
///
/// This is mostly the `KeyCode` type found in [`winit`].
///
/// If you are using [`winit`], consider enabling the `winit` feature to get
/// conversion implementations for free!
///
/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
@ -199,176 +196,3 @@ pub enum KeyCode {
Paste,
Cut,
}
#[cfg(feature = "winit")]
impl From<winit::event::VirtualKeyCode> for KeyCode {
fn from(virtual_keycode: winit::event::VirtualKeyCode) -> Self {
match virtual_keycode {
winit::event::VirtualKeyCode::Key1 => KeyCode::Key1,
winit::event::VirtualKeyCode::Key2 => KeyCode::Key2,
winit::event::VirtualKeyCode::Key3 => KeyCode::Key3,
winit::event::VirtualKeyCode::Key4 => KeyCode::Key4,
winit::event::VirtualKeyCode::Key5 => KeyCode::Key5,
winit::event::VirtualKeyCode::Key6 => KeyCode::Key6,
winit::event::VirtualKeyCode::Key7 => KeyCode::Key7,
winit::event::VirtualKeyCode::Key8 => KeyCode::Key8,
winit::event::VirtualKeyCode::Key9 => KeyCode::Key9,
winit::event::VirtualKeyCode::Key0 => KeyCode::Key0,
winit::event::VirtualKeyCode::A => KeyCode::A,
winit::event::VirtualKeyCode::B => KeyCode::B,
winit::event::VirtualKeyCode::C => KeyCode::C,
winit::event::VirtualKeyCode::D => KeyCode::D,
winit::event::VirtualKeyCode::E => KeyCode::E,
winit::event::VirtualKeyCode::F => KeyCode::F,
winit::event::VirtualKeyCode::G => KeyCode::G,
winit::event::VirtualKeyCode::H => KeyCode::H,
winit::event::VirtualKeyCode::I => KeyCode::I,
winit::event::VirtualKeyCode::J => KeyCode::J,
winit::event::VirtualKeyCode::K => KeyCode::K,
winit::event::VirtualKeyCode::L => KeyCode::L,
winit::event::VirtualKeyCode::M => KeyCode::M,
winit::event::VirtualKeyCode::N => KeyCode::N,
winit::event::VirtualKeyCode::O => KeyCode::O,
winit::event::VirtualKeyCode::P => KeyCode::P,
winit::event::VirtualKeyCode::Q => KeyCode::Q,
winit::event::VirtualKeyCode::R => KeyCode::R,
winit::event::VirtualKeyCode::S => KeyCode::S,
winit::event::VirtualKeyCode::T => KeyCode::T,
winit::event::VirtualKeyCode::U => KeyCode::U,
winit::event::VirtualKeyCode::V => KeyCode::V,
winit::event::VirtualKeyCode::W => KeyCode::W,
winit::event::VirtualKeyCode::X => KeyCode::X,
winit::event::VirtualKeyCode::Y => KeyCode::Y,
winit::event::VirtualKeyCode::Z => KeyCode::Z,
winit::event::VirtualKeyCode::Escape => KeyCode::Escape,
winit::event::VirtualKeyCode::F1 => KeyCode::F1,
winit::event::VirtualKeyCode::F2 => KeyCode::F2,
winit::event::VirtualKeyCode::F3 => KeyCode::F3,
winit::event::VirtualKeyCode::F4 => KeyCode::F4,
winit::event::VirtualKeyCode::F5 => KeyCode::F5,
winit::event::VirtualKeyCode::F6 => KeyCode::F6,
winit::event::VirtualKeyCode::F7 => KeyCode::F7,
winit::event::VirtualKeyCode::F8 => KeyCode::F8,
winit::event::VirtualKeyCode::F9 => KeyCode::F9,
winit::event::VirtualKeyCode::F10 => KeyCode::F10,
winit::event::VirtualKeyCode::F11 => KeyCode::F11,
winit::event::VirtualKeyCode::F12 => KeyCode::F12,
winit::event::VirtualKeyCode::F13 => KeyCode::F13,
winit::event::VirtualKeyCode::F14 => KeyCode::F14,
winit::event::VirtualKeyCode::F15 => KeyCode::F15,
winit::event::VirtualKeyCode::F16 => KeyCode::F16,
winit::event::VirtualKeyCode::F17 => KeyCode::F17,
winit::event::VirtualKeyCode::F18 => KeyCode::F18,
winit::event::VirtualKeyCode::F19 => KeyCode::F19,
winit::event::VirtualKeyCode::F20 => KeyCode::F20,
winit::event::VirtualKeyCode::F21 => KeyCode::F21,
winit::event::VirtualKeyCode::F22 => KeyCode::F22,
winit::event::VirtualKeyCode::F23 => KeyCode::F23,
winit::event::VirtualKeyCode::F24 => KeyCode::F24,
winit::event::VirtualKeyCode::Snapshot => KeyCode::Snapshot,
winit::event::VirtualKeyCode::Scroll => KeyCode::Scroll,
winit::event::VirtualKeyCode::Pause => KeyCode::Pause,
winit::event::VirtualKeyCode::Insert => KeyCode::Insert,
winit::event::VirtualKeyCode::Home => KeyCode::Home,
winit::event::VirtualKeyCode::Delete => KeyCode::Delete,
winit::event::VirtualKeyCode::End => KeyCode::End,
winit::event::VirtualKeyCode::PageDown => KeyCode::PageDown,
winit::event::VirtualKeyCode::PageUp => KeyCode::PageUp,
winit::event::VirtualKeyCode::Left => KeyCode::Left,
winit::event::VirtualKeyCode::Up => KeyCode::Up,
winit::event::VirtualKeyCode::Right => KeyCode::Right,
winit::event::VirtualKeyCode::Down => KeyCode::Down,
winit::event::VirtualKeyCode::Back => KeyCode::Backspace,
winit::event::VirtualKeyCode::Return => KeyCode::Enter,
winit::event::VirtualKeyCode::Space => KeyCode::Space,
winit::event::VirtualKeyCode::Compose => KeyCode::Compose,
winit::event::VirtualKeyCode::Caret => KeyCode::Caret,
winit::event::VirtualKeyCode::Numlock => KeyCode::Numlock,
winit::event::VirtualKeyCode::Numpad0 => KeyCode::Numpad0,
winit::event::VirtualKeyCode::Numpad1 => KeyCode::Numpad1,
winit::event::VirtualKeyCode::Numpad2 => KeyCode::Numpad2,
winit::event::VirtualKeyCode::Numpad3 => KeyCode::Numpad3,
winit::event::VirtualKeyCode::Numpad4 => KeyCode::Numpad4,
winit::event::VirtualKeyCode::Numpad5 => KeyCode::Numpad5,
winit::event::VirtualKeyCode::Numpad6 => KeyCode::Numpad6,
winit::event::VirtualKeyCode::Numpad7 => KeyCode::Numpad7,
winit::event::VirtualKeyCode::Numpad8 => KeyCode::Numpad8,
winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9,
winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1,
winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2,
winit::event::VirtualKeyCode::Add => KeyCode::Add,
winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe,
winit::event::VirtualKeyCode::Apps => KeyCode::Apps,
winit::event::VirtualKeyCode::At => KeyCode::At,
winit::event::VirtualKeyCode::Ax => KeyCode::Ax,
winit::event::VirtualKeyCode::Backslash => KeyCode::Backslash,
winit::event::VirtualKeyCode::Calculator => KeyCode::Calculator,
winit::event::VirtualKeyCode::Capital => KeyCode::Capital,
winit::event::VirtualKeyCode::Colon => KeyCode::Colon,
winit::event::VirtualKeyCode::Comma => KeyCode::Comma,
winit::event::VirtualKeyCode::Convert => KeyCode::Convert,
winit::event::VirtualKeyCode::Decimal => KeyCode::Decimal,
winit::event::VirtualKeyCode::Divide => KeyCode::Divide,
winit::event::VirtualKeyCode::Equals => KeyCode::Equals,
winit::event::VirtualKeyCode::Grave => KeyCode::Grave,
winit::event::VirtualKeyCode::Kana => KeyCode::Kana,
winit::event::VirtualKeyCode::Kanji => KeyCode::Kanji,
winit::event::VirtualKeyCode::LAlt => KeyCode::LAlt,
winit::event::VirtualKeyCode::LBracket => KeyCode::LBracket,
winit::event::VirtualKeyCode::LControl => KeyCode::LControl,
winit::event::VirtualKeyCode::LShift => KeyCode::LShift,
winit::event::VirtualKeyCode::LWin => KeyCode::LWin,
winit::event::VirtualKeyCode::Mail => KeyCode::Mail,
winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect,
winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop,
winit::event::VirtualKeyCode::Minus => KeyCode::Minus,
winit::event::VirtualKeyCode::Multiply => KeyCode::Multiply,
winit::event::VirtualKeyCode::Mute => KeyCode::Mute,
winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer,
winit::event::VirtualKeyCode::NavigateForward => {
KeyCode::NavigateForward
}
winit::event::VirtualKeyCode::NavigateBackward => {
KeyCode::NavigateBackward
}
winit::event::VirtualKeyCode::NextTrack => KeyCode::NextTrack,
winit::event::VirtualKeyCode::NoConvert => KeyCode::NoConvert,
winit::event::VirtualKeyCode::NumpadComma => KeyCode::NumpadComma,
winit::event::VirtualKeyCode::NumpadEnter => KeyCode::NumpadEnter,
winit::event::VirtualKeyCode::NumpadEquals => KeyCode::NumpadEquals,
winit::event::VirtualKeyCode::OEM102 => KeyCode::OEM102,
winit::event::VirtualKeyCode::Period => KeyCode::Period,
winit::event::VirtualKeyCode::PlayPause => KeyCode::PlayPause,
winit::event::VirtualKeyCode::Power => KeyCode::Power,
winit::event::VirtualKeyCode::PrevTrack => KeyCode::PrevTrack,
winit::event::VirtualKeyCode::RAlt => KeyCode::RAlt,
winit::event::VirtualKeyCode::RBracket => KeyCode::RBracket,
winit::event::VirtualKeyCode::RControl => KeyCode::RControl,
winit::event::VirtualKeyCode::RShift => KeyCode::RShift,
winit::event::VirtualKeyCode::RWin => KeyCode::RWin,
winit::event::VirtualKeyCode::Semicolon => KeyCode::Semicolon,
winit::event::VirtualKeyCode::Slash => KeyCode::Slash,
winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep,
winit::event::VirtualKeyCode::Stop => KeyCode::Stop,
winit::event::VirtualKeyCode::Subtract => KeyCode::Subtract,
winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq,
winit::event::VirtualKeyCode::Tab => KeyCode::Tab,
winit::event::VirtualKeyCode::Underline => KeyCode::Underline,
winit::event::VirtualKeyCode::Unlabeled => KeyCode::Unlabeled,
winit::event::VirtualKeyCode::VolumeDown => KeyCode::VolumeDown,
winit::event::VirtualKeyCode::VolumeUp => KeyCode::VolumeUp,
winit::event::VirtualKeyCode::Wake => KeyCode::Wake,
winit::event::VirtualKeyCode::WebBack => KeyCode::WebBack,
winit::event::VirtualKeyCode::WebFavorites => KeyCode::WebFavorites,
winit::event::VirtualKeyCode::WebForward => KeyCode::WebForward,
winit::event::VirtualKeyCode::WebHome => KeyCode::WebHome,
winit::event::VirtualKeyCode::WebRefresh => KeyCode::WebRefresh,
winit::event::VirtualKeyCode::WebSearch => KeyCode::WebSearch,
winit::event::VirtualKeyCode::WebStop => KeyCode::WebStop,
winit::event::VirtualKeyCode::Yen => KeyCode::Yen,
winit::event::VirtualKeyCode::Copy => KeyCode::Copy,
winit::event::VirtualKeyCode::Paste => KeyCode::Paste,
winit::event::VirtualKeyCode::Cut => KeyCode::Cut,
}
}
}

View file

@ -1,9 +1,4 @@
/// The button of a mouse.
///
/// If you are using [`winit`], consider enabling the `winit` feature to get
/// conversion implementations for free!
///
/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum Button {
/// The left mouse button.
@ -18,15 +13,3 @@ pub enum Button {
/// Some other button.
Other(u8),
}
#[cfg(feature = "winit")]
impl From<winit::event::MouseButton> for super::Button {
fn from(mouse_button: winit::event::MouseButton) -> Self {
match mouse_button {
winit::event::MouseButton::Left => Button::Left,
winit::event::MouseButton::Right => Button::Right,
winit::event::MouseButton::Middle => Button::Middle,
winit::event::MouseButton::Other(other) => Button::Other(other),
}
}
}

View file

@ -77,28 +77,29 @@
//! #
//! # mod iced_wgpu {
//! # use iced_native::{
//! # button, text, Button, Text,
//! # MouseCursor, Node, Point, Rectangle, Style, Color, Layout
//! # button, text, Button, Text, Node, Point, Rectangle, Style, Color, Layout
//! # };
//! #
//! # pub struct Renderer {}
//! #
//! # impl iced_native::Renderer for Renderer {
//! # type Output = ();
//! # }
//! #
//! # impl button::Renderer for Renderer {
//! # fn node<Message>(
//! # &self,
//! # _button: &Button<'_, Message>
//! # _button: &Button<'_, Message, Self>
//! # ) -> Node {
//! # Node::new(Style::default())
//! # }
//! #
//! # fn draw<Message>(
//! # &mut self,
//! # _button: &Button<'_, Message>,
//! # _button: &Button<'_, Message, Self>,
//! # _layout: Layout<'_>,
//! # _cursor_position: Point,
//! # ) -> MouseCursor {
//! # MouseCursor::OutOfBounds
//! # }
//! # ) {}
//! # }
//! #
//! # impl text::Renderer for Renderer {
@ -124,7 +125,7 @@
//! .push(
//! // The increment button. We tell it to produce an
//! // `IncrementPressed` message when pressed
//! Button::new(&mut self.increment_button, "+")
//! Button::new(&mut self.increment_button, Text::new("+"))
//! .on_press(Message::IncrementPressed),
//! )
//! .push(
@ -134,7 +135,7 @@
//! .push(
//! // The decrement button. We tell it to produce a
//! // `DecrementPressed` message when pressed
//! Button::new(&mut self.decrement_button, "-")
//! Button::new(&mut self.decrement_button, Text::new("-"))
//! .on_press(Message::DecrementPressed),
//! )
//! }
@ -192,7 +193,7 @@
//! [documentation]: https://docs.rs/iced
//! [examples]: https://github.com/hecrj/iced/tree/master/examples
//! [`UserInterface`]: struct.UserInterface.html
#![deny(missing_docs)]
//#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
#![deny(unsafe_code)]
@ -212,7 +213,9 @@ mod user_interface;
pub(crate) use iced_core::Vector;
pub use iced_core::{Align, Color, Justify, Length, Point, Rectangle};
pub use iced_core::{
Align, Background, Color, Justify, Length, Point, Rectangle,
};
#[doc(no_inline)]
pub use stretch::{geometry::Size, number::Number};
@ -223,6 +226,7 @@ pub use hasher::Hasher;
pub use layout::Layout;
pub use mouse_cursor::MouseCursor;
pub use node::Node;
pub use renderer::Renderer;
pub use style::Style;
pub use user_interface::{Cache, UserInterface};
pub use widget::*;

View file

@ -1,5 +1,5 @@
/// The state of the mouse cursor.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)]
pub enum MouseCursor {
/// The cursor is out of the bounds of the user interface.
OutOfBounds,
@ -19,17 +19,3 @@ pub enum MouseCursor {
/// The cursor is grabbing a widget.
Grabbing,
}
#[cfg(feature = "winit")]
impl From<MouseCursor> for winit::window::CursorIcon {
fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon {
match mouse_cursor {
MouseCursor::OutOfBounds => winit::window::CursorIcon::Default,
MouseCursor::Idle => winit::window::CursorIcon::Default,
MouseCursor::Pointer => winit::window::CursorIcon::Hand,
MouseCursor::Working => winit::window::CursorIcon::Progress,
MouseCursor::Grab => winit::window::CursorIcon::Grab,
MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing,
}
}
}

View file

@ -1,8 +1,10 @@
//! Write your own renderer.
//!
//! There is not a common entrypoint or trait for a __renderer__ in Iced.
//! Instead, every [`Widget`] constrains its generic `Renderer` type as
//! necessary.
//! You will need to implement the `Renderer` trait first. It simply contains
//! an `Output` associated type.
//!
//! There is no common trait to draw all the widgets. Instead, every [`Widget`]
//! constrains its generic `Renderer` type as necessary.
//!
//! This approach is flexible and composable. For instance, the
//! [`Text`] widget only needs a [`text::Renderer`] while a [`Checkbox`] widget
@ -17,22 +19,13 @@
//! [`text::Renderer`]: ../widget/text/trait.Renderer.html
//! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html
//! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html
use crate::{Color, Layout};
/// A renderer able to graphically explain a [`Layout`].
///
/// [`Layout`]: ../struct.Layout.html
pub trait Debugger {
/// Explains the [`Layout`] of an [`Element`] for debugging purposes.
///
/// This will be called when [`Element::explain`] has been used. It should
/// _explain_ the given [`Layout`] graphically.
///
/// A common approach consists in recursively rendering the bounds of the
/// [`Layout`] and its children.
///
/// [`Layout`]: struct.Layout.html
/// [`Element`]: struct.Element.html
/// [`Element::explain`]: struct.Element.html#method.explain
fn explain(&mut self, layout: &Layout<'_>, color: Color);
mod debugger;
mod windowed;
pub use debugger::Debugger;
pub use windowed::Windowed;
pub trait Renderer {
type Output;
}

View 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;
}

View 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;
}

View file

@ -74,12 +74,12 @@ impl Style {
self
}
pub(crate) fn align_items(mut self, align: Align) -> Self {
pub fn align_items(mut self, align: Align) -> Self {
self.0.align_items = into_align_items(align);
self
}
pub(crate) fn justify_content(mut self, justify: Justify) -> Self {
pub fn justify_content(mut self, justify: Justify) -> Self {
self.0.justify_content = into_justify_content(justify);
self
}

View file

@ -1,7 +1,7 @@
use crate::{input::mouse, Column, Element, Event, Layout, MouseCursor, Point};
use crate::{input::mouse, Element, Event, Layout, Point};
use std::hash::Hasher;
use stretch::result;
use stretch::{geometry, result};
/// A set of interactive graphical elements with a specific [`Layout`].
///
@ -19,7 +19,10 @@ pub struct UserInterface<'a, Message, Renderer> {
cursor_position: Point,
}
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Builds a user interface for an [`Element`].
///
/// It is able to avoid expensive computations when using a [`Cache`]
@ -44,6 +47,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// # impl Renderer {
/// # pub fn new() -> Self { Renderer }
/// # }
/// #
/// # impl iced_native::Renderer for Renderer { type Output = (); }
/// #
/// # impl iced_native::column::Renderer for Renderer {
/// # fn draw<Message>(
/// # &mut self,
/// # _column: &iced_native::Column<'_, Message, Self>,
/// # _layout: iced_native::Layout<'_>,
/// # _cursor_position: iced_native::Point,
/// # ) -> Self::Output {
/// # ()
/// # }
/// # }
/// # }
/// #
/// # use iced_native::Column;
@ -82,7 +98,7 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
pub fn build<E: Into<Element<'a, Message, Renderer>>>(
root: E,
cache: Cache,
renderer: &mut Renderer,
renderer: &Renderer,
) -> Self {
let root = root.into();
@ -127,6 +143,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// # impl Renderer {
/// # pub fn new() -> Self { Renderer }
/// # }
/// #
/// # impl iced_native::Renderer for Renderer { type Output = (); }
/// #
/// # impl iced_native::column::Renderer for Renderer {
/// # fn draw<Message>(
/// # &mut self,
/// # _column: &iced_native::Column<'_, Message, Self>,
/// # _layout: iced_native::Layout<'_>,
/// # _cursor_position: iced_native::Point,
/// # ) -> Self::Output {
/// # ()
/// # }
/// # }
/// # }
/// #
/// # use iced_native::Column;
@ -212,6 +241,19 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// # impl Renderer {
/// # pub fn new() -> Self { Renderer }
/// # }
/// #
/// # impl iced_native::Renderer for Renderer { type Output = (); }
/// #
/// # impl iced_native::column::Renderer for Renderer {
/// # fn draw<Message>(
/// # &mut self,
/// # _column: &iced_native::Column<'_, Message, Self>,
/// # _layout: iced_native::Layout<'_>,
/// # _cursor_position: iced_native::Point,
/// # ) -> Self::Output {
/// # ()
/// # }
/// # }
/// # }
/// #
/// # use iced_native::Column;
@ -254,7 +296,7 @@ impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> {
/// // Flush rendering operations...
/// }
/// ```
pub fn draw(&self, renderer: &mut Renderer) -> MouseCursor {
pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {
self.root.widget.draw(
renderer,
Layout::new(&self.layout),
@ -295,14 +337,16 @@ impl Cache {
/// [`Cache`]: struct.Cache.html
/// [`UserInterface`]: struct.UserInterface.html
pub fn new() -> Cache {
let root: Element<'_, (), ()> = Column::new().into();
use crate::{Node, Style};
let hasher = &mut crate::Hasher::default();
root.hash_layout(hasher);
let empty_node = Node::new(Style::default());
Cache {
hash: hasher.finish(),
layout: root.compute_layout(&mut ()),
hash: 0,
layout: empty_node
.0
.compute_layout(geometry::Size::undefined())
.unwrap(),
cursor_position: Point::new(0.0, 0.0),
}
}

View file

@ -20,13 +20,12 @@
//!
//! [`Widget`]: trait.Widget.html
//! [renderer]: ../renderer/index.html
mod column;
mod row;
pub mod button;
pub mod checkbox;
pub mod column;
pub mod image;
pub mod radio;
pub mod row;
pub mod slider;
pub mod text;
@ -47,7 +46,7 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text::Text;
use crate::{Event, Hasher, Layout, MouseCursor, Node, Point};
use crate::{Event, Hasher, Layout, Node, Point};
/// A component that displays information and allows interaction.
///
@ -56,7 +55,10 @@ use crate::{Event, Hasher, Layout, MouseCursor, Node, Point};
///
/// [`Widget`]: trait.Widget.html
/// [`Element`]: ../struct.Element.html
pub trait Widget<Message, Renderer>: std::fmt::Debug {
pub trait Widget<Message, Renderer>: std::fmt::Debug
where
Renderer: crate::Renderer,
{
/// Returns the [`Node`] of the [`Widget`].
///
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
@ -65,20 +67,17 @@ pub trait Widget<Message, Renderer>: std::fmt::Debug {
/// [`Node`]: ../struct.Node.html
/// [`Widget`]: trait.Widget.html
/// [`Layout`]: ../struct.Layout.html
fn node(&self, renderer: &mut Renderer) -> Node;
fn node(&self, renderer: &Renderer) -> Node;
/// Draws the [`Widget`] using the associated `Renderer`.
///
/// It must return the [`MouseCursor`] state for the [`Widget`].
///
/// [`Widget`]: trait.Widget.html
/// [`MouseCursor`]: ../enum.MouseCursor.html
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor;
) -> Renderer::Output;
/// Computes the _layout_ hash of the [`Widget`].
///

View file

@ -7,17 +7,21 @@
//! [`Class`]: enum.Class.html
use crate::input::{mouse, ButtonState};
use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
pub use iced_core::button::*;
pub use iced_core::button::State;
impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message>
pub type Button<'a, Message, Renderer> =
iced_core::Button<'a, Message, Element<'a, Message, Renderer>>;
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
Renderer: self::Renderer,
Message: Copy + std::fmt::Debug,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@ -63,14 +67,14 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
self.label.hash(state);
self.width.hash(state);
self.align_self.hash(state);
self.content.hash_layout(state);
}
}
@ -81,31 +85,33 @@ where
///
/// [`Button`]: struct.Button.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer {
pub trait Renderer: crate::Renderer + Sized {
/// Creates a [`Node`] for the provided [`Button`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Button`]: struct.Button.html
fn node<Message>(&self, button: &Button<'_, Message>) -> Node;
fn node<Message>(&self, button: &Button<'_, Message, Self>) -> Node;
/// Draws a [`Button`].
///
/// [`Button`]: struct.Button.html
fn draw<Message>(
&mut self,
button: &Button<'_, Message>,
button: &Button<'_, Message, Self>,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor;
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Button<'a, Message>>
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: 'static + self::Renderer,
Message: 'static + Copy + std::fmt::Debug,
{
fn from(button: Button<'a, Message>) -> Element<'a, Message, Renderer> {
fn from(
button: Button<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(button)
}
}

View file

@ -2,7 +2,7 @@
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
pub use iced_core::Checkbox;
@ -10,7 +10,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message>
where
Renderer: self::Renderer,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@ -26,9 +26,7 @@ where
button: mouse::Button::Left,
state: ButtonState::Pressed,
}) => {
let mouse_over = layout
.children()
.any(|child| child.bounds().contains(cursor_position));
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
messages.push((self.on_toggle)(!self.is_checked));
@ -43,7 +41,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
@ -59,12 +57,12 @@ where
///
/// [`Checkbox`]: struct.Checkbox.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer {
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Checkbox`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Checkbox`]: struct.Checkbox.html
fn node<Message>(&mut self, checkbox: &Checkbox<Message>) -> Node;
fn node<Message>(&self, checkbox: &Checkbox<Message>) -> Node;
/// Draws a [`Checkbox`].
///
@ -80,7 +78,7 @@ pub trait Renderer {
checkbox: &Checkbox<Message>,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor;
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Checkbox<Message>>

View file

@ -1,8 +1,6 @@
use std::hash::Hash;
use crate::{
Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget,
};
use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget};
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Renderer> =
@ -10,8 +8,10 @@ pub type Column<'a, Message, Renderer> =
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
let mut children: Vec<Node> = self
.children
.iter()
@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
let mut cursor = MouseCursor::OutOfBounds;
self.children.iter().zip(layout.children()).for_each(
|(child, layout)| {
let new_cursor =
child.widget.draw(renderer, layout, cursor_position);
if new_cursor != MouseCursor::OutOfBounds {
cursor = new_cursor;
}
},
);
cursor
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -104,10 +91,19 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
}
}
pub trait Renderer: crate::Renderer + Sized {
fn draw<Message>(
&mut self,
row: &Column<'_, Message, Self>,
layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a,
Renderer: 'a + self::Renderer,
Message: 'static,
{
fn from(

View file

@ -1,17 +1,16 @@
//! Display images in your user interface.
use crate::{Element, Hasher, Layout, MouseCursor, Node, Point, Widget};
use crate::{Element, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
pub use iced_core::Image;
impl<I, Message, Renderer> Widget<Message, Renderer> for Image<I>
impl<Message, Renderer> Widget<Message, Renderer> for Image
where
Renderer: self::Renderer<I>,
I: Clone,
Renderer: self::Renderer,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@ -20,10 +19,8 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
_cursor_position: Point,
) -> MouseCursor {
renderer.draw(&self, layout);
MouseCursor::OutOfBounds
) -> Renderer::Output {
renderer.draw(&self, layout)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -40,27 +37,26 @@ where
///
/// [`Image`]: struct.Image.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer<I> {
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Image`].
///
/// You should probably keep the original aspect ratio, if possible.
///
/// [`Node`]: ../../struct.Node.html
/// [`Image`]: struct.Image.html
fn node(&mut self, image: &Image<I>) -> Node;
fn node(&self, image: &Image) -> Node;
/// Draws an [`Image`].
///
/// [`Image`]: struct.Image.html
fn draw(&mut self, image: &Image<I>, layout: Layout<'_>);
fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output;
}
impl<'a, I, Message, Renderer> From<Image<I>> for Element<'a, Message, Renderer>
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
where
Renderer: self::Renderer<I>,
I: Clone + 'a,
Renderer: self::Renderer,
{
fn from(image: Image<I>) -> Element<'a, Message, Renderer> {
fn from(image: Image) -> Element<'a, Message, Renderer> {
Element::new(image)
}
}

View file

@ -1,6 +1,6 @@
//! Create choices using radio buttons.
use crate::input::{mouse, ButtonState};
use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
@ -11,7 +11,7 @@ where
Renderer: self::Renderer,
Message: Copy + std::fmt::Debug,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@ -40,7 +40,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
@ -56,12 +56,12 @@ where
///
/// [`Radio`]: struct.Radio.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer {
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Radio`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Radio`]: struct.Radio.html
fn node<Message>(&mut self, radio: &Radio<Message>) -> Node;
fn node<Message>(&self, radio: &Radio<Message>) -> Node;
/// Draws a [`Radio`] button.
///
@ -77,7 +77,7 @@ pub trait Renderer {
radio: &Radio<Message>,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor;
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Radio<Message>>

View file

@ -1,8 +1,6 @@
use std::hash::Hash;
use crate::{
Element, Event, Hasher, Layout, MouseCursor, Node, Point, Style, Widget,
};
use crate::{Element, Event, Hasher, Layout, Node, Point, Style, Widget};
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Renderer> =
@ -10,8 +8,10 @@ pub type Row<'a, Message, Renderer> =
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
let mut children: Vec<Node> = self
.children
.iter()
@ -70,21 +70,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
let mut cursor = MouseCursor::OutOfBounds;
self.children.iter().zip(layout.children()).for_each(
|(child, layout)| {
let new_cursor =
child.widget.draw(renderer, layout, cursor_position);
if new_cursor != MouseCursor::OutOfBounds {
cursor = new_cursor;
}
},
);
cursor
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -105,10 +92,19 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
}
}
pub trait Renderer: crate::Renderer + Sized {
fn draw<Message>(
&mut self,
row: &Row<'_, Message, Self>,
layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a,
Renderer: 'a + self::Renderer,
Message: 'static,
{
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {

View file

@ -7,7 +7,7 @@
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
use crate::{Element, Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
use crate::{Element, Event, Hasher, Layout, Node, Point, Widget};
pub use iced_core::slider::*;
@ -15,7 +15,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message>
where
Renderer: self::Renderer,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@ -71,7 +71,7 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
}
@ -87,7 +87,7 @@ where
///
/// [`Slider`]: struct.Slider.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer {
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Radio`].
///
/// [`Node`]: ../../struct.Node.html
@ -111,7 +111,7 @@ pub trait Renderer {
slider: &Slider<'_, Message>,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor;
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Slider<'a, Message>>

View file

@ -1,5 +1,5 @@
//! Write some text for your users to read.
use crate::{Element, Hasher, Layout, MouseCursor, Node, Point, Widget};
use crate::{Element, Hasher, Layout, Node, Point, Widget};
use std::hash::Hash;
@ -9,7 +9,7 @@ impl<Message, Renderer> Widget<Message, Renderer> for Text
where
Renderer: self::Renderer,
{
fn node(&self, renderer: &mut Renderer) -> Node {
fn node(&self, renderer: &Renderer) -> Node {
renderer.node(&self)
}
@ -18,10 +18,8 @@ where
renderer: &mut Renderer,
layout: Layout<'_>,
_cursor_position: Point,
) -> MouseCursor {
renderer.draw(&self, layout);
MouseCursor::OutOfBounds
) -> Renderer::Output {
renderer.draw(&self, layout)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -40,7 +38,7 @@ where
/// [`Text`]: struct.Text.html
/// [renderer]: ../../renderer/index.html
/// [`UserInterface`]: ../../struct.UserInterface.html
pub trait Renderer {
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] with the given [`Style`] for the provided [`Text`]
/// contents and size.
///
@ -66,7 +64,7 @@ pub trait Renderer {
/// [`Text`]: struct.Text.html
/// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
/// [`VerticalAlignment`]: enum.VerticalAlignment.html
fn draw(&mut self, text: &Text, layout: Layout<'_>);
fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output;
}
impl<'a, Message, Renderer> From<Text> for Element<'a, Message, Renderer>

View file

@ -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
View file

@ -0,0 +1 @@
pub use iced_web::*;

11
src/winit.rs Normal file
View 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>;

View file

@ -17,8 +17,7 @@ maintenance = { status = "actively-developed" }
[dependencies]
iced_core = { version = "0.1.0-alpha", path = "../core" }
dodrio = "0.1.0"
futures-preview = "=0.3.0-alpha.18"
wasm-bindgen = "0.2.50"
wasm-bindgen = "0.2.51"
[dependencies.web-sys]
version = "0.3.27"

View file

@ -1,4 +1,4 @@
use crate::Application;
use crate::Instance;
use std::rc::Rc;
@ -14,7 +14,7 @@ where
pub fn new() -> Self {
Self {
publish: Rc::new(Box::new(|message, root| {
let app = root.unwrap_mut::<Application<Message>>();
let app = root.unwrap_mut::<Instance<Message>>();
app.update(message)
})),

View file

@ -14,6 +14,14 @@ impl<'a, Message> Element<'a, Message> {
}
}
pub fn node<'b>(
&self,
bump: &'b bumpalo::Bump,
bus: &Bus<Message>,
) -> dodrio::Node<'b> {
self.widget.node(bump, bus)
}
pub fn explain(self, _color: Color) -> Element<'a, Message> {
self
}

View file

@ -1,5 +1,4 @@
use dodrio::bumpalo;
use futures::Future;
use std::cell::RefCell;
mod bus;
@ -8,16 +7,13 @@ pub mod widget;
pub use bus::Bus;
pub use element::Element;
pub use iced_core::{Align, Color, Justify, Length};
pub use iced_core::{Align, Background, Color, Justify, Length};
pub use widget::*;
pub trait UserInterface {
pub trait Application {
type Message;
fn update(
&mut self,
message: Self::Message,
) -> Option<Box<dyn Future<Output = Self::Message>>>;
fn update(&mut self, message: Self::Message);
fn view(&mut self) -> Element<Self::Message>;
@ -25,37 +21,34 @@ pub trait UserInterface {
where
Self: 'static + Sized,
{
let app = Instance::new(self);
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
let app = Application::new(self);
let vdom = dodrio::Vdom::new(&body, app);
vdom.forget();
}
}
struct Application<Message> {
ui: RefCell<Box<dyn UserInterface<Message = Message>>>,
struct Instance<Message> {
ui: RefCell<Box<dyn Application<Message = Message>>>,
}
impl<Message> Application<Message> {
fn new(ui: impl UserInterface<Message = Message> + 'static) -> Self {
impl<Message> Instance<Message> {
fn new(ui: impl Application<Message = Message> + 'static) -> Self {
Self {
ui: RefCell::new(Box::new(ui)),
}
}
fn update(&mut self, message: Message) {
let mut ui = self.ui.borrow_mut();
// TODO: Resolve futures and publish resulting messages
let _ = ui.update(message);
self.ui.borrow_mut().update(message);
}
}
impl<Message> dodrio::Render for Application<Message>
impl<Message> dodrio::Render for Instance<Message>
where
Message: 'static,
{

View file

@ -2,7 +2,10 @@ use crate::{Bus, Element, Widget};
use dodrio::bumpalo;
pub use iced_core::button::*;
pub use iced_core::button::State;
pub type Button<'a, Message> =
iced_core::Button<'a, Message, Element<'a, Message>>;
impl<'a, Message> Widget<Message> for Button<'a, Message>
where
@ -15,9 +18,8 @@ where
) -> dodrio::Node<'b> {
use dodrio::builder::*;
let label = bumpalo::format!(in bump, "{}", self.label);
let mut node = button(bump).children(vec![text(label.into_bump_str())]);
let mut node =
button(bump).children(vec![self.content.node(bump, bus)]);
if let Some(on_press) = self.on_press {
let event_bus = bus.clone();

View file

@ -2,9 +2,9 @@ use crate::{Bus, Element, Length, Widget};
use dodrio::bumpalo;
pub type Image<'a> = iced_core::Image<&'a str>;
pub use iced_core::Image;
impl<'a, Message> Widget<Message> for Image<'a> {
impl<Message> Widget<Message> for Image {
fn node<'b>(
&self,
bump: &'b bumpalo::Bump,
@ -12,7 +12,7 @@ impl<'a, Message> Widget<Message> for Image<'a> {
) -> dodrio::Node<'b> {
use dodrio::builder::*;
let src = bumpalo::format!(in bump, "{}", self.handle);
let src = bumpalo::format!(in bump, "{}", self.path);
let mut image = img(bump).attr("src", src.into_bump_str());
@ -35,8 +35,8 @@ impl<'a, Message> Widget<Message> for Image<'a> {
}
}
impl<'a, Message> From<Image<'a>> for Element<'a, Message> {
fn from(image: Image<'a>) -> Element<'a, Message> {
impl<'a, Message> From<Image> for Element<'a, Message> {
fn from(image: Image) -> Element<'a, Message> {
Element::new(image)
}
}

16
wgpu/Cargo.toml Normal file
View 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
View 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
View 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
View 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
View 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
View 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);
}
}

View 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
},
)
}
}

View 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
},
)
}
}

View 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,
)
}
}

View 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
View 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
View 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
View 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
View 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,
)
}
}

View 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);
}

Binary file not shown.

View 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);
}

Binary file not shown.

37
wgpu/src/shader/quad.frag Normal file
View 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);
}

Binary file not shown.

32
wgpu/src/shader/quad.vert Normal file
View 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);
}

Binary file not shown.

View 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
View 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
View 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
View 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
View file

@ -0,0 +1,8 @@
pub use iced_native::*;
pub use winit;
pub mod conversion;
mod application;
pub use application::Application;