From 99e0a71504456976ba88040f5d1d3bbc347694ea Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Mar 2023 06:35:20 +0100 Subject: Rename `iced_native` to `iced_runtime` --- Cargo.toml | 2 +- examples/integration/src/controls.rs | 2 +- examples/integration/src/main.rs | 4 +- native/Cargo.toml | 23 -- native/README.md | 37 --- native/src/clipboard.rs | 40 --- native/src/command.rs | 108 ------- native/src/command/action.rs | 86 ----- native/src/debug/basic.rs | 226 ------------- native/src/debug/null.rs | 47 --- native/src/font.rs | 19 -- native/src/keyboard.rs | 2 - native/src/lib.rs | 71 ----- native/src/program.rs | 33 -- native/src/program/state.rs | 194 ------------ native/src/system.rs | 6 - native/src/system/action.rs | 39 --- native/src/system/information.rs | 29 -- native/src/user_interface.rs | 592 ----------------------------------- native/src/window.rs | 23 -- native/src/window/action.rs | 147 --------- runtime/Cargo.toml | 23 ++ runtime/README.md | 37 +++ runtime/src/clipboard.rs | 53 ++++ runtime/src/command.rs | 108 +++++++ runtime/src/command/action.rs | 86 +++++ runtime/src/debug/basic.rs | 226 +++++++++++++ runtime/src/debug/null.rs | 47 +++ runtime/src/font.rs | 19 ++ runtime/src/keyboard.rs | 2 + runtime/src/lib.rs | 71 +++++ runtime/src/program.rs | 33 ++ runtime/src/program/state.rs | 194 ++++++++++++ runtime/src/system.rs | 6 + runtime/src/system/action.rs | 39 +++ runtime/src/system/information.rs | 29 ++ runtime/src/user_interface.rs | 592 +++++++++++++++++++++++++++++++++++ runtime/src/window.rs | 112 +++++++ runtime/src/window/action.rs | 147 +++++++++ src/advanced.rs | 2 +- src/application.rs | 2 +- src/lib.rs | 10 +- src/window.rs | 3 +- tiny_skia/Cargo.toml | 4 - tiny_skia/src/text.rs | 2 +- widget/Cargo.toml | 4 +- widget/src/helpers.rs | 2 +- widget/src/lib.rs | 4 +- widget/src/scrollable.rs | 2 +- widget/src/text_input.rs | 2 +- winit/Cargo.toml | 6 +- winit/src/application.rs | 14 +- winit/src/application/state.rs | 2 +- winit/src/clipboard.rs | 14 - winit/src/lib.rs | 7 +- winit/src/system.rs | 4 +- winit/src/window.rs | 92 ------ 57 files changed, 1860 insertions(+), 1870 deletions(-) delete mode 100644 native/Cargo.toml delete mode 100644 native/README.md delete mode 100644 native/src/clipboard.rs delete mode 100644 native/src/command.rs delete mode 100644 native/src/command/action.rs delete mode 100644 native/src/debug/basic.rs delete mode 100644 native/src/debug/null.rs delete mode 100644 native/src/font.rs delete mode 100644 native/src/keyboard.rs delete mode 100644 native/src/lib.rs delete mode 100644 native/src/program.rs delete mode 100644 native/src/program/state.rs delete mode 100644 native/src/system.rs delete mode 100644 native/src/system/action.rs delete mode 100644 native/src/system/information.rs delete mode 100644 native/src/user_interface.rs delete mode 100644 native/src/window.rs delete mode 100644 native/src/window/action.rs create mode 100644 runtime/Cargo.toml create mode 100644 runtime/README.md create mode 100644 runtime/src/clipboard.rs create mode 100644 runtime/src/command.rs create mode 100644 runtime/src/command/action.rs create mode 100644 runtime/src/debug/basic.rs create mode 100644 runtime/src/debug/null.rs create mode 100644 runtime/src/font.rs create mode 100644 runtime/src/keyboard.rs create mode 100644 runtime/src/lib.rs create mode 100644 runtime/src/program.rs create mode 100644 runtime/src/program/state.rs create mode 100644 runtime/src/system.rs create mode 100644 runtime/src/system/action.rs create mode 100644 runtime/src/system/information.rs create mode 100644 runtime/src/user_interface.rs create mode 100644 runtime/src/window.rs create mode 100644 runtime/src/window/action.rs delete mode 100644 winit/src/window.rs diff --git a/Cargo.toml b/Cargo.toml index 49a52311..a677569a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ members = [ "core", "futures", "graphics", - "native", + "runtime", "renderer", "style", "tiny_skia", diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 16e21709..5849f730 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,7 +1,7 @@ use iced_wgpu::Renderer; use iced_widget::{slider, text_input, Column, Row, Text}; use iced_winit::core::{Alignment, Color, Element, Length}; -use iced_winit::native::{Command, Program}; +use iced_winit::runtime::{Command, Program}; use iced_winit::style::Theme; pub struct Controls { diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index c1f1f076..9707eda7 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -8,8 +8,8 @@ use iced_wgpu::graphics::Viewport; use iced_wgpu::{wgpu, Backend, Renderer, Settings}; use iced_winit::core::renderer; use iced_winit::core::{Color, Size}; -use iced_winit::native::program; -use iced_winit::native::Debug; +use iced_winit::runtime::program; +use iced_winit::runtime::Debug; use iced_winit::style::Theme; use iced_winit::{conversion, futures, winit, Clipboard}; diff --git a/native/Cargo.toml b/native/Cargo.toml deleted file mode 100644 index bc4e7ca1..00000000 --- a/native/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "iced_native" -version = "0.9.1" -authors = ["Héctor Ramón Jiménez "] -edition = "2021" -description = "A renderer-agnostic library for native GUIs" -license = "MIT" -repository = "https://github.com/iced-rs/iced" - -[features] -debug = [] - -[dependencies] -thiserror = "1" - -[dependencies.iced_core] -version = "0.8" -path = "../core" - -[dependencies.iced_futures] -version = "0.6" -path = "../futures" -features = ["thread-pool"] diff --git a/native/README.md b/native/README.md deleted file mode 100644 index 996daa76..00000000 --- a/native/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# `iced_native` -[![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) -[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) - -`iced_native` takes [`iced_core`] and builds a native runtime on top of it, featuring: -- 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 bunch 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. -- A `Windowed` trait, leveraging [`raw-window-handle`], which can be implemented by graphical renderers that target _windows_. Window-based shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic. - -

- The native target -

- -[documentation]: https://docs.rs/iced_native -[`iced_core`]: ../core -[`iced_winit`]: ../winit -[`druid`]: https://github.com/xi-editor/druid -[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle - -## Installation -Add `iced_native` as a dependency in your `Cargo.toml`: - -```toml -iced_native = "0.9" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs deleted file mode 100644 index e727c4a7..00000000 --- a/native/src/clipboard.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Access the clipboard. -use iced_futures::MaybeSend; - -use std::fmt; - -/// A clipboard action to be performed by some [`Command`]. -/// -/// [`Command`]: crate::Command -pub enum Action { - /// Read the clipboard and produce `T` with the result. - Read(Box) -> T>), - - /// Write the given contents to the clipboard. - Write(String), -} - -impl Action { - /// Maps the output of a clipboard [`Action`] using the provided closure. - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - where - T: 'static, - { - match self { - Self::Read(o) => Action::Read(Box::new(move |s| f(o(s)))), - Self::Write(content) => Action::Write(content), - } - } -} - -impl fmt::Debug for Action { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Read(_) => write!(f, "Action::Read"), - Self::Write(_) => write!(f, "Action::Write"), - } - } -} diff --git a/native/src/command.rs b/native/src/command.rs deleted file mode 100644 index cd4c51ff..00000000 --- a/native/src/command.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Run asynchronous actions. -mod action; - -pub use action::Action; - -use crate::core::widget; -use crate::futures::MaybeSend; - -use std::fmt; -use std::future::Future; - -/// A set of asynchronous actions to be performed by some runtime. -#[must_use = "`Command` must be returned to runtime to take effect"] -pub struct Command(Internal>); - -#[derive(Debug)] -enum Internal { - None, - Single(T), - Batch(Vec), -} - -impl Command { - /// 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: Action) -> Self { - Self(Internal::Single(action)) - } - - /// Creates a [`Command`] that performs a [`widget::Operation`]. - pub fn widget(operation: impl widget::Operation + 'static) -> Self { - Self::single(Action::Widget(Box::new(operation))) - } - - /// Creates a [`Command`] that performs the action of the given future. - pub fn perform( - future: impl Future + 'static + MaybeSend, - f: impl FnOnce(T) -> A + 'static + MaybeSend, - ) -> Command { - use iced_futures::futures::FutureExt; - - Command::single(Action::Future(Box::pin(future.map(f)))) - } - - /// 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>) -> 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( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync + Clone, - ) -> Command - where - T: 'static, - A: 'static, - { - match self.0 { - Internal::None => Command::none(), - Internal::Single(action) => Command::single(action.map(f)), - Internal::Batch(batch) => Command(Internal::Batch( - batch - .into_iter() - .map(|action| action.map(f.clone())) - .collect(), - )), - } - } - - /// Returns all of the actions of the [`Command`]. - pub fn actions(self) -> Vec> { - let Command(command) = self; - - match command { - Internal::None => Vec::new(), - Internal::Single(action) => vec![action], - Internal::Batch(batch) => batch, - } - } -} - -impl fmt::Debug for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Command(command) = self; - - command.fmt(f) - } -} diff --git a/native/src/command/action.rs b/native/src/command/action.rs deleted file mode 100644 index 6c74f0ef..00000000 --- a/native/src/command/action.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::clipboard; -use crate::core::widget; -use crate::font; -use crate::system; -use crate::window; - -use iced_futures::MaybeSend; - -use std::borrow::Cow; -use std::fmt; - -/// An action that a [`Command`] can perform. -/// -/// [`Command`]: crate::Command -pub enum Action { - /// Run a [`Future`] to completion. - /// - /// [`Future`]: iced_futures::BoxFuture - Future(iced_futures::BoxFuture), - - /// Run a clipboard action. - Clipboard(clipboard::Action), - - /// Run a window action. - Window(window::Action), - - /// Run a system action. - System(system::Action), - - /// Run a widget action. - Widget(Box>), - - /// Load a font from its bytes. - LoadFont { - /// The bytes of the font to load. - bytes: Cow<'static, [u8]>, - - /// The message to produce when the font has been loaded. - tagger: Box) -> T>, - }, -} - -impl Action { - /// Applies a transformation to the result of a [`Command`]. - /// - /// [`Command`]: crate::Command - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - where - A: 'static, - T: 'static, - { - use iced_futures::futures::FutureExt; - - match self { - Self::Future(future) => Action::Future(Box::pin(future.map(f))), - Self::Clipboard(action) => Action::Clipboard(action.map(f)), - Self::Window(window) => Action::Window(window.map(f)), - Self::System(system) => Action::System(system.map(f)), - Self::Widget(operation) => { - Action::Widget(Box::new(widget::operation::map(operation, f))) - } - Self::LoadFont { bytes, tagger } => Action::LoadFont { - bytes, - tagger: Box::new(move |result| f(tagger(result))), - }, - } - } -} - -impl fmt::Debug for Action { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Future(_) => write!(f, "Action::Future"), - Self::Clipboard(action) => { - write!(f, "Action::Clipboard({action:?})") - } - Self::Window(action) => write!(f, "Action::Window({action:?})"), - Self::System(action) => write!(f, "Action::System({action:?})"), - Self::Widget(_action) => write!(f, "Action::Widget"), - Self::LoadFont { .. } => write!(f, "Action::LoadFont"), - } - } -} diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs deleted file mode 100644 index 32f725a1..00000000 --- a/native/src/debug/basic.rs +++ /dev/null @@ -1,226 +0,0 @@ -#![allow(missing_docs)] -use crate::core::time; - -use std::collections::VecDeque; - -/// A bunch of time measurements for debugging purposes. -#[derive(Debug)] -pub struct Debug { - is_enabled: bool, - - startup_start: time::Instant, - startup_duration: time::Duration, - - update_start: time::Instant, - update_durations: TimeBuffer, - - view_start: time::Instant, - view_durations: TimeBuffer, - - layout_start: time::Instant, - layout_durations: TimeBuffer, - - event_start: time::Instant, - event_durations: TimeBuffer, - - draw_start: time::Instant, - draw_durations: TimeBuffer, - - render_start: time::Instant, - render_durations: TimeBuffer, - - message_count: usize, - last_messages: VecDeque, -} - -impl Debug { - /// Creates a new [`struct@Debug`]. - pub fn new() -> Self { - let now = time::Instant::now(); - - Self { - is_enabled: false, - startup_start: now, - startup_duration: time::Duration::from_secs(0), - - update_start: now, - update_durations: TimeBuffer::new(200), - - view_start: now, - view_durations: TimeBuffer::new(200), - - layout_start: now, - layout_durations: TimeBuffer::new(200), - - event_start: now, - event_durations: TimeBuffer::new(200), - - draw_start: now, - draw_durations: TimeBuffer::new(200), - - render_start: now, - render_durations: TimeBuffer::new(50), - - message_count: 0, - last_messages: VecDeque::new(), - } - } - - pub fn toggle(&mut self) { - self.is_enabled = !self.is_enabled; - } - - pub fn startup_started(&mut self) { - self.startup_start = time::Instant::now(); - } - - pub fn startup_finished(&mut self) { - self.startup_duration = time::Instant::now() - self.startup_start; - } - - pub fn update_started(&mut self) { - self.update_start = time::Instant::now(); - } - - pub fn update_finished(&mut self) { - self.update_durations - .push(time::Instant::now() - self.update_start); - } - - pub fn view_started(&mut self) { - self.view_start = time::Instant::now(); - } - - pub fn view_finished(&mut self) { - self.view_durations - .push(time::Instant::now() - self.view_start); - } - - pub fn layout_started(&mut self) { - self.layout_start = time::Instant::now(); - } - - pub fn layout_finished(&mut self) { - self.layout_durations - .push(time::Instant::now() - self.layout_start); - } - - pub fn event_processing_started(&mut self) { - self.event_start = time::Instant::now(); - } - - pub fn event_processing_finished(&mut self) { - self.event_durations - .push(time::Instant::now() - self.event_start); - } - - pub fn draw_started(&mut self) { - self.draw_start = time::Instant::now(); - } - - pub fn draw_finished(&mut self) { - self.draw_durations - .push(time::Instant::now() - self.draw_start); - } - - pub fn render_started(&mut self) { - self.render_start = time::Instant::now(); - } - - pub fn render_finished(&mut self) { - self.render_durations - .push(time::Instant::now() - self.render_start); - } - - pub fn log_message(&mut self, message: &Message) { - self.last_messages.push_back(format!("{message:?}")); - - if self.last_messages.len() > 10 { - let _ = self.last_messages.pop_front(); - } - - self.message_count += 1; - } - - pub fn overlay(&self) -> Vec { - if !self.is_enabled { - return Vec::new(); - } - - let mut lines = Vec::new(); - - fn key_value(key: &str, value: T) -> String { - format!("{key} {value:?}") - } - - lines.push(format!( - "{} {} - {}", - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_VERSION"), - env!("CARGO_PKG_REPOSITORY"), - )); - lines.push(key_value("Startup:", self.startup_duration)); - lines.push(key_value("Update:", self.update_durations.average())); - lines.push(key_value("View:", self.view_durations.average())); - lines.push(key_value("Layout:", self.layout_durations.average())); - lines.push(key_value( - "Event processing:", - self.event_durations.average(), - )); - lines.push(key_value( - "Primitive generation:", - self.draw_durations.average(), - )); - lines.push(key_value("Render:", self.render_durations.average())); - lines.push(key_value("Message count:", self.message_count)); - lines.push(String::from("Last messages:")); - lines.extend(self.last_messages.iter().map(|msg| { - if msg.len() <= 100 { - format!(" {msg}") - } else { - format!(" {msg:.100}...") - } - })); - - lines - } -} - -impl Default for Debug { - fn default() -> Self { - Self::new() - } -} - -#[derive(Debug)] -struct TimeBuffer { - head: usize, - size: usize, - contents: Vec, -} - -impl TimeBuffer { - fn new(capacity: usize) -> TimeBuffer { - TimeBuffer { - head: 0, - size: 0, - contents: vec![time::Duration::from_secs(0); capacity], - } - } - - fn push(&mut self, duration: time::Duration) { - self.head = (self.head + 1) % self.contents.len(); - self.contents[self.head] = duration; - self.size = (self.size + 1).min(self.contents.len()); - } - - fn average(&self) -> time::Duration { - let sum: time::Duration = if self.size == self.contents.len() { - self.contents[..].iter().sum() - } else { - self.contents[..self.size].iter().sum() - }; - - sum / self.size.max(1) as u32 - } -} diff --git a/native/src/debug/null.rs b/native/src/debug/null.rs deleted file mode 100644 index 2db0eebb..00000000 --- a/native/src/debug/null.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![allow(missing_docs)] -#[derive(Debug, Default)] -pub struct Debug; - -impl Debug { - pub fn new() -> Self { - Self - } - - pub fn startup_started(&mut self) {} - - pub fn startup_finished(&mut self) {} - - pub fn update_started(&mut self) {} - - pub fn update_finished(&mut self) {} - - pub fn view_started(&mut self) {} - - pub fn view_finished(&mut self) {} - - pub fn layout_started(&mut self) {} - - pub fn layout_finished(&mut self) {} - - pub fn event_processing_started(&mut self) {} - - pub fn event_processing_finished(&mut self) {} - - pub fn draw_started(&mut self) {} - - pub fn draw_finished(&mut self) {} - - pub fn render_started(&mut self) {} - - pub fn render_finished(&mut self) {} - - pub fn log_message( - &mut self, - _message: &Message, - ) { - } - - pub fn overlay(&self) -> Vec { - Vec::new() - } -} diff --git a/native/src/font.rs b/native/src/font.rs deleted file mode 100644 index 15359694..00000000 --- a/native/src/font.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Load and use fonts. -pub use iced_core::font::*; - -use crate::command::{self, Command}; -use std::borrow::Cow; - -/// An error while loading a font. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Error {} - -/// Load a font from its bytes. -pub fn load( - bytes: impl Into>, -) -> Command> { - Command::single(command::Action::LoadFont { - bytes: bytes.into(), - tagger: Box::new(std::convert::identity), - }) -} diff --git a/native/src/keyboard.rs b/native/src/keyboard.rs deleted file mode 100644 index 012538e3..00000000 --- a/native/src/keyboard.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! Track keyboard events. -pub use iced_core::keyboard::*; diff --git a/native/src/lib.rs b/native/src/lib.rs deleted file mode 100644 index aa45e57a..00000000 --- a/native/src/lib.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! A renderer-agnostic native GUI runtime. -//! -//! ![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: -//! -//! - 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.8/core -//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.8/winit -//! [`druid`]: https://github.com/xi-editor/druid -//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle -//! [renderer]: crate::renderer -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![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 -)] -#![forbid(unsafe_code, rust_2018_idioms)] -#![cfg_attr(docsrs, feature(doc_cfg))] -pub mod clipboard; -pub mod command; -pub mod font; -pub mod keyboard; -pub mod program; -pub mod system; -pub mod user_interface; -pub mod window; - -// We disable debug capabilities on release builds unless the `debug` feature -// is explicitly enabled. -#[cfg(feature = "debug")] -#[path = "debug/basic.rs"] -mod debug; -#[cfg(not(feature = "debug"))] -#[path = "debug/null.rs"] -mod debug; - -pub use iced_core as core; -pub use iced_futures as futures; - -pub use command::Command; -pub use debug::Debug; -pub use font::Font; -pub use program::Program; -pub use user_interface::UserInterface; diff --git a/native/src/program.rs b/native/src/program.rs deleted file mode 100644 index 44585cc5..00000000 --- a/native/src/program.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Build interactive programs using The Elm Architecture. -use crate::Command; - -use iced_core::text; -use iced_core::{Element, Renderer}; - -mod state; - -pub use state::State; - -/// The core of a user interface application following The Elm Architecture. -pub trait Program: Sized { - /// The graphics backend to use to draw the [`Program`]. - type Renderer: Renderer + text::Renderer; - - /// The type of __messages__ your [`Program`] will produce. - type Message: std::fmt::Debug + Send; - - /// Handles a __message__ and updates the state of the [`Program`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the - /// background by shells. - fn update(&mut self, message: Self::Message) -> Command; - - /// Returns the widgets to display in the [`Program`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view(&self) -> Element<'_, Self::Message, Self::Renderer>; -} diff --git a/native/src/program/state.rs b/native/src/program/state.rs deleted file mode 100644 index 2fa9934d..00000000 --- a/native/src/program/state.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::core::event::{self, Event}; -use crate::core::mouse; -use crate::core::renderer; -use crate::core::{Clipboard, Point, Size}; -use crate::user_interface::{self, UserInterface}; -use crate::{Command, Debug, Program}; - -/// The execution state of a [`Program`]. It leverages caching, event -/// processing, and rendering primitive storage. -#[allow(missing_debug_implementations)] -pub struct State

-where - P: Program + 'static, -{ - program: P, - cache: Option, - queued_events: Vec, - queued_messages: Vec, - mouse_interaction: mouse::Interaction, -} - -impl

State

-where - P: Program + 'static, -{ - /// Creates a new [`State`] with the provided [`Program`], initializing its - /// primitive with the given logical bounds and renderer. - pub fn new( - mut program: P, - bounds: Size, - renderer: &mut P::Renderer, - debug: &mut Debug, - ) -> Self { - let user_interface = build_user_interface( - &mut program, - user_interface::Cache::default(), - renderer, - bounds, - debug, - ); - - let cache = Some(user_interface.into_cache()); - - State { - program, - cache, - queued_events: Vec::new(), - queued_messages: Vec::new(), - mouse_interaction: mouse::Interaction::Idle, - } - } - - /// Returns a reference to the [`Program`] of the [`State`]. - pub fn program(&self) -> &P { - &self.program - } - - /// Queues an event in the [`State`] for processing during an [`update`]. - /// - /// [`update`]: Self::update - pub fn queue_event(&mut self, event: Event) { - self.queued_events.push(event); - } - - /// Queues a message in the [`State`] for processing during an [`update`]. - /// - /// [`update`]: Self::update - pub fn queue_message(&mut self, message: P::Message) { - self.queued_messages.push(message); - } - - /// Returns whether the event queue of the [`State`] is empty or not. - pub fn is_queue_empty(&self) -> bool { - self.queued_events.is_empty() && self.queued_messages.is_empty() - } - - /// Returns the current [`mouse::Interaction`] of the [`State`]. - pub fn mouse_interaction(&self) -> mouse::Interaction { - self.mouse_interaction - } - - /// Processes all the queued events and messages, rebuilding and redrawing - /// the widgets of the linked [`Program`] if necessary. - /// - /// Returns a list containing the instances of [`Event`] that were not - /// captured by any widget, and the [`Command`] obtained from [`Program`] - /// after updating it, only if an update was necessary. - pub fn update( - &mut self, - bounds: Size, - cursor_position: Point, - renderer: &mut P::Renderer, - theme: &::Theme, - style: &renderer::Style, - clipboard: &mut dyn Clipboard, - debug: &mut Debug, - ) -> (Vec, Option>) { - let mut user_interface = build_user_interface( - &mut self.program, - self.cache.take().unwrap(), - renderer, - bounds, - debug, - ); - - debug.event_processing_started(); - let mut messages = Vec::new(); - - let (_, event_statuses) = user_interface.update( - &self.queued_events, - cursor_position, - renderer, - clipboard, - &mut messages, - ); - - let uncaptured_events = self - .queued_events - .iter() - .zip(event_statuses) - .filter_map(|(event, status)| { - matches!(status, event::Status::Ignored).then_some(event) - }) - .cloned() - .collect(); - - self.queued_events.clear(); - messages.append(&mut self.queued_messages); - debug.event_processing_finished(); - - let command = if messages.is_empty() { - debug.draw_started(); - self.mouse_interaction = - user_interface.draw(renderer, theme, style, cursor_position); - debug.draw_finished(); - - self.cache = Some(user_interface.into_cache()); - - None - } else { - // When there are messages, we are forced to rebuild twice - // for now :^) - let temp_cache = user_interface.into_cache(); - - let commands = - Command::batch(messages.into_iter().map(|message| { - debug.log_message(&message); - - debug.update_started(); - let command = self.program.update(message); - debug.update_finished(); - - command - })); - - let mut user_interface = build_user_interface( - &mut self.program, - temp_cache, - renderer, - bounds, - debug, - ); - - debug.draw_started(); - self.mouse_interaction = - user_interface.draw(renderer, theme, style, cursor_position); - debug.draw_finished(); - - self.cache = Some(user_interface.into_cache()); - - Some(commands) - }; - - (uncaptured_events, command) - } -} - -fn build_user_interface<'a, P: Program>( - program: &'a mut P, - cache: user_interface::Cache, - renderer: &mut P::Renderer, - size: Size, - debug: &mut Debug, -) -> UserInterface<'a, P::Message, P::Renderer> { - debug.view_started(); - let view = program.view(); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - user_interface -} diff --git a/native/src/system.rs b/native/src/system.rs deleted file mode 100644 index 61c8ff29..00000000 --- a/native/src/system.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Access the native system. -mod action; -mod information; - -pub use action::Action; -pub use information::Information; diff --git a/native/src/system/action.rs b/native/src/system/action.rs deleted file mode 100644 index dea9536f..00000000 --- a/native/src/system/action.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::system; - -use iced_futures::MaybeSend; -use std::fmt; - -/// An operation to be performed on the system. -pub enum Action { - /// Query system information and produce `T` with the result. - QueryInformation(Box>), -} - -pub trait Closure: Fn(system::Information) -> T + MaybeSend {} - -impl Closure for T where T: Fn(system::Information) -> O + MaybeSend {} - -impl Action { - /// Maps the output of a system [`Action`] using the provided closure. - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - where - T: 'static, - { - match self { - Self::QueryInformation(o) => { - Action::QueryInformation(Box::new(move |s| f(o(s)))) - } - } - } -} - -impl fmt::Debug for Action { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::QueryInformation(_) => write!(f, "Action::QueryInformation"), - } - } -} diff --git a/native/src/system/information.rs b/native/src/system/information.rs deleted file mode 100644 index 93e7a5a4..00000000 --- a/native/src/system/information.rs +++ /dev/null @@ -1,29 +0,0 @@ -/// Contains informations about the system (e.g. system name, processor, memory, graphics adapter). -#[derive(Clone, Debug)] -pub struct Information { - /// The operating system name - pub system_name: Option, - /// Operating system kernel version - pub system_kernel: Option, - /// Long operating system version - /// - /// Examples: - /// - MacOS 10.15 Catalina - /// - Windows 10 Pro - /// - Ubuntu 20.04 LTS (Focal Fossa) - pub system_version: Option, - /// Short operating system version number - pub system_short_version: Option, - /// Detailed processor model information - pub cpu_brand: String, - /// The number of physical cores on the processor - pub cpu_cores: Option, - /// Total RAM size, KB - pub memory_total: u64, - /// Memory used by this process, KB - pub memory_used: Option, - /// Underlying graphics backend for rendering - pub graphics_backend: String, - /// Model information for the active graphics adapter - pub graphics_adapter: String, -} diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs deleted file mode 100644 index 315027fa..00000000 --- a/native/src/user_interface.rs +++ /dev/null @@ -1,592 +0,0 @@ -//! Implement your own event loop to drive a user interface. -use crate::core::event::{self, Event}; -use crate::core::layout; -use crate::core::mouse; -use crate::core::renderer; -use crate::core::widget; -use crate::core::window; -use crate::core::{Clipboard, Point, Rectangle, Size, Vector}; -use crate::core::{Element, Layout, Shell}; - -/// A set of interactive graphical elements with a specific [`Layout`]. -/// -/// It can be updated and drawn. -/// -/// Iced tries to avoid dictating how to write your event loop. You are in -/// charge of using this type in your system in any way you want. -/// -/// # Example -/// The [`integration_opengl`] & [`integration_wgpu`] examples use a -/// [`UserInterface`] to integrate Iced in an existing graphical application. -/// -/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_opengl -/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_wgpu -#[allow(missing_debug_implementations)] -pub struct UserInterface<'a, Message, Renderer> { - root: Element<'a, Message, Renderer>, - base: layout::Node, - state: widget::Tree, - overlay: Option, - bounds: Size, -} - -impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> -where - Renderer: iced_core::Renderer, -{ - /// Builds a user interface for an [`Element`]. - /// - /// It is able to avoid expensive computations when using a [`Cache`] - /// obtained from a previous instance of a [`UserInterface`]. - /// - /// # Example - /// Imagine we want to build a [`UserInterface`] for - /// [the counter example that we previously wrote](index.html#usage). Here - /// is naive way to set up our application loop: - /// - /// ```no_run - /// # mod iced_wgpu { - /// # pub use iced_native::core::renderer::Null as Renderer; - /// # } - /// # - /// # pub struct Counter; - /// # - /// # impl Counter { - /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } - /// # pub fn update(&mut self, _: ()) {} - /// # } - /// use iced_native::core::Size; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// - /// // Initialization - /// let mut counter = Counter::new(); - /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); - /// let mut window_size = Size::new(1024.0, 768.0); - /// - /// // Application loop - /// loop { - /// // Process system events here... - /// - /// // Build the user interface - /// let user_interface = UserInterface::build( - /// counter.view(), - /// window_size, - /// cache, - /// &mut renderer, - /// ); - /// - /// // Update and draw the user interface here... - /// // ... - /// - /// // Obtain the cache for the next iteration - /// cache = user_interface.into_cache(); - /// } - /// ``` - pub fn build>>( - root: E, - bounds: Size, - cache: Cache, - renderer: &mut Renderer, - ) -> Self { - let root = root.into(); - - let Cache { mut state } = cache; - state.diff(root.as_widget()); - - let base = - renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); - - UserInterface { - root, - base, - state, - overlay: None, - bounds, - } - } - - /// Updates the [`UserInterface`] by processing each provided [`Event`]. - /// - /// It returns __messages__ that may have been produced as a result of user - /// interactions. You should feed these to your __update logic__. - /// - /// # Example - /// Let's allow our [counter](index.html#usage) to change state by - /// completing [the previous example](#example): - /// - /// ```no_run - /// # mod iced_wgpu { - /// # pub use iced_native::core::renderer::Null as Renderer; - /// # } - /// # - /// # pub struct Counter; - /// # - /// # impl Counter { - /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } - /// # pub fn update(&mut self, _: ()) {} - /// # } - /// use iced_native::core::{clipboard, Size, Point}; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::Renderer; - /// - /// let mut counter = Counter::new(); - /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); - /// let mut window_size = Size::new(1024.0, 768.0); - /// let mut cursor_position = Point::default(); - /// let mut clipboard = clipboard::Null; - /// - /// // Initialize our event storage - /// let mut events = Vec::new(); - /// let mut messages = Vec::new(); - /// - /// loop { - /// // Obtain system events... - /// - /// let mut user_interface = UserInterface::build( - /// counter.view(), - /// window_size, - /// cache, - /// &mut renderer, - /// ); - /// - /// // Update the user interface - /// let (state, event_statuses) = user_interface.update( - /// &events, - /// cursor_position, - /// &mut renderer, - /// &mut clipboard, - /// &mut messages - /// ); - /// - /// cache = user_interface.into_cache(); - /// - /// // Process the produced messages - /// for message in messages.drain(..) { - /// counter.update(message); - /// } - /// } - /// ``` - pub fn update( - &mut self, - events: &[Event], - cursor_position: Point, - renderer: &mut Renderer, - clipboard: &mut dyn Clipboard, - messages: &mut Vec, - ) -> (State, Vec) { - use std::mem::ManuallyDrop; - - let mut outdated = false; - let mut redraw_request = None; - - let mut manual_overlay = - ManuallyDrop::new(self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - )); - - let (base_cursor, overlay_statuses) = if manual_overlay.is_some() { - let bounds = self.bounds; - - let mut overlay = manual_overlay.as_mut().unwrap(); - let mut layout = overlay.layout(renderer, bounds, Vector::ZERO); - let mut event_statuses = Vec::new(); - - for event in events.iter().cloned() { - let mut shell = Shell::new(messages); - - let event_status = overlay.on_event( - event, - Layout::new(&layout), - cursor_position, - renderer, - clipboard, - &mut shell, - ); - - event_statuses.push(event_status); - - match (redraw_request, shell.redraw_request()) { - (None, Some(at)) => { - redraw_request = Some(at); - } - (Some(current), Some(new)) if new < current => { - redraw_request = Some(new); - } - _ => {} - } - - if shell.is_layout_invalid() { - let _ = ManuallyDrop::into_inner(manual_overlay); - - self.base = renderer.layout( - &self.root, - &layout::Limits::new(Size::ZERO, self.bounds), - ); - - manual_overlay = - ManuallyDrop::new(self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - )); - - if manual_overlay.is_none() { - break; - } - - overlay = manual_overlay.as_mut().unwrap(); - - shell.revalidate_layout(|| { - layout = overlay.layout(renderer, bounds, Vector::ZERO); - }); - } - - if shell.are_widgets_invalid() { - outdated = true; - } - } - - let base_cursor = manual_overlay - .as_ref() - .filter(|overlay| { - overlay.is_over(Layout::new(&layout), cursor_position) - }) - .map(|_| { - // TODO: Type-safe cursor availability - Point::new(-1.0, -1.0) - }) - .unwrap_or(cursor_position); - - self.overlay = Some(layout); - - (base_cursor, event_statuses) - } else { - (cursor_position, vec![event::Status::Ignored; events.len()]) - }; - - let _ = ManuallyDrop::into_inner(manual_overlay); - - let event_statuses = events - .iter() - .cloned() - .zip(overlay_statuses.into_iter()) - .map(|(event, overlay_status)| { - if matches!(overlay_status, event::Status::Captured) { - return overlay_status; - } - - let mut shell = Shell::new(messages); - - let event_status = self.root.as_widget_mut().on_event( - &mut self.state, - event, - Layout::new(&self.base), - base_cursor, - renderer, - clipboard, - &mut shell, - ); - - if matches!(event_status, event::Status::Captured) { - self.overlay = None; - } - - match (redraw_request, shell.redraw_request()) { - (None, Some(at)) => { - redraw_request = Some(at); - } - (Some(current), Some(new)) if new < current => { - redraw_request = Some(new); - } - _ => {} - } - - shell.revalidate_layout(|| { - self.base = renderer.layout( - &self.root, - &layout::Limits::new(Size::ZERO, self.bounds), - ); - - self.overlay = None; - }); - - if shell.are_widgets_invalid() { - outdated = true; - } - - event_status.merge(overlay_status) - }) - .collect(); - - ( - if outdated { - State::Outdated - } else { - State::Updated { redraw_request } - }, - event_statuses, - ) - } - - /// Draws the [`UserInterface`] with the provided [`Renderer`]. - /// - /// It returns the current [`mouse::Interaction`]. You should update the - /// icon of the mouse cursor accordingly in your system. - /// - /// [`Renderer`]: crate::Renderer - /// - /// # Example - /// We can finally draw our [counter](index.html#usage) by - /// [completing the last example](#example-1): - /// - /// ```no_run - /// # mod iced_wgpu { - /// # pub use iced_native::core::renderer::Null as Renderer; - /// # pub type Theme = (); - /// # } - /// # - /// # pub struct Counter; - /// # - /// # impl Counter { - /// # pub fn new() -> Self { Counter } - /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() } - /// # pub fn update(&mut self, _: ()) {} - /// # } - /// use iced_native::core::clipboard; - /// use iced_native::core::renderer; - /// use iced_native::core::{Element, Size, Point}; - /// use iced_native::user_interface::{self, UserInterface}; - /// use iced_wgpu::{Renderer, Theme}; - /// - /// let mut counter = Counter::new(); - /// let mut cache = user_interface::Cache::new(); - /// let mut renderer = Renderer::new(); - /// let mut window_size = Size::new(1024.0, 768.0); - /// let mut cursor_position = Point::default(); - /// let mut clipboard = clipboard::Null; - /// let mut events = Vec::new(); - /// let mut messages = Vec::new(); - /// let mut theme = Theme::default(); - /// - /// loop { - /// // Obtain system events... - /// - /// let mut user_interface = UserInterface::build( - /// counter.view(), - /// window_size, - /// cache, - /// &mut renderer, - /// ); - /// - /// // Update the user interface - /// let event_statuses = user_interface.update( - /// &events, - /// cursor_position, - /// &mut renderer, - /// &mut clipboard, - /// &mut messages - /// ); - /// - /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position); - /// - /// cache = user_interface.into_cache(); - /// - /// for message in messages.drain(..) { - /// counter.update(message); - /// } - /// - /// // Update mouse cursor icon... - /// // Flush rendering operations... - /// } - /// ``` - pub fn draw( - &mut self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - cursor_position: Point, - ) -> mouse::Interaction { - // TODO: Move to shell level (?) - renderer.clear(); - - let viewport = Rectangle::with_size(self.bounds); - - let base_cursor = if let Some(overlay) = self - .root - .as_widget_mut() - .overlay(&mut self.state, Layout::new(&self.base), renderer) - { - let overlay_layout = self.overlay.take().unwrap_or_else(|| { - overlay.layout(renderer, self.bounds, Vector::ZERO) - }); - - let new_cursor_position = if overlay - .is_over(Layout::new(&overlay_layout), cursor_position) - { - Point::new(-1.0, -1.0) - } else { - cursor_position - }; - - self.overlay = Some(overlay_layout); - - new_cursor_position - } else { - cursor_position - }; - - self.root.as_widget().draw( - &self.state, - renderer, - theme, - style, - Layout::new(&self.base), - base_cursor, - &viewport, - ); - - let base_interaction = self.root.as_widget().mouse_interaction( - &self.state, - Layout::new(&self.base), - cursor_position, - &viewport, - renderer, - ); - - let Self { - overlay, - root, - base, - .. - } = self; - - // TODO: Currently, we need to call Widget::overlay twice to - // implement the painter's algorithm properly. - // - // Once we have a proper persistent widget tree, we should be able to - // avoid this additional call. - overlay - .as_ref() - .and_then(|layout| { - root.as_widget_mut() - .overlay(&mut self.state, Layout::new(base), renderer) - .map(|overlay| { - let overlay_interaction = overlay.mouse_interaction( - Layout::new(layout), - cursor_position, - &viewport, - renderer, - ); - - let overlay_bounds = layout.bounds(); - - renderer.with_layer(overlay_bounds, |renderer| { - overlay.draw( - renderer, - theme, - style, - Layout::new(layout), - cursor_position, - ); - }); - - if overlay.is_over(Layout::new(layout), cursor_position) - { - overlay_interaction - } else { - base_interaction - } - }) - }) - .unwrap_or(base_interaction) - } - - /// Applies a [`widget::Operation`] to the [`UserInterface`]. - pub fn operate( - &mut self, - renderer: &Renderer, - operation: &mut dyn widget::Operation, - ) { - self.root.as_widget().operate( - &mut self.state, - Layout::new(&self.base), - renderer, - operation, - ); - - if let Some(mut overlay) = self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { - if self.overlay.is_none() { - self.overlay = - Some(overlay.layout(renderer, self.bounds, Vector::ZERO)); - } - - overlay.operate( - Layout::new(self.overlay.as_ref().unwrap()), - renderer, - operation, - ); - } - } - - /// Relayouts and returns a new [`UserInterface`] using the provided - /// bounds. - pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { - Self::build(self.root, bounds, Cache { state: self.state }, renderer) - } - - /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the - /// process. - pub fn into_cache(self) -> Cache { - Cache { state: self.state } - } -} - -/// Reusable data of a specific [`UserInterface`]. -#[derive(Debug)] -pub struct Cache { - state: widget::Tree, -} - -impl Cache { - /// Creates an empty [`Cache`]. - /// - /// You should use this to initialize a [`Cache`] before building your first - /// [`UserInterface`]. - pub fn new() -> Cache { - Cache { - state: widget::Tree::empty(), - } - } -} - -impl Default for Cache { - fn default() -> Cache { - Cache::new() - } -} - -/// The resulting state after updating a [`UserInterface`]. -#[derive(Debug, Clone, Copy)] -pub enum State { - /// The [`UserInterface`] is outdated and needs to be rebuilt. - Outdated, - - /// The [`UserInterface`] is up-to-date and can be reused without - /// rebuilding. - Updated { - /// The [`Instant`] when a redraw should be performed. - redraw_request: Option, - }, -} diff --git a/native/src/window.rs b/native/src/window.rs deleted file mode 100644 index aa3f35c7..00000000 --- a/native/src/window.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Build window-based GUI applications. -mod action; - -pub use action::Action; - -use crate::core::time::Instant; -use crate::core::window::Event; -use crate::futures::subscription::{self, Subscription}; - -/// Subscribes to the frames of the window of the running application. -/// -/// The resulting [`Subscription`] will produce items at a rate equal to the -/// refresh rate of the window. Note that this rate may be variable, as it is -/// normally managed by the graphics driver and/or the OS. -/// -/// In any case, this [`Subscription`] is useful to smoothly draw application-driven -/// animations without missing any frames. -pub fn frames() -> Subscription { - subscription::raw_events(|event, _status| match event { - iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), - _ => None, - }) -} diff --git a/native/src/window/action.rs b/native/src/window/action.rs deleted file mode 100644 index c1dbd84f..00000000 --- a/native/src/window/action.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::core::window::{Mode, UserAttention}; -use crate::futures::MaybeSend; - -use std::fmt; - -/// An operation to be performed on some window. -pub enum Action { - /// Closes the current window and exits the application. - Close, - /// Moves the window with the left mouse button until the button is - /// released. - /// - /// There’s no guarantee that this will work unless the left mouse - /// button was pressed immediately before this function is called. - Drag, - /// Resize the window. - Resize { - /// The new logical width of the window - width: u32, - /// The new logical height of the window - height: u32, - }, - /// Sets the window to maximized or back - Maximize(bool), - /// Set the window to minimized or back - Minimize(bool), - /// Move the window. - /// - /// Unsupported on Wayland. - Move { - /// The new logical x location of the window - x: i32, - /// The new logical y location of the window - y: i32, - }, - /// Change the [`Mode`] of the window. - ChangeMode(Mode), - /// Fetch the current [`Mode`] of the window. - FetchMode(Box T + 'static>), - /// Toggle the window to maximized or back - ToggleMaximize, - /// Toggle whether window has decorations. - /// - /// ## Platform-specific - /// - **X11:** Not implemented. - /// - **Web:** Unsupported. - ToggleDecorations, - /// Request user attention to the window, this has no effect if the application - /// is already focused. How requesting for user attention manifests is platform dependent, - /// see [`UserAttention`] for details. - /// - /// Providing `None` will unset the request for user attention. Unsetting the request for - /// user attention might not be done automatically by the WM when the window receives input. - /// - /// ## Platform-specific - /// - /// - **iOS / Android / Web:** Unsupported. - /// - **macOS:** `None` has no effect. - /// - **X11:** Requests for user attention must be manually cleared. - /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. - RequestUserAttention(Option), - /// Bring the window to the front and sets input focus. Has no effect if the window is - /// already in focus, minimized, or not visible. - /// - /// This method steals input focus from other applications. Do not use this method unless - /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive - /// user experience. - /// - /// ## Platform-specific - /// - /// - **Web / Wayland:** Unsupported. - GainFocus, - /// Change whether or not the window will always be on top of other windows. - /// - /// ## Platform-specific - /// - /// - **Web / Wayland:** Unsupported. - ChangeAlwaysOnTop(bool), - /// Fetch an identifier unique to the window. - FetchId(Box T + 'static>), -} - -impl Action { - /// Maps the output of a window [`Action`] using the provided closure. - pub fn map( - self, - f: impl Fn(T) -> A + 'static + MaybeSend + Sync, - ) -> Action - where - T: 'static, - { - match self { - Self::Close => Action::Close, - Self::Drag => Action::Drag, - Self::Resize { width, height } => Action::Resize { width, height }, - Self::Maximize(maximized) => Action::Maximize(maximized), - Self::Minimize(minimized) => Action::Minimize(minimized), - Self::Move { x, y } => Action::Move { x, y }, - Self::ChangeMode(mode) => Action::ChangeMode(mode), - Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), - Self::ToggleMaximize => Action::ToggleMaximize, - Self::ToggleDecorations => Action::ToggleDecorations, - Self::RequestUserAttention(attention_type) => { - Action::RequestUserAttention(attention_type) - } - Self::GainFocus => Action::GainFocus, - Self::ChangeAlwaysOnTop(on_top) => { - Action::ChangeAlwaysOnTop(on_top) - } - Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))), - } - } -} - -impl fmt::Debug for Action { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Close => write!(f, "Action::Close"), - Self::Drag => write!(f, "Action::Drag"), - Self::Resize { width, height } => write!( - f, - "Action::Resize {{ widget: {width}, height: {height} }}" - ), - Self::Maximize(maximized) => { - write!(f, "Action::Maximize({maximized})") - } - Self::Minimize(minimized) => { - write!(f, "Action::Minimize({minimized}") - } - Self::Move { x, y } => { - write!(f, "Action::Move {{ x: {x}, y: {y} }}") - } - Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"), - Self::FetchMode(_) => write!(f, "Action::FetchMode"), - Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), - Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"), - Self::RequestUserAttention(_) => { - write!(f, "Action::RequestUserAttention") - } - Self::GainFocus => write!(f, "Action::GainFocus"), - Self::ChangeAlwaysOnTop(on_top) => { - write!(f, "Action::AlwaysOnTop({on_top})") - } - Self::FetchId(_) => write!(f, "Action::FetchId"), - } - } -} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml new file mode 100644 index 00000000..2d3e8db3 --- /dev/null +++ b/runtime/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "iced_runtime" +version = "0.9.1" +authors = ["Héctor Ramón Jiménez "] +edition = "2021" +description = "A renderer-agnostic library for native GUIs" +license = "MIT" +repository = "https://github.com/iced-rs/iced" + +[features] +debug = [] + +[dependencies] +thiserror = "1" + +[dependencies.iced_core] +version = "0.8" +path = "../core" + +[dependencies.iced_futures] +version = "0.6" +path = "../futures" +features = ["thread-pool"] diff --git a/runtime/README.md b/runtime/README.md new file mode 100644 index 00000000..996daa76 --- /dev/null +++ b/runtime/README.md @@ -0,0 +1,37 @@ +# `iced_native` +[![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) +[![Discord Server](https://img.shields.io/discord/628993209984614400?label=&labelColor=6A7EC2&logo=discord&logoColor=ffffff&color=7389D8)](https://discord.gg/3xZJ65GAhd) + +`iced_native` takes [`iced_core`] and builds a native runtime on top of it, featuring: +- 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 bunch 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. +- A `Windowed` trait, leveraging [`raw-window-handle`], which can be implemented by graphical renderers that target _windows_. Window-based shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic. + +

+ The native target +

+ +[documentation]: https://docs.rs/iced_native +[`iced_core`]: ../core +[`iced_winit`]: ../winit +[`druid`]: https://github.com/xi-editor/druid +[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle + +## Installation +Add `iced_native` as a dependency in your `Cargo.toml`: + +```toml +iced_native = "0.9" +``` + +__Iced moves fast and the `master` branch can contain breaking changes!__ If +you want to learn about a specific release, check out [the release list]. + +[the release list]: https://github.com/iced-rs/iced/releases diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs new file mode 100644 index 00000000..bc450912 --- /dev/null +++ b/runtime/src/clipboard.rs @@ -0,0 +1,53 @@ +//! Access the clipboard. +use crate::command::{self, Command}; +use crate::futures::MaybeSend; + +use std::fmt; + +/// A clipboard action to be performed by some [`Command`]. +/// +/// [`Command`]: crate::Command +pub enum Action { + /// Read the clipboard and produce `T` with the result. + Read(Box) -> T>), + + /// Write the given contents to the clipboard. + Write(String), +} + +impl Action { + /// Maps the output of a clipboard [`Action`] using the provided closure. + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Self::Read(o) => Action::Read(Box::new(move |s| f(o(s)))), + Self::Write(content) => Action::Write(content), + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Read(_) => write!(f, "Action::Read"), + Self::Write(_) => write!(f, "Action::Write"), + } + } +} + +/// Read the current contents of the clipboard. +pub fn read( + f: impl Fn(Option) -> Message + 'static, +) -> Command { + Command::single(command::Action::Clipboard(Action::Read(Box::new(f)))) +} + +/// Write the given contents to the clipboard. +pub fn write(contents: String) -> Command { + Command::single(command::Action::Clipboard(Action::Write(contents))) +} diff --git a/runtime/src/command.rs b/runtime/src/command.rs new file mode 100644 index 00000000..cd4c51ff --- /dev/null +++ b/runtime/src/command.rs @@ -0,0 +1,108 @@ +//! Run asynchronous actions. +mod action; + +pub use action::Action; + +use crate::core::widget; +use crate::futures::MaybeSend; + +use std::fmt; +use std::future::Future; + +/// A set of asynchronous actions to be performed by some runtime. +#[must_use = "`Command` must be returned to runtime to take effect"] +pub struct Command(Internal>); + +#[derive(Debug)] +enum Internal { + None, + Single(T), + Batch(Vec), +} + +impl Command { + /// 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: Action) -> Self { + Self(Internal::Single(action)) + } + + /// Creates a [`Command`] that performs a [`widget::Operation`]. + pub fn widget(operation: impl widget::Operation + 'static) -> Self { + Self::single(Action::Widget(Box::new(operation))) + } + + /// Creates a [`Command`] that performs the action of the given future. + pub fn perform( + future: impl Future + 'static + MaybeSend, + f: impl FnOnce(T) -> A + 'static + MaybeSend, + ) -> Command { + use iced_futures::futures::FutureExt; + + Command::single(Action::Future(Box::pin(future.map(f)))) + } + + /// 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>) -> 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( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync + Clone, + ) -> Command + where + T: 'static, + A: 'static, + { + match self.0 { + Internal::None => Command::none(), + Internal::Single(action) => Command::single(action.map(f)), + Internal::Batch(batch) => Command(Internal::Batch( + batch + .into_iter() + .map(|action| action.map(f.clone())) + .collect(), + )), + } + } + + /// Returns all of the actions of the [`Command`]. + pub fn actions(self) -> Vec> { + let Command(command) = self; + + match command { + Internal::None => Vec::new(), + Internal::Single(action) => vec![action], + Internal::Batch(batch) => batch, + } + } +} + +impl fmt::Debug for Command { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Command(command) = self; + + command.fmt(f) + } +} diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs new file mode 100644 index 00000000..6c74f0ef --- /dev/null +++ b/runtime/src/command/action.rs @@ -0,0 +1,86 @@ +use crate::clipboard; +use crate::core::widget; +use crate::font; +use crate::system; +use crate::window; + +use iced_futures::MaybeSend; + +use std::borrow::Cow; +use std::fmt; + +/// An action that a [`Command`] can perform. +/// +/// [`Command`]: crate::Command +pub enum Action { + /// Run a [`Future`] to completion. + /// + /// [`Future`]: iced_futures::BoxFuture + Future(iced_futures::BoxFuture), + + /// Run a clipboard action. + Clipboard(clipboard::Action), + + /// Run a window action. + Window(window::Action), + + /// Run a system action. + System(system::Action), + + /// Run a widget action. + Widget(Box>), + + /// Load a font from its bytes. + LoadFont { + /// The bytes of the font to load. + bytes: Cow<'static, [u8]>, + + /// The message to produce when the font has been loaded. + tagger: Box) -> T>, + }, +} + +impl Action { + /// Applies a transformation to the result of a [`Command`]. + /// + /// [`Command`]: crate::Command + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + A: 'static, + T: 'static, + { + use iced_futures::futures::FutureExt; + + match self { + Self::Future(future) => Action::Future(Box::pin(future.map(f))), + Self::Clipboard(action) => Action::Clipboard(action.map(f)), + Self::Window(window) => Action::Window(window.map(f)), + Self::System(system) => Action::System(system.map(f)), + Self::Widget(operation) => { + Action::Widget(Box::new(widget::operation::map(operation, f))) + } + Self::LoadFont { bytes, tagger } => Action::LoadFont { + bytes, + tagger: Box::new(move |result| f(tagger(result))), + }, + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Future(_) => write!(f, "Action::Future"), + Self::Clipboard(action) => { + write!(f, "Action::Clipboard({action:?})") + } + Self::Window(action) => write!(f, "Action::Window({action:?})"), + Self::System(action) => write!(f, "Action::System({action:?})"), + Self::Widget(_action) => write!(f, "Action::Widget"), + Self::LoadFont { .. } => write!(f, "Action::LoadFont"), + } + } +} diff --git a/runtime/src/debug/basic.rs b/runtime/src/debug/basic.rs new file mode 100644 index 00000000..32f725a1 --- /dev/null +++ b/runtime/src/debug/basic.rs @@ -0,0 +1,226 @@ +#![allow(missing_docs)] +use crate::core::time; + +use std::collections::VecDeque; + +/// A bunch of time measurements for debugging purposes. +#[derive(Debug)] +pub struct Debug { + is_enabled: bool, + + startup_start: time::Instant, + startup_duration: time::Duration, + + update_start: time::Instant, + update_durations: TimeBuffer, + + view_start: time::Instant, + view_durations: TimeBuffer, + + layout_start: time::Instant, + layout_durations: TimeBuffer, + + event_start: time::Instant, + event_durations: TimeBuffer, + + draw_start: time::Instant, + draw_durations: TimeBuffer, + + render_start: time::Instant, + render_durations: TimeBuffer, + + message_count: usize, + last_messages: VecDeque, +} + +impl Debug { + /// Creates a new [`struct@Debug`]. + pub fn new() -> Self { + let now = time::Instant::now(); + + Self { + is_enabled: false, + startup_start: now, + startup_duration: time::Duration::from_secs(0), + + update_start: now, + update_durations: TimeBuffer::new(200), + + view_start: now, + view_durations: TimeBuffer::new(200), + + layout_start: now, + layout_durations: TimeBuffer::new(200), + + event_start: now, + event_durations: TimeBuffer::new(200), + + draw_start: now, + draw_durations: TimeBuffer::new(200), + + render_start: now, + render_durations: TimeBuffer::new(50), + + message_count: 0, + last_messages: VecDeque::new(), + } + } + + pub fn toggle(&mut self) { + self.is_enabled = !self.is_enabled; + } + + pub fn startup_started(&mut self) { + self.startup_start = time::Instant::now(); + } + + pub fn startup_finished(&mut self) { + self.startup_duration = time::Instant::now() - self.startup_start; + } + + pub fn update_started(&mut self) { + self.update_start = time::Instant::now(); + } + + pub fn update_finished(&mut self) { + self.update_durations + .push(time::Instant::now() - self.update_start); + } + + pub fn view_started(&mut self) { + self.view_start = time::Instant::now(); + } + + pub fn view_finished(&mut self) { + self.view_durations + .push(time::Instant::now() - self.view_start); + } + + pub fn layout_started(&mut self) { + self.layout_start = time::Instant::now(); + } + + pub fn layout_finished(&mut self) { + self.layout_durations + .push(time::Instant::now() - self.layout_start); + } + + pub fn event_processing_started(&mut self) { + self.event_start = time::Instant::now(); + } + + pub fn event_processing_finished(&mut self) { + self.event_durations + .push(time::Instant::now() - self.event_start); + } + + pub fn draw_started(&mut self) { + self.draw_start = time::Instant::now(); + } + + pub fn draw_finished(&mut self) { + self.draw_durations + .push(time::Instant::now() - self.draw_start); + } + + pub fn render_started(&mut self) { + self.render_start = time::Instant::now(); + } + + pub fn render_finished(&mut self) { + self.render_durations + .push(time::Instant::now() - self.render_start); + } + + pub fn log_message(&mut self, message: &Message) { + self.last_messages.push_back(format!("{message:?}")); + + if self.last_messages.len() > 10 { + let _ = self.last_messages.pop_front(); + } + + self.message_count += 1; + } + + pub fn overlay(&self) -> Vec { + if !self.is_enabled { + return Vec::new(); + } + + let mut lines = Vec::new(); + + fn key_value(key: &str, value: T) -> String { + format!("{key} {value:?}") + } + + lines.push(format!( + "{} {} - {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_REPOSITORY"), + )); + lines.push(key_value("Startup:", self.startup_duration)); + lines.push(key_value("Update:", self.update_durations.average())); + lines.push(key_value("View:", self.view_durations.average())); + lines.push(key_value("Layout:", self.layout_durations.average())); + lines.push(key_value( + "Event processing:", + self.event_durations.average(), + )); + lines.push(key_value( + "Primitive generation:", + self.draw_durations.average(), + )); + lines.push(key_value("Render:", self.render_durations.average())); + lines.push(key_value("Message count:", self.message_count)); + lines.push(String::from("Last messages:")); + lines.extend(self.last_messages.iter().map(|msg| { + if msg.len() <= 100 { + format!(" {msg}") + } else { + format!(" {msg:.100}...") + } + })); + + lines + } +} + +impl Default for Debug { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +struct TimeBuffer { + head: usize, + size: usize, + contents: Vec, +} + +impl TimeBuffer { + fn new(capacity: usize) -> TimeBuffer { + TimeBuffer { + head: 0, + size: 0, + contents: vec![time::Duration::from_secs(0); capacity], + } + } + + fn push(&mut self, duration: time::Duration) { + self.head = (self.head + 1) % self.contents.len(); + self.contents[self.head] = duration; + self.size = (self.size + 1).min(self.contents.len()); + } + + fn average(&self) -> time::Duration { + let sum: time::Duration = if self.size == self.contents.len() { + self.contents[..].iter().sum() + } else { + self.contents[..self.size].iter().sum() + }; + + sum / self.size.max(1) as u32 + } +} diff --git a/runtime/src/debug/null.rs b/runtime/src/debug/null.rs new file mode 100644 index 00000000..2db0eebb --- /dev/null +++ b/runtime/src/debug/null.rs @@ -0,0 +1,47 @@ +#![allow(missing_docs)] +#[derive(Debug, Default)] +pub struct Debug; + +impl Debug { + pub fn new() -> Self { + Self + } + + pub fn startup_started(&mut self) {} + + pub fn startup_finished(&mut self) {} + + pub fn update_started(&mut self) {} + + pub fn update_finished(&mut self) {} + + pub fn view_started(&mut self) {} + + pub fn view_finished(&mut self) {} + + pub fn layout_started(&mut self) {} + + pub fn layout_finished(&mut self) {} + + pub fn event_processing_started(&mut self) {} + + pub fn event_processing_finished(&mut self) {} + + pub fn draw_started(&mut self) {} + + pub fn draw_finished(&mut self) {} + + pub fn render_started(&mut self) {} + + pub fn render_finished(&mut self) {} + + pub fn log_message( + &mut self, + _message: &Message, + ) { + } + + pub fn overlay(&self) -> Vec { + Vec::new() + } +} diff --git a/runtime/src/font.rs b/runtime/src/font.rs new file mode 100644 index 00000000..15359694 --- /dev/null +++ b/runtime/src/font.rs @@ -0,0 +1,19 @@ +//! Load and use fonts. +pub use iced_core::font::*; + +use crate::command::{self, Command}; +use std::borrow::Cow; + +/// An error while loading a font. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error {} + +/// Load a font from its bytes. +pub fn load( + bytes: impl Into>, +) -> Command> { + Command::single(command::Action::LoadFont { + bytes: bytes.into(), + tagger: Box::new(std::convert::identity), + }) +} diff --git a/runtime/src/keyboard.rs b/runtime/src/keyboard.rs new file mode 100644 index 00000000..012538e3 --- /dev/null +++ b/runtime/src/keyboard.rs @@ -0,0 +1,2 @@ +//! Track keyboard events. +pub use iced_core::keyboard::*; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs new file mode 100644 index 00000000..aa45e57a --- /dev/null +++ b/runtime/src/lib.rs @@ -0,0 +1,71 @@ +//! A renderer-agnostic native GUI runtime. +//! +//! ![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: +//! +//! - 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.8/core +//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.8/winit +//! [`druid`]: https://github.com/xi-editor/druid +//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle +//! [renderer]: crate::renderer +#![doc( + html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" +)] +#![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 +)] +#![forbid(unsafe_code, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] +pub mod clipboard; +pub mod command; +pub mod font; +pub mod keyboard; +pub mod program; +pub mod system; +pub mod user_interface; +pub mod window; + +// We disable debug capabilities on release builds unless the `debug` feature +// is explicitly enabled. +#[cfg(feature = "debug")] +#[path = "debug/basic.rs"] +mod debug; +#[cfg(not(feature = "debug"))] +#[path = "debug/null.rs"] +mod debug; + +pub use iced_core as core; +pub use iced_futures as futures; + +pub use command::Command; +pub use debug::Debug; +pub use font::Font; +pub use program::Program; +pub use user_interface::UserInterface; diff --git a/runtime/src/program.rs b/runtime/src/program.rs new file mode 100644 index 00000000..44585cc5 --- /dev/null +++ b/runtime/src/program.rs @@ -0,0 +1,33 @@ +//! Build interactive programs using The Elm Architecture. +use crate::Command; + +use iced_core::text; +use iced_core::{Element, Renderer}; + +mod state; + +pub use state::State; + +/// The core of a user interface application following The Elm Architecture. +pub trait Program: Sized { + /// The graphics backend to use to draw the [`Program`]. + type Renderer: Renderer + text::Renderer; + + /// The type of __messages__ your [`Program`] will produce. + type Message: std::fmt::Debug + Send; + + /// Handles a __message__ and updates the state of the [`Program`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the + /// background by shells. + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Program`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view(&self) -> Element<'_, Self::Message, Self::Renderer>; +} diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs new file mode 100644 index 00000000..2fa9934d --- /dev/null +++ b/runtime/src/program/state.rs @@ -0,0 +1,194 @@ +use crate::core::event::{self, Event}; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::{Clipboard, Point, Size}; +use crate::user_interface::{self, UserInterface}; +use crate::{Command, Debug, Program}; + +/// The execution state of a [`Program`]. It leverages caching, event +/// processing, and rendering primitive storage. +#[allow(missing_debug_implementations)] +pub struct State

+where + P: Program + 'static, +{ + program: P, + cache: Option, + queued_events: Vec, + queued_messages: Vec, + mouse_interaction: mouse::Interaction, +} + +impl

State

+where + P: Program + 'static, +{ + /// Creates a new [`State`] with the provided [`Program`], initializing its + /// primitive with the given logical bounds and renderer. + pub fn new( + mut program: P, + bounds: Size, + renderer: &mut P::Renderer, + debug: &mut Debug, + ) -> Self { + let user_interface = build_user_interface( + &mut program, + user_interface::Cache::default(), + renderer, + bounds, + debug, + ); + + let cache = Some(user_interface.into_cache()); + + State { + program, + cache, + queued_events: Vec::new(), + queued_messages: Vec::new(), + mouse_interaction: mouse::Interaction::Idle, + } + } + + /// Returns a reference to the [`Program`] of the [`State`]. + pub fn program(&self) -> &P { + &self.program + } + + /// Queues an event in the [`State`] for processing during an [`update`]. + /// + /// [`update`]: Self::update + pub fn queue_event(&mut self, event: Event) { + self.queued_events.push(event); + } + + /// Queues a message in the [`State`] for processing during an [`update`]. + /// + /// [`update`]: Self::update + pub fn queue_message(&mut self, message: P::Message) { + self.queued_messages.push(message); + } + + /// Returns whether the event queue of the [`State`] is empty or not. + pub fn is_queue_empty(&self) -> bool { + self.queued_events.is_empty() && self.queued_messages.is_empty() + } + + /// Returns the current [`mouse::Interaction`] of the [`State`]. + pub fn mouse_interaction(&self) -> mouse::Interaction { + self.mouse_interaction + } + + /// Processes all the queued events and messages, rebuilding and redrawing + /// the widgets of the linked [`Program`] if necessary. + /// + /// Returns a list containing the instances of [`Event`] that were not + /// captured by any widget, and the [`Command`] obtained from [`Program`] + /// after updating it, only if an update was necessary. + pub fn update( + &mut self, + bounds: Size, + cursor_position: Point, + renderer: &mut P::Renderer, + theme: &::Theme, + style: &renderer::Style, + clipboard: &mut dyn Clipboard, + debug: &mut Debug, + ) -> (Vec, Option>) { + let mut user_interface = build_user_interface( + &mut self.program, + self.cache.take().unwrap(), + renderer, + bounds, + debug, + ); + + debug.event_processing_started(); + let mut messages = Vec::new(); + + let (_, event_statuses) = user_interface.update( + &self.queued_events, + cursor_position, + renderer, + clipboard, + &mut messages, + ); + + let uncaptured_events = self + .queued_events + .iter() + .zip(event_statuses) + .filter_map(|(event, status)| { + matches!(status, event::Status::Ignored).then_some(event) + }) + .cloned() + .collect(); + + self.queued_events.clear(); + messages.append(&mut self.queued_messages); + debug.event_processing_finished(); + + let command = if messages.is_empty() { + debug.draw_started(); + self.mouse_interaction = + user_interface.draw(renderer, theme, style, cursor_position); + debug.draw_finished(); + + self.cache = Some(user_interface.into_cache()); + + None + } else { + // When there are messages, we are forced to rebuild twice + // for now :^) + let temp_cache = user_interface.into_cache(); + + let commands = + Command::batch(messages.into_iter().map(|message| { + debug.log_message(&message); + + debug.update_started(); + let command = self.program.update(message); + debug.update_finished(); + + command + })); + + let mut user_interface = build_user_interface( + &mut self.program, + temp_cache, + renderer, + bounds, + debug, + ); + + debug.draw_started(); + self.mouse_interaction = + user_interface.draw(renderer, theme, style, cursor_position); + debug.draw_finished(); + + self.cache = Some(user_interface.into_cache()); + + Some(commands) + }; + + (uncaptured_events, command) + } +} + +fn build_user_interface<'a, P: Program>( + program: &'a mut P, + cache: user_interface::Cache, + renderer: &mut P::Renderer, + size: Size, + debug: &mut Debug, +) -> UserInterface<'a, P::Message, P::Renderer> { + debug.view_started(); + let view = program.view(); + debug.view_finished(); + + debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + debug.layout_finished(); + + user_interface +} diff --git a/runtime/src/system.rs b/runtime/src/system.rs new file mode 100644 index 00000000..61c8ff29 --- /dev/null +++ b/runtime/src/system.rs @@ -0,0 +1,6 @@ +//! Access the native system. +mod action; +mod information; + +pub use action::Action; +pub use information::Information; diff --git a/runtime/src/system/action.rs b/runtime/src/system/action.rs new file mode 100644 index 00000000..dea9536f --- /dev/null +++ b/runtime/src/system/action.rs @@ -0,0 +1,39 @@ +use crate::system; + +use iced_futures::MaybeSend; +use std::fmt; + +/// An operation to be performed on the system. +pub enum Action { + /// Query system information and produce `T` with the result. + QueryInformation(Box>), +} + +pub trait Closure: Fn(system::Information) -> T + MaybeSend {} + +impl Closure for T where T: Fn(system::Information) -> O + MaybeSend {} + +impl Action { + /// Maps the output of a system [`Action`] using the provided closure. + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Self::QueryInformation(o) => { + Action::QueryInformation(Box::new(move |s| f(o(s)))) + } + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::QueryInformation(_) => write!(f, "Action::QueryInformation"), + } + } +} diff --git a/runtime/src/system/information.rs b/runtime/src/system/information.rs new file mode 100644 index 00000000..93e7a5a4 --- /dev/null +++ b/runtime/src/system/information.rs @@ -0,0 +1,29 @@ +/// Contains informations about the system (e.g. system name, processor, memory, graphics adapter). +#[derive(Clone, Debug)] +pub struct Information { + /// The operating system name + pub system_name: Option, + /// Operating system kernel version + pub system_kernel: Option, + /// Long operating system version + /// + /// Examples: + /// - MacOS 10.15 Catalina + /// - Windows 10 Pro + /// - Ubuntu 20.04 LTS (Focal Fossa) + pub system_version: Option, + /// Short operating system version number + pub system_short_version: Option, + /// Detailed processor model information + pub cpu_brand: String, + /// The number of physical cores on the processor + pub cpu_cores: Option, + /// Total RAM size, KB + pub memory_total: u64, + /// Memory used by this process, KB + pub memory_used: Option, + /// Underlying graphics backend for rendering + pub graphics_backend: String, + /// Model information for the active graphics adapter + pub graphics_adapter: String, +} diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs new file mode 100644 index 00000000..2c76fd8a --- /dev/null +++ b/runtime/src/user_interface.rs @@ -0,0 +1,592 @@ +//! Implement your own event loop to drive a user interface. +use crate::core::event::{self, Event}; +use crate::core::layout; +use crate::core::mouse; +use crate::core::renderer; +use crate::core::widget; +use crate::core::window; +use crate::core::{Clipboard, Point, Rectangle, Size, Vector}; +use crate::core::{Element, Layout, Shell}; + +/// A set of interactive graphical elements with a specific [`Layout`]. +/// +/// It can be updated and drawn. +/// +/// Iced tries to avoid dictating how to write your event loop. You are in +/// charge of using this type in your system in any way you want. +/// +/// # Example +/// The [`integration_opengl`] & [`integration_wgpu`] examples use a +/// [`UserInterface`] to integrate Iced in an existing graphical application. +/// +/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_opengl +/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_wgpu +#[allow(missing_debug_implementations)] +pub struct UserInterface<'a, Message, Renderer> { + root: Element<'a, Message, Renderer>, + base: layout::Node, + state: widget::Tree, + overlay: Option, + bounds: Size, +} + +impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ + /// Builds a user interface for an [`Element`]. + /// + /// It is able to avoid expensive computations when using a [`Cache`] + /// obtained from a previous instance of a [`UserInterface`]. + /// + /// # Example + /// Imagine we want to build a [`UserInterface`] for + /// [the counter example that we previously wrote](index.html#usage). Here + /// is naive way to set up our application loop: + /// + /// ```no_run + /// # mod iced_wgpu { + /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # } + /// # + /// # pub struct Counter; + /// # + /// # impl Counter { + /// # pub fn new() -> Self { Counter } + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} + /// # } + /// use iced_runtime::core::Size; + /// use iced_runtime::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// + /// // Initialization + /// let mut counter = Counter::new(); + /// let mut cache = user_interface::Cache::new(); + /// let mut renderer = Renderer::new(); + /// let mut window_size = Size::new(1024.0, 768.0); + /// + /// // Application loop + /// loop { + /// // Process system events here... + /// + /// // Build the user interface + /// let user_interface = UserInterface::build( + /// counter.view(), + /// window_size, + /// cache, + /// &mut renderer, + /// ); + /// + /// // Update and draw the user interface here... + /// // ... + /// + /// // Obtain the cache for the next iteration + /// cache = user_interface.into_cache(); + /// } + /// ``` + pub fn build>>( + root: E, + bounds: Size, + cache: Cache, + renderer: &mut Renderer, + ) -> Self { + let root = root.into(); + + let Cache { mut state } = cache; + state.diff(root.as_widget()); + + let base = + renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); + + UserInterface { + root, + base, + state, + overlay: None, + bounds, + } + } + + /// Updates the [`UserInterface`] by processing each provided [`Event`]. + /// + /// It returns __messages__ that may have been produced as a result of user + /// interactions. You should feed these to your __update logic__. + /// + /// # Example + /// Let's allow our [counter](index.html#usage) to change state by + /// completing [the previous example](#example): + /// + /// ```no_run + /// # mod iced_wgpu { + /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # } + /// # + /// # pub struct Counter; + /// # + /// # impl Counter { + /// # pub fn new() -> Self { Counter } + /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} + /// # } + /// use iced_runtime::core::{clipboard, Size, Point}; + /// use iced_runtime::user_interface::{self, UserInterface}; + /// use iced_wgpu::Renderer; + /// + /// let mut counter = Counter::new(); + /// let mut cache = user_interface::Cache::new(); + /// let mut renderer = Renderer::new(); + /// let mut window_size = Size::new(1024.0, 768.0); + /// let mut cursor_position = Point::default(); + /// let mut clipboard = clipboard::Null; + /// + /// // Initialize our event storage + /// let mut events = Vec::new(); + /// let mut messages = Vec::new(); + /// + /// loop { + /// // Obtain system events... + /// + /// let mut user_interface = UserInterface::build( + /// counter.view(), + /// window_size, + /// cache, + /// &mut renderer, + /// ); + /// + /// // Update the user interface + /// let (state, event_statuses) = user_interface.update( + /// &events, + /// cursor_position, + /// &mut renderer, + /// &mut clipboard, + /// &mut messages + /// ); + /// + /// cache = user_interface.into_cache(); + /// + /// // Process the produced messages + /// for message in messages.drain(..) { + /// counter.update(message); + /// } + /// } + /// ``` + pub fn update( + &mut self, + events: &[Event], + cursor_position: Point, + renderer: &mut Renderer, + clipboard: &mut dyn Clipboard, + messages: &mut Vec, + ) -> (State, Vec) { + use std::mem::ManuallyDrop; + + let mut outdated = false; + let mut redraw_request = None; + + let mut manual_overlay = + ManuallyDrop::new(self.root.as_widget_mut().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + )); + + let (base_cursor, overlay_statuses) = if manual_overlay.is_some() { + let bounds = self.bounds; + + let mut overlay = manual_overlay.as_mut().unwrap(); + let mut layout = overlay.layout(renderer, bounds, Vector::ZERO); + let mut event_statuses = Vec::new(); + + for event in events.iter().cloned() { + let mut shell = Shell::new(messages); + + let event_status = overlay.on_event( + event, + Layout::new(&layout), + cursor_position, + renderer, + clipboard, + &mut shell, + ); + + event_statuses.push(event_status); + + match (redraw_request, shell.redraw_request()) { + (None, Some(at)) => { + redraw_request = Some(at); + } + (Some(current), Some(new)) if new < current => { + redraw_request = Some(new); + } + _ => {} + } + + if shell.is_layout_invalid() { + let _ = ManuallyDrop::into_inner(manual_overlay); + + self.base = renderer.layout( + &self.root, + &layout::Limits::new(Size::ZERO, self.bounds), + ); + + manual_overlay = + ManuallyDrop::new(self.root.as_widget_mut().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + )); + + if manual_overlay.is_none() { + break; + } + + overlay = manual_overlay.as_mut().unwrap(); + + shell.revalidate_layout(|| { + layout = overlay.layout(renderer, bounds, Vector::ZERO); + }); + } + + if shell.are_widgets_invalid() { + outdated = true; + } + } + + let base_cursor = manual_overlay + .as_ref() + .filter(|overlay| { + overlay.is_over(Layout::new(&layout), cursor_position) + }) + .map(|_| { + // TODO: Type-safe cursor availability + Point::new(-1.0, -1.0) + }) + .unwrap_or(cursor_position); + + self.overlay = Some(layout); + + (base_cursor, event_statuses) + } else { + (cursor_position, vec![event::Status::Ignored; events.len()]) + }; + + let _ = ManuallyDrop::into_inner(manual_overlay); + + let event_statuses = events + .iter() + .cloned() + .zip(overlay_statuses.into_iter()) + .map(|(event, overlay_status)| { + if matches!(overlay_status, event::Status::Captured) { + return overlay_status; + } + + let mut shell = Shell::new(messages); + + let event_status = self.root.as_widget_mut().on_event( + &mut self.state, + event, + Layout::new(&self.base), + base_cursor, + renderer, + clipboard, + &mut shell, + ); + + if matches!(event_status, event::Status::Captured) { + self.overlay = None; + } + + match (redraw_request, shell.redraw_request()) { + (None, Some(at)) => { + redraw_request = Some(at); + } + (Some(current), Some(new)) if new < current => { + redraw_request = Some(new); + } + _ => {} + } + + shell.revalidate_layout(|| { + self.base = renderer.layout( + &self.root, + &layout::Limits::new(Size::ZERO, self.bounds), + ); + + self.overlay = None; + }); + + if shell.are_widgets_invalid() { + outdated = true; + } + + event_status.merge(overlay_status) + }) + .collect(); + + ( + if outdated { + State::Outdated + } else { + State::Updated { redraw_request } + }, + event_statuses, + ) + } + + /// Draws the [`UserInterface`] with the provided [`Renderer`]. + /// + /// It returns the current [`mouse::Interaction`]. You should update the + /// icon of the mouse cursor accordingly in your system. + /// + /// [`Renderer`]: crate::Renderer + /// + /// # Example + /// We can finally draw our [counter](index.html#usage) by + /// [completing the last example](#example-1): + /// + /// ```no_run + /// # mod iced_wgpu { + /// # pub use iced_runtime::core::renderer::Null as Renderer; + /// # pub type Theme = (); + /// # } + /// # + /// # pub struct Counter; + /// # + /// # impl Counter { + /// # pub fn new() -> Self { Counter } + /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() } + /// # pub fn update(&mut self, _: ()) {} + /// # } + /// use iced_runtime::core::clipboard; + /// use iced_runtime::core::renderer; + /// use iced_runtime::core::{Element, Size, Point}; + /// use iced_runtime::user_interface::{self, UserInterface}; + /// use iced_wgpu::{Renderer, Theme}; + /// + /// let mut counter = Counter::new(); + /// let mut cache = user_interface::Cache::new(); + /// let mut renderer = Renderer::new(); + /// let mut window_size = Size::new(1024.0, 768.0); + /// let mut cursor_position = Point::default(); + /// let mut clipboard = clipboard::Null; + /// let mut events = Vec::new(); + /// let mut messages = Vec::new(); + /// let mut theme = Theme::default(); + /// + /// loop { + /// // Obtain system events... + /// + /// let mut user_interface = UserInterface::build( + /// counter.view(), + /// window_size, + /// cache, + /// &mut renderer, + /// ); + /// + /// // Update the user interface + /// let event_statuses = user_interface.update( + /// &events, + /// cursor_position, + /// &mut renderer, + /// &mut clipboard, + /// &mut messages + /// ); + /// + /// // Draw the user interface + /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position); + /// + /// cache = user_interface.into_cache(); + /// + /// for message in messages.drain(..) { + /// counter.update(message); + /// } + /// + /// // Update mouse cursor icon... + /// // Flush rendering operations... + /// } + /// ``` + pub fn draw( + &mut self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + cursor_position: Point, + ) -> mouse::Interaction { + // TODO: Move to shell level (?) + renderer.clear(); + + let viewport = Rectangle::with_size(self.bounds); + + let base_cursor = if let Some(overlay) = self + .root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + { + let overlay_layout = self.overlay.take().unwrap_or_else(|| { + overlay.layout(renderer, self.bounds, Vector::ZERO) + }); + + let new_cursor_position = if overlay + .is_over(Layout::new(&overlay_layout), cursor_position) + { + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + self.overlay = Some(overlay_layout); + + new_cursor_position + } else { + cursor_position + }; + + self.root.as_widget().draw( + &self.state, + renderer, + theme, + style, + Layout::new(&self.base), + base_cursor, + &viewport, + ); + + let base_interaction = self.root.as_widget().mouse_interaction( + &self.state, + Layout::new(&self.base), + cursor_position, + &viewport, + renderer, + ); + + let Self { + overlay, + root, + base, + .. + } = self; + + // TODO: Currently, we need to call Widget::overlay twice to + // implement the painter's algorithm properly. + // + // Once we have a proper persistent widget tree, we should be able to + // avoid this additional call. + overlay + .as_ref() + .and_then(|layout| { + root.as_widget_mut() + .overlay(&mut self.state, Layout::new(base), renderer) + .map(|overlay| { + let overlay_interaction = overlay.mouse_interaction( + Layout::new(layout), + cursor_position, + &viewport, + renderer, + ); + + let overlay_bounds = layout.bounds(); + + renderer.with_layer(overlay_bounds, |renderer| { + overlay.draw( + renderer, + theme, + style, + Layout::new(layout), + cursor_position, + ); + }); + + if overlay.is_over(Layout::new(layout), cursor_position) + { + overlay_interaction + } else { + base_interaction + } + }) + }) + .unwrap_or(base_interaction) + } + + /// Applies a [`widget::Operation`] to the [`UserInterface`]. + pub fn operate( + &mut self, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.root.as_widget().operate( + &mut self.state, + Layout::new(&self.base), + renderer, + operation, + ); + + if let Some(mut overlay) = self.root.as_widget_mut().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { + if self.overlay.is_none() { + self.overlay = + Some(overlay.layout(renderer, self.bounds, Vector::ZERO)); + } + + overlay.operate( + Layout::new(self.overlay.as_ref().unwrap()), + renderer, + operation, + ); + } + } + + /// Relayouts and returns a new [`UserInterface`] using the provided + /// bounds. + pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { + Self::build(self.root, bounds, Cache { state: self.state }, renderer) + } + + /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the + /// process. + pub fn into_cache(self) -> Cache { + Cache { state: self.state } + } +} + +/// Reusable data of a specific [`UserInterface`]. +#[derive(Debug)] +pub struct Cache { + state: widget::Tree, +} + +impl Cache { + /// Creates an empty [`Cache`]. + /// + /// You should use this to initialize a [`Cache`] before building your first + /// [`UserInterface`]. + pub fn new() -> Cache { + Cache { + state: widget::Tree::empty(), + } + } +} + +impl Default for Cache { + fn default() -> Cache { + Cache::new() + } +} + +/// The resulting state after updating a [`UserInterface`]. +#[derive(Debug, Clone, Copy)] +pub enum State { + /// The [`UserInterface`] is outdated and needs to be rebuilt. + Outdated, + + /// The [`UserInterface`] is up-to-date and can be reused without + /// rebuilding. + Updated { + /// The [`Instant`] when a redraw should be performed. + redraw_request: Option, + }, +} diff --git a/runtime/src/window.rs b/runtime/src/window.rs new file mode 100644 index 00000000..236064f7 --- /dev/null +++ b/runtime/src/window.rs @@ -0,0 +1,112 @@ +//! Build window-based GUI applications. +mod action; + +pub use action::Action; + +use crate::command::{self, Command}; +use crate::core::time::Instant; +use crate::core::window::{Event, Mode, UserAttention}; +use crate::futures::subscription::{self, Subscription}; + +/// Subscribes to the frames of the window of the running application. +/// +/// The resulting [`Subscription`] will produce items at a rate equal to the +/// refresh rate of the window. Note that this rate may be variable, as it is +/// normally managed by the graphics driver and/or the OS. +/// +/// In any case, this [`Subscription`] is useful to smoothly draw application-driven +/// animations without missing any frames. +pub fn frames() -> Subscription { + subscription::raw_events(|event, _status| match event { + iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), + _ => None, + }) +} + +/// Closes the current window and exits the application. +pub fn close() -> Command { + Command::single(command::Action::Window(Action::Close)) +} + +/// Begins dragging the window while the left mouse button is held. +pub fn drag() -> Command { + Command::single(command::Action::Window(Action::Drag)) +} + +/// Resizes the window to the given logical dimensions. +pub fn resize(width: u32, height: u32) -> Command { + Command::single(command::Action::Window(Action::Resize { width, height })) +} + +/// Maximizes the window. +pub fn maximize(maximized: bool) -> Command { + Command::single(command::Action::Window(Action::Maximize(maximized))) +} + +/// Minimes the window. +pub fn minimize(minimized: bool) -> Command { + Command::single(command::Action::Window(Action::Minimize(minimized))) +} + +/// Moves a window to the given logical coordinates. +pub fn move_to(x: i32, y: i32) -> Command { + Command::single(command::Action::Window(Action::Move { x, y })) +} + +/// Sets the [`Mode`] of the window. +pub fn change_mode(mode: Mode) -> Command { + Command::single(command::Action::Window(Action::ChangeMode(mode))) +} + +/// Fetches the current [`Mode`] of the window. +pub fn fetch_mode( + f: impl FnOnce(Mode) -> Message + 'static, +) -> Command { + Command::single(command::Action::Window(Action::FetchMode(Box::new(f)))) +} + +/// Toggles the window to maximized or back. +pub fn toggle_maximize() -> Command { + Command::single(command::Action::Window(Action::ToggleMaximize)) +} + +/// Toggles the window decorations. +pub fn toggle_decorations() -> Command { + Command::single(command::Action::Window(Action::ToggleDecorations)) +} + +/// Request user attention to the window, this has no effect if the application +/// is already focused. How requesting for user attention manifests is platform dependent, +/// see [`UserAttention`] for details. +/// +/// Providing `None` will unset the request for user attention. Unsetting the request for +/// user attention might not be done automatically by the WM when the window receives input. +pub fn request_user_attention( + user_attention: Option, +) -> Command { + Command::single(command::Action::Window(Action::RequestUserAttention( + user_attention, + ))) +} + +/// Brings the window to the front and sets input focus. Has no effect if the window is +/// already in focus, minimized, or not visible. +/// +/// This [`Command`] steals input focus from other applications. Do not use this method unless +/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive +/// user experience. +pub fn gain_focus() -> Command { + Command::single(command::Action::Window(Action::GainFocus)) +} + +/// Changes whether or not the window will always be on top of other windows. +pub fn change_always_on_top(on_top: bool) -> Command { + Command::single(command::Action::Window(Action::ChangeAlwaysOnTop(on_top))) +} + +/// Fetches an identifier unique to the window. +pub fn fetch_id( + f: impl FnOnce(u64) -> Message + 'static, +) -> Command { + Command::single(command::Action::Window(Action::FetchId(Box::new(f)))) +} diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs new file mode 100644 index 00000000..c1dbd84f --- /dev/null +++ b/runtime/src/window/action.rs @@ -0,0 +1,147 @@ +use crate::core::window::{Mode, UserAttention}; +use crate::futures::MaybeSend; + +use std::fmt; + +/// An operation to be performed on some window. +pub enum Action { + /// Closes the current window and exits the application. + Close, + /// Moves the window with the left mouse button until the button is + /// released. + /// + /// There’s no guarantee that this will work unless the left mouse + /// button was pressed immediately before this function is called. + Drag, + /// Resize the window. + Resize { + /// The new logical width of the window + width: u32, + /// The new logical height of the window + height: u32, + }, + /// Sets the window to maximized or back + Maximize(bool), + /// Set the window to minimized or back + Minimize(bool), + /// Move the window. + /// + /// Unsupported on Wayland. + Move { + /// The new logical x location of the window + x: i32, + /// The new logical y location of the window + y: i32, + }, + /// Change the [`Mode`] of the window. + ChangeMode(Mode), + /// Fetch the current [`Mode`] of the window. + FetchMode(Box T + 'static>), + /// Toggle the window to maximized or back + ToggleMaximize, + /// Toggle whether window has decorations. + /// + /// ## Platform-specific + /// - **X11:** Not implemented. + /// - **Web:** Unsupported. + ToggleDecorations, + /// Request user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see [`UserAttention`] for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web:** Unsupported. + /// - **macOS:** `None` has no effect. + /// - **X11:** Requests for user attention must be manually cleared. + /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. + RequestUserAttention(Option), + /// Bring the window to the front and sets input focus. Has no effect if the window is + /// already in focus, minimized, or not visible. + /// + /// This method steals input focus from other applications. Do not use this method unless + /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive + /// user experience. + /// + /// ## Platform-specific + /// + /// - **Web / Wayland:** Unsupported. + GainFocus, + /// Change whether or not the window will always be on top of other windows. + /// + /// ## Platform-specific + /// + /// - **Web / Wayland:** Unsupported. + ChangeAlwaysOnTop(bool), + /// Fetch an identifier unique to the window. + FetchId(Box T + 'static>), +} + +impl Action { + /// Maps the output of a window [`Action`] using the provided closure. + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + { + match self { + Self::Close => Action::Close, + Self::Drag => Action::Drag, + Self::Resize { width, height } => Action::Resize { width, height }, + Self::Maximize(maximized) => Action::Maximize(maximized), + Self::Minimize(minimized) => Action::Minimize(minimized), + Self::Move { x, y } => Action::Move { x, y }, + Self::ChangeMode(mode) => Action::ChangeMode(mode), + Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), + Self::ToggleMaximize => Action::ToggleMaximize, + Self::ToggleDecorations => Action::ToggleDecorations, + Self::RequestUserAttention(attention_type) => { + Action::RequestUserAttention(attention_type) + } + Self::GainFocus => Action::GainFocus, + Self::ChangeAlwaysOnTop(on_top) => { + Action::ChangeAlwaysOnTop(on_top) + } + Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))), + } + } +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Close => write!(f, "Action::Close"), + Self::Drag => write!(f, "Action::Drag"), + Self::Resize { width, height } => write!( + f, + "Action::Resize {{ widget: {width}, height: {height} }}" + ), + Self::Maximize(maximized) => { + write!(f, "Action::Maximize({maximized})") + } + Self::Minimize(minimized) => { + write!(f, "Action::Minimize({minimized}") + } + Self::Move { x, y } => { + write!(f, "Action::Move {{ x: {x}, y: {y} }}") + } + Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"), + Self::FetchMode(_) => write!(f, "Action::FetchMode"), + Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), + Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"), + Self::RequestUserAttention(_) => { + write!(f, "Action::RequestUserAttention") + } + Self::GainFocus => write!(f, "Action::GainFocus"), + Self::ChangeAlwaysOnTop(on_top) => { + write!(f, "Action::AlwaysOnTop({on_top})") + } + Self::FetchId(_) => write!(f, "Action::FetchId"), + } + } +} diff --git a/src/advanced.rs b/src/advanced.rs index 9621c3bc..7afba85c 100644 --- a/src/advanced.rs +++ b/src/advanced.rs @@ -10,5 +10,5 @@ pub use crate::core::{Clipboard, Shell}; pub mod subscription { //! Write your own subscriptions. - pub use crate::native::futures::subscription::{EventStream, Recipe}; + pub use crate::runtime::futures::subscription::{EventStream, Recipe}; } diff --git a/src/application.rs b/src/application.rs index f5cf3317..c9ddf840 100644 --- a/src/application.rs +++ b/src/application.rs @@ -215,7 +215,7 @@ pub trait Application: Sized { struct Instance(A); -impl crate::native::Program for Instance +impl crate::runtime::Program for Instance where A: Application, { diff --git a/src/lib.rs b/src/lib.rs index b9f87d5d..c59d5058 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,7 +169,7 @@ use iced_widget::renderer; use iced_widget::style; use iced_winit as shell; use iced_winit::core; -use iced_winit::native; +use iced_winit::runtime; pub use iced_futures::futures; @@ -192,11 +192,11 @@ pub use crate::core::{ color, Alignment, Background, Color, ContentFit, Length, Padding, Point, Rectangle, Size, Vector, }; -pub use crate::native::Command; +pub use crate::runtime::Command; pub mod clipboard { //! Access the clipboard. - pub use crate::shell::clipboard::{read, write}; + pub use crate::runtime::clipboard::{read, write}; } pub mod executor { @@ -219,7 +219,7 @@ pub mod executor { pub mod font { //! Load and use fonts. pub use crate::core::font::*; - pub use crate::native::font::*; + pub use crate::runtime::font::*; } pub mod keyboard { @@ -242,7 +242,7 @@ pub mod subscription { #[cfg(feature = "system")] pub mod system { //! Retrieve system information. - pub use crate::native::system::Information; + pub use crate::runtime::system::Information; pub use crate::shell::system::*; } diff --git a/src/window.rs b/src/window.rs index 26239065..824915b2 100644 --- a/src/window.rs +++ b/src/window.rs @@ -9,5 +9,4 @@ pub use position::Position; pub use settings::Settings; pub use crate::core::window::*; -pub use crate::native::window::*; -pub use crate::shell::window::*; +pub use crate::runtime::window::*; diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml index c4c36aba..08e79bb8 100644 --- a/tiny_skia/Cargo.toml +++ b/tiny_skia/Cargo.toml @@ -17,10 +17,6 @@ rustc-hash = "1.1" ouroboros = "0.15" kurbo = "0.9" -[dependencies.iced_native] -version = "0.9" -path = "../native" - [dependencies.iced_graphics] version = "0.7" path = "../graphics" diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 7a5034c2..c4edadb3 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -187,7 +187,7 @@ impl Pipeline { &self, content: &str, size: f32, - font: iced_native::Font, + font: Font, bounds: Size, point: Point, _nearest_only: bool, diff --git a/widget/Cargo.toml b/widget/Cargo.toml index fb617079..4c23f3e8 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -15,9 +15,9 @@ unicode-segmentation = "1.6" num-traits = "0.2" thiserror = "1" -[dependencies.iced_native] +[dependencies.iced_runtime] version = "0.9" -path = "../native" +path = "../runtime" [dependencies.iced_renderer] version = "0.1" diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 1a73c16f..a43e7248 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -5,12 +5,12 @@ use crate::container::{self, Container}; use crate::core; use crate::core::widget::operation; use crate::core::{Element, Length, Pixels}; -use crate::native::Command; use crate::overlay; use crate::pick_list::{self, PickList}; use crate::progress_bar::{self, ProgressBar}; use crate::radio::{self, Radio}; use crate::rule::{self, Rule}; +use crate::runtime::Command; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; use crate::text::{self, Text}; diff --git a/widget/src/lib.rs b/widget/src/lib.rs index 4c1e7c1c..a3e7c8bc 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -14,10 +14,10 @@ )] #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] -pub use iced_native as native; -pub use iced_native::core; pub use iced_renderer as renderer; pub use iced_renderer::graphics; +pub use iced_runtime as runtime; +pub use iced_runtime::core; pub use iced_style as style; mod column; diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 49c780de..5a7481f7 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -13,7 +13,7 @@ use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; -use crate::native::Command; +use crate::runtime::Command; pub use crate::style::scrollable::{Scrollbar, Scroller, StyleSheet}; pub use operation::scrollable::RelativeOffset; diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index d1c48fbd..d066109a 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -28,7 +28,7 @@ use crate::core::{ Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; -use crate::native::Command; +use crate::runtime::Command; pub use iced_style::text_input::{Appearance, StyleSheet}; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 21c14f68..bfd22093 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -13,7 +13,7 @@ categories = ["gui"] [features] trace = ["tracing", "tracing-core", "tracing-subscriber"] chrome-trace = ["trace", "tracing-chrome"] -debug = ["iced_native/debug"] +debug = ["iced_runtime/debug"] system = ["sysinfo"] application = [] @@ -27,9 +27,9 @@ version = "0.27" git = "https://github.com/iced-rs/winit.git" rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c" -[dependencies.iced_native] +[dependencies.iced_runtime] version = "0.9" -path = "../native" +path = "../runtime" [dependencies.iced_graphics] version = "0.7" diff --git a/winit/src/application.rs b/winit/src/application.rs index d863c846..9666fcae 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -16,10 +16,10 @@ use crate::core::{Event, Size}; use crate::futures::futures; use crate::futures::{Executor, Runtime, Subscription}; use crate::graphics::compositor::{self, Compositor}; -use crate::native::clipboard; -use crate::native::program::Program; -use crate::native::user_interface::{self, UserInterface}; -use crate::native::{Command, Debug}; +use crate::runtime::clipboard; +use crate::runtime::program::Program; +use crate::runtime::user_interface::{self, UserInterface}; +use crate::runtime::{Command, Debug}; use crate::style::application::{Appearance, StyleSheet}; use crate::{Clipboard, Error, Proxy, Settings}; @@ -709,9 +709,9 @@ pub fn run_command( E: Executor, ::Theme: StyleSheet, { - use iced_native::command; - use iced_native::system; - use iced_native::window; + use crate::runtime::command; + use crate::runtime::system; + use crate::runtime::window; for action in command.actions() { match action { diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index b727e03c..c37ccca6 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -3,7 +3,7 @@ use crate::conversion; use crate::core; use crate::core::{Color, Point, Size}; use crate::graphics::Viewport; -use crate::native::Debug; +use crate::runtime::Debug; use crate::Application; use std::marker::PhantomData; diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 22509130..7271441d 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,6 +1,4 @@ //! Access the clipboard. -use crate::native::clipboard::Action; -use crate::native::command::{self, Command}; /// A buffer for short-term storage and transfer within and between /// applications. @@ -64,15 +62,3 @@ impl crate::core::Clipboard for Clipboard { self.write(contents) } } - -/// Read the current contents of the clipboard. -pub fn read( - f: impl Fn(Option) -> Message + 'static, -) -> Command { - Command::single(command::Action::Clipboard(Action::Read(Box::new(f)))) -} - -/// Write the given contents to the clipboard. -pub fn write(contents: String) -> Command { - Command::single(command::Action::Clipboard(Action::Write(contents))) -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 0d8c04d3..5cde510a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -31,9 +31,9 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] pub use iced_graphics as graphics; -pub use iced_native as native; -pub use iced_native::core; -pub use iced_native::futures; +pub use iced_runtime as runtime; +pub use iced_runtime::core; +pub use iced_runtime::futures; pub use iced_style as style; pub use winit; @@ -42,7 +42,6 @@ pub mod application; pub mod clipboard; pub mod conversion; pub mod settings; -pub mod window; #[cfg(feature = "system")] pub mod system; diff --git a/winit/src/system.rs b/winit/src/system.rs index 3a6a8a8e..069efa29 100644 --- a/winit/src/system.rs +++ b/winit/src/system.rs @@ -1,7 +1,7 @@ //! Access the native system. use crate::graphics::compositor; -use crate::native::command::{self, Command}; -use crate::native::system::{Action, Information}; +use crate::runtime::command::{self, Command}; +use crate::runtime::system::{Action, Information}; /// Query for available system information. pub fn fetch_information( diff --git a/winit/src/window.rs b/winit/src/window.rs deleted file mode 100644 index 6ac58e20..00000000 --- a/winit/src/window.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Interact with the window of your application. -use crate::core::window::{Mode, UserAttention}; -use crate::native::command::{self, Command}; -use crate::native::window::Action; - -/// Closes the current window and exits the application. -pub fn close() -> Command { - Command::single(command::Action::Window(Action::Close)) -} - -/// Begins dragging the window while the left mouse button is held. -pub fn drag() -> Command { - Command::single(command::Action::Window(Action::Drag)) -} - -/// Resizes the window to the given logical dimensions. -pub fn resize(width: u32, height: u32) -> Command { - Command::single(command::Action::Window(Action::Resize { width, height })) -} - -/// Maximizes the window. -pub fn maximize(maximized: bool) -> Command { - Command::single(command::Action::Window(Action::Maximize(maximized))) -} - -/// Minimes the window. -pub fn minimize(minimized: bool) -> Command { - Command::single(command::Action::Window(Action::Minimize(minimized))) -} - -/// Moves a window to the given logical coordinates. -pub fn move_to(x: i32, y: i32) -> Command { - Command::single(command::Action::Window(Action::Move { x, y })) -} - -/// Sets the [`Mode`] of the window. -pub fn change_mode(mode: Mode) -> Command { - Command::single(command::Action::Window(Action::ChangeMode(mode))) -} - -/// Fetches the current [`Mode`] of the window. -pub fn fetch_mode( - f: impl FnOnce(Mode) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchMode(Box::new(f)))) -} - -/// Toggles the window to maximized or back. -pub fn toggle_maximize() -> Command { - Command::single(command::Action::Window(Action::ToggleMaximize)) -} - -/// Toggles the window decorations. -pub fn toggle_decorations() -> Command { - Command::single(command::Action::Window(Action::ToggleDecorations)) -} - -/// Request user attention to the window, this has no effect if the application -/// is already focused. How requesting for user attention manifests is platform dependent, -/// see [`UserAttention`] for details. -/// -/// Providing `None` will unset the request for user attention. Unsetting the request for -/// user attention might not be done automatically by the WM when the window receives input. -pub fn request_user_attention( - user_attention: Option, -) -> Command { - Command::single(command::Action::Window(Action::RequestUserAttention( - user_attention, - ))) -} - -/// Brings the window to the front and sets input focus. Has no effect if the window is -/// already in focus, minimized, or not visible. -/// -/// This [`Command`] steals input focus from other applications. Do not use this method unless -/// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive -/// user experience. -pub fn gain_focus() -> Command { - Command::single(command::Action::Window(Action::GainFocus)) -} - -/// Changes whether or not the window will always be on top of other windows. -pub fn change_always_on_top(on_top: bool) -> Command { - Command::single(command::Action::Window(Action::ChangeAlwaysOnTop(on_top))) -} - -/// Fetches an identifier unique to the window. -pub fn fetch_id( - f: impl FnOnce(u64) -> Message + 'static, -) -> Command { - Command::single(command::Action::Window(Action::FetchId(Box::new(f)))) -} -- cgit