Merge branch 'master' into feat/multi-window-support

This commit is contained in:
Héctor Ramón Jiménez 2023-11-29 22:28:31 +01:00
commit e09b4e24dd
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
331 changed files with 12085 additions and 3976 deletions

View file

@ -1,24 +1,22 @@
[package]
name = "iced_runtime"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A renderer-agnostic library for native GUIs"
license = "MIT"
repository = "https://github.com/iced-rs/iced"
description = "A renderer-agnostic runtime for iced"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[features]
debug = []
multi-window = []
[dependencies]
thiserror = "1"
iced_core.workspace = true
iced_futures.workspace = true
iced_futures.features = ["thread-pool"]
[dependencies.iced_core]
version = "0.9"
path = "../core"
[dependencies.iced_futures]
version = "0.6"
path = "../futures"
features = ["thread-pool"]
thiserror.workspace = true

View file

@ -1,12 +1,12 @@
# `iced_runtime`
[![Documentation](https://docs.rs/iced_native/badge.svg)][documentation]
[![Crates.io](https://img.shields.io/crates/v/iced_native.svg)](https://crates.io/crates/iced_native)
[![License](https://img.shields.io/crates/l/iced_native.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE)
[![Documentation](https://docs.rs/iced_runtime/badge.svg)][documentation]
[![Crates.io](https://img.shields.io/crates/v/iced_runtime.svg)](https://crates.io/crates/iced_runtime)
[![License](https://img.shields.io/crates/l/iced_runtime.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE)
[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd)
`iced_runtime` takes [`iced_core`] and builds a native runtime on top of it.
`iced_runtime` takes [`iced_core`] and builds a runtime on top of it.
[documentation]: https://docs.rs/iced_native
[documentation]: https://docs.rs/iced_runtime
[`iced_core`]: ../core
[`iced_winit`]: ../winit
[`druid`]: https://github.com/xi-editor/druid

View file

@ -4,8 +4,11 @@ mod action;
pub use action::Action;
use crate::core::widget;
use crate::futures::futures;
use crate::futures::MaybeSend;
use futures::channel::mpsc;
use futures::Stream;
use std::fmt;
use std::future::Future;
@ -40,14 +43,24 @@ impl<T> Command<T> {
/// Creates a [`Command`] that performs the action of the given future.
pub fn perform<A>(
future: impl Future<Output = T> + 'static + MaybeSend,
f: impl FnOnce(T) -> A + 'static + MaybeSend,
) -> Command<A> {
use iced_futures::futures::FutureExt;
future: impl Future<Output = A> + 'static + MaybeSend,
f: impl FnOnce(A) -> T + 'static + MaybeSend,
) -> Command<T> {
use futures::FutureExt;
Command::single(Action::Future(Box::pin(future.map(f))))
}
/// Creates a [`Command`] that runs the given stream to completion.
pub fn run<A>(
stream: impl Stream<Item = A> + 'static + MaybeSend,
f: impl Fn(A) -> T + 'static + MaybeSend,
) -> Command<T> {
use futures::StreamExt;
Command::single(Action::Stream(Box::pin(stream.map(f))))
}
/// Creates a [`Command`] that performs the actions of all the given
/// commands.
///
@ -106,3 +119,23 @@ impl<T> fmt::Debug for Command<T> {
command.fmt(f)
}
}
/// Creates a [`Command`] that produces the `Message`s published from a [`Future`]
/// to an [`mpsc::Sender`] with the given bounds.
pub fn channel<Fut, Message>(
size: usize,
f: impl FnOnce(mpsc::Sender<Message>) -> Fut + MaybeSend + 'static,
) -> Command<Message>
where
Fut: Future<Output = ()> + MaybeSend + 'static,
Message: 'static + MaybeSend,
{
use futures::future;
use futures::stream::{self, StreamExt};
let (sender, receiver) = mpsc::channel(size);
let runner = stream::once(f(sender)).filter_map(|_| future::ready(None));
Command::single(Action::Stream(Box::pin(stream::select(receiver, runner))))
}

View file

@ -18,6 +18,11 @@ pub enum Action<T> {
/// [`Future`]: iced_futures::BoxFuture
Future(iced_futures::BoxFuture<T>),
/// Run a [`Stream`] to completion.
///
/// [`Stream`]: iced_futures::BoxStream
Stream(iced_futures::BoxStream<T>),
/// Run a clipboard action.
Clipboard(clipboard::Action<T>),
@ -52,10 +57,11 @@ impl<T> Action<T> {
A: 'static,
T: 'static,
{
use iced_futures::futures::FutureExt;
use iced_futures::futures::{FutureExt, StreamExt};
match self {
Self::Future(future) => Action::Future(Box::pin(future.map(f))),
Self::Stream(stream) => Action::Stream(Box::pin(stream.map(f))),
Self::Clipboard(action) => Action::Clipboard(action.map(f)),
Self::Window(id, window) => Action::Window(id, window.map(f)),
Self::System(system) => Action::System(system.map(f)),
@ -74,6 +80,7 @@ impl<T> fmt::Debug for Action<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Future(_) => write!(f, "Action::Future"),
Self::Stream(_) => write!(f, "Action::Stream"),
Self::Clipboard(action) => {
write!(f, "Action::Clipboard({action:?})")
}

View file

@ -75,7 +75,7 @@ impl Debug {
}
pub fn startup_finished(&mut self) {
self.startup_duration = time::Instant::now() - self.startup_start;
self.startup_duration = self.startup_start.elapsed();
}
pub fn update_started(&mut self) {
@ -83,8 +83,7 @@ impl Debug {
}
pub fn update_finished(&mut self) {
self.update_durations
.push(time::Instant::now() - self.update_start);
self.update_durations.push(self.update_start.elapsed());
}
pub fn view_started(&mut self) {
@ -92,8 +91,7 @@ impl Debug {
}
pub fn view_finished(&mut self) {
self.view_durations
.push(time::Instant::now() - self.view_start);
self.view_durations.push(self.view_start.elapsed());
}
pub fn layout_started(&mut self) {
@ -101,8 +99,7 @@ impl Debug {
}
pub fn layout_finished(&mut self) {
self.layout_durations
.push(time::Instant::now() - self.layout_start);
self.layout_durations.push(self.layout_start.elapsed());
}
pub fn event_processing_started(&mut self) {
@ -110,8 +107,7 @@ impl Debug {
}
pub fn event_processing_finished(&mut self) {
self.event_durations
.push(time::Instant::now() - self.event_start);
self.event_durations.push(self.event_start.elapsed());
}
pub fn draw_started(&mut self) {
@ -119,8 +115,7 @@ impl Debug {
}
pub fn draw_finished(&mut self) {
self.draw_durations
.push(time::Instant::now() - self.draw_start);
self.draw_durations.push(self.draw_start.elapsed());
}
pub fn render_started(&mut self) {
@ -128,8 +123,7 @@ impl Debug {
}
pub fn render_finished(&mut self) {
self.render_durations
.push(time::Instant::now() - self.render_start);
self.render_durations.push(self.render_start.elapsed());
}
pub fn log_message<Message: std::fmt::Debug>(&mut self, message: &Message) {

View file

@ -2,46 +2,19 @@
//!
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/raw/improvement/update-ecosystem-and-roadmap/docs/graphs/native.png)
//!
//! `iced_native` takes [`iced_core`] and builds a native runtime on top of it,
//! featuring:
//! `iced_runtime` takes [`iced_core`] and builds a native runtime on top of it.
//!
//! - A custom layout engine, greatly inspired by [`druid`]
//! - Event handling for all the built-in widgets
//! - A renderer-agnostic API
//!
//! To achieve this, it introduces a couple of reusable interfaces:
//!
//! - A [`Widget`] trait, which is used to implement new widgets: from layout
//! requirements to event and drawing logic.
//! - A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic.
//!
//! # Usage
//! The strategy to use this crate depends on your particular use case. If you
//! want to:
//! - Implement a custom shell or integrate it in your own system, check out the
//! [`UserInterface`] type.
//! - Build a new renderer, see the [renderer] module.
//! - Build a custom widget, start at the [`Widget`] trait.
//!
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.9/core
//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.9/winit
//! [`druid`]: https://github.com/xi-editor/druid
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
//! [renderer]: crate::renderer
//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.10/core
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default,
clippy::useless_conversion
rustdoc::broken_intra_doc_links
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod clipboard;
pub mod command;

View file

@ -4,9 +4,11 @@ use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget;
use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size};
use crate::core::{
Clipboard, Event, Layout, Point, Rectangle, Shell, Size, Vector,
};
/// An [`Overlay`] container that displays nested overlays
/// An overlay container that displays nested overlays
#[allow(missing_debug_implementations)]
pub struct Nested<'a, Message, Renderer> {
overlay: overlay::Element<'a, Message, Renderer>,
@ -27,23 +29,24 @@ where
}
/// Returns the layout [`Node`] of the [`Nested`] overlay.
///
/// [`Node`]: layout::Node
pub fn layout(
&mut self,
renderer: &Renderer,
bounds: Size,
position: Point,
_position: Point,
translation: Vector,
) -> layout::Node {
fn recurse<Message, Renderer>(
element: &mut overlay::Element<'_, Message, Renderer>,
renderer: &Renderer,
bounds: Size,
position: Point,
translation: Vector,
) -> layout::Node
where
Renderer: renderer::Renderer,
{
let translation = position - Point::ORIGIN;
let node = element.layout(renderer, bounds, translation);
if let Some(mut nested) =
@ -53,7 +56,7 @@ where
node.size(),
vec![
node,
recurse(&mut nested, renderer, bounds, position),
recurse(&mut nested, renderer, bounds, translation),
],
)
} else {
@ -61,7 +64,7 @@ where
}
}
recurse(&mut self.overlay, renderer, bounds, position)
recurse(&mut self.overlay, renderer, bounds, translation)
}
/// Draws the [`Nested`] overlay using the associated `Renderer`.
@ -162,7 +165,7 @@ where
}
}
recurse(&mut self.overlay, layout, renderer, operation)
recurse(&mut self.overlay, layout, renderer, operation);
}
/// Processes a runtime [`Event`].

View file

@ -175,7 +175,7 @@ where
(uncaptured_events, command)
}
/// Applies [`widget::Operation`]s to the [`State`]
/// Applies [`Operation`]s to the [`State`]
pub fn operate(
&mut self,
renderer: &mut P::Renderer,
@ -200,7 +200,7 @@ where
match operation.finish() {
operation::Outcome::None => {}
operation::Outcome::Some(message) => {
self.queued_messages.push(message)
self.queued_messages.push(message);
}
operation::Outcome::Chain(next) => {
current_operation = Some(next);

View file

@ -5,7 +5,9 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
use crate::core::{
Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
};
use crate::overlay;
/// A set of interactive graphical elements with a specific [`Layout`].
@ -19,7 +21,7 @@ use crate::overlay;
/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
/// existing graphical application.
///
/// [`integration`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration
/// [`integration`]: https://github.com/iced-rs/iced/tree/0.10/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
@ -95,8 +97,11 @@ where
let Cache { mut state } = cache;
state.diff(root.as_widget());
let base =
renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));
let base = root.as_widget().layout(
&mut state,
renderer,
&layout::Limits::new(Size::ZERO, bounds),
);
UserInterface {
root,
@ -196,7 +201,8 @@ where
let bounds = self.bounds;
let mut overlay = manual_overlay.as_mut().unwrap();
let mut layout = overlay.layout(renderer, bounds, Point::ORIGIN);
let mut layout =
overlay.layout(renderer, bounds, Point::ORIGIN, Vector::ZERO);
let mut event_statuses = Vec::new();
for event in events.iter().cloned() {
@ -226,8 +232,9 @@ where
if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay);
self.base = renderer.layout(
&self.root,
self.base = self.root.as_widget().layout(
&mut self.state,
renderer,
&layout::Limits::new(Size::ZERO, self.bounds),
);
@ -249,8 +256,12 @@ where
overlay = manual_overlay.as_mut().unwrap();
shell.revalidate_layout(|| {
layout =
overlay.layout(renderer, bounds, Point::ORIGIN);
layout = overlay.layout(
renderer,
bounds,
Point::ORIGIN,
Vector::ZERO,
);
});
}
@ -284,12 +295,14 @@ where
(cursor, vec![event::Status::Ignored; events.len()])
};
let viewport = Rectangle::with_size(self.bounds);
let _ = ManuallyDrop::into_inner(manual_overlay);
let event_statuses = events
.iter()
.cloned()
.zip(overlay_statuses.into_iter())
.zip(overlay_statuses)
.map(|(event, overlay_status)| {
if matches!(overlay_status, event::Status::Captured) {
return overlay_status;
@ -305,6 +318,7 @@ where
renderer,
clipboard,
&mut shell,
&viewport,
);
if matches!(event_status, event::Status::Captured) {
@ -322,8 +336,9 @@ where
}
shell.revalidate_layout(|| {
self.base = renderer.layout(
&self.root,
self.base = self.root.as_widget().layout(
&mut self.state,
renderer,
&layout::Limits::new(Size::ZERO, self.bounds),
);
@ -353,7 +368,7 @@ where
/// It returns the current [`mouse::Interaction`]. You should update the
/// icon of the mouse cursor accordingly in your system.
///
/// [`Renderer`]: crate::Renderer
/// [`Renderer`]: crate::core::Renderer
///
/// # Example
/// We can finally draw our [counter](index.html#usage) by
@ -440,7 +455,12 @@ where
.map(overlay::Nested::new)
{
let overlay_layout = self.overlay.take().unwrap_or_else(|| {
overlay.layout(renderer, self.bounds, Point::ORIGIN)
overlay.layout(
renderer,
self.bounds,
Point::ORIGIN,
Vector::ZERO,
)
});
let cursor = if cursor
@ -510,17 +530,13 @@ where
renderer,
);
let overlay_bounds = layout.bounds();
renderer.with_layer(overlay_bounds, |renderer| {
overlay.draw(
renderer,
theme,
style,
Layout::new(layout),
cursor,
);
});
overlay.draw(
renderer,
theme,
style,
Layout::new(layout),
cursor,
);
if cursor
.position()
@ -562,8 +578,12 @@ where
.map(overlay::Nested::new)
{
if self.overlay.is_none() {
self.overlay =
Some(overlay.layout(renderer, self.bounds, Point::ORIGIN));
self.overlay = Some(overlay.layout(
renderer,
self.bounds,
Point::ORIGIN,
Vector::ZERO,
));
}
overlay.operate(
@ -620,7 +640,7 @@ pub enum State {
/// The [`UserInterface`] is up-to-date and can be reused without
/// rebuilding.
Updated {
/// The [`Instant`] when a redraw should be performed.
/// The [`window::RedrawRequest`] when a redraw should be performed.
redraw_request: Option<window::RedrawRequest>,
},
}

View file

@ -11,7 +11,8 @@ use crate::command::{self, Command};
use crate::core::time::Instant;
use crate::core::window::{self, Event, Icon, Level, Mode, UserAttention};
use crate::core::Size;
use crate::futures::subscription::{self, Subscription};
use crate::futures::event;
use crate::futures::Subscription;
/// Subscribes to the frames of the window of the running application.
///
@ -22,8 +23,8 @@ use crate::futures::subscription::{self, Subscription};
/// In any case, this [`Subscription`] is useful to smoothly draw application-driven
/// animations without missing any frames.
pub fn frames() -> Subscription<Instant> {
subscription::raw_events(|event, _status| match event {
iced_core::Event::Window(_, Event::RedrawRequested(at)) => Some(at),
event::listen_raw(|event, _status| match event {
crate::core::Event::Window(_, Event::RedrawRequested(at)) => Some(at),
_ => None,
})
}

View file

@ -6,7 +6,7 @@ use std::sync::Arc;
/// Data of a screenshot, captured with `window::screenshot()`.
///
/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space.
/// The `bytes` of this screenshot will always be ordered as `RGBA` in the `sRGB` color space.
#[derive(Clone)]
pub struct Screenshot {
/// The bytes of the [`Screenshot`].