Merge branch 'master' into theming

This commit is contained in:
Héctor Ramón Jiménez 2022-07-08 19:31:45 +02:00
commit fa55dff61d
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
34 changed files with 444 additions and 337 deletions

View file

@ -18,21 +18,22 @@ Inspired by [Elm].
<img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="350px">
</a>
<a href="https://gfycat.com/politeadorableiberianmole">
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
<img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif" height="350px">
</a>
</div>
## Features
* Simple, easy-to-use, batteries-included API
* Type-safe, reactive programming model
* [Cross-platform support] (Windows, macOS, Linux, and [the Web])
* Responsive layout
* Built-in widgets (including [text inputs], [scrollables], and more!)
* Custom widget support (create your own!)
* [Debug overlay with performance metrics]
* First-class support for async actions (use futures!)
* [Modular ecosystem] split into reusable parts:
* Simple, easy-to-use, batteries-included API
* Type-safe, reactive programming model
* [Cross-platform support] (Windows, macOS, Linux, and [the Web])
* Responsive layout
* Built-in widgets (including [text inputs], [scrollables], and more!)
* Custom widget support (create your own!)
* [Debug overlay with performance metrics]
* First-class support for async actions (use futures!)
* [Modular ecosystem] split into reusable parts:
* A [renderer-agnostic native runtime] enabling integration with existing systems
* Two [built-in renderers] leveraging [`wgpu`] and [`glow`]
* [`iced_wgpu`] supporting Vulkan, Metal and DX12
@ -63,6 +64,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
[feel free to contribute!]: #contributing--feedback
## Installation
Add `iced` as a dependency in your `Cargo.toml`:
```toml
@ -78,15 +80,16 @@ you want to learn about a specific release, check out [the release list].
[the release list]: https://github.com/iced-rs/iced/releases
## Overview
Inspired by [The Elm Architecture], Iced expects you to split user interfaces
into four different concepts:
* __State__ — the state of your application
* __Messages__ — user interactions or meaningful events that you care
* __State__ — the state of your application
* __Messages__ — user interactions or meaningful events that you care
about
* __View logic__ — a way to display your __state__ as widgets that
* __View logic__ — a way to display your __state__ as widgets that
may produce __messages__ on user interaction
* __Update logic__ — a way to react to __messages__ and update your
* __Update logic__ — a way to react to __messages__ and update your
__state__
We can build something to see how this works! Let's say we want a simple counter
@ -179,6 +182,7 @@ to:
Browse the [documentation] and the [examples] to learn more!
## Implementation details
Iced was originally born as an attempt at bringing the simplicity of [Elm] and
[The Elm Architecture] into [Coffee], a 2D game engine I am working on.
@ -204,7 +208,9 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular:
[the ecosystem]: ECOSYSTEM.md
## Troubleshooting
### `GraphicsAdapterNotFound`
This occurs when the selected [built-in renderer] is not able to create a context.
Often this will occur while using [`iced_wgpu`] as the renderer without
@ -212,22 +218,25 @@ supported hardware (needs Vulkan, Metal or DX12). In this case, you could try us
[`iced_glow`] renderer:
First, check if it works with
```console
$ cargo run --features iced/glow --package game_of_life
cargo run --features iced/glow --package game_of_life
```
and then use it in your project with
```toml
iced = { version = "0.4", default-features = false, features = ["glow"] }
```
**NOTE:** Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
but if you don't, right now there's no software fallback, so it means your hardware
doesn't support Iced.
[built-in renderer]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md#Renderers
## Contributing / Feedback
Contributions are greatly appreciated! If you want to contribute, please
read our [contributing guidelines] for more details.
@ -237,6 +246,7 @@ awesome folks) over the `#games-and-graphics` and `#gui-and-ui` channels in
the [Rust Community Discord]. I go by `lone_scientist#9554` there.
## Sponsors
The development of Iced is sponsored by the [Cryptowatch] team at [Kraken.com]
[documentation]: https://docs.rs/iced/

View file

@ -27,10 +27,6 @@ You can run the native version with `cargo run`:
cargo run --package tour
```
The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)!
[the usage instructions of `iced_web`]: https://github.com/iced-rs/iced_web#usage
## [Todos](todos)
A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them.
@ -46,7 +42,6 @@ You can run the native version with `cargo run`:
```
cargo run --package todos
```
We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_!
[TodoMVC]: http://todomvc.com/

View file

@ -7,7 +7,6 @@ use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size};
use futures::task::SpawnExt;
use winit::{
dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
@ -91,7 +90,9 @@ pub fn main() {
(
surface
.get_preferred_format(&adapter)
.get_supported_formats(&adapter)
.first()
.copied()
.expect("Get preferred format"),
adapter
.request_device(
@ -114,15 +115,14 @@ pub fn main() {
format,
width: physical_size.width,
height: physical_size.height,
present_mode: wgpu::PresentMode::Mailbox,
present_mode: wgpu::PresentMode::AutoVsync,
},
);
let mut resized = false;
// Initialize staging belt and local pool
// Initialize staging belt
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
let mut local_pool = futures::executor::LocalPool::new();
// Initialize scene and GUI controls
let scene = Scene::new(&mut device, format);
@ -208,7 +208,7 @@ pub fn main() {
format: format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
present_mode: wgpu::PresentMode::AutoVsync,
},
);
@ -263,12 +263,8 @@ pub fn main() {
);
// And recall staging buffers
local_pool
.spawner()
.spawn(staging_belt.recall())
.expect("Recall staging buffers");
staging_belt.recall();
local_pool.run_until_stalled();
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {

View file

@ -23,7 +23,7 @@ impl Scene {
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[wgpu::RenderPassColorAttachment {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
@ -39,7 +39,7 @@ impl Scene {
}),
store: true,
},
}],
})],
depth_stencil_attachment: None,
})
}
@ -55,8 +55,8 @@ fn build_pipeline(
texture_format: wgpu::TextureFormat,
) -> wgpu::RenderPipeline {
let (vs_module, fs_module) = (
device.create_shader_module(&wgpu::include_wgsl!("shader/vert.wgsl")),
device.create_shader_module(&wgpu::include_wgsl!("shader/frag.wgsl")),
device.create_shader_module(wgpu::include_wgsl!("shader/vert.wgsl")),
device.create_shader_module(wgpu::include_wgsl!("shader/frag.wgsl")),
);
let pipeline_layout =
@ -78,14 +78,14 @@ fn build_pipeline(
fragment: Some(wgpu::FragmentState {
module: &fs_module,
entry_point: "main",
targets: &[wgpu::ColorTargetState {
targets: &[Some(wgpu::ColorTargetState {
format: texture_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
}),
write_mask: wgpu::ColorWrites::ALL,
}],
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,

View file

@ -1,4 +1,4 @@
[[stage(fragment)]]
fn main() -> [[location(0)]] vec4<f32> {
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

View file

@ -1,5 +1,5 @@
[[stage(vertex)]]
fn main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4<f32> {
@vertex
fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
let x = f32(1 - i32(in_vertex_index)) * 0.5;
let y = f32(1 - i32(in_vertex_index & 1u) * 2) * 0.5;
return vec4<f32>(x, y, 0.0, 1.0);

View file

@ -73,6 +73,10 @@ impl Application for SolarSystem {
.height(Length::Fill)
.into()
}
fn theme(&self) -> Theme {
Theme::Dark
}
}
#[derive(Debug)]
@ -142,16 +146,12 @@ impl<Message> canvas::Program<Message> for State {
use std::f32::consts::PI;
let background = self.space_cache.draw(bounds.size(), |frame| {
let space = Path::rectangle(Point::new(0.0, 0.0), frame.size());
let stars = Path::new(|path| {
for (p, size) in &self.stars {
path.rectangle(*p, Size::new(*size, *size));
}
});
frame.fill(&space, Color::BLACK);
frame.translate(frame.center() - Point::ORIGIN);
frame.fill(&stars, Color::WHITE);
});

View file

@ -23,6 +23,11 @@ You can run the native version with `cargo run`:
cargo run --package tour
```
The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)!
The web version can be run with [`trunk`]:
[the usage instructions of `iced_web`]: https://github.com/iced-rs/iced_web#usage
```
cd examples/tour
trunk serve
```
[`trunk`]: https://trunkrs.dev/

View file

@ -1,6 +1,6 @@
[package]
name = "iced_futures"
version = "0.4.0"
version = "0.4.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "Commands, subscriptions, and runtimes for Iced"

View file

@ -1,6 +1,9 @@
use crate::{BoxFuture, MaybeSend, Subscription};
use futures::{channel::mpsc, sink::Sink};
use futures::{
channel::mpsc,
sink::{Sink, SinkExt},
};
use std::{collections::HashMap, marker::PhantomData};
/// A registry of subscription streams.
@ -64,7 +67,7 @@ where
+ MaybeSend
+ Clone,
{
use futures::{future::FutureExt, stream::StreamExt};
use futures::stream::StreamExt;
let mut futures: Vec<BoxFuture<()>> = Vec::new();
@ -85,19 +88,29 @@ where
continue;
}
let (cancel, cancelled) = futures::channel::oneshot::channel();
let (cancel, mut canceled) = futures::channel::oneshot::channel();
// TODO: Use bus if/when it supports async
let (event_sender, event_receiver) =
futures::channel::mpsc::channel(100);
let stream = recipe.stream(event_receiver.boxed());
let mut receiver = receiver.clone();
let mut stream = recipe.stream(event_receiver.boxed());
let future = futures::future::select(
cancelled,
stream.map(Ok).forward(receiver.clone()),
)
.map(|_| ());
let future = async move {
loop {
let select =
futures::future::select(&mut canceled, stream.next());
match select.await {
futures::future::Either::Left(_)
| futures::future::Either::Right((None, _)) => break,
futures::future::Either::Right((Some(message), _)) => {
let _ = receiver.send(message).await;
}
}
}
};
let _ = self.subscriptions.insert(
id,

View file

@ -1,6 +1,6 @@
[package]
name = "iced_graphics"
version = "0.3.0"
version = "0.3.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced"

View file

@ -6,7 +6,14 @@ use crate::{Color, Font, Point};
pub struct Text {
/// The contents of the text
pub content: String,
/// The position where to begin drawing the text (top-left corner coordinates)
/// The position of the text relative to the alignment properties.
/// By default, this position will be relative to the top-left corner coordinate meaning that
/// if the horizontal and vertical alignments are unchanged, this property will tell where the
/// top-left corner of the text should be placed.
/// By changing the horizontal_alignment and vertical_alignment properties, you are are able to
/// change what part of text is placed at this positions.
/// For example, when the horizontal_alignment and vertical_alignment are set to Center, the
/// center of the text will be placed at the given position NOT the top-left coordinate.
pub position: Point,
/// The color of the text
pub color: Color,

View file

@ -111,7 +111,8 @@ where
B: Backend,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<P::State>()
struct Tag<T>(T);
tree::Tag::of::<Tag<P::State>>()
}
fn state(&self) -> tree::State {

View file

@ -1,6 +1,6 @@
[package]
name = "iced_lazy"
version = "0.1.0"
version = "0.1.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "Lazy widgets for Iced"

View file

@ -419,9 +419,7 @@ where
Some(
CacheBuilder {
element: state.view(),
overlay_builder: |element| {
element.overlay(layout, renderer)
},
overlay_builder: |_| None,
}
.build(),
)

View file

@ -70,8 +70,6 @@ where
})
}
struct Tag<T>(T);
struct Instance<'a, Message, Renderer, Event, S> {
state: RefCell<Option<State<'a, Message, Renderer, Event, S>>>,
}
@ -132,6 +130,7 @@ where
Renderer: iced_native::Renderer,
{
fn tag(&self) -> tree::Tag {
struct Tag<T>(T);
tree::Tag::of::<Tag<S>>()
}
@ -467,16 +466,7 @@ where
instance_ref_builder: |instance| instance.state.borrow(),
tree: overlay.tree,
types: PhantomData,
overlay_builder: |instance, tree| {
instance
.as_ref()
.unwrap()
.borrow_element()
.as_ref()
.unwrap()
.as_widget()
.overlay(&mut tree.children[0], layout, renderer)
},
overlay_builder: |_, _| None,
}
.build(),
);

View file

@ -1,6 +1,6 @@
[package]
name = "iced_native"
version = "0.5.0"
version = "0.5.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A renderer-agnostic library for native GUIs"

View file

@ -31,6 +31,11 @@ impl<'a, Message> Shell<'a, Message> {
}
}
/// Returns whether the current layout is invalid or not.
pub fn is_layout_invalid(&self) -> bool {
self.is_layout_invalid
}
/// Publish the given `Message` for an application to process it.
pub fn publish(&mut self, message: Message) {
self.messages.push(message);

View file

@ -179,18 +179,21 @@ where
clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
) -> (State, Vec<event::Status>) {
use std::mem::ManuallyDrop;
let mut state = State::Updated;
let mut manual_overlay = ManuallyDrop::new(
self.root.overlay(Layout::new(&self.base), renderer),
);
let (base_cursor, overlay_statuses) = if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base), renderer)
{
let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
let bounds = self.bounds;
let mut layout = overlay.layout(renderer, bounds);
let event_statuses = events
.iter()
.cloned()
.map(|event| {
let mut overlay = manual_overlay.as_mut().unwrap();
let mut layout = overlay.layout(renderer, bounds);
let mut event_statuses = Vec::new();
for event in events.iter().cloned() {
let mut shell = Shell::new(messages);
let event_status = overlay.on_event(
@ -202,17 +205,35 @@ where
&mut shell,
);
event_statuses.push(event_status);
if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay);
self.base = renderer.layout(
&self.root,
&layout::Limits::new(Size::ZERO, self.bounds),
);
manual_overlay = ManuallyDrop::new(
self.root.overlay(Layout::new(&self.base), renderer),
);
if manual_overlay.is_none() {
break;
}
overlay = manual_overlay.as_mut().unwrap();
shell.revalidate_layout(|| {
layout = overlay.layout(renderer, bounds);
});
}
if shell.are_widgets_invalid() {
state = State::Outdated;
}
event_status
})
.collect();
}
let base_cursor = if layout.bounds().contains(cursor_position) {
// TODO: Type-safe cursor availability
@ -228,11 +249,17 @@ where
(cursor_position, vec![event::Status::Ignored; events.len()])
};
let _ = ManuallyDrop::into_inner(manual_overlay);
let event_statuses = events
.iter()
.cloned()
.zip(overlay_statuses.into_iter())
.map(|(event, overlay_status)| {
if matches!(overlay_status, event::Status::Captured) {
return overlay_status;
}
let mut shell = Shell::new(messages);
let event_status = self.root.widget.on_event(

View file

@ -114,20 +114,17 @@ where
let mut children = padded.children();
let title_layout = children.next().unwrap();
self.content.draw(
renderer,
theme,
&inherited_style,
title_layout,
cursor_position,
viewport,
);
let mut show_title = true;
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
if show_controls || self.always_show_controls {
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
controls.draw(
renderer,
theme,
@ -138,6 +135,17 @@ where
);
}
}
if show_title {
self.content.draw(
renderer,
theme,
&inherited_style,
title_layout,
cursor_position,
viewport,
);
}
}
/// Returns whether the mouse cursor is over the pick area of the
@ -225,9 +233,15 @@ where
let mut children = padded.children();
let title_layout = children.next().unwrap();
let mut show_title = true;
let control_status = if let Some(controls) = &mut self.controls {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
controls.on_event(
event.clone(),
@ -241,14 +255,18 @@ where
event::Status::Ignored
};
let title_status = self.content.on_event(
let title_status = if show_title {
self.content.on_event(
event,
title_layout,
cursor_position,
renderer,
clipboard,
shell,
);
)
} else {
event::Status::Ignored
};
control_status.merge(title_status)
}
@ -275,15 +293,20 @@ where
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
controls
.mouse_interaction(
let controls_interaction = controls.mouse_interaction(
controls_layout,
cursor_position,
viewport,
renderer,
)
.max(title_interaction)
);
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
controls_interaction
} else {
controls_interaction.max(title_interaction)
}
} else {
title_interaction
}

View file

@ -1,6 +1,6 @@
[package]
name = "iced_pure"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
description = "Pure widgets for Iced"
license = "MIT"

View file

@ -141,19 +141,15 @@ where
let mut children = padded.children();
let title_layout = children.next().unwrap();
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
&inherited_style,
title_layout,
cursor_position,
viewport,
);
let mut show_title = true;
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
if show_controls || self.always_show_controls {
controls.as_widget().draw(
@ -167,6 +163,18 @@ where
);
}
}
if show_title {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
&inherited_style,
title_layout,
cursor_position,
viewport,
);
}
}
/// Returns whether the mouse cursor is over the pick area of the
@ -258,9 +266,15 @@ where
let mut children = padded.children();
let title_layout = children.next().unwrap();
let mut show_title = true;
let control_status = if let Some(controls) = &mut self.controls {
let controls_layout = children.next().unwrap();
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
show_title = false;
}
controls.as_widget_mut().on_event(
&mut tree.children[1],
@ -275,7 +289,8 @@ where
event::Status::Ignored
};
let title_status = self.content.as_widget_mut().on_event(
let title_status = if show_title {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
title_layout,
@ -283,7 +298,10 @@ where
renderer,
clipboard,
shell,
);
)
} else {
event::Status::Ignored
};
control_status.merge(title_status)
}
@ -312,17 +330,21 @@ where
if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
controls
.as_widget()
.mouse_interaction(
let controls_interaction = controls.as_widget().mouse_interaction(
&tree.children[1],
controls_layout,
cursor_position,
viewport,
renderer,
)
.max(title_interaction)
);
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
controls_interaction
} else {
controls_interaction.max(title_interaction)
}
} else {
title_interaction
}

View file

@ -126,6 +126,34 @@ where
self.style = style.into();
self
}
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`text_input::Value`] if provided.
///
/// [`Renderer`]: text::Renderer
pub fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
cursor_position: Point,
value: Option<&text_input::Value>,
) {
text_input::draw(
renderer,
theme,
layout,
cursor_position,
tree.state.downcast_ref::<text_input::State>(),
value.unwrap_or(&self.value),
&self.placeholder,
self.size,
&self.font,
self.is_secure,
self.style,
)
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>

View file

@ -1,6 +1,6 @@
[package]
name = "iced_wgpu"
version = "0.5.0"
version = "0.5.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A wgpu renderer for Iced"
@ -28,8 +28,8 @@ spirv = ["wgpu/spirv"]
webgl = ["wgpu/webgl"]
[dependencies]
wgpu = "0.12"
wgpu_glyph = "0.16"
wgpu = "0.13"
wgpu_glyph = "0.17"
glyph_brush = "0.7"
raw-window-handle = "0.4"
log = "0.4"
@ -39,7 +39,7 @@ kamadak-exif = "0.5"
bitflags = "1.2"
[dependencies.bytemuck]
version = "1.4"
version = "1.9"
features = ["derive"]
[dependencies.iced_native]

View file

@ -136,7 +136,7 @@ impl Pipeline {
});
let shader =
device.create_shader_module(&wgpu::ShaderModuleDescriptor {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu::image::shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("shader/image.wgsl"),
@ -176,7 +176,7 @@ impl Pipeline {
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
@ -191,7 +191,7 @@ impl Pipeline {
},
}),
write_mask: wgpu::ColorWrites::ALL,
}],
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -406,14 +406,16 @@ impl Pipeline {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::image render pass"),
color_attachments: &[wgpu::RenderPassColorAttachment {
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
}],
},
)],
depth_stencil_attachment: None,
});

View file

@ -59,7 +59,7 @@ impl Pipeline {
});
let shader =
device.create_shader_module(&wgpu::ShaderModuleDescriptor {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu::quad::shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("shader/quad.wgsl"),
@ -100,7 +100,7 @@ impl Pipeline {
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
@ -115,7 +115,7 @@ impl Pipeline {
},
}),
write_mask: wgpu::ColorWrites::ALL,
}],
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -211,14 +211,16 @@ impl Pipeline {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::quad render pass"),
color_attachments: &[wgpu::RenderPassColorAttachment {
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
}],
},
)],
depth_stencil_attachment: None,
});

View file

@ -63,7 +63,7 @@ impl Settings {
impl Default for Settings {
fn default() -> Settings {
Settings {
present_mode: wgpu::PresentMode::Mailbox,
present_mode: wgpu::PresentMode::AutoVsync,
internal_backend: wgpu::Backends::all(),
default_font: None,
default_text_size: 20,

View file

@ -16,19 +16,19 @@ var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(1.0, 1.0)
);
[[group(0), binding(0)]] var u_sampler: sampler;
[[group(1), binding(0)]] var u_texture: texture_2d<f32>;
@group(0) @binding(0) var u_sampler: sampler;
@group(1) @binding(0) var u_texture: texture_2d<f32>;
struct VertexInput {
[[builtin(vertex_index)]] vertex_index: u32;
};
@builtin(vertex_index) vertex_index: u32,
}
struct VertexOutput {
[[builtin(position)]] position: vec4<f32>;
[[location(0)]] uv: vec2<f32>;
};
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
}
[[stage(vertex)]]
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.uv = uvs[input.vertex_index];
@ -37,7 +37,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
return out;
}
[[stage(fragment)]]
fn fs_main(input: VertexOutput) -> [[location(0)]] vec4<f32> {
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(u_texture, u_sampler, input.uv);
}

View file

@ -1,27 +1,27 @@
struct Globals {
transform: mat4x4<f32>;
};
transform: mat4x4<f32>,
}
[[group(0), binding(0)]] var<uniform> globals: Globals;
[[group(0), binding(1)]] var u_sampler: sampler;
[[group(1), binding(0)]] var u_texture: texture_2d_array<f32>;
@group(0) @binding(0) var<uniform> globals: Globals;
@group(0) @binding(1) var u_sampler: sampler;
@group(1) @binding(0) var u_texture: texture_2d_array<f32>;
struct VertexInput {
[[location(0)]] v_pos: vec2<f32>;
[[location(1)]] pos: vec2<f32>;
[[location(2)]] scale: vec2<f32>;
[[location(3)]] atlas_pos: vec2<f32>;
[[location(4)]] atlas_scale: vec2<f32>;
[[location(5)]] layer: i32;
};
@location(0) v_pos: vec2<f32>,
@location(1) pos: vec2<f32>,
@location(2) scale: vec2<f32>,
@location(3) atlas_pos: vec2<f32>,
@location(4) atlas_scale: vec2<f32>,
@location(5) layer: i32,
}
struct VertexOutput {
[[builtin(position)]] position: vec4<f32>;
[[location(0)]] uv: vec2<f32>;
[[location(1)]] layer: f32; // this should be an i32, but naga currently reads that as requiring interpolation.
};
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation.
}
[[stage(vertex)]]
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
@ -40,7 +40,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
return out;
}
[[stage(fragment)]]
fn fs_main(input: VertexOutput) -> [[location(0)]] vec4<f32> {
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(u_texture, u_sampler, input.uv, i32(input.layer));
}

View file

@ -1,31 +1,31 @@
struct Globals {
transform: mat4x4<f32>;
scale: f32;
};
transform: mat4x4<f32>,
scale: f32,
}
[[group(0), binding(0)]] var<uniform> globals: Globals;
@group(0) @binding(0) var<uniform> globals: Globals;
struct VertexInput {
[[location(0)]] v_pos: vec2<f32>;
[[location(1)]] pos: vec2<f32>;
[[location(2)]] scale: vec2<f32>;
[[location(3)]] color: vec4<f32>;
[[location(4)]] border_color: vec4<f32>;
[[location(5)]] border_radius: f32;
[[location(6)]] border_width: f32;
};
@location(0) v_pos: vec2<f32>,
@location(1) pos: vec2<f32>,
@location(2) scale: vec2<f32>,
@location(3) color: vec4<f32>,
@location(4) border_color: vec4<f32>,
@location(5) border_radius: f32,
@location(6) border_width: f32,
}
struct VertexOutput {
[[builtin(position)]] position: vec4<f32>;
[[location(0)]] color: vec4<f32>;
[[location(1)]] border_color: vec4<f32>;
[[location(2)]] pos: vec2<f32>;
[[location(3)]] scale: vec2<f32>;
[[location(4)]] border_radius: f32;
[[location(5)]] border_width: f32;
};
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) border_color: vec4<f32>,
@location(2) pos: vec2<f32>,
@location(3) scale: vec2<f32>,
@location(4) border_radius: f32,
@location(5) border_width: f32,
}
[[stage(vertex)]]
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
@ -77,10 +77,10 @@ fn distance_alg(
}
[[stage(fragment)]]
@fragment
fn fs_main(
input: VertexOutput
) -> [[location(0)]] vec4<f32> {
) -> @location(0) vec4<f32> {
var mixed_color: vec4<f32> = input.color;
if (input.border_width > 0.0) {
@ -96,7 +96,7 @@ fn fs_main(
internal_border
);
var border_mix: f32 = smoothStep(
var border_mix: f32 = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
@ -112,7 +112,7 @@ fn fs_main(
input.border_radius
);
var radius_alpha: f32 = 1.0 - smoothStep(
var radius_alpha: f32 = 1.0 - smoothstep(
max(input.border_radius - 0.5, 0.0),
input.border_radius + 0.5,
dist);

View file

@ -1,20 +1,20 @@
struct Globals {
transform: mat4x4<f32>;
};
transform: mat4x4<f32>,
}
[[group(0), binding(0)]] var<uniform> globals: Globals;
@group(0) @binding(0) var<uniform> globals: Globals;
struct VertexInput {
[[location(0)]] position: vec2<f32>;
[[location(1)]] color: vec4<f32>;
};
@location(0) position: vec2<f32>,
@location(1) color: vec4<f32>,
}
struct VertexOutput {
[[builtin(position)]] position: vec4<f32>;
[[location(0)]] color: vec4<f32>;
};
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
}
[[stage(vertex)]]
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
@ -24,7 +24,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
return out;
}
[[stage(fragment)]]
fn fs_main(input: VertexOutput) -> [[location(0)]] vec4<f32> {
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
return input.color;
}

View file

@ -132,7 +132,7 @@ impl Pipeline {
});
let shader =
device.create_shader_module(&wgpu::ShaderModuleDescriptor {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu::triangle::shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("shader/triangle.wgsl"),
@ -160,22 +160,11 @@ impl Pipeline {
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
}],
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -361,11 +350,13 @@ impl Pipeline {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::triangle render pass"),
color_attachments: &[wgpu::RenderPassColorAttachment {
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: attachment,
resolve_target,
ops: wgpu::Operations { load, store: true },
}],
},
)],
depth_stencil_attachment: None,
});

View file

@ -74,7 +74,7 @@ impl Blit {
});
let shader =
device.create_shader_module(&wgpu::ShaderModuleDescriptor {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu::triangle::blit_shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("../shader/blit.wgsl"),
@ -93,22 +93,13 @@ impl Blit {
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),
blend: Some(
wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
),
write_mask: wgpu::ColorWrites::ALL,
}],
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -178,14 +169,14 @@ impl Blit {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::triangle::msaa render pass"),
color_attachments: &[wgpu::RenderPassColorAttachment {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: true,
},
}],
})],
depth_stencil_attachment: None,
});

View file

@ -1,6 +1,7 @@
use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
use futures::task::SpawnExt;
use futures::stream::{self, StreamExt};
use iced_graphics::compositor;
use iced_native::futures;
use raw_window_handle::HasRawWindowHandle;
@ -16,7 +17,6 @@ pub struct Compositor<Theme> {
device: wgpu::Device,
queue: wgpu::Queue,
staging_belt: wgpu::util::StagingBelt,
local_pool: futures::executor::LocalPool,
format: wgpu::TextureFormat,
theme: PhantomData<Theme>,
}
@ -62,38 +62,43 @@ impl<Theme> Compositor<Theme> {
log::info!("Selected: {:#?}", adapter.get_info());
let format = compatible_surface
.as_ref()
.and_then(|surface| surface.get_preferred_format(&adapter))?;
let format = compatible_surface.as_ref().and_then(|surface| {
surface.get_supported_formats(&adapter).first().copied()
})?;
log::info!("Selected format: {:?}", format);
#[cfg(target_arch = "wasm32")]
let limits = wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits());
let limits = [wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits())];
#[cfg(not(target_arch = "wasm32"))]
let limits = wgpu::Limits::downlevel_defaults();
let limits =
[wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
let (device, queue) = adapter
.request_device(
let limits = limits.into_iter().map(|limits| wgpu::Limits {
max_bind_groups: 2,
..limits
});
let (device, queue) = stream::iter(limits)
.filter_map(|limits| async {
adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some(
"iced_wgpu::window::compositor device descriptor",
),
features: wgpu::Features::empty(),
limits: wgpu::Limits {
max_bind_groups: 2,
..limits
},
limits,
},
None,
)
.await
.ok()?;
).await.ok()
})
.boxed()
.next()
.await?;
let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE);
let local_pool = futures::executor::LocalPool::new();
Some(Compositor {
instance,
@ -102,7 +107,6 @@ impl<Theme> Compositor<Theme> {
device,
queue,
staging_belt,
local_pool,
format,
theme: PhantomData,
})
@ -196,7 +200,8 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
label: Some(
"iced_wgpu::window::Compositor render pass",
),
color_attachments: &[wgpu::RenderPassColorAttachment {
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
@ -213,7 +218,8 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
}),
store: true,
},
}],
},
)],
depth_stencil_attachment: None,
});
@ -231,16 +237,11 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
// Submit work
self.staging_belt.finish();
self.queue.submit(Some(encoder.finish()));
let _submission = self.queue.submit(Some(encoder.finish()));
frame.present();
// Recall staging buffers
self.local_pool
.spawner()
.spawn(self.staging_belt.recall())
.expect("Recall staging belt");
self.local_pool.run_until_stalled();
self.staging_belt.recall();
Ok(())
}