Merge pull request #1748 from iced-rs/feature/software-renderer

Software renderer, runtime renderer fallback, and core consolidation
This commit is contained in:
Héctor Ramón 2023-03-09 19:05:38 +01:00 committed by GitHub
commit caf2836b1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
261 changed files with 5933 additions and 3961 deletions

View file

@ -37,5 +37,5 @@ jobs:
run: cargo build --package tour --target wasm32-unknown-unknown run: cargo build --package tour --target wasm32-unknown-unknown
- name: Check compilation of `todos` example - name: Check compilation of `todos` example
run: cargo build --package todos --target wasm32-unknown-unknown run: cargo build --package todos --target wasm32-unknown-unknown
- name: Check compilation of `integration_wgpu` example - name: Check compilation of `integration` example
run: cargo build --package integration --target wasm32-unknown-unknown run: cargo build --package integration --target wasm32-unknown-unknown

View file

@ -12,14 +12,21 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"] categories = ["gui"]
[features] [features]
default = ["wgpu", "tiny-skia"]
# Enable the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu"]
# Enable the `tiny-skia` software renderer backend
tiny-skia = ["iced_renderer/tiny-skia"]
# Enables the `Image` widget # Enables the `Image` widget
image = ["iced_wgpu/image", "image_rs"] image = ["iced_widget/image", "image_rs"]
# Enables the `Svg` widget # Enables the `Svg` widget
svg = ["iced_wgpu/svg"] svg = ["iced_widget/svg"]
# Enables the `Canvas` widget # Enables the `Canvas` widget
canvas = ["iced_graphics/canvas"] canvas = ["iced_widget/canvas"]
# Enables the `QRCode` widget # Enables the `QRCode` widget
qr_code = ["iced_graphics/qr_code"] qr_code = ["iced_widget/qr_code"]
# Enables lazy widgets
lazy = ["iced_widget/lazy"]
# Enables a debug view in native platforms (press F12) # Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"] debug = ["iced_winit/debug"]
# Enables `tokio` as the `executor::Default` on native platforms # Enables `tokio` as the `executor::Default` on native platforms
@ -32,11 +39,8 @@ smol = ["iced_futures/smol"]
palette = ["iced_core/palette"] palette = ["iced_core/palette"]
# Enables querying system information # Enables querying system information
system = ["iced_winit/system"] system = ["iced_winit/system"]
# Enables chrome traces # Enables the advanced module
chrome-trace = [ advanced = []
"iced_winit/chrome-trace",
"iced_wgpu/tracing",
]
[badges] [badges]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
@ -46,10 +50,12 @@ members = [
"core", "core",
"futures", "futures",
"graphics", "graphics",
"lazy", "runtime",
"native", "renderer",
"style", "style",
"tiny_skia",
"wgpu", "wgpu",
"widget",
"winit", "winit",
"examples/*", "examples/*",
] ]
@ -57,22 +63,16 @@ members = [
[dependencies] [dependencies]
iced_core = { version = "0.8", path = "core" } iced_core = { version = "0.8", path = "core" }
iced_futures = { version = "0.6", path = "futures" } iced_futures = { version = "0.6", path = "futures" }
iced_native = { version = "0.9", path = "native" } iced_renderer = { version = "0.1", path = "renderer" }
iced_graphics = { version = "0.7", path = "graphics" } iced_widget = { version = "0.1", path = "widget" }
iced_winit = { version = "0.8", path = "winit", features = ["application"] } iced_winit = { version = "0.8", path = "winit", features = ["application"] }
thiserror = "1.0" thiserror = "1"
[dependencies.image_rs] [dependencies.image_rs]
version = "0.24" version = "0.24"
package = "image" package = "image"
optional = true optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
iced_wgpu = { version = "0.9", path = "wgpu" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"] }
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
features = ["image", "svg", "canvas", "qr_code"] features = ["image", "svg", "canvas", "qr_code"]

View file

@ -1,6 +1,6 @@
[package] [package]
name = "iced_core" name = "iced_core"
version = "0.8.0" version = "0.8.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021" edition = "2021"
description = "The essential concepts of Iced" description = "The essential concepts of Iced"
@ -9,6 +9,8 @@ repository = "https://github.com/iced-rs/iced"
[dependencies] [dependencies]
bitflags = "1.2" bitflags = "1.2"
thiserror = "1"
twox-hash = { version = "1.5", default-features = false }
[dependencies.palette] [dependencies.palette]
version = "0.6" version = "0.6"

View file

@ -11,9 +11,6 @@ pub enum Alignment {
/// Align at the end of the axis. /// Align at the end of the axis.
End, End,
/// Fill the entire axis.
Fill,
} }
impl From<Horizontal> for Alignment { impl From<Horizontal> for Alignment {

23
core/src/clipboard.rs Normal file
View file

@ -0,0 +1,23 @@
//! Access the clipboard.
/// A buffer for short-term storage and transfer within and between
/// applications.
pub trait Clipboard {
/// Reads the current content of the [`Clipboard`] as text.
fn read(&self) -> Option<String>;
/// Writes the given text contents to the [`Clipboard`].
fn write(&mut self, contents: String);
}
/// A null implementation of the [`Clipboard`] trait.
#[derive(Debug, Clone, Copy)]
pub struct Null;
impl Clipboard for Null {
fn read(&self) -> Option<String> {
None
}
fn write(&mut self, _contents: String) {}
}

View file

@ -90,41 +90,65 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// We compose the previous __messages__ with the index of the counter /// We compose the previous __messages__ with the index of the counter
/// producing them. Let's implement our __view logic__ now: /// producing them. Let's implement our __view logic__ now:
/// ///
/// ``` /// ```no_run
/// # mod counter { /// # mod counter {
/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
/// #
/// # #[derive(Debug, Clone, Copy)] /// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {} /// # pub enum Message {}
/// # pub struct Counter; /// # pub struct Counter;
/// # /// #
/// # impl Counter { /// # impl Counter {
/// # pub fn view(&mut self) -> Text { /// # pub fn view(
/// # Text::new("") /// # &self,
/// # ) -> iced_core::Element<Message, iced_core::renderer::Null> {
/// # unimplemented!()
/// # } /// # }
/// # } /// # }
/// # } /// # }
/// # /// #
/// # mod iced_wgpu { /// # mod iced {
/// # pub use iced_native::renderer::Null as Renderer; /// # pub use iced_core::renderer::Null as Renderer;
/// # pub use iced_core::Element;
/// #
/// # pub mod widget {
/// # pub struct Row<Message> {
/// # _t: std::marker::PhantomData<Message>,
/// # }
/// #
/// # impl<Message> Row<Message> {
/// # pub fn new() -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn spacing(mut self, _: u32) -> Self {
/// # unimplemented!()
/// # }
/// #
/// # pub fn push(
/// # mut self,
/// # _: iced_core::Element<Message, iced_core::renderer::Null>,
/// # ) -> Self {
/// # unimplemented!()
/// # }
/// # }
/// # }
/// # } /// # }
/// # /// #
/// # use counter::Counter; /// use counter::Counter;
/// # ///
/// # struct ManyCounters { /// use iced::widget::Row;
/// # counters: Vec<Counter>, /// use iced::{Element, Renderer};
/// # } ///
/// # /// struct ManyCounters {
/// # #[derive(Debug, Clone, Copy)] /// counters: Vec<Counter>,
/// # pub enum Message { /// }
/// # Counter(usize, counter::Message) ///
/// # } /// #[derive(Debug, Clone, Copy)]
/// use iced_native::Element; /// pub enum Message {
/// use iced_native::widget::Row; /// Counter(usize, counter::Message),
/// use iced_wgpu::Renderer; /// }
/// ///
/// impl ManyCounters { /// impl ManyCounters {
/// pub fn view(&mut self) -> Row<Message, Renderer> { /// pub fn view(&mut self) -> Row<Message> {
/// // We can quickly populate a `Row` by folding over our counters /// // We can quickly populate a `Row` by folding over our counters
/// self.counters.iter_mut().enumerate().fold( /// self.counters.iter_mut().enumerate().fold(
/// Row::new().spacing(20), /// Row::new().spacing(20),
@ -137,9 +161,10 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
/// // Here we turn our `Element<counter::Message>` into /// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the /// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`. /// // message of the `element`.
/// element.map(move |message| Message::Counter(index, message)) /// element
/// .map(move |message| Message::Counter(index, message)),
/// ) /// )
/// } /// },
/// ) /// )
/// } /// }
/// } /// }

View file

@ -62,7 +62,7 @@ impl Status {
/// `Captured` takes precedence over `Ignored`: /// `Captured` takes precedence over `Ignored`:
/// ///
/// ``` /// ```
/// use iced_native::event::Status; /// use iced_core::event::Status;
/// ///
/// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored); /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored);
/// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured);

View file

@ -81,32 +81,6 @@ where
let mut nodes: Vec<Node> = Vec::with_capacity(items.len()); let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
nodes.resize(items.len(), Node::default()); nodes.resize(items.len(), Node::default());
if align_items == Alignment::Fill {
let mut fill_cross = axis.cross(limits.min());
items.iter().for_each(|child| {
let cross_fill_factor = match axis {
Axis::Horizontal => child.as_widget().height(),
Axis::Vertical => child.as_widget().width(),
}
.fill_factor();
if cross_fill_factor == 0 {
let (max_width, max_height) = axis.pack(available, max_cross);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout = child.as_widget().layout(renderer, &child_limits);
let size = layout.size();
fill_cross = fill_cross.max(axis.cross(size));
}
});
cross = fill_cross;
}
for (i, child) in items.iter().enumerate() { for (i, child) in items.iter().enumerate() {
let fill_factor = match axis { let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(), Axis::Horizontal => child.as_widget().width(),
@ -115,31 +89,16 @@ where
.fill_factor(); .fill_factor();
if fill_factor == 0 { if fill_factor == 0 {
let (min_width, min_height) = if align_items == Alignment::Fill { let (max_width, max_height) = axis.pack(available, max_cross);
axis.pack(0.0, cross)
} else {
axis.pack(0.0, 0.0)
};
let (max_width, max_height) = if align_items == Alignment::Fill { let child_limits =
axis.pack(available, cross) Limits::new(Size::ZERO, Size::new(max_width, max_height));
} else {
axis.pack(available, max_cross)
};
let child_limits = Limits::new(
Size::new(min_width, min_height),
Size::new(max_width, max_height),
);
let layout = child.as_widget().layout(renderer, &child_limits); let layout = child.as_widget().layout(renderer, &child_limits);
let size = layout.size(); let size = layout.size();
available -= axis.main(size); available -= axis.main(size);
cross = cross.max(axis.cross(size));
if align_items != Alignment::Fill {
cross = cross.max(axis.cross(size));
}
nodes[i] = layout; nodes[i] = layout;
} else { } else {
@ -164,17 +123,10 @@ where
max_main max_main
}; };
let (min_width, min_height) = if align_items == Alignment::Fill { let (min_width, min_height) =
axis.pack(min_main, cross) axis.pack(min_main, axis.cross(limits.min()));
} else {
axis.pack(min_main, axis.cross(limits.min()))
};
let (max_width, max_height) = if align_items == Alignment::Fill { let (max_width, max_height) = axis.pack(max_main, max_cross);
axis.pack(max_main, cross)
} else {
axis.pack(max_main, max_cross)
};
let child_limits = Limits::new( let child_limits = Limits::new(
Size::new(min_width, min_height), Size::new(min_width, min_height),
@ -182,10 +134,7 @@ where
); );
let layout = child.as_widget().layout(renderer, &child_limits); let layout = child.as_widget().layout(renderer, &child_limits);
cross = cross.max(axis.cross(layout.size()));
if align_items != Alignment::Fill {
cross = cross.max(axis.cross(layout.size()));
}
nodes[i] = layout; nodes[i] = layout;
} }

View file

@ -56,9 +56,6 @@ impl Node {
Alignment::End => { Alignment::End => {
self.bounds.x += space.width - self.bounds.width; self.bounds.x += space.width - self.bounds.width;
} }
Alignment::Fill => {
self.bounds.width = space.width;
}
} }
match vertical_alignment { match vertical_alignment {
@ -69,9 +66,6 @@ impl Node {
Alignment::End => { Alignment::End => {
self.bounds.y += space.height - self.bounds.height; self.bounds.y += space.height - self.bounds.height;
} }
Alignment::Fill => {
self.bounds.height = space.height;
}
} }
} }

View file

@ -25,31 +25,57 @@
#![forbid(unsafe_code, rust_2018_idioms)] #![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![allow(clippy::inherent_to_string, clippy::type_complexity)]
pub mod alignment; pub mod alignment;
pub mod clipboard;
pub mod event;
pub mod font; pub mod font;
pub mod gradient;
pub mod image;
pub mod keyboard; pub mod keyboard;
pub mod layout;
pub mod mouse; pub mod mouse;
pub mod overlay;
pub mod renderer;
pub mod svg;
pub mod text;
pub mod time; pub mod time;
pub mod touch;
pub mod widget;
pub mod window;
mod background; mod background;
mod color; mod color;
mod content_fit; mod content_fit;
mod element;
mod hasher;
mod length; mod length;
mod padding; mod padding;
mod pixels; mod pixels;
mod point; mod point;
mod rectangle; mod rectangle;
mod shell;
mod size; mod size;
mod vector; mod vector;
pub use alignment::Alignment; pub use alignment::Alignment;
pub use background::Background; pub use background::Background;
pub use clipboard::Clipboard;
pub use color::Color; pub use color::Color;
pub use content_fit::ContentFit; pub use content_fit::ContentFit;
pub use element::Element;
pub use event::Event;
pub use font::Font; pub use font::Font;
pub use gradient::Gradient;
pub use hasher::Hasher;
pub use layout::Layout;
pub use length::Length; pub use length::Length;
pub use overlay::Overlay;
pub use padding::Padding; pub use padding::Padding;
pub use pixels::Pixels; pub use pixels::Pixels;
pub use point::Point; pub use point::Point;
pub use rectangle::Rectangle; pub use rectangle::Rectangle;
pub use renderer::Renderer;
pub use shell::Shell;
pub use size::Size; pub use size::Size;
pub use text::Text;
pub use vector::Vector; pub use vector::Vector;
pub use widget::Widget;

View file

@ -1,8 +1,11 @@
//! Handle mouse events. //! Handle mouse events.
pub mod click;
mod button; mod button;
mod event; mod event;
mod interaction; mod interaction;
pub use button::Button; pub use button::Button;
pub use click::Click;
pub use event::{Event, ScrollDelta}; pub use event::{Event, ScrollDelta};
pub use interaction::Interaction; pub use interaction::Interaction;

View file

@ -2,11 +2,8 @@
mod element; mod element;
mod group; mod group;
pub mod menu;
pub use element::Element; pub use element::Element;
pub use group::Group; pub use group::Group;
pub use menu::Menu;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout;

View file

@ -1,12 +1,10 @@
use iced_core::{Point, Rectangle, Size};
use crate::event; use crate::event;
use crate::layout; use crate::layout;
use crate::mouse; use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer; use crate::renderer;
use crate::widget; use crate::widget;
use crate::{Clipboard, Event, Layout, Overlay, Shell}; use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size};
/// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`]
/// children. /// children.

View file

@ -77,12 +77,14 @@ impl Padding {
/// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`]. /// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`].
pub fn fit(self, inner: Size, outer: Size) -> Self { pub fn fit(self, inner: Size, outer: Size) -> Self {
let available = (outer - inner).max(Size::ZERO); let available = (outer - inner).max(Size::ZERO);
let new_top = self.top.min(available.height);
let new_left = self.left.min(available.width);
Padding { Padding {
top: self.top.min(available.height / 2.0), top: new_top,
right: self.right.min(available.width / 2.0), bottom: self.bottom.min(available.height - new_top),
bottom: self.bottom.min(available.height / 2.0), left: new_left,
left: self.left.min(available.width / 2.0), right: self.right.min(available.width - new_left),
} }
} }
} }

View file

@ -1,6 +1,7 @@
//! Write your own renderer. //! Write your own renderer.
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
mod null; mod null;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub use null::Null; pub use null::Null;

View file

@ -1,6 +1,6 @@
use crate::renderer::{self, Renderer}; use crate::renderer::{self, Renderer};
use crate::text::{self, Text}; use crate::text::{self, Text};
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; use crate::{Background, Font, Point, Rectangle, Size, Vector};
use std::borrow::Cow; use std::borrow::Cow;
@ -18,7 +18,7 @@ impl Null {
} }
impl Renderer for Null { impl Renderer for Null {
type Theme = Theme; type Theme = ();
fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {} fn with_layer(&mut self, _bounds: Rectangle, _f: impl FnOnce(&mut Self)) {}

View file

@ -1,7 +1,7 @@
use crate::{Padding, Vector}; use crate::{Padding, Vector};
/// An amount of space in 2 dimensions. /// An amount of space in 2 dimensions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Size<T = f32> { pub struct Size<T = f32> {
/// The width. /// The width.
pub width: T, pub width: T,

View file

@ -1,98 +1,21 @@
//! Use the built-in widgets or create your own. //! Create custom widgets and operate on them.
//!
//! # Built-in widgets
//! Every built-in drawable widget has its own module with a `Renderer` trait
//! that must be implemented by a [renderer] before being able to use it as
//! a [`Widget`].
//!
//! # Custom widgets
//! If you want to implement a custom widget, you simply need to implement the
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
//! source of inspiration.
//!
//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
pub mod helpers;
pub mod image;
pub mod operation; pub mod operation;
pub mod pane_grid;
pub mod pick_list;
pub mod progress_bar;
pub mod radio;
pub mod row;
pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod space;
pub mod svg;
pub mod text; pub mod text;
pub mod text_input;
pub mod toggler;
pub mod tooltip;
pub mod tree; pub mod tree;
pub mod vertical_slider;
mod action;
mod id; mod id;
#[doc(no_inline)]
pub use button::Button;
#[doc(no_inline)]
pub use checkbox::Checkbox;
#[doc(no_inline)]
pub use column::Column;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
pub use helpers::*;
#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
#[doc(no_inline)]
pub use row::Row;
#[doc(no_inline)]
pub use rule::Rule;
#[doc(no_inline)]
pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;
#[doc(no_inline)]
pub use space::Space;
#[doc(no_inline)]
pub use svg::Svg;
#[doc(no_inline)]
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
pub use toggler::Toggler;
#[doc(no_inline)]
pub use tooltip::Tooltip;
#[doc(no_inline)]
pub use tree::Tree;
#[doc(no_inline)]
pub use vertical_slider::VerticalSlider;
pub use action::Action;
pub use id::Id; pub use id::Id;
pub use operation::Operation; pub use operation::Operation;
pub use text::Text;
pub use tree::Tree;
use crate::event::{self, Event}; use crate::event::{self, Event};
use crate::layout; use crate::layout::{self, Layout};
use crate::mouse; use crate::mouse;
use crate::overlay; use crate::overlay;
use crate::renderer; use crate::renderer;
use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; use crate::{Clipboard, Length, Point, Rectangle, Shell};
/// A component that displays information and allows interaction. /// A component that displays information and allows interaction.
/// ///

View file

@ -24,7 +24,7 @@ impl Id {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Internal { enum Internal {
Unique(usize), Unique(usize),
Custom(borrow::Cow<'static, str>), Custom(borrow::Cow<'static, str>),
} }

View file

@ -0,0 +1,226 @@
//! Query or update internal widget state.
pub mod focusable;
pub mod scrollable;
pub mod text_input;
pub use focusable::Focusable;
pub use scrollable::Scrollable;
pub use text_input::TextInput;
use crate::widget::Id;
use std::any::Any;
use std::fmt;
use std::rc::Rc;
/// A piece of logic that can traverse the widget tree of an application in
/// order to query or update some widget state.
pub trait Operation<T> {
/// Operates on a widget that contains other widgets.
///
/// The `operate_on_children` function can be called to return control to
/// the widget tree and keep traversing it.
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
);
/// Operates on a widget that can be focused.
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
/// Operates on a widget that can be scrolled.
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
/// Operates on a widget that has text input.
fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
/// Operates on a custom widget with some state.
fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {}
/// Finishes the [`Operation`] and returns its [`Outcome`].
fn finish(&self) -> Outcome<T> {
Outcome::None
}
}
/// The result of an [`Operation`].
pub enum Outcome<T> {
/// The [`Operation`] produced no result.
None,
/// The [`Operation`] produced some result.
Some(T),
/// The [`Operation`] needs to be followed by another [`Operation`].
Chain(Box<dyn Operation<T>>),
}
impl<T> fmt::Debug for Outcome<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "Outcome::None"),
Self::Some(output) => write!(f, "Outcome::Some({output:?})"),
Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
}
}
}
/// Maps the output of an [`Operation`] using the given function.
pub fn map<A, B>(
operation: Box<dyn Operation<A>>,
f: impl Fn(A) -> B + 'static,
) -> impl Operation<B>
where
A: 'static,
B: 'static,
{
#[allow(missing_debug_implementations)]
struct Map<A, B> {
operation: Box<dyn Operation<A>>,
f: Rc<dyn Fn(A) -> B>,
}
impl<A, B> Operation<B> for Map<A, B>
where
A: 'static,
B: 'static,
{
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
struct MapRef<'a, A> {
operation: &'a mut dyn Operation<A>,
}
impl<'a, A, B> Operation<B> for MapRef<'a, A> {
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
let Self { operation, .. } = self;
operation.container(id, &mut |operation| {
operate_on_children(&mut MapRef { operation });
});
}
fn scrollable(
&mut self,
state: &mut dyn Scrollable,
id: Option<&Id>,
) {
self.operation.scrollable(state, id);
}
fn focusable(
&mut self,
state: &mut dyn Focusable,
id: Option<&Id>,
) {
self.operation.focusable(state, id);
}
fn text_input(
&mut self,
state: &mut dyn TextInput,
id: Option<&Id>,
) {
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
self.operation.custom(state, id);
}
}
let Self { operation, .. } = self;
MapRef {
operation: operation.as_mut(),
}
.container(id, operate_on_children);
}
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
self.operation.focusable(state, id);
}
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
self.operation.scrollable(state, id);
}
fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
self.operation.custom(state, id);
}
fn finish(&self) -> Outcome<B> {
match self.operation.finish() {
Outcome::None => Outcome::None,
Outcome::Some(output) => Outcome::Some((self.f)(output)),
Outcome::Chain(next) => Outcome::Chain(Box::new(Map {
operation: next,
f: self.f.clone(),
})),
}
}
}
Map {
operation,
f: Rc::new(f),
}
}
/// Produces an [`Operation`] that applies the given [`Operation`] to the
/// children of a container with the given [`Id`].
pub fn scope<T: 'static>(
target: Id,
operation: impl Operation<T> + 'static,
) -> impl Operation<T> {
struct ScopedOperation<Message> {
target: Id,
operation: Box<dyn Operation<Message>>,
}
impl<Message: 'static> Operation<Message> for ScopedOperation<Message> {
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
) {
if id == Some(&self.target) {
operate_on_children(self.operation.as_mut());
} else {
operate_on_children(self);
}
}
fn finish(&self) -> Outcome<Message> {
match self.operation.finish() {
Outcome::Chain(next) => {
Outcome::Chain(Box::new(ScopedOperation {
target: self.target.clone(),
operation: next,
}))
}
outcome => outcome,
}
}
}
ScopedOperation {
target,
operation: Box::new(operation),
}
}

View file

@ -4,27 +4,13 @@ use crate::layout;
use crate::renderer; use crate::renderer;
use crate::text; use crate::text;
use crate::widget::Tree; use crate::widget::Tree;
use crate::{Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget}; use crate::{
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
};
use std::borrow::Cow; use std::borrow::Cow;
pub use iced_style::text::{Appearance, StyleSheet};
/// A paragraph of text. /// A paragraph of text.
///
/// # Example
///
/// ```
/// # use iced_native::Color;
/// #
/// # type Text<'a> = iced_native::widget::Text<'a, iced_native::renderer::Null>;
/// #
/// Text::new("I <3 iced!")
/// .size(40)
/// .style(Color::from([0.0, 0.0, 1.0]));
/// ```
///
/// ![Text drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Text<'a, Renderer> pub struct Text<'a, Renderer>
where where
@ -211,7 +197,7 @@ pub fn draw<Renderer>(
alignment::Vertical::Bottom => bounds.y + bounds.height, alignment::Vertical::Bottom => bounds.y + bounds.height,
}; };
renderer.fill_text(crate::text::Text { renderer.fill_text(crate::Text {
content, content,
size: size.unwrap_or_else(|| renderer.default_size()), size: size.unwrap_or_else(|| renderer.default_size()),
bounds: Rectangle { x, y, ..bounds }, bounds: Rectangle { x, y, ..bounds },
@ -252,12 +238,40 @@ where
} }
} }
impl<'a, Renderer> From<&'a str> for Text<'a, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn from(content: &'a str) -> Self {
Self::new(content)
}
}
impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer> impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
where where
Renderer: text::Renderer + 'a, Renderer: text::Renderer + 'a,
Renderer::Theme: StyleSheet, Renderer::Theme: StyleSheet,
{ {
fn from(contents: &'a str) -> Self { fn from(content: &'a str) -> Self {
Text::new(contents).into() Text::from(content).into()
} }
} }
/// The style sheet of some text.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default + Copy;
/// Produces the [`Appearance`] of some text.
fn appearance(&self, style: Self::Style) -> Appearance;
}
/// The apperance of some text.
#[derive(Debug, Clone, Copy, Default)]
pub struct Appearance {
/// The [`Color`] of the text.
///
/// The default, `None`, means using the inherited color.
pub color: Option<Color>,
}

10
core/src/window.rs Normal file
View file

@ -0,0 +1,10 @@
//! Build window-based GUI applications.
mod event;
mod mode;
mod redraw_request;
mod user_attention;
pub use event::Event;
pub use mode::Mode;
pub use redraw_request::RedrawRequest;
pub use user_attention::UserAttention;

View file

@ -5,8 +5,8 @@ use iced::widget::canvas::{
self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke, self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
}; };
use iced::{ use iced::{
Application, Command, Element, Length, Point, Rectangle, Settings, Application, Command, Element, Length, Point, Rectangle, Renderer,
Subscription, Theme, Settings, Subscription, Theme,
}; };
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
@ -75,11 +75,12 @@ impl<Message> canvas::Program<Message> for Arc {
fn draw( fn draw(
&self, &self,
_state: &Self::State, _state: &Self::State,
renderer: &Renderer,
theme: &Theme, theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: Cursor, _cursor: Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let geometry = self.cache.draw(bounds.size(), |frame| { let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
let palette = theme.palette(); let palette = theme.palette();
let center = frame.center(); let center = frame.center();

View file

@ -64,7 +64,7 @@ mod bezier {
use iced::widget::canvas::{ use iced::widget::canvas::{
self, Canvas, Cursor, Frame, Geometry, Path, Stroke, self, Canvas, Cursor, Frame, Geometry, Path, Stroke,
}; };
use iced::{Element, Length, Point, Rectangle, Theme}; use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
#[derive(Default)] #[derive(Default)]
pub struct State { pub struct State {
@ -152,22 +152,26 @@ mod bezier {
fn draw( fn draw(
&self, &self,
state: &Self::State, state: &Self::State,
renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
cursor: Cursor, cursor: Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let content = let content = self.state.cache.draw(
self.state.cache.draw(bounds.size(), |frame: &mut Frame| { renderer,
bounds.size(),
|frame: &mut Frame| {
Curve::draw_all(self.curves, frame); Curve::draw_all(self.curves, frame);
frame.stroke( frame.stroke(
&Path::rectangle(Point::ORIGIN, frame.size()), &Path::rectangle(Point::ORIGIN, frame.size()),
Stroke::default().with_width(2.0), Stroke::default().with_width(2.0),
); );
}); },
);
if let Some(pending) = state { if let Some(pending) = state {
let pending_curve = pending.draw(bounds, cursor); let pending_curve = pending.draw(renderer, bounds, cursor);
vec![content, pending_curve] vec![content, pending_curve]
} else { } else {
@ -216,8 +220,13 @@ mod bezier {
} }
impl Pending { impl Pending {
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry { fn draw(
let mut frame = Frame::new(bounds.size()); &self,
renderer: &Renderer,
bounds: Rectangle,
cursor: Cursor,
) -> Geometry {
let mut frame = Frame::new(renderer, bounds.size());
if let Some(cursor_position) = cursor.position_in(&bounds) { if let Some(cursor_position) = cursor.position_in(&bounds) {
match *self { match *self {

View file

@ -4,8 +4,8 @@ use iced::widget::canvas::{
}; };
use iced::widget::{canvas, container}; use iced::widget::{canvas, container};
use iced::{ use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings, Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
Subscription, Theme, Vector, Settings, Subscription, Theme, Vector,
}; };
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
@ -83,17 +83,18 @@ impl Application for Clock {
} }
} }
impl<Message> canvas::Program<Message> for Clock { impl<Message> canvas::Program<Message, Renderer> for Clock {
type State = (); type State = ();
fn draw( fn draw(
&self, &self,
_state: &Self::State, _state: &Self::State,
renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: Cursor, _cursor: Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let clock = self.clock.draw(bounds.size(), |frame| { let clock = self.clock.draw(renderer, bounds.size(), |frame| {
let center = frame.center(); let center = frame.center();
let radius = frame.width().min(frame.height()) / 2.0; let radius = frame.width().min(frame.height()) / 2.0;

View file

@ -1,8 +1,8 @@
use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}; use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider}; use iced::widget::{column, row, text, Slider};
use iced::{ use iced::{
alignment, Alignment, Color, Element, Length, Point, Rectangle, Sandbox, alignment, Alignment, Color, Element, Length, Point, Rectangle, Renderer,
Settings, Size, Vector, Sandbox, Settings, Size, Vector,
}; };
use palette::{self, convert::FromColor, Hsl, Srgb}; use palette::{self, convert::FromColor, Hsl, Srgb};
use std::marker::PhantomData; use std::marker::PhantomData;
@ -243,11 +243,12 @@ impl<Message> canvas::Program<Message> for Theme {
fn draw( fn draw(
&self, &self,
_state: &Self::State, _state: &Self::State,
renderer: &Renderer,
_theme: &iced::Theme, _theme: &iced::Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: Cursor, _cursor: Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let theme = self.canvas_cache.draw(bounds.size(), |frame| { let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
self.draw(frame); self.draw(frame);
}); });

View file

@ -6,6 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["debug"] } iced = { path = "../..", features = ["debug", "lazy"] }
iced_native = { path = "../../native" }
iced_lazy = { path = "../../lazy" }

View file

@ -47,9 +47,8 @@ impl Sandbox for Component {
mod numeric_input { mod numeric_input {
use iced::alignment::{self, Alignment}; use iced::alignment::{self, Alignment};
use iced::widget::{self, button, row, text, text_input}; use iced::widget::{button, component, row, text, text_input, Component};
use iced::{Element, Length}; use iced::{Element, Length, Renderer};
use iced_lazy::{self, Component};
pub struct NumericInput<Message> { pub struct NumericInput<Message> {
value: Option<u32>, value: Option<u32>,
@ -82,13 +81,7 @@ mod numeric_input {
} }
} }
impl<Message, Renderer> Component<Message, Renderer> for NumericInput<Message> impl<Message> Component<Message, Renderer> for NumericInput<Message> {
where
Renderer: iced_native::text::Renderer + 'static,
Renderer::Theme: widget::button::StyleSheet
+ widget::text_input::StyleSheet
+ widget::text::StyleSheet,
{
type State = (); type State = ();
type Event = Event; type Event = Event;
@ -127,7 +120,8 @@ mod numeric_input {
.horizontal_alignment(alignment::Horizontal::Center) .horizontal_alignment(alignment::Horizontal::Center)
.vertical_alignment(alignment::Vertical::Center), .vertical_alignment(alignment::Vertical::Center),
) )
.width(50) .width(40)
.height(40)
.on_press(on_press) .on_press(on_press)
}; };
@ -145,23 +139,18 @@ mod numeric_input {
.padding(10), .padding(10),
button("+", Event::IncrementPressed), button("+", Event::IncrementPressed),
] ]
.align_items(Alignment::Fill) .align_items(Alignment::Center)
.spacing(10) .spacing(10)
.into() .into()
} }
} }
impl<'a, Message, Renderer> From<NumericInput<Message>> impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message, Renderer>
for Element<'a, Message, Renderer>
where where
Message: 'a, Message: 'a,
Renderer: 'static + iced_native::text::Renderer,
Renderer::Theme: widget::button::StyleSheet
+ widget::text_input::StyleSheet
+ widget::text::StyleSheet,
{ {
fn from(numeric_input: NumericInput<Message>) -> Self { fn from(numeric_input: NumericInput<Message>) -> Self {
iced_lazy::component(numeric_input) component(numeric_input)
} }
} }
} }

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../.." } iced = { path = "../..", features = ["advanced"] }
iced_native = { path = "../../native" }

View file

@ -1,9 +1,9 @@
//! This example showcases a drawing a quad. //! This example showcases a drawing a quad.
mod quad { mod quad {
use iced_native::layout::{self, Layout}; use iced::advanced::layout::{self, Layout};
use iced_native::renderer; use iced::advanced::renderer;
use iced_native::widget::{self, Widget}; use iced::advanced::widget::{self, Widget};
use iced_native::{Color, Element, Length, Point, Rectangle, Size}; use iced::{Color, Element, Length, Point, Rectangle, Size};
pub struct CustomQuad { pub struct CustomQuad {
size: f32, size: f32,

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../.." } iced = { path = "../..", features = ["advanced"] }
iced_native = { path = "../../native" }

View file

@ -9,10 +9,10 @@ mod circle {
// Of course, you can choose to make the implementation renderer-agnostic, // Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be // if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers. // implemented by `iced_wgpu` and other renderers.
use iced_native::layout::{self, Layout}; use iced::advanced::layout::{self, Layout};
use iced_native::renderer; use iced::advanced::renderer;
use iced_native::widget::{self, Widget}; use iced::advanced::widget::{self, Widget};
use iced_native::{Color, Element, Length, Point, Rectangle, Size}; use iced::{Color, Element, Length, Point, Rectangle, Size};
pub struct Circle { pub struct Circle {
radius: f32, radius: f32,

View file

@ -7,8 +7,6 @@ publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["tokio"] } iced = { path = "../..", features = ["tokio"] }
iced_native = { path = "../../native" }
iced_futures = { path = "../../futures" }
[dependencies.reqwest] [dependencies.reqwest]
version = "0.11" version = "0.11"

View file

@ -1,4 +1,4 @@
use iced_native::subscription; use iced::subscription;
use std::hash::Hash; use std::hash::Hash;

View file

@ -7,4 +7,3 @@ publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["debug"] } iced = { path = "../..", features = ["debug"] }
iced_native = { path = "../../native" }

View file

@ -1,12 +1,13 @@
use iced::alignment; use iced::alignment;
use iced::executor; use iced::executor;
use iced::subscription;
use iced::widget::{button, checkbox, container, text, Column}; use iced::widget::{button, checkbox, container, text, Column};
use iced::window; use iced::window;
use iced::Event;
use iced::{ use iced::{
Alignment, Application, Command, Element, Length, Settings, Subscription, Alignment, Application, Command, Element, Length, Settings, Subscription,
Theme, Theme,
}; };
use iced_native::Event;
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
Events::run(Settings { Events::run(Settings {
@ -17,13 +18,13 @@ pub fn main() -> iced::Result {
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct Events { struct Events {
last: Vec<iced_native::Event>, last: Vec<Event>,
enabled: bool, enabled: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
EventOccurred(iced_native::Event), EventOccurred(Event),
Toggled(bool), Toggled(bool),
Exit, Exit,
} }
@ -70,7 +71,7 @@ impl Application for Events {
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
iced_native::subscription::events().map(Message::EventOccurred) subscription::events().map(Message::EventOccurred)
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {

View file

@ -145,7 +145,7 @@ impl Application for GameOfLife {
self.grid self.grid
.view() .view()
.map(move |message| Message::Grid(message, version)), .map(move |message| Message::Grid(message, version)),
controls controls,
]; ];
container(content) container(content)
@ -211,8 +211,8 @@ mod grid {
Cache, Canvas, Cursor, Frame, Geometry, Path, Text, Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
}; };
use iced::{ use iced::{
alignment, mouse, Color, Element, Length, Point, Rectangle, Size, alignment, mouse, Color, Element, Length, Point, Rectangle, Renderer,
Theme, Vector, Size, Theme, Vector,
}; };
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use std::future::Future; use std::future::Future;
@ -536,13 +536,14 @@ mod grid {
fn draw( fn draw(
&self, &self,
_interaction: &Interaction, _interaction: &Interaction,
renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
cursor: Cursor, cursor: Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0); let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
let life = self.life_cache.draw(bounds.size(), |frame| { let life = self.life_cache.draw(renderer, bounds.size(), |frame| {
let background = Path::rectangle(Point::ORIGIN, frame.size()); let background = Path::rectangle(Point::ORIGIN, frame.size());
frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B)); frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B));
@ -565,7 +566,7 @@ mod grid {
}); });
let overlay = { let overlay = {
let mut frame = Frame::new(bounds.size()); let mut frame = Frame::new(renderer, bounds.size());
let hovered_cell = let hovered_cell =
cursor.position_in(&bounds).map(|position| { cursor.position_in(&bounds).map(|position| {
@ -626,38 +627,40 @@ mod grid {
if self.scaling < 0.2 || !self.show_lines { if self.scaling < 0.2 || !self.show_lines {
vec![life, overlay] vec![life, overlay]
} else { } else {
let grid = self.grid_cache.draw(bounds.size(), |frame| { let grid =
frame.translate(center); self.grid_cache.draw(renderer, bounds.size(), |frame| {
frame.scale(self.scaling); frame.translate(center);
frame.translate(self.translation); frame.scale(self.scaling);
frame.scale(Cell::SIZE as f32); frame.translate(self.translation);
frame.scale(Cell::SIZE as f32);
let region = self.visible_region(frame.size()); let region = self.visible_region(frame.size());
let rows = region.rows(); let rows = region.rows();
let columns = region.columns(); let columns = region.columns();
let (total_rows, total_columns) = let (total_rows, total_columns) =
(rows.clone().count(), columns.clone().count()); (rows.clone().count(), columns.clone().count());
let width = 2.0 / Cell::SIZE as f32; let width = 2.0 / Cell::SIZE as f32;
let color = Color::from_rgb8(70, 74, 83); let color = Color::from_rgb8(70, 74, 83);
frame.translate(Vector::new(-width / 2.0, -width / 2.0)); frame
.translate(Vector::new(-width / 2.0, -width / 2.0));
for row in region.rows() { for row in region.rows() {
frame.fill_rectangle( frame.fill_rectangle(
Point::new(*columns.start() as f32, row as f32), Point::new(*columns.start() as f32, row as f32),
Size::new(total_columns as f32, width), Size::new(total_columns as f32, width),
color, color,
); );
} }
for column in region.columns() { for column in region.columns() {
frame.fill_rectangle( frame.fill_rectangle(
Point::new(column as f32, *rows.start() as f32), Point::new(column as f32, *rows.start() as f32),
Size::new(width, total_rows as f32), Size::new(width, total_rows as f32),
color, color,
); );
} }
}); });
vec![life, grid, overlay] vec![life, grid, overlay]
} }

View file

@ -6,6 +6,5 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../.." } iced = { path = "../..", features = ["advanced"] }
iced_native = { path = "../../native" }
iced_graphics = { path = "../../graphics" } iced_graphics = { path = "../../graphics" }

View file

@ -1,23 +1,13 @@
//! This example showcases a simple native custom widget that renders using //! This example showcases a simple native custom widget that renders using
//! arbitrary low-level geometry. //! arbitrary low-level geometry.
mod rainbow { mod rainbow {
// For now, to implement a custom native widget you will need to add use iced_graphics::primitive::{ColoredVertex2D, Primitive};
// `iced_native` and `iced_wgpu` to your dependencies.
//
// Then, you simply need to define your widget type and implement the
// `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
//
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
use iced_graphics::renderer::{self, Renderer};
use iced_graphics::triangle::ColoredVertex2D;
use iced_graphics::{Backend, Primitive};
use iced_native::layout; use iced::advanced::layout::{self, Layout};
use iced_native::widget::{self, Widget}; use iced::advanced::renderer;
use iced_native::{ use iced::advanced::widget::{self, Widget};
Element, Layout, Length, Point, Rectangle, Size, Vector, use iced::{
Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
}; };
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
@ -27,10 +17,7 @@ mod rainbow {
Rainbow Rainbow
} }
impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow impl<Message> Widget<Message, Renderer> for Rainbow {
where
B: Backend,
{
fn width(&self) -> Length { fn width(&self) -> Length {
Length::Fill Length::Fill
} }
@ -41,7 +28,7 @@ mod rainbow {
fn layout( fn layout(
&self, &self,
_renderer: &Renderer<B, T>, _renderer: &Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO); let size = limits.width(Length::Fill).resolve(Size::ZERO);
@ -52,15 +39,15 @@ mod rainbow {
fn draw( fn draw(
&self, &self,
_tree: &widget::Tree, _tree: &widget::Tree,
renderer: &mut Renderer<B, T>, renderer: &mut Renderer,
_theme: &T, _theme: &Theme,
_style: &renderer::Style, _style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) {
use iced_graphics::triangle::Mesh2D; use iced::advanced::Renderer as _;
use iced_native::Renderer as _; use iced_graphics::primitive::Mesh2D;
let b = layout.bounds(); let b = layout.bounds();
@ -151,10 +138,7 @@ mod rainbow {
} }
} }
impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>> impl<'a, Message> From<Rainbow> for Element<'a, Message, Renderer> {
where
B: Backend,
{
fn from(rainbow: Rainbow) -> Self { fn from(rainbow: Rainbow) -> Self {
Self::new(rainbow) Self::new(rainbow)
} }

View file

@ -7,7 +7,9 @@ publish = false
[dependencies] [dependencies]
iced_winit = { path = "../../winit" } iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu", features = ["webgl"] } iced_wgpu = { path = "../../wgpu" }
iced_widget = { path = "../../widget" }
iced_renderer = { path = "../../renderer", features = ["wgpu", "tiny-skia"] }
env_logger = "0.8" env_logger = "0.8"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]

View file

@ -1,6 +1,8 @@
use iced_wgpu::Renderer; use iced_wgpu::Renderer;
use iced_winit::widget::{slider, text_input, Column, Row, Text}; use iced_widget::{slider, text_input, Column, Row, Text};
use iced_winit::{Alignment, Color, Command, Element, Length, Program}; use iced_winit::core::{Alignment, Color, Element, Length};
use iced_winit::runtime::{Command, Program};
use iced_winit::style::Theme;
pub struct Controls { pub struct Controls {
background_color: Color, background_color: Color,
@ -27,7 +29,7 @@ impl Controls {
} }
impl Program for Controls { impl Program for Controls {
type Renderer = Renderer; type Renderer = Renderer<Theme>;
type Message = Message; type Message = Message;
fn update(&mut self, message: Message) -> Command<Message> { fn update(&mut self, message: Message) -> Command<Message> {
@ -43,7 +45,7 @@ impl Program for Controls {
Command::none() Command::none()
} }
fn view(&self) -> Element<Message, Renderer> { fn view(&self) -> Element<Message, Renderer<Theme>> {
let background_color = self.background_color; let background_color = self.background_color;
let text = &self.text; let text = &self.text;

View file

@ -4,11 +4,14 @@ mod scene;
use controls::Controls; use controls::Controls;
use scene::Scene; use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; use iced_wgpu::graphics::Viewport;
use iced_winit::{ use iced_wgpu::{wgpu, Backend, Renderer, Settings};
conversion, futures, program, renderer, winit, Clipboard, Color, Debug, use iced_winit::core::renderer;
Size, use iced_winit::core::{Color, Size};
}; use iced_winit::runtime::program;
use iced_winit::runtime::Debug;
use iced_winit::style::Theme;
use iced_winit::{conversion, futures, winit, Clipboard};
use winit::{ use winit::{
dpi::PhysicalPosition, dpi::PhysicalPosition,
@ -73,43 +76,45 @@ pub fn main() {
let instance = wgpu::Instance::new(backend); let instance = wgpu::Instance::new(backend);
let surface = unsafe { instance.create_surface(&window) }; let surface = unsafe { instance.create_surface(&window) };
let (format, (device, queue)) = futures::executor::block_on(async { let (format, (device, queue)) =
let adapter = wgpu::util::initialize_adapter_from_env_or_default( futures::futures::executor::block_on(async {
&instance, let adapter = wgpu::util::initialize_adapter_from_env_or_default(
backend, &instance,
Some(&surface), backend,
) Some(&surface),
.await )
.expect("No suitable GPU adapters found on the system!"); .await
.expect("No suitable GPU adapters found on the system!");
let adapter_features = adapter.features(); let adapter_features = adapter.features();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()); .using_resolution(adapter.limits());
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let needed_limits = wgpu::Limits::default(); let needed_limits = wgpu::Limits::default();
( (
surface surface
.get_supported_formats(&adapter) .get_supported_formats(&adapter)
.first() .first()
.copied() .copied()
.expect("Get preferred format"), .expect("Get preferred format"),
adapter adapter
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: adapter_features & wgpu::Features::default(), features: adapter_features
limits: needed_limits, & wgpu::Features::default(),
}, limits: needed_limits,
None, },
) None,
.await )
.expect("Request device"), .await
) .expect("Request device"),
}); )
});
surface.configure( surface.configure(
&device, &device,
@ -188,7 +193,7 @@ pub fn main() {
viewport.scale_factor(), viewport.scale_factor(),
), ),
&mut renderer, &mut renderer,
&iced_wgpu::Theme::Dark, &Theme::Dark,
&renderer::Style { text_color: Color::WHITE }, &renderer::Style { text_color: Color::WHITE },
&mut clipboard, &mut clipboard,
&mut debug, &mut debug,

View file

@ -1,5 +1,5 @@
use iced_wgpu::wgpu; use iced_wgpu::wgpu;
use iced_winit::Color; use iced_winit::core::Color;
pub struct Scene { pub struct Scene {
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["debug"] } iced = { path = "../..", features = ["debug", "lazy"] }
iced_lazy = { path = "../../lazy" }

View file

@ -1,10 +1,9 @@
use iced::theme; use iced::theme;
use iced::widget::{ use iced::widget::{
button, column, horizontal_space, pick_list, row, scrollable, text, button, column, horizontal_space, lazy, pick_list, row, scrollable, text,
text_input, text_input,
}; };
use iced::{Element, Length, Sandbox, Settings}; use iced::{Element, Length, Sandbox, Settings};
use iced_lazy::lazy;
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::Hash; use std::hash::Hash;

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = [] } iced = { path = "../..", features = ["advanced"] }
iced_native = { path = "../../native" }

View file

@ -178,12 +178,15 @@ impl App {
} }
mod modal { mod modal {
use iced_native::alignment::Alignment; use iced::advanced::layout::{self, Layout};
use iced_native::widget::{self, Tree}; use iced::advanced::overlay;
use iced_native::{ use iced::advanced::renderer;
event, layout, mouse, overlay, renderer, Clipboard, Color, Element, use iced::advanced::widget::{self, Widget};
Event, Layout, Length, Point, Rectangle, Shell, Size, Widget, use iced::advanced::{self, Clipboard, Shell};
}; use iced::alignment::Alignment;
use iced::event;
use iced::mouse;
use iced::{Color, Element, Event, Length, Point, Rectangle, Size};
/// A widget that centers a modal element over some base element /// A widget that centers a modal element over some base element
pub struct Modal<'a, Message, Renderer> { pub struct Modal<'a, Message, Renderer> {
@ -218,14 +221,17 @@ mod modal {
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
for Modal<'a, Message, Renderer> for Modal<'a, Message, Renderer>
where where
Renderer: iced_native::Renderer, Renderer: advanced::Renderer,
Message: Clone, Message: Clone,
{ {
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<widget::Tree> {
vec![Tree::new(&self.base), Tree::new(&self.modal)] vec![
widget::Tree::new(&self.base),
widget::Tree::new(&self.modal),
]
} }
fn diff(&self, tree: &mut Tree) { fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(&[&self.base, &self.modal]); tree.diff_children(&[&self.base, &self.modal]);
} }
@ -247,7 +253,7 @@ mod modal {
fn on_event( fn on_event(
&mut self, &mut self,
state: &mut Tree, state: &mut widget::Tree,
event: Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
@ -268,9 +274,9 @@ mod modal {
fn draw( fn draw(
&self, &self,
state: &Tree, state: &widget::Tree,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme, theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
@ -289,7 +295,7 @@ mod modal {
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
state: &'b mut Tree, state: &'b mut widget::Tree,
layout: Layout<'_>, layout: Layout<'_>,
_renderer: &Renderer, _renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> { ) -> Option<overlay::Element<'b, Message, Renderer>> {
@ -306,7 +312,7 @@ mod modal {
fn mouse_interaction( fn mouse_interaction(
&self, &self,
state: &Tree, state: &widget::Tree,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
viewport: &Rectangle, viewport: &Rectangle,
@ -323,7 +329,7 @@ mod modal {
fn operate( fn operate(
&self, &self,
state: &mut Tree, state: &mut widget::Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>, operation: &mut dyn widget::Operation<Message>,
@ -339,7 +345,7 @@ mod modal {
struct Overlay<'a, 'b, Message, Renderer> { struct Overlay<'a, 'b, Message, Renderer> {
content: &'b mut Element<'a, Message, Renderer>, content: &'b mut Element<'a, Message, Renderer>,
tree: &'b mut Tree, tree: &'b mut widget::Tree,
size: Size, size: Size,
on_blur: Option<Message>, on_blur: Option<Message>,
} }
@ -347,7 +353,7 @@ mod modal {
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer> impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
for Overlay<'a, 'b, Message, Renderer> for Overlay<'a, 'b, Message, Renderer>
where where
Renderer: iced_native::Renderer, Renderer: advanced::Renderer,
Message: Clone, Message: Clone,
{ {
fn layout( fn layout(
@ -469,7 +475,7 @@ mod modal {
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
for Element<'a, Message, Renderer> for Element<'a, Message, Renderer>
where where
Renderer: 'a + iced_native::Renderer, Renderer: 'a + advanced::Renderer,
Message: 'a + Clone, Message: 'a + Clone,
{ {
fn from(modal: Modal<'a, Message, Renderer>) -> Self { fn from(modal: Modal<'a, Message, Renderer>) -> Self {

View file

@ -55,17 +55,18 @@ impl Application for ModernArt {
} }
} }
impl<Message> canvas::Program<Message> for ModernArt { impl<Message> canvas::Program<Message, Renderer> for ModernArt {
type State = (); type State = ();
fn draw( fn draw(
&self, &self,
_state: &Self::State, _state: &Self::State,
renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: Cursor, _cursor: Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let geometry = self.cache.draw(bounds.size(), |frame| { let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
let num_squares = thread_rng().gen_range(0..1200); let num_squares = thread_rng().gen_range(0..1200);
let mut i = 0; let mut i = 0;

View file

@ -6,7 +6,7 @@ use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{self, Canvas, Cursor, Geometry}; use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
use iced::{ use iced::{
executor, touch, window, Application, Color, Command, Element, Length, executor, touch, window, Application, Color, Command, Element, Length,
Point, Rectangle, Settings, Subscription, Theme, Point, Rectangle, Renderer, Settings, Subscription, Theme,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -95,7 +95,7 @@ impl Application for Multitouch {
} }
} }
impl canvas::Program<Message> for State { impl canvas::Program<Message, Renderer> for State {
type State = (); type State = ();
fn update( fn update(
@ -125,11 +125,12 @@ impl canvas::Program<Message> for State {
fn draw( fn draw(
&self, &self,
_state: &Self::State, _state: &Self::State,
renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: Cursor, _cursor: Cursor,
) -> Vec<Geometry> { ) -> Vec<Geometry> {
let fingerweb = self.cache.draw(bounds.size(), |frame| { let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
if self.fingers.len() < 2 { if self.fingers.len() < 2 {
return; return;
} }

View file

@ -6,6 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["debug"] } iced = { path = "../..", features = ["debug", "lazy"] }
iced_native = { path = "../../native" }
iced_lazy = { path = "../../lazy" }

View file

@ -1,14 +1,16 @@
use iced::alignment::{self, Alignment}; use iced::alignment::{self, Alignment};
use iced::event::{self, Event};
use iced::executor; use iced::executor;
use iced::keyboard; use iced::keyboard;
use iced::subscription;
use iced::theme::{self, Theme}; use iced::theme::{self, Theme};
use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::pane_grid::{self, PaneGrid};
use iced::widget::{button, column, container, row, scrollable, text}; use iced::widget::{
button, column, container, responsive, row, scrollable, text,
};
use iced::{ use iced::{
Application, Color, Command, Element, Length, Settings, Size, Subscription, Application, Color, Command, Element, Length, Settings, Size, Subscription,
}; };
use iced_lazy::responsive;
use iced_native::{event, subscription, Event};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
Example::run(Settings::default()) Example::run(Settings::default())

View file

@ -254,7 +254,6 @@ impl Application for ScrollableDemo {
scroll_to_beginning_button(), scroll_to_beginning_button(),
vertical_space(40), vertical_space(40),
] ]
.align_items(Alignment::Fill)
.spacing(40), .spacing(40),
horizontal_space(1200), horizontal_space(1200),
text("Horizontal - End!"), text("Horizontal - End!"),

View file

@ -5,8 +5,8 @@ use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{self, Canvas}; use iced::widget::canvas::{self, Canvas};
use iced::widget::{column, row, slider, text}; use iced::widget::{column, row, slider, text};
use iced::{ use iced::{
Application, Color, Command, Length, Point, Rectangle, Settings, Size, Application, Color, Command, Length, Point, Rectangle, Renderer, Settings,
Theme, Size, Theme,
}; };
use rand::Rng; use rand::Rng;
@ -134,11 +134,12 @@ impl canvas::Program<Message> for SierpinskiGraph {
fn draw( fn draw(
&self, &self,
_state: &Self::State, _state: &Self::State,
renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: canvas::Cursor, _cursor: canvas::Cursor,
) -> Vec<canvas::Geometry> { ) -> Vec<canvas::Geometry> {
let geom = self.cache.draw(bounds.size(), |frame| { let geom = self.cache.draw(renderer, bounds.size(), |frame| {
frame.stroke( frame.stroke(
&canvas::Path::rectangle(Point::ORIGIN, frame.size()), &canvas::Path::rectangle(Point::ORIGIN, frame.size()),
canvas::Stroke::default(), canvas::Stroke::default(),

View file

@ -15,8 +15,8 @@ use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{Cursor, Path}; use iced::widget::canvas::{Cursor, Path};
use iced::window; use iced::window;
use iced::{ use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Settings, Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
Size, Subscription, Vector, Settings, Size, Subscription, Vector,
}; };
use std::time::Instant; use std::time::Instant;
@ -156,24 +156,26 @@ impl<Message> canvas::Program<Message> for State {
fn draw( fn draw(
&self, &self,
_state: &Self::State, _state: &Self::State,
renderer: &Renderer,
_theme: &Theme, _theme: &Theme,
bounds: Rectangle, bounds: Rectangle,
_cursor: Cursor, _cursor: Cursor,
) -> Vec<canvas::Geometry> { ) -> Vec<canvas::Geometry> {
use std::f32::consts::PI; use std::f32::consts::PI;
let background = self.space_cache.draw(bounds.size(), |frame| { let background =
let stars = Path::new(|path| { self.space_cache.draw(renderer, bounds.size(), |frame| {
for (p, size) in &self.stars { let stars = Path::new(|path| {
path.rectangle(*p, Size::new(*size, *size)); for (p, size) in &self.stars {
} path.rectangle(*p, Size::new(*size, *size));
}
});
frame.translate(frame.center() - Point::ORIGIN);
frame.fill(&stars, Color::WHITE);
}); });
frame.translate(frame.center() - Point::ORIGIN); let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
frame.fill(&stars, Color::WHITE);
});
let system = self.system_cache.draw(bounds.size(), |frame| {
let center = frame.center(); let center = frame.center();
let sun = Path::circle(center, Self::SUN_RADIUS); let sun = Path::circle(center, Self::SUN_RADIUS);

View file

@ -6,5 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = [] } iced = { path = "../..", features = ["advanced"] }
iced_native = { path = "../../native" }

View file

@ -176,17 +176,23 @@ mod toast {
use std::fmt; use std::fmt;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use iced::advanced;
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
use iced::advanced::renderer;
use iced::advanced::widget::{self, Operation, Tree};
use iced::advanced::{Clipboard, Shell, Widget};
use iced::event::{self, Event};
use iced::mouse;
use iced::theme; use iced::theme;
use iced::widget::{ use iced::widget::{
button, column, container, horizontal_rule, horizontal_space, row, text, button, column, container, horizontal_rule, horizontal_space, row, text,
}; };
use iced::window;
use iced::{ use iced::{
Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme, Alignment, Element, Length, Point, Rectangle, Renderer, Size, Theme,
Vector, Vector,
}; };
use iced_native::widget::{tree, Operation, Tree};
use iced_native::{event, layout, mouse, overlay, renderer, window};
use iced_native::{Clipboard, Event, Layout, Shell, Widget};
pub const DEFAULT_TIMEOUT: u64 = 5; pub const DEFAULT_TIMEOUT: u64 = 5;
@ -324,13 +330,13 @@ mod toast {
self.content.as_widget().layout(renderer, limits) self.content.as_widget().layout(renderer, limits)
} }
fn tag(&self) -> tree::Tag { fn tag(&self) -> widget::tree::Tag {
struct Marker(Vec<Instant>); struct Marker(Vec<Instant>);
iced_native::widget::tree::Tag::of::<Marker>() widget::tree::Tag::of::<Marker>()
} }
fn state(&self) -> tree::State { fn state(&self) -> widget::tree::State {
iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new()) widget::tree::State::new(Vec::<Option<Instant>>::new())
} }
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<Tree> {
@ -584,7 +590,7 @@ mod toast {
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme, theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
@ -613,7 +619,7 @@ mod toast {
&mut self, &mut self,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &Renderer, renderer: &Renderer,
operation: &mut dyn iced_native::widget::Operation<Message>, operation: &mut dyn widget::Operation<Message>,
) { ) {
operation.container(None, &mut |operation| { operation.container(None, &mut |operation| {
self.toasts self.toasts

View file

@ -1,7 +1,7 @@
use iced::alignment::{self, Alignment}; use iced::alignment::{self, Alignment};
use iced::event::{self, Event}; use iced::event::{self, Event};
use iced::font::{self, Font}; use iced::font::{self, Font};
use iced::keyboard; use iced::keyboard::{self, KeyCode, Modifiers};
use iced::subscription; use iced::subscription;
use iced::theme::{self, Theme}; use iced::theme::{self, Theme};
use iced::widget::{ use iced::widget::{
@ -52,6 +52,7 @@ enum Message {
FilterChanged(Filter), FilterChanged(Filter),
TaskMessage(usize, TaskMessage), TaskMessage(usize, TaskMessage),
TabPressed { shift: bool }, TabPressed { shift: bool },
ToggleFullscreen(window::Mode),
} }
impl Application for Todos { impl Application for Todos {
@ -162,6 +163,9 @@ impl Application for Todos {
widget::focus_next() widget::focus_next()
} }
} }
Message::ToggleFullscreen(mode) => {
window::change_mode(mode)
}
_ => Command::none(), _ => Command::none(),
}; };
@ -272,6 +276,21 @@ impl Application for Todos {
) => Some(Message::TabPressed { ) => Some(Message::TabPressed {
shift: modifiers.shift(), shift: modifiers.shift(),
}), }),
(
Event::Keyboard(keyboard::Event::KeyPressed {
key_code,
modifiers: Modifiers::SHIFT,
}),
event::Status::Ignored,
) => match key_code {
KeyCode::Up => {
Some(Message::ToggleFullscreen(window::Mode::Fullscreen))
}
KeyCode::Down => {
Some(Message::ToggleFullscreen(window::Mode::Windowed))
}
_ => None,
},
_ => None, _ => None,
}) })
} }

View file

@ -7,4 +7,3 @@ publish = false
[dependencies] [dependencies]
iced = { path = "../.." } iced = { path = "../.." }
iced_native = { path = "../../native" }

View file

@ -1,12 +1,10 @@
use iced::event::{Event, MacOS, PlatformSpecific};
use iced::executor; use iced::executor;
use iced::subscription;
use iced::widget::{container, text}; use iced::widget::{container, text};
use iced::{ use iced::{
Application, Command, Element, Length, Settings, Subscription, Theme, Application, Command, Element, Length, Settings, Subscription, Theme,
}; };
use iced_native::{
event::{MacOS, PlatformSpecific},
Event,
};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
App::run(Settings::default()) App::run(Settings::default())
@ -19,7 +17,7 @@ struct App {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
EventOccurred(iced_native::Event), EventOccurred(Event),
} }
impl Application for App { impl Application for App {
@ -52,7 +50,7 @@ impl Application for App {
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
iced_native::subscription::events().map(Message::EventOccurred) subscription::events().map(Message::EventOccurred)
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {

View file

@ -7,8 +7,6 @@ publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["tokio", "debug"] } iced = { path = "../..", features = ["tokio", "debug"] }
iced_native = { path = "../../native" }
iced_futures = { path = "../../futures" }
once_cell = "1.15" once_cell = "1.15"
[dependencies.async-tungstenite] [dependencies.async-tungstenite]

View file

@ -1,7 +1,7 @@
pub mod server; pub mod server;
use iced_futures::futures; use iced::futures;
use iced_native::subscription::{self, Subscription}; use iced::subscription::{self, Subscription};
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::sink::SinkExt; use futures::sink::SinkExt;

View file

@ -1,4 +1,4 @@
use iced_futures::futures; use iced::futures;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};

View file

@ -146,7 +146,9 @@ impl Application for WebSocket {
} }
} }
row![input, button].spacing(10).align_items(Alignment::Fill) row![input, button]
.spacing(10)
.align_items(Alignment::Center)
}; };
column![message_log, new_message_input] column![message_log, new_message_input]

View file

@ -16,6 +16,10 @@ thread-pool = ["futures/thread-pool"]
[dependencies] [dependencies]
log = "0.4" log = "0.4"
[dependencies.iced_core]
version = "0.8"
path = "../core"
[dependencies.futures] [dependencies.futures]
version = "0.3" version = "0.3"

View file

@ -18,28 +18,26 @@ impl crate::Executor for Executor {
pub mod time { pub mod time {
//! Listen and react to time. //! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription}; use crate::subscription::{self, Subscription};
/// Returns a [`Subscription`] that produces messages at a set interval. /// Returns a [`Subscription`] that produces messages at a set interval.
/// ///
/// The first message is produced after a `duration`, and then continues to /// The first message is produced after a `duration`, and then continues to
/// produce more messages every `duration` after that. /// produce more messages every `duration` after that.
pub fn every<H: std::hash::Hasher, E>( pub fn every(
duration: std::time::Duration, duration: std::time::Duration,
) -> Subscription<H, E, std::time::Instant> { ) -> Subscription<std::time::Instant> {
Subscription::from_recipe(Every(duration)) Subscription::from_recipe(Every(duration))
} }
#[derive(Debug)] #[derive(Debug)]
struct Every(std::time::Duration); struct Every(std::time::Duration);
impl<H, E> subscription::Recipe<H, E> for Every impl subscription::Recipe for Every {
where
H: std::hash::Hasher,
{
type Output = std::time::Instant; type Output = std::time::Instant;
fn hash(&self, state: &mut H) { fn hash(&self, state: &mut Hasher) {
use std::hash::Hash; use std::hash::Hash;
std::any::TypeId::of::<Self>().hash(state); std::any::TypeId::of::<Self>().hash(state);
@ -48,7 +46,7 @@ pub mod time {
fn stream( fn stream(
self: Box<Self>, self: Box<Self>,
_input: futures::stream::BoxStream<'static, E>, _input: subscription::EventStream,
) -> futures::stream::BoxStream<'static, Self::Output> { ) -> futures::stream::BoxStream<'static, Self::Output> {
use futures::stream::StreamExt; use futures::stream::StreamExt;

View file

@ -19,28 +19,26 @@ impl crate::Executor for Executor {
pub mod time { pub mod time {
//! Listen and react to time. //! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription}; use crate::subscription::{self, Subscription};
/// Returns a [`Subscription`] that produces messages at a set interval. /// Returns a [`Subscription`] that produces messages at a set interval.
/// ///
/// The first message is produced after a `duration`, and then continues to /// The first message is produced after a `duration`, and then continues to
/// produce more messages every `duration` after that. /// produce more messages every `duration` after that.
pub fn every<H: std::hash::Hasher, E>( pub fn every(
duration: std::time::Duration, duration: std::time::Duration,
) -> Subscription<H, E, std::time::Instant> { ) -> Subscription<std::time::Instant> {
Subscription::from_recipe(Every(duration)) Subscription::from_recipe(Every(duration))
} }
#[derive(Debug)] #[derive(Debug)]
struct Every(std::time::Duration); struct Every(std::time::Duration);
impl<H, E> subscription::Recipe<H, E> for Every impl subscription::Recipe for Every {
where
H: std::hash::Hasher,
{
type Output = std::time::Instant; type Output = std::time::Instant;
fn hash(&self, state: &mut H) { fn hash(&self, state: &mut Hasher) {
use std::hash::Hash; use std::hash::Hash;
std::any::TypeId::of::<Self>().hash(state); std::any::TypeId::of::<Self>().hash(state);
@ -49,7 +47,7 @@ pub mod time {
fn stream( fn stream(
self: Box<Self>, self: Box<Self>,
_input: futures::stream::BoxStream<'static, E>, _input: subscription::EventStream,
) -> futures::stream::BoxStream<'static, Self::Output> { ) -> futures::stream::BoxStream<'static, Self::Output> {
use futures::stream::StreamExt; use futures::stream::StreamExt;

View file

@ -22,28 +22,26 @@ impl crate::Executor for Executor {
pub mod time { pub mod time {
//! Listen and react to time. //! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription}; use crate::subscription::{self, Subscription};
/// Returns a [`Subscription`] that produces messages at a set interval. /// Returns a [`Subscription`] that produces messages at a set interval.
/// ///
/// The first message is produced after a `duration`, and then continues to /// The first message is produced after a `duration`, and then continues to
/// produce more messages every `duration` after that. /// produce more messages every `duration` after that.
pub fn every<H: std::hash::Hasher, E>( pub fn every(
duration: std::time::Duration, duration: std::time::Duration,
) -> Subscription<H, E, std::time::Instant> { ) -> Subscription<std::time::Instant> {
Subscription::from_recipe(Every(duration)) Subscription::from_recipe(Every(duration))
} }
#[derive(Debug)] #[derive(Debug)]
struct Every(std::time::Duration); struct Every(std::time::Duration);
impl<H, E> subscription::Recipe<H, E> for Every impl subscription::Recipe for Every {
where
H: std::hash::Hasher,
{
type Output = std::time::Instant; type Output = std::time::Instant;
fn hash(&self, state: &mut H) { fn hash(&self, state: &mut Hasher) {
use std::hash::Hash; use std::hash::Hash;
std::any::TypeId::of::<Self>().hash(state); std::any::TypeId::of::<Self>().hash(state);
@ -52,7 +50,7 @@ pub mod time {
fn stream( fn stream(
self: Box<Self>, self: Box<Self>,
_input: futures::stream::BoxStream<'static, E>, _input: subscription::EventStream,
) -> futures::stream::BoxStream<'static, Self::Output> { ) -> futures::stream::BoxStream<'static, Self::Output> {
use futures::stream::StreamExt; use futures::stream::StreamExt;

View file

@ -16,6 +16,7 @@ impl crate::Executor for Executor {
pub mod time { pub mod time {
//! Listen and react to time. //! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription}; use crate::subscription::{self, Subscription};
use crate::BoxStream; use crate::BoxStream;
@ -23,22 +24,19 @@ pub mod time {
/// ///
/// The first message is produced after a `duration`, and then continues to /// The first message is produced after a `duration`, and then continues to
/// produce more messages every `duration` after that. /// produce more messages every `duration` after that.
pub fn every<H: std::hash::Hasher, E>( pub fn every(
duration: std::time::Duration, duration: std::time::Duration,
) -> Subscription<H, E, wasm_timer::Instant> { ) -> Subscription<wasm_timer::Instant> {
Subscription::from_recipe(Every(duration)) Subscription::from_recipe(Every(duration))
} }
#[derive(Debug)] #[derive(Debug)]
struct Every(std::time::Duration); struct Every(std::time::Duration);
impl<H, E> subscription::Recipe<H, E> for Every impl subscription::Recipe for Every {
where
H: std::hash::Hasher,
{
type Output = wasm_timer::Instant; type Output = wasm_timer::Instant;
fn hash(&self, state: &mut H) { fn hash(&self, state: &mut Hasher) {
use std::hash::Hash; use std::hash::Hash;
std::any::TypeId::of::<Self>().hash(state); std::any::TypeId::of::<Self>().hash(state);
@ -47,7 +45,7 @@ pub mod time {
fn stream( fn stream(
self: Box<Self>, self: Box<Self>,
_input: BoxStream<E>, _input: subscription::EventStream,
) -> BoxStream<Self::Output> { ) -> BoxStream<Self::Output> {
use futures::stream::StreamExt; use futures::stream::StreamExt;

View file

@ -1,70 +0,0 @@
/// A set of asynchronous actions to be performed by some runtime.
#[must_use = "`Command` must be returned to runtime to take effect"]
#[derive(Debug)]
pub struct Command<T>(Internal<T>);
#[derive(Debug)]
enum Internal<T> {
None,
Single(T),
Batch(Vec<T>),
}
impl<T> Command<T> {
/// Creates an empty [`Command`].
///
/// In other words, a [`Command`] that does nothing.
pub const fn none() -> Self {
Self(Internal::None)
}
/// Creates a [`Command`] that performs a single action.
pub const fn single(action: T) -> Self {
Self(Internal::Single(action))
}
/// Creates a [`Command`] that performs the actions of all the given
/// commands.
///
/// Once this command is run, all the commands will be executed at once.
pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self {
let mut batch = Vec::new();
for Command(command) in commands {
match command {
Internal::None => {}
Internal::Single(command) => batch.push(command),
Internal::Batch(commands) => batch.extend(commands),
}
}
Self(Internal::Batch(batch))
}
/// Applies a transformation to the result of a [`Command`].
pub fn map<A>(self, f: impl Fn(T) -> A) -> Command<A>
where
T: 'static,
{
let Command(command) = self;
match command {
Internal::None => Command::none(),
Internal::Single(action) => Command::single(f(action)),
Internal::Batch(batch) => {
Command(Internal::Batch(batch.into_iter().map(f).collect()))
}
}
}
/// Returns all of the actions of the [`Command`].
pub fn actions(self) -> Vec<T> {
let Command(command) = self;
match command {
Internal::None => Vec::new(),
Internal::Single(action) => vec![action],
Internal::Batch(batch) => batch,
}
}
}

View file

@ -18,8 +18,8 @@
#![allow(clippy::inherent_to_string, clippy::type_complexity)] #![allow(clippy::inherent_to_string, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
pub use futures; pub use futures;
pub use iced_core as core;
mod command;
mod maybe_send; mod maybe_send;
mod runtime; mod runtime;
@ -27,7 +27,6 @@ pub mod backend;
pub mod executor; pub mod executor;
pub mod subscription; pub mod subscription;
pub use command::Command;
pub use executor::Executor; pub use executor::Executor;
pub use maybe_send::MaybeSend; pub use maybe_send::MaybeSend;
pub use platform::*; pub use platform::*;

View file

@ -1,6 +1,7 @@
//! Run commands and keep track of subscriptions. //! Run commands and keep track of subscriptions.
use crate::core::event::{self, Event};
use crate::subscription; use crate::subscription;
use crate::{BoxFuture, Executor, MaybeSend, Subscription}; use crate::{BoxFuture, Executor, MaybeSend};
use futures::{channel::mpsc, Sink}; use futures::{channel::mpsc, Sink};
use std::marker::PhantomData; use std::marker::PhantomData;
@ -12,18 +13,15 @@ use std::marker::PhantomData;
/// ///
/// [`Command`]: crate::Command /// [`Command`]: crate::Command
#[derive(Debug)] #[derive(Debug)]
pub struct Runtime<Hasher, Event, Executor, Sender, Message> { pub struct Runtime<Executor, Sender, Message> {
executor: Executor, executor: Executor,
sender: Sender, sender: Sender,
subscriptions: subscription::Tracker<Hasher, Event>, subscriptions: subscription::Tracker,
_message: PhantomData<Message>, _message: PhantomData<Message>,
} }
impl<Hasher, Event, Executor, Sender, Message> impl<Executor, Sender, Message> Runtime<Executor, Sender, Message>
Runtime<Hasher, Event, Executor, Sender, Message>
where where
Hasher: std::hash::Hasher + Default,
Event: Send + Clone + 'static,
Executor: self::Executor, Executor: self::Executor,
Sender: Sink<Message, Error = mpsc::SendError> Sender: Sink<Message, Error = mpsc::SendError>
+ Unpin + Unpin
@ -79,7 +77,9 @@ where
/// [`Tracker::update`]: subscription::Tracker::update /// [`Tracker::update`]: subscription::Tracker::update
pub fn track( pub fn track(
&mut self, &mut self,
subscription: Subscription<Hasher, Event, Message>, recipes: impl IntoIterator<
Item = Box<dyn subscription::Recipe<Output = Message>>,
>,
) { ) {
let Runtime { let Runtime {
executor, executor,
@ -88,8 +88,9 @@ where
.. ..
} = self; } = self;
let futures = executor let futures = executor.enter(|| {
.enter(|| subscriptions.update(subscription, sender.clone())); subscriptions.update(recipes.into_iter(), sender.clone())
});
for future in futures { for future in futures {
executor.spawn(future); executor.spawn(future);
@ -102,7 +103,7 @@ where
/// See [`Tracker::broadcast`] to learn more. /// See [`Tracker::broadcast`] to learn more.
/// ///
/// [`Tracker::broadcast`]: subscription::Tracker::broadcast /// [`Tracker::broadcast`]: subscription::Tracker::broadcast
pub fn broadcast(&mut self, event: Event) { pub fn broadcast(&mut self, event: Event, status: event::Status) {
self.subscriptions.broadcast(event); self.subscriptions.broadcast(event, status);
} }
} }

View file

@ -3,7 +3,18 @@ mod tracker;
pub use tracker::Tracker; pub use tracker::Tracker;
use crate::BoxStream; use crate::core::event::{self, Event};
use crate::core::window;
use crate::core::Hasher;
use crate::futures::{Future, Stream};
use crate::{BoxStream, MaybeSend};
use std::hash::Hash;
/// A stream of runtime events.
///
/// It is the input of a [`Subscription`].
pub type EventStream = BoxStream<(Event, event::Status)>;
/// A request to listen to external events. /// A request to listen to external events.
/// ///
@ -16,19 +27,13 @@ use crate::BoxStream;
/// For instance, you can use a [`Subscription`] to listen to a WebSocket /// For instance, you can use a [`Subscription`] to listen to a WebSocket
/// connection, keyboard presses, mouse events, time ticks, etc. /// connection, keyboard presses, mouse events, time ticks, etc.
/// ///
/// This type is normally aliased by runtimes with a specific `Event` and/or
/// `Hasher`.
///
/// [`Command`]: crate::Command /// [`Command`]: crate::Command
#[must_use = "`Subscription` must be returned to runtime to take effect"] #[must_use = "`Subscription` must be returned to runtime to take effect"]
pub struct Subscription<Hasher, Event, Output> { pub struct Subscription<Message> {
recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>, recipes: Vec<Box<dyn Recipe<Output = Message>>>,
} }
impl<H, E, O> Subscription<H, E, O> impl<Message> Subscription<Message> {
where
H: std::hash::Hasher,
{
/// Returns an empty [`Subscription`] that will not produce any output. /// Returns an empty [`Subscription`] that will not produce any output.
pub fn none() -> Self { pub fn none() -> Self {
Self { Self {
@ -38,7 +43,7 @@ where
/// Creates a [`Subscription`] from a [`Recipe`] describing it. /// Creates a [`Subscription`] from a [`Recipe`] describing it.
pub fn from_recipe( pub fn from_recipe(
recipe: impl Recipe<H, E, Output = O> + 'static, recipe: impl Recipe<Output = Message> + 'static,
) -> Self { ) -> Self {
Self { Self {
recipes: vec![Box::new(recipe)], recipes: vec![Box::new(recipe)],
@ -48,7 +53,7 @@ where
/// Batches all the provided subscriptions and returns the resulting /// Batches all the provided subscriptions and returns the resulting
/// [`Subscription`]. /// [`Subscription`].
pub fn batch( pub fn batch(
subscriptions: impl IntoIterator<Item = Subscription<H, E, O>>, subscriptions: impl IntoIterator<Item = Subscription<Message>>,
) -> Self { ) -> Self {
Self { Self {
recipes: subscriptions recipes: subscriptions
@ -59,18 +64,16 @@ where
} }
/// Returns the different recipes of the [`Subscription`]. /// Returns the different recipes of the [`Subscription`].
pub fn recipes(self) -> Vec<Box<dyn Recipe<H, E, Output = O>>> { pub fn into_recipes(self) -> Vec<Box<dyn Recipe<Output = Message>>> {
self.recipes self.recipes
} }
/// Adds a value to the [`Subscription`] context. /// Adds a value to the [`Subscription`] context.
/// ///
/// The value will be part of the identity of a [`Subscription`]. /// The value will be part of the identity of a [`Subscription`].
pub fn with<T>(mut self, value: T) -> Subscription<H, E, (T, O)> pub fn with<T>(mut self, value: T) -> Subscription<(T, Message)>
where where
H: 'static, Message: 'static,
E: 'static,
O: 'static,
T: std::hash::Hash + Clone + Send + Sync + 'static, T: std::hash::Hash + Clone + Send + Sync + 'static,
{ {
Subscription { Subscription {
@ -79,18 +82,16 @@ where
.drain(..) .drain(..)
.map(|recipe| { .map(|recipe| {
Box::new(With::new(recipe, value.clone())) Box::new(With::new(recipe, value.clone()))
as Box<dyn Recipe<H, E, Output = (T, O)>> as Box<dyn Recipe<Output = (T, Message)>>
}) })
.collect(), .collect(),
} }
} }
/// Transforms the [`Subscription`] output with the given function. /// Transforms the [`Subscription`] output with the given function.
pub fn map<A>(mut self, f: fn(O) -> A) -> Subscription<H, E, A> pub fn map<A>(mut self, f: fn(Message) -> A) -> Subscription<A>
where where
H: 'static, Message: 'static,
E: 'static,
O: 'static,
A: 'static, A: 'static,
{ {
Subscription { Subscription {
@ -98,15 +99,14 @@ where
.recipes .recipes
.drain(..) .drain(..)
.map(|recipe| { .map(|recipe| {
Box::new(Map::new(recipe, f)) Box::new(Map::new(recipe, f)) as Box<dyn Recipe<Output = A>>
as Box<dyn Recipe<H, E, Output = A>>
}) })
.collect(), .collect(),
} }
} }
} }
impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { impl<Message> std::fmt::Debug for Subscription<Message> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Subscription").finish() f.debug_struct("Subscription").finish()
} }
@ -129,7 +129,7 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples /// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress /// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress
/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/examples/stopwatch /// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/examples/stopwatch
pub trait Recipe<Hasher: std::hash::Hasher, Event> { pub trait Recipe {
/// The events that will be produced by a [`Subscription`] with this /// The events that will be produced by a [`Subscription`] with this
/// [`Recipe`]. /// [`Recipe`].
type Output; type Output;
@ -141,45 +141,33 @@ pub trait Recipe<Hasher: std::hash::Hasher, Event> {
/// Executes the [`Recipe`] and produces the stream of events of its /// Executes the [`Recipe`] and produces the stream of events of its
/// [`Subscription`]. /// [`Subscription`].
/// fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output>;
/// It receives some stream of generic events, which is normally defined by
/// shells.
fn stream(
self: Box<Self>,
input: BoxStream<Event>,
) -> BoxStream<Self::Output>;
} }
struct Map<Hasher, Event, A, B> { struct Map<A, B> {
recipe: Box<dyn Recipe<Hasher, Event, Output = A>>, recipe: Box<dyn Recipe<Output = A>>,
mapper: fn(A) -> B, mapper: fn(A) -> B,
} }
impl<H, E, A, B> Map<H, E, A, B> { impl<A, B> Map<A, B> {
fn new( fn new(recipe: Box<dyn Recipe<Output = A>>, mapper: fn(A) -> B) -> Self {
recipe: Box<dyn Recipe<H, E, Output = A>>,
mapper: fn(A) -> B,
) -> Self {
Map { recipe, mapper } Map { recipe, mapper }
} }
} }
impl<H, E, A, B> Recipe<H, E> for Map<H, E, A, B> impl<A, B> Recipe for Map<A, B>
where where
A: 'static, A: 'static,
B: 'static, B: 'static,
H: std::hash::Hasher,
{ {
type Output = B; type Output = B;
fn hash(&self, state: &mut H) { fn hash(&self, state: &mut Hasher) {
use std::hash::Hash;
self.recipe.hash(state); self.recipe.hash(state);
self.mapper.hash(state); self.mapper.hash(state);
} }
fn stream(self: Box<Self>, input: BoxStream<E>) -> BoxStream<Self::Output> { fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> {
use futures::StreamExt; use futures::StreamExt;
let mapper = self.mapper; let mapper = self.mapper;
@ -188,34 +176,31 @@ where
} }
} }
struct With<Hasher, Event, A, B> { struct With<A, B> {
recipe: Box<dyn Recipe<Hasher, Event, Output = A>>, recipe: Box<dyn Recipe<Output = A>>,
value: B, value: B,
} }
impl<H, E, A, B> With<H, E, A, B> { impl<A, B> With<A, B> {
fn new(recipe: Box<dyn Recipe<H, E, Output = A>>, value: B) -> Self { fn new(recipe: Box<dyn Recipe<Output = A>>, value: B) -> Self {
With { recipe, value } With { recipe, value }
} }
} }
impl<H, E, A, B> Recipe<H, E> for With<H, E, A, B> impl<A, B> Recipe for With<A, B>
where where
A: 'static, A: 'static,
B: 'static + std::hash::Hash + Clone + Send + Sync, B: 'static + std::hash::Hash + Clone + Send + Sync,
H: std::hash::Hasher,
{ {
type Output = (B, A); type Output = (B, A);
fn hash(&self, state: &mut H) { fn hash(&self, state: &mut Hasher) {
use std::hash::Hash;
std::any::TypeId::of::<B>().hash(state); std::any::TypeId::of::<B>().hash(state);
self.value.hash(state); self.value.hash(state);
self.recipe.hash(state); self.recipe.hash(state);
} }
fn stream(self: Box<Self>, input: BoxStream<E>) -> BoxStream<Self::Output> { fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> {
use futures::StreamExt; use futures::StreamExt;
let value = self.value; let value = self.value;
@ -227,3 +212,222 @@ where
) )
} }
} }
/// Returns a [`Subscription`] to all the ignored runtime events.
///
/// This subscription will notify your application of any [`Event`] that was
/// not captured by any widget.
pub fn events() -> Subscription<Event> {
events_with(|event, status| match status {
event::Status::Ignored => Some(event),
event::Status::Captured => None,
})
}
/// Returns a [`Subscription`] that filters all the runtime events with the
/// provided function, producing messages accordingly.
///
/// This subscription will call the provided function for every [`Event`]
/// handled by the runtime. If the function:
///
/// - Returns `None`, the [`Event`] will be discarded.
/// - Returns `Some` message, the `Message` will be produced.
pub fn events_with<Message>(
f: fn(Event, event::Status) -> Option<Message>,
) -> Subscription<Message>
where
Message: 'static + MaybeSend,
{
#[derive(Hash)]
struct EventsWith;
Subscription::from_recipe(Runner {
id: (EventsWith, f),
spawn: move |events| {
use futures::future;
use futures::stream::StreamExt;
events.filter_map(move |(event, status)| {
future::ready(match event {
Event::Window(window::Event::RedrawRequested(_)) => None,
_ => f(event, status),
})
})
},
})
}
/// Returns a [`Subscription`] that produces a message for every runtime event,
/// including the redraw request events.
///
/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in
/// an infinite loop.
pub fn raw_events<Message>(
f: fn(Event, event::Status) -> Option<Message>,
) -> Subscription<Message>
where
Message: 'static + MaybeSend,
{
#[derive(Hash)]
struct RawEvents;
Subscription::from_recipe(Runner {
id: (RawEvents, f),
spawn: move |events| {
use futures::future;
use futures::stream::StreamExt;
events.filter_map(move |(event, status)| {
future::ready(f(event, status))
})
},
})
}
/// Returns a [`Subscription`] that will call the given function to create and
/// asynchronously run the given [`Stream`].
pub fn run<S, Message>(builder: fn() -> S) -> Subscription<Message>
where
S: Stream<Item = Message> + MaybeSend + 'static,
Message: 'static,
{
Subscription::from_recipe(Runner {
id: builder,
spawn: move |_| builder(),
})
}
/// Returns a [`Subscription`] that will create and asynchronously run the
/// given [`Stream`].
///
/// The `id` will be used to uniquely identify the [`Subscription`].
pub fn run_with_id<I, S, Message>(id: I, stream: S) -> Subscription<Message>
where
I: Hash + 'static,
S: Stream<Item = Message> + MaybeSend + 'static,
Message: 'static,
{
Subscription::from_recipe(Runner {
id,
spawn: move |_| stream,
})
}
/// Returns a [`Subscription`] that will create and asynchronously run a
/// [`Stream`] that will call the provided closure to produce every `Message`.
///
/// The `id` will be used to uniquely identify the [`Subscription`].
///
/// # Creating an asynchronous worker with bidirectional communication
/// You can leverage this helper to create a [`Subscription`] that spawns
/// an asynchronous worker in the background and establish a channel of
/// communication with an `iced` application.
///
/// You can achieve this by creating an `mpsc` channel inside the closure
/// and returning the `Sender` as a `Message` for the `Application`:
///
/// ```
/// use iced_futures::subscription::{self, Subscription};
/// use iced_futures::futures;
///
/// use futures::channel::mpsc;
///
/// pub enum Event {
/// Ready(mpsc::Sender<Input>),
/// WorkFinished,
/// // ...
/// }
///
/// enum Input {
/// DoSomeWork,
/// // ...
/// }
///
/// enum State {
/// Starting,
/// Ready(mpsc::Receiver<Input>),
/// }
///
/// fn some_worker() -> Subscription<Event> {
/// struct SomeWorker;
///
/// subscription::unfold(std::any::TypeId::of::<SomeWorker>(), State::Starting, |state| async move {
/// match state {
/// State::Starting => {
/// // Create channel
/// let (sender, receiver) = mpsc::channel(100);
///
/// (Some(Event::Ready(sender)), State::Ready(receiver))
/// }
/// State::Ready(mut receiver) => {
/// use futures::StreamExt;
///
/// // Read next input sent from `Application`
/// let input = receiver.select_next_some().await;
///
/// match input {
/// Input::DoSomeWork => {
/// // Do some async work...
///
/// // Finally, we can optionally return a message to tell the
/// // `Application` the work is done
/// (Some(Event::WorkFinished), State::Ready(receiver))
/// }
/// }
/// }
/// }
/// })
/// }
/// ```
///
/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
/// connection open.
///
/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket
pub fn unfold<I, T, Fut, Message>(
id: I,
initial: T,
mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
) -> Subscription<Message>
where
I: Hash + 'static,
T: MaybeSend + 'static,
Fut: Future<Output = (Option<Message>, T)> + MaybeSend + 'static,
Message: 'static + MaybeSend,
{
use futures::future::{self, FutureExt};
use futures::stream::StreamExt;
run_with_id(
id,
futures::stream::unfold(initial, move |state| f(state).map(Some))
.filter_map(future::ready),
)
}
struct Runner<I, F, S, Message>
where
F: FnOnce(EventStream) -> S,
S: Stream<Item = Message>,
{
id: I,
spawn: F,
}
impl<I, S, F, Message> Recipe for Runner<I, F, S, Message>
where
I: Hash + 'static,
F: FnOnce(EventStream) -> S,
S: Stream<Item = Message> + MaybeSend + 'static,
{
type Output = Message;
fn hash(&self, state: &mut Hasher) {
std::any::TypeId::of::<I>().hash(state);
self.id.hash(state);
}
fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> {
crate::boxed_stream((self.spawn)(input))
}
}

View file

@ -1,38 +1,35 @@
use crate::{BoxFuture, MaybeSend, Subscription}; use crate::core::event::{self, Event};
use crate::core::Hasher;
use crate::subscription::Recipe;
use crate::{BoxFuture, MaybeSend};
use futures::{ use futures::channel::mpsc;
channel::mpsc, use futures::sink::{Sink, SinkExt};
sink::{Sink, SinkExt},
}; use std::collections::HashMap;
use std::{collections::HashMap, marker::PhantomData}; use std::hash::Hasher as _;
/// A registry of subscription streams. /// A registry of subscription streams.
/// ///
/// If you have an application that continuously returns a [`Subscription`], /// If you have an application that continuously returns a [`Subscription`],
/// you can use a [`Tracker`] to keep track of the different recipes and keep /// you can use a [`Tracker`] to keep track of the different recipes and keep
/// its executions alive. /// its executions alive.
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Tracker<Hasher, Event> { pub struct Tracker {
subscriptions: HashMap<u64, Execution<Event>>, subscriptions: HashMap<u64, Execution>,
_hasher: PhantomData<Hasher>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Execution<Event> { pub struct Execution {
_cancel: futures::channel::oneshot::Sender<()>, _cancel: futures::channel::oneshot::Sender<()>,
listener: Option<futures::channel::mpsc::Sender<Event>>, listener: Option<futures::channel::mpsc::Sender<(Event, event::Status)>>,
} }
impl<Hasher, Event> Tracker<Hasher, Event> impl Tracker {
where
Hasher: std::hash::Hasher + Default,
Event: 'static + Send + Clone,
{
/// Creates a new empty [`Tracker`]. /// Creates a new empty [`Tracker`].
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
subscriptions: HashMap::new(), subscriptions: HashMap::new(),
_hasher: PhantomData,
} }
} }
@ -56,7 +53,7 @@ where
/// [`Recipe`]: crate::subscription::Recipe /// [`Recipe`]: crate::subscription::Recipe
pub fn update<Message, Receiver>( pub fn update<Message, Receiver>(
&mut self, &mut self,
subscription: Subscription<Hasher, Event, Message>, recipes: impl Iterator<Item = Box<dyn Recipe<Output = Message>>>,
receiver: Receiver, receiver: Receiver,
) -> Vec<BoxFuture<()>> ) -> Vec<BoxFuture<()>>
where where
@ -70,8 +67,6 @@ where
use futures::stream::StreamExt; use futures::stream::StreamExt;
let mut futures: Vec<BoxFuture<()>> = Vec::new(); let mut futures: Vec<BoxFuture<()>> = Vec::new();
let recipes = subscription.recipes();
let mut alive = std::collections::HashSet::new(); let mut alive = std::collections::HashSet::new();
for recipe in recipes { for recipe in recipes {
@ -142,12 +137,12 @@ where
/// currently open. /// currently open.
/// ///
/// [`Recipe::stream`]: crate::subscription::Recipe::stream /// [`Recipe::stream`]: crate::subscription::Recipe::stream
pub fn broadcast(&mut self, event: Event) { pub fn broadcast(&mut self, event: Event, status: event::Status) {
self.subscriptions self.subscriptions
.values_mut() .values_mut()
.filter_map(|connection| connection.listener.as_mut()) .filter_map(|connection| connection.listener.as_mut())
.for_each(|listener| { .for_each(|listener| {
if let Err(error) = listener.try_send(event.clone()) { if let Err(error) = listener.try_send((event.clone(), status)) {
log::warn!( log::warn!(
"Error sending event to subscription: {:?}", "Error sending event to subscription: {:?}",
error error
@ -156,13 +151,3 @@ where
}); });
} }
} }
impl<Hasher, Event> Default for Tracker<Hasher, Event>
where
Hasher: std::hash::Hasher + Default,
Event: 'static + Send + Clone,
{
fn default() -> Self {
Self::new()
}
}

View file

@ -11,23 +11,9 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"] categories = ["gui"]
[features] [features]
svg = ["resvg", "usvg", "tiny-skia"] geometry = ["lyon_path"]
image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
png = ["image_rs/png"]
jpeg = ["image_rs/jpeg"]
jpeg_rayon = ["image_rs/jpeg_rayon"]
gif = ["image_rs/gif"]
webp = ["image_rs/webp"]
pnm = ["image_rs/pnm"]
ico = ["image_rs/ico"]
bmp = ["image_rs/bmp"]
hdr = ["image_rs/hdr"]
dds = ["image_rs/dds"]
farbfeld = ["image_rs/farbfeld"]
canvas = ["lyon"]
qr_code = ["qrcode", "canvas"]
opengl = [] opengl = []
image_rs = ["kamadak-exif"] image = ["dep:image", "kamadak-exif"]
[dependencies] [dependencies]
glam = "0.21.3" glam = "0.21.3"
@ -40,45 +26,26 @@ bitflags = "1.2"
version = "1.4" version = "1.4"
features = ["derive"] features = ["derive"]
[dependencies.iced_native] [dependencies.iced_core]
version = "0.9" version = "0.8"
path = "../native" path = "../core"
[dependencies.iced_style]
version = "0.7"
path = "../style"
[dependencies.lyon]
version = "1.0"
optional = true
[dependencies.qrcode]
version = "0.12"
optional = true
default-features = false
[dependencies.image_rs]
version = "0.24"
package = "image"
default-features = false
optional = true
[dependencies.resvg]
version = "0.18"
optional = true
[dependencies.usvg]
version = "0.18"
optional = true
[dependencies.tiny-skia] [dependencies.tiny-skia]
version = "0.6" version = "0.8"
optional = true
[dependencies.image]
version = "0.24"
optional = true optional = true
[dependencies.kamadak-exif] [dependencies.kamadak-exif]
version = "0.5" version = "0.5"
optional = true optional = true
[dependencies.lyon_path]
version = "1"
optional = true
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
all-features = true all-features = true

View file

@ -1,8 +1,8 @@
//! Write a graphics backend. //! Write a graphics backend.
use iced_native::image; use iced_core::image;
use iced_native::svg; use iced_core::svg;
use iced_native::text; use iced_core::text;
use iced_native::{Font, Point, Size}; use iced_core::{Font, Point, Size};
use std::borrow::Cow; use std::borrow::Cow;

View file

@ -1,6 +1,8 @@
//! A compositor is responsible for initializing a renderer and managing window //! A compositor is responsible for initializing a renderer and managing window
//! surfaces. //! surfaces.
use crate::{Color, Error, Viewport}; use crate::{Error, Viewport};
use iced_core::Color;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use thiserror::Error; use thiserror::Error;
@ -11,7 +13,7 @@ pub trait Compositor: Sized {
type Settings: Default; type Settings: Default;
/// The iced renderer of the backend. /// The iced renderer of the backend.
type Renderer: iced_native::Renderer; type Renderer: iced_core::Renderer;
/// The surface of the backend. /// The surface of the backend.
type Surface; type Surface;
@ -28,6 +30,8 @@ pub trait Compositor: Sized {
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>( fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
&mut self, &mut self,
window: &W, window: &W,
width: u32,
height: u32,
) -> Self::Surface; ) -> Self::Surface;
/// Configures a new [`Surface`] with the given dimensions. /// Configures a new [`Surface`] with the given dimensions.

36
graphics/src/geometry.rs Normal file
View file

@ -0,0 +1,36 @@
//! Draw 2D graphics for your users.
//!
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
pub mod fill;
pub mod path;
pub mod stroke;
mod style;
mod text;
pub use fill::Fill;
pub use path::Path;
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use style::Style;
pub use text::Text;
pub use iced_core::gradient::{self, Gradient};
use crate::Primitive;
#[derive(Debug, Clone)]
pub struct Geometry(pub Primitive);
impl From<Geometry> for Primitive {
fn from(geometry: Geometry) -> Self {
geometry.0
}
}
pub trait Renderer: iced_core::Renderer {
type Geometry;
fn draw(&mut self, geometry: Vec<Self::Geometry>);
}

View file

@ -1,7 +1,7 @@
//! Fill [crate::widget::canvas::Geometry] with a certain style. //! Fill [crate::widget::canvas::Geometry] with a certain style.
use crate::{Color, Gradient}; use iced_core::{Color, Gradient};
pub use crate::widget::canvas::Style; pub use crate::geometry::Style;
/// The style used to fill geometry. /// The style used to fill geometry.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -19,14 +19,14 @@ pub struct Fill {
/// By default, it is set to `NonZero`. /// By default, it is set to `NonZero`.
/// ///
/// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty /// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
pub rule: FillRule, pub rule: Rule,
} }
impl Default for Fill { impl Default for Fill {
fn default() -> Self { fn default() -> Self {
Self { Self {
style: Style::Solid(Color::BLACK), style: Style::Solid(Color::BLACK),
rule: FillRule::NonZero, rule: Rule::NonZero,
} }
} }
} }
@ -57,16 +57,7 @@ impl From<Gradient> for Fill {
/// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty /// [1]: https://www.w3.org/TR/SVG/painting.html#FillRuleProperty
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum FillRule { pub enum Rule {
NonZero, NonZero,
EvenOdd, EvenOdd,
} }
impl From<FillRule> for lyon::tessellation::FillRule {
fn from(rule: FillRule) -> lyon::tessellation::FillRule {
match rule {
FillRule::NonZero => lyon::tessellation::FillRule::NonZero,
FillRule::EvenOdd => lyon::tessellation::FillRule::EvenOdd,
}
}
}

View file

@ -7,18 +7,16 @@ mod builder;
pub use arc::Arc; pub use arc::Arc;
pub use builder::Builder; pub use builder::Builder;
use crate::widget::canvas::LineDash; pub use lyon_path;
use iced_native::{Point, Size}; use iced_core::{Point, Size};
use lyon::algorithms::walk::{walk_along_path, RepeatedPattern, WalkerEvent};
use lyon::path::iterator::PathIterator;
/// An immutable set of points that may or may not be connected. /// An immutable set of points that may or may not be connected.
/// ///
/// A single [`Path`] can represent different kinds of 2D shapes! /// A single [`Path`] can represent different kinds of 2D shapes!
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Path { pub struct Path {
raw: lyon::path::Path, raw: lyon_path::Path,
} }
impl Path { impl Path {
@ -56,54 +54,14 @@ impl Path {
} }
#[inline] #[inline]
pub(crate) fn raw(&self) -> &lyon::path::Path { pub fn raw(&self) -> &lyon_path::Path {
&self.raw &self.raw
} }
#[inline] #[inline]
pub(crate) fn transformed( pub fn transform(&self, transform: &lyon_path::math::Transform) -> Path {
&self,
transform: &lyon::math::Transform,
) -> Path {
Path { Path {
raw: self.raw.clone().transformed(transform), raw: self.raw.clone().transformed(transform),
} }
} }
} }
pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
Path::new(|builder| {
let segments_odd = (line_dash.segments.len() % 2 == 1)
.then(|| [line_dash.segments, line_dash.segments].concat());
let mut draw_line = false;
walk_along_path(
path.raw().iter().flattened(0.01),
0.0,
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
&mut RepeatedPattern {
callback: |event: WalkerEvent<'_>| {
let point = Point {
x: event.position.x,
y: event.position.y,
};
if draw_line {
builder.line_to(point);
} else {
builder.move_to(point);
}
draw_line = !draw_line;
true
},
index: line_dash.offset,
intervals: segments_odd
.as_deref()
.unwrap_or(line_dash.segments),
},
);
})
}

Some files were not shown because too many files have changed in this diff Show more