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` --- 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 ++++++++++ 18 files changed, 1824 insertions(+) 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 (limited to 'runtime') 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"), + } + } +} -- cgit From 1c36446115dcb46c373fbc62b9e4f1941bd5a383 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Mar 2023 06:36:36 +0100 Subject: Fix `README` of `iced_runtime` --- runtime/README.md | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) (limited to 'runtime') diff --git a/runtime/README.md b/runtime/README.md index 996daa76..497fd145 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -1,22 +1,10 @@ -# `iced_native` +# `iced_runtime` [![Documentation](https://docs.rs/iced_native/badge.svg)][documentation] [![Crates.io](https://img.shields.io/crates/v/iced_native.svg)](https://crates.io/crates/iced_native) [![License](https://img.shields.io/crates/l/iced_native.svg)](https://github.com/iced-rs/iced/blob/master/LICENSE) [![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 -

+`iced_runtime` takes [`iced_core`] and builds a native runtime on top of it. [documentation]: https://docs.rs/iced_native [`iced_core`]: ../core @@ -25,10 +13,10 @@ To achieve this, it introduces a bunch of reusable interfaces: [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle ## Installation -Add `iced_native` as a dependency in your `Cargo.toml`: +Add `iced_runtime` as a dependency in your `Cargo.toml`: ```toml -iced_native = "0.9" +iced_runtime = "0.9" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If -- cgit From de638f44a5c62459008a5c024b39c2443b72bf25 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 15:37:56 +0200 Subject: Write missing documentation in `iced_wgpu` --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime') diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8a277e47..d3b84c7d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -33,7 +33,7 @@ )] #![deny( missing_debug_implementations, - //missing_docs, + missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, -- cgit From 63d3fc6996b848e10e77e6924bfebdf6ba82852e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 15:50:35 +0200 Subject: Remove OpenGL mentions in `README`s --- runtime/src/user_interface.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index c29de7db..d9206134 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -16,11 +16,10 @@ use crate::core::{Element, Layout, Shell}; /// 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. +/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an +/// existing graphical application. /// -/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_opengl -/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_wgpu +/// [`integration`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, -- cgit From cf434236e7e15e0fa05e5915b8d4d78dcaf1b7e8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 17:28:51 +0200 Subject: Enable `doc_auto_cfg` when generating documentation --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime') diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d3b84c7d..50abf7b2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -42,7 +42,7 @@ clippy::useless_conversion )] #![forbid(unsafe_code, rust_2018_idioms)] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod clipboard; pub mod command; pub mod font; -- cgit From f0788b9f373f44248e55b068d9d0d494d628ba93 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 25 May 2023 23:18:50 +0200 Subject: Replace `change_always_on_top` action with `change_level` --- runtime/src/window.rs | 10 +++++----- runtime/src/window/action.rs | 26 ++++++++++---------------- 2 files changed, 15 insertions(+), 21 deletions(-) (limited to 'runtime') diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 833a1125..d4111293 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -5,7 +5,7 @@ pub use action::Action; use crate::command::{self, Command}; use crate::core::time::Instant; -use crate::core::window::{Event, Icon, Mode, UserAttention}; +use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; use crate::futures::subscription::{self, Subscription}; /// Subscribes to the frames of the window of the running application. @@ -53,7 +53,7 @@ pub fn move_to(x: i32, y: i32) -> Command { Command::single(command::Action::Window(Action::Move { x, y })) } -/// Sets the [`Mode`] of the window. +/// Changes the [`Mode`] of the window. pub fn change_mode(mode: Mode) -> Command { Command::single(command::Action::Window(Action::ChangeMode(mode))) } @@ -99,9 +99,9 @@ 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))) +/// Changes the window [`Level`]. +pub fn change_level(level: Level) -> Command { + Command::single(command::Action::Window(Action::ChangeLevel(level))) } /// Fetches an identifier unique to the window. diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index 83b71c75..a9d2a3d0 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,13 +1,13 @@ -use crate::core::window::{Icon, Mode, UserAttention}; +use crate::core::window::{Icon, Level, 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 the current window and exits the application. Close, - /// Moves the window with the left mouse button until the button is + /// Move the window with the left mouse button until the button is /// released. /// /// There’s no guarantee that this will work unless the left mouse @@ -20,7 +20,7 @@ pub enum Action { /// The new logical height of the window height: u32, }, - /// Sets the window to maximized or back + /// Set the window to maximized or back Maximize(bool), /// Set the window to minimized or back Minimize(bool), @@ -70,15 +70,11 @@ pub enum Action { /// /// - **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), + /// Change the window [`Level`]. + ChangeLevel(Level), /// Fetch an identifier unique to the window. FetchId(Box T + 'static>), - /// Changes the window [`Icon`]. + /// Change the window [`Icon`]. /// /// On Windows and X11, this is typically the small icon in the top-left /// corner of the titlebar. @@ -119,9 +115,7 @@ impl Action { Action::RequestUserAttention(attention_type) } Self::GainFocus => Action::GainFocus, - Self::ChangeAlwaysOnTop(on_top) => { - Action::ChangeAlwaysOnTop(on_top) - } + Self::ChangeLevel(level) => Action::ChangeLevel(level), Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))), Self::ChangeIcon(icon) => Action::ChangeIcon(icon), } @@ -154,8 +148,8 @@ impl fmt::Debug for Action { write!(f, "Action::RequestUserAttention") } Self::GainFocus => write!(f, "Action::GainFocus"), - Self::ChangeAlwaysOnTop(on_top) => { - write!(f, "Action::AlwaysOnTop({on_top})") + Self::ChangeLevel(level) => { + write!(f, "Action::ChangeLevel({level:?})") } Self::FetchId(_) => write!(f, "Action::FetchId"), Self::ChangeIcon(_icon) => { -- cgit From 233196eb14b40f8bd5201ea0262571f82136ad53 Mon Sep 17 00:00:00 2001 From: Bingus Date: Sat, 25 Mar 2023 10:45:39 -0700 Subject: Added offscreen rendering support for wgpu & tiny-skia exposed with the window::screenshot command. --- runtime/src/lib.rs | 2 ++ runtime/src/screenshot.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++ runtime/src/window.rs | 8 +++++ runtime/src/window/action.rs | 9 +++++ 4 files changed, 99 insertions(+) create mode 100644 runtime/src/screenshot.rs (limited to 'runtime') diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 50abf7b2..32ed14d8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -60,6 +60,7 @@ mod debug; #[cfg(not(feature = "debug"))] #[path = "debug/null.rs"] mod debug; +mod screenshot; pub use iced_core as core; pub use iced_futures as futures; @@ -68,4 +69,5 @@ pub use command::Command; pub use debug::Debug; pub use font::Font; pub use program::Program; +pub use screenshot::{CropError, Screenshot}; pub use user_interface::UserInterface; diff --git a/runtime/src/screenshot.rs b/runtime/src/screenshot.rs new file mode 100644 index 00000000..527e400f --- /dev/null +++ b/runtime/src/screenshot.rs @@ -0,0 +1,80 @@ +use iced_core::{Rectangle, Size}; +use std::fmt::{Debug, Formatter}; + +/// Data of a screenshot, captured with `window::screenshot()`. +/// +/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. +#[derive(Clone)] +pub struct Screenshot { + /// The bytes of the [`Screenshot`]. + pub bytes: Vec, + /// The size of the [`Screenshot`]. + pub size: Size, +} + +impl Debug for Screenshot { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Screenshot: {{ \n bytes: {}\n size: {:?} }}", + self.bytes.len(), + self.size + ) + } +} + +impl Screenshot { + /// Creates a new [`Screenshot`]. + pub fn new(bytes: Vec, size: Size) -> Self { + Self { bytes, size } + } + + /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the + /// top-left corner of the [`Screenshot`]. + pub fn crop(&self, region: Rectangle) -> Result { + if region.width == 0 || region.height == 0 { + return Err(CropError::Zero); + } + + if region.x + region.width > self.size.width + || region.y + region.height > self.size.height + { + return Err(CropError::OutOfBounds); + } + + // Image is always RGBA8 = 4 bytes per pixel + const PIXEL_SIZE: usize = 4; + + let bytes_per_row = self.size.width as usize * PIXEL_SIZE; + let row_range = region.y as usize..(region.y + region.height) as usize; + let column_range = region.x as usize * PIXEL_SIZE + ..(region.x + region.width) as usize * PIXEL_SIZE; + + let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold( + vec![], + |mut acc, (row, bytes)| { + if row_range.contains(&row) { + acc.extend(&bytes[column_range.clone()]); + } + + acc + }, + ); + + Ok(Self { + bytes: chopped, + size: Size::new(region.width, region.height), + }) + } +} + +#[derive(Debug, thiserror::Error)] +/// Errors that can occur when cropping a [`Screenshot`]. +pub enum CropError { + #[error("The cropped region is out of bounds.")] + /// The cropped region's size is out of bounds. + OutOfBounds, + #[error("The cropped region is not visible.")] + /// The cropped region's size is zero. + Zero, +} diff --git a/runtime/src/window.rs b/runtime/src/window.rs index d4111293..9b66cb0e 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -7,6 +7,7 @@ use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; use crate::futures::subscription::{self, Subscription}; +use crate::screenshot::Screenshot; /// Subscribes to the frames of the window of the running application. /// @@ -115,3 +116,10 @@ pub fn fetch_id( pub fn change_icon(icon: Icon) -> Command { Command::single(command::Action::Window(Action::ChangeIcon(icon))) } + +/// Captures a [`Screenshot`] from the window. +pub fn screenshot( + f: impl FnOnce(Screenshot) -> Message + Send + 'static, +) -> Command { + Command::single(command::Action::Window(Action::Screenshot(Box::new(f)))) +} diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index a9d2a3d0..cb430681 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,6 +1,7 @@ use crate::core::window::{Icon, Level, Mode, UserAttention}; use crate::futures::MaybeSend; +use crate::screenshot::Screenshot; use std::fmt; /// An operation to be performed on some window. @@ -89,6 +90,8 @@ pub enum Action { /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That /// said, it's usually in the same ballpark as on Windows. ChangeIcon(Icon), + /// Screenshot the viewport of the window. + Screenshot(Box T + 'static>), } impl Action { @@ -118,6 +121,11 @@ impl Action { Self::ChangeLevel(level) => Action::ChangeLevel(level), Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))), Self::ChangeIcon(icon) => Action::ChangeIcon(icon), + Self::Screenshot(tag) => { + Action::Screenshot(Box::new(move |screenshot| { + f(tag(screenshot)) + })) + } } } } @@ -155,6 +163,7 @@ impl fmt::Debug for Action { Self::ChangeIcon(_icon) => { write!(f, "Action::ChangeIcon(icon)") } + Self::Screenshot(_) => write!(f, "Action::Screenshot"), } } } -- cgit From 5324928044cba800454b1861eb9999038bc28c2e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 6 Jun 2023 16:14:42 +0200 Subject: Wrap `Screenshot::bytes` in an `Arc` and implement `AsRef<[u8]>` --- runtime/src/screenshot.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'runtime') diff --git a/runtime/src/screenshot.rs b/runtime/src/screenshot.rs index 527e400f..b88f2c20 100644 --- a/runtime/src/screenshot.rs +++ b/runtime/src/screenshot.rs @@ -1,5 +1,7 @@ -use iced_core::{Rectangle, Size}; +use crate::core::{Rectangle, Size}; + use std::fmt::{Debug, Formatter}; +use std::sync::Arc; /// Data of a screenshot, captured with `window::screenshot()`. /// @@ -7,7 +9,7 @@ use std::fmt::{Debug, Formatter}; #[derive(Clone)] pub struct Screenshot { /// The bytes of the [`Screenshot`]. - pub bytes: Vec, + pub bytes: Arc>, /// The size of the [`Screenshot`]. pub size: Size, } @@ -26,7 +28,10 @@ impl Debug for Screenshot { impl Screenshot { /// Creates a new [`Screenshot`]. pub fn new(bytes: Vec, size: Size) -> Self { - Self { bytes, size } + Self { + bytes: Arc::new(bytes), + size, + } } /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the @@ -62,12 +67,18 @@ impl Screenshot { ); Ok(Self { - bytes: chopped, + bytes: Arc::new(chopped), size: Size::new(region.width, region.height), }) } } +impl AsRef<[u8]> for Screenshot { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + #[derive(Debug, thiserror::Error)] /// Errors that can occur when cropping a [`Screenshot`]. pub enum CropError { -- cgit From 34451bff185d8875f55747ee97ed746828e30f40 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 8 Jun 2023 20:11:59 +0200 Subject: Implement basic cursor availability --- runtime/src/user_interface.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index d9206134..7dee13bc 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -203,7 +203,7 @@ where let event_status = overlay.on_event( event, Layout::new(&layout), - cursor_position, + mouse::Cursor::Available(cursor_position), renderer, clipboard, &mut shell, @@ -257,17 +257,17 @@ where .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); + .map(|_| mouse::Cursor::Unavailable) + .unwrap_or(mouse::Cursor::Available(cursor_position)); self.overlay = Some(layout); (base_cursor, event_statuses) } else { - (cursor_position, vec![event::Status::Ignored; events.len()]) + ( + mouse::Cursor::Available(cursor_position), + vec![event::Status::Ignored; events.len()], + ) }; let _ = ManuallyDrop::into_inner(manual_overlay); @@ -427,19 +427,19 @@ where overlay.layout(renderer, self.bounds, Vector::ZERO) }); - let new_cursor_position = if overlay + let cursor = if overlay .is_over(Layout::new(&overlay_layout), cursor_position) { - Point::new(-1.0, -1.0) + mouse::Cursor::Unavailable } else { - cursor_position + mouse::Cursor::Available(cursor_position) }; self.overlay = Some(overlay_layout); - new_cursor_position + cursor } else { - cursor_position + mouse::Cursor::Available(cursor_position) }; self.root.as_widget().draw( @@ -455,7 +455,7 @@ where let base_interaction = self.root.as_widget().mouse_interaction( &self.state, Layout::new(&self.base), - cursor_position, + base_cursor, &viewport, renderer, ); @@ -480,7 +480,7 @@ where .map(|overlay| { let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), - cursor_position, + mouse::Cursor::Available(cursor_position), &viewport, renderer, ); @@ -493,7 +493,7 @@ where theme, style, Layout::new(layout), - cursor_position, + mouse::Cursor::Available(cursor_position), ); }); -- cgit From aba98e49654852281ed17bedd1becac6f9db8700 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 8 Jun 2023 20:35:40 +0200 Subject: Extend cursor availability to the shell level --- runtime/src/program/state.rs | 10 +++---- runtime/src/user_interface.rs | 67 +++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 29 deletions(-) (limited to 'runtime') diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 2fa9934d..d83e3f54 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,7 +1,7 @@ use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; -use crate::core::{Clipboard, Point, Size}; +use crate::core::{Clipboard, Size}; use crate::user_interface::{self, UserInterface}; use crate::{Command, Debug, Program}; @@ -88,7 +88,7 @@ where pub fn update( &mut self, bounds: Size, - cursor_position: Point, + cursor: mouse::Cursor, renderer: &mut P::Renderer, theme: &::Theme, style: &renderer::Style, @@ -108,7 +108,7 @@ where let (_, event_statuses) = user_interface.update( &self.queued_events, - cursor_position, + cursor, renderer, clipboard, &mut messages, @@ -131,7 +131,7 @@ where let command = if messages.is_empty() { debug.draw_started(); self.mouse_interaction = - user_interface.draw(renderer, theme, style, cursor_position); + user_interface.draw(renderer, theme, style, cursor); debug.draw_finished(); self.cache = Some(user_interface.into_cache()); @@ -163,7 +163,7 @@ where debug.draw_started(); self.mouse_interaction = - user_interface.draw(renderer, theme, style, cursor_position); + user_interface.draw(renderer, theme, style, cursor); debug.draw_finished(); self.cache = Some(user_interface.into_cache()); diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 7dee13bc..68ff6158 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -5,7 +5,7 @@ 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::{Clipboard, Rectangle, Size, Vector}; use crate::core::{Element, Layout, Shell}; /// A set of interactive graphical elements with a specific [`Layout`]. @@ -128,7 +128,9 @@ where /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } - /// use iced_runtime::core::{clipboard, Size, Point}; + /// use iced_runtime::core::clipboard; + /// use iced_runtime::core::mouse; + /// use iced_runtime::core::Size; /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// @@ -136,7 +138,7 @@ where /// 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 cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; /// /// // Initialize our event storage @@ -156,7 +158,7 @@ where /// // Update the user interface /// let (state, event_statuses) = user_interface.update( /// &events, - /// cursor_position, + /// cursor, /// &mut renderer, /// &mut clipboard, /// &mut messages @@ -173,7 +175,7 @@ where pub fn update( &mut self, events: &[Event], - cursor_position: Point, + cursor: mouse::Cursor, renderer: &mut Renderer, clipboard: &mut dyn Clipboard, messages: &mut Vec, @@ -203,7 +205,7 @@ where let event_status = overlay.on_event( event, Layout::new(&layout), - mouse::Cursor::Available(cursor_position), + cursor, renderer, clipboard, &mut shell, @@ -255,19 +257,22 @@ where let base_cursor = manual_overlay .as_ref() .filter(|overlay| { - overlay.is_over(Layout::new(&layout), cursor_position) + cursor + .position() + .map(|cursor_position| { + overlay + .is_over(Layout::new(&layout), cursor_position) + }) + .unwrap_or_default() }) .map(|_| mouse::Cursor::Unavailable) - .unwrap_or(mouse::Cursor::Available(cursor_position)); + .unwrap_or(cursor); self.overlay = Some(layout); (base_cursor, event_statuses) } else { - ( - mouse::Cursor::Available(cursor_position), - vec![event::Status::Ignored; events.len()], - ) + (cursor, vec![event::Status::Ignored; events.len()]) }; let _ = ManuallyDrop::into_inner(manual_overlay); @@ -359,8 +364,9 @@ where /// # pub fn update(&mut self, _: ()) {} /// # } /// use iced_runtime::core::clipboard; + /// use iced_runtime::core::mouse; /// use iced_runtime::core::renderer; - /// use iced_runtime::core::{Element, Size, Point}; + /// use iced_runtime::core::{Element, Size}; /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::{Renderer, Theme}; /// @@ -368,7 +374,7 @@ where /// 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 cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); @@ -387,14 +393,14 @@ where /// // Update the user interface /// let event_statuses = user_interface.update( /// &events, - /// cursor_position, + /// cursor, /// &mut renderer, /// &mut clipboard, /// &mut messages /// ); /// /// // Draw the user interface - /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position); + /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor); /// /// cache = user_interface.into_cache(); /// @@ -411,7 +417,7 @@ where renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, - cursor_position: Point, + cursor: mouse::Cursor, ) -> mouse::Interaction { // TODO: Move to shell level (?) renderer.clear(); @@ -427,19 +433,24 @@ where overlay.layout(renderer, self.bounds, Vector::ZERO) }); - let cursor = if overlay - .is_over(Layout::new(&overlay_layout), cursor_position) + let cursor = if cursor + .position() + .map(|cursor_position| { + overlay + .is_over(Layout::new(&overlay_layout), cursor_position) + }) + .unwrap_or_default() { mouse::Cursor::Unavailable } else { - mouse::Cursor::Available(cursor_position) + cursor }; self.overlay = Some(overlay_layout); cursor } else { - mouse::Cursor::Available(cursor_position) + cursor }; self.root.as_widget().draw( @@ -480,7 +491,7 @@ where .map(|overlay| { let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), - mouse::Cursor::Available(cursor_position), + cursor, &viewport, renderer, ); @@ -493,11 +504,19 @@ where theme, style, Layout::new(layout), - mouse::Cursor::Available(cursor_position), + cursor, ); }); - if overlay.is_over(Layout::new(layout), cursor_position) + if cursor + .position() + .map(|cursor_position| { + overlay.is_over( + Layout::new(layout), + cursor_position, + ) + }) + .unwrap_or_default() { overlay_interaction } else { -- cgit From 55dc3b5619392f4a20389255708c61082b3d4c1a Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 14:31:38 -0800 Subject: Introduce internal `overlay::Nested` for `UserInterface` --- runtime/src/user_interface.rs | 71 +++++---- runtime/src/user_interface/overlay.rs | 288 ++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+), 27 deletions(-) create mode 100644 runtime/src/user_interface/overlay.rs (limited to 'runtime') diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 68ff6158..8ae0363a 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -1,12 +1,14 @@ //! Implement your own event loop to drive a user interface. +mod overlay; + 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, Rectangle, Size, Vector}; -use crate::core::{Element, Layout, Shell}; +use crate::core::{Clipboard, Point, Rectangle, Size}; +use crate::core::{Element, Layout, Overlay, Shell}; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -185,18 +187,18 @@ where 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 mut manual_overlay = ManuallyDrop::new( + self.root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new), + ); 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 layout = overlay.layout(renderer, bounds, Point::ORIGIN); let mut event_statuses = Vec::new(); for event in events.iter().cloned() { @@ -231,12 +233,16 @@ where &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, - )); + manual_overlay = ManuallyDrop::new( + self.root + .as_widget_mut() + .overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) + .map(overlay::Nested::new), + ); if manual_overlay.is_none() { break; @@ -245,7 +251,8 @@ where overlay = manual_overlay.as_mut().unwrap(); shell.revalidate_layout(|| { - layout = overlay.layout(renderer, bounds, Vector::ZERO); + layout = + overlay.layout(renderer, bounds, Point::ORIGIN); }); } @@ -260,8 +267,11 @@ where cursor .position() .map(|cursor_position| { - overlay - .is_over(Layout::new(&layout), cursor_position) + overlay.is_over( + Layout::new(&layout), + renderer, + cursor_position, + ) }) .unwrap_or_default() }) @@ -428,16 +438,20 @@ where .root .as_widget_mut() .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new) { let overlay_layout = self.overlay.take().unwrap_or_else(|| { - overlay.layout(renderer, self.bounds, Vector::ZERO) + overlay.layout(renderer, self.bounds, Point::ORIGIN) }); let cursor = if cursor .position() .map(|cursor_position| { - overlay - .is_over(Layout::new(&overlay_layout), cursor_position) + overlay.is_over( + Layout::new(&overlay_layout), + renderer, + cursor_position, + ) }) .unwrap_or_default() { @@ -488,6 +502,7 @@ where .and_then(|layout| { root.as_widget_mut() .overlay(&mut self.state, Layout::new(base), renderer) + .map(overlay::Nested::new) .map(|overlay| { let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), @@ -513,6 +528,7 @@ where .map(|cursor_position| { overlay.is_over( Layout::new(layout), + renderer, cursor_position, ) }) @@ -540,14 +556,15 @@ where operation, ); - if let Some(mut overlay) = self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { + if let Some(mut overlay) = self + .root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new) + { if self.overlay.is_none() { self.overlay = - Some(overlay.layout(renderer, self.bounds, Vector::ZERO)); + Some(overlay.layout(renderer, self.bounds, Point::ORIGIN)); } overlay.operate( diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs new file mode 100644 index 00000000..6dfed153 --- /dev/null +++ b/runtime/src/user_interface/overlay.rs @@ -0,0 +1,288 @@ +use crate::core::event; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::{ + Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, +}; + +use std::cell::RefCell; + +/// An [`Overlay`] container that displays nested overlays +#[allow(missing_debug_implementations)] +pub struct Nested<'a, Message, Renderer> { + overlay: Inner<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Nested<'a, Message, Renderer> { + /// Creates a nested overlay from the provided [`overlay::Element`] + pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self { + Self { + overlay: Inner(RefCell::new(element)), + } + } +} + +struct Inner<'a, Message, Renderer>( + RefCell>, +); + +impl<'a, Message, Renderer> Inner<'a, Message, Renderer> { + fn with_element_mut( + &self, + mut f: impl FnMut(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> T { + (f)(&mut self.0.borrow_mut()) + } +} + +impl<'a, Message, Renderer> Overlay + for Nested<'a, Message, Renderer> +where + Renderer: renderer::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> Vec + where + Renderer: renderer::Renderer, + { + let translation = position - Point::ORIGIN; + + let node = element.layout(renderer, bounds, translation); + + if let Some(mut overlay) = + element.overlay(Layout::new(&node), renderer) + { + vec![node] + .into_iter() + .chain(recurse(&mut overlay, renderer, bounds, position)) + .collect() + } else { + vec![node] + } + } + + self.overlay.with_element_mut(|element| { + layout::Node::with_children( + bounds, + recurse(element, renderer, bounds, position), + ) + }) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) { + fn recurse<'a, Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + mut layouts: impl Iterator>, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + cursor: mouse::Cursor, + ) where + Renderer: renderer::Renderer, + { + let layout = layouts.next().unwrap(); + + element.draw(renderer, theme, style, layout, cursor); + + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse(&mut overlay, layouts, renderer, theme, style, cursor); + } + } + + self.overlay.with_element_mut(|element| { + let layouts = layout.children(); + + recurse(element, layouts, renderer, theme, style, cursor); + }) + } + + fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + fn recurse<'a, Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + mut layouts: impl Iterator>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) where + Renderer: renderer::Renderer, + { + let layout = layouts.next().unwrap(); + + element.operate(layout, renderer, operation); + + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse(&mut overlay, layouts, renderer, operation); + } + } + + let layouts = layout.children(); + + recurse(self.overlay.0.get_mut(), layouts, renderer, operation) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + fn recurse<'a, Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + mut layouts: impl Iterator>, + event: Event, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status + where + Renderer: renderer::Renderer, + { + let layout = layouts.next().unwrap(); + + let status = + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse( + &mut overlay, + layouts, + event.clone(), + cursor, + renderer, + clipboard, + shell, + ) + } else { + event::Status::Ignored + }; + + if matches!(status, event::Status::Ignored) { + element + .on_event(event, layout, cursor, renderer, clipboard, shell) + } else { + status + } + } + + let layouts = layout.children(); + + recurse( + self.overlay.0.get_mut(), + layouts, + event, + cursor, + renderer, + clipboard, + shell, + ) + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + fn recurse<'a, Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + mut layouts: impl Iterator>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction + where + Renderer: renderer::Renderer, + { + let layout = layouts.next().unwrap(); + + let interaction = + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse(&mut overlay, layouts, cursor, viewport, renderer) + } else { + mouse::Interaction::default() + }; + + element + .mouse_interaction(layout, cursor, viewport, renderer) + .max(interaction) + } + + self.overlay.with_element_mut(|element| { + let layouts = layout.children(); + + recurse(element, layouts, cursor, viewport, renderer) + }) + } + + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { + fn recurse<'a, Message, Renderer>( + element: &mut overlay::Element<'_, Message, Renderer>, + mut layouts: impl Iterator>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool + where + Renderer: renderer::Renderer, + { + let layout = layouts.next().unwrap(); + + let is_over = element.is_over(layout, renderer, cursor_position); + + if is_over { + return true; + } + + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse(&mut overlay, layouts, renderer, cursor_position) + } else { + false + } + } + + self.overlay.with_element_mut(|element| { + let layouts = layout.children(); + + recurse(element, layouts, renderer, cursor_position) + }) + } + + fn overlay<'b>( + &'b mut self, + _layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + None + } +} -- cgit From f44d4292838f0eaa3fca3ce074977082bb2a6be9 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 13:49:25 -0800 Subject: Render nested in layer --- runtime/src/user_interface/overlay.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime') diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs index 6dfed153..590cc49f 100644 --- a/runtime/src/user_interface/overlay.rs +++ b/runtime/src/user_interface/overlay.rs @@ -102,7 +102,9 @@ where { let layout = layouts.next().unwrap(); - element.draw(renderer, theme, style, layout, cursor); + renderer.with_layer(layout.bounds(), |renderer| { + element.draw(renderer, theme, style, layout, cursor); + }); if let Some(mut overlay) = element.overlay(layout, renderer) { recurse(&mut overlay, layouts, renderer, theme, style, cursor); -- cgit From 1ce047cdb3bb210a8a949794d3db88b3a029df81 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 13:53:22 -0800 Subject: Prioritize mouse interaction of deepest `Overlay` --- runtime/src/user_interface/overlay.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs index 590cc49f..f80d1914 100644 --- a/runtime/src/user_interface/overlay.rs +++ b/runtime/src/user_interface/overlay.rs @@ -224,16 +224,28 @@ where { let layout = layouts.next().unwrap(); - let interaction = - if let Some(mut overlay) = element.overlay(layout, renderer) { - recurse(&mut overlay, layouts, cursor, viewport, renderer) - } else { - mouse::Interaction::default() - }; + if let Some(cursor_position) = cursor.position() { + match element.overlay(layout, renderer) { + Some(mut overlay) + if overlay.is_over( + layout, + renderer, + cursor_position, + ) => + { + return recurse( + &mut overlay, + layouts, + cursor, + viewport, + renderer, + ); + } + _ => {} + } + } - element - .mouse_interaction(layout, cursor, viewport, renderer) - .max(interaction) + element.mouse_interaction(layout, cursor, viewport, renderer) } self.overlay.with_element_mut(|element| { -- cgit From d4bb7c0b24e3bf7939bdecf2f3e1e0dba67587fc Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 15:06:44 -0800 Subject: Remove unwraps in `overlay::Nested` and fix `mouse_interaction` --- runtime/src/user_interface/overlay.rs | 120 +++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 51 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs index f80d1914..332eb32d 100644 --- a/runtime/src/user_interface/overlay.rs +++ b/runtime/src/user_interface/overlay.rs @@ -100,14 +100,25 @@ where ) where Renderer: renderer::Renderer, { - let layout = layouts.next().unwrap(); + if let Some(layout) = layouts.next() { + renderer.with_layer(layout.bounds(), |renderer| { + element.draw(renderer, theme, style, layout, cursor); + }); - renderer.with_layer(layout.bounds(), |renderer| { - element.draw(renderer, theme, style, layout, cursor); - }); + renderer.with_layer(layout.bounds(), |renderer| { + element.draw(renderer, theme, style, layout, cursor); + }); - if let Some(mut overlay) = element.overlay(layout, renderer) { - recurse(&mut overlay, layouts, renderer, theme, style, cursor); + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse( + &mut overlay, + layouts, + renderer, + theme, + style, + cursor, + ); + } } } @@ -132,12 +143,12 @@ where ) where Renderer: renderer::Renderer, { - let layout = layouts.next().unwrap(); + if let Some(layout) = layouts.next() { + element.operate(layout, renderer, operation); - element.operate(layout, renderer, operation); - - if let Some(mut overlay) = element.overlay(layout, renderer) { - recurse(&mut overlay, layouts, renderer, operation); + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse(&mut overlay, layouts, renderer, operation); + } } } @@ -167,10 +178,10 @@ where where Renderer: renderer::Renderer, { - let layout = layouts.next().unwrap(); - - let status = - if let Some(mut overlay) = element.overlay(layout, renderer) { + if let Some(layout) = layouts.next() { + let status = if let Some(mut overlay) = + element.overlay(layout, renderer) + { recurse( &mut overlay, layouts, @@ -184,11 +195,15 @@ where event::Status::Ignored }; - if matches!(status, event::Status::Ignored) { - element - .on_event(event, layout, cursor, renderer, clipboard, shell) + if matches!(status, event::Status::Ignored) { + element.on_event( + event, layout, cursor, renderer, clipboard, shell, + ) + } else { + status + } } else { - status + event::Status::Ignored } } @@ -218,41 +233,44 @@ where cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, - ) -> mouse::Interaction + ) -> Option where Renderer: renderer::Renderer, { - let layout = layouts.next().unwrap(); + let layout = layouts.next()?; + let cursor_position = cursor.position()?; - if let Some(cursor_position) = cursor.position() { - match element.overlay(layout, renderer) { - Some(mut overlay) - if overlay.is_over( - layout, - renderer, - cursor_position, - ) => - { - return recurse( + if !element.is_over(layout, renderer, cursor_position) { + return None; + } + + Some( + element + .overlay(layout, renderer) + .and_then(|mut overlay| { + recurse( &mut overlay, layouts, cursor, viewport, renderer, - ); - } - _ => {} - } - } - - element.mouse_interaction(layout, cursor, viewport, renderer) + ) + }) + .unwrap_or_else(|| { + element.mouse_interaction( + layout, cursor, viewport, renderer, + ) + }), + ) } - self.overlay.with_element_mut(|element| { - let layouts = layout.children(); + self.overlay + .with_element_mut(|element| { + let layouts = layout.children(); - recurse(element, layouts, cursor, viewport, renderer) - }) + recurse(element, layouts, cursor, viewport, renderer) + }) + .unwrap_or_default() } fn is_over( @@ -270,16 +288,16 @@ where where Renderer: renderer::Renderer, { - let layout = layouts.next().unwrap(); - - let is_over = element.is_over(layout, renderer, cursor_position); - - if is_over { - return true; - } + if let Some(layout) = layouts.next() { + if element.is_over(layout, renderer, cursor_position) { + return true; + } - if let Some(mut overlay) = element.overlay(layout, renderer) { - recurse(&mut overlay, layouts, renderer, cursor_position) + if let Some(mut overlay) = element.overlay(layout, renderer) { + recurse(&mut overlay, layouts, renderer, cursor_position) + } else { + false + } } else { false } -- cgit From 12c623f35bea0b76bae5e2eeb7d46390b8b9f0e4 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 15:58:07 -0800 Subject: Cursor availability by layer --- runtime/src/user_interface/overlay.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs index 332eb32d..44f64c61 100644 --- a/runtime/src/user_interface/overlay.rs +++ b/runtime/src/user_interface/overlay.rs @@ -9,6 +9,7 @@ use crate::core::{ }; use std::cell::RefCell; +use std::iter::Peekable; /// An [`Overlay`] container that displays nested overlays #[allow(missing_debug_implementations)] @@ -92,7 +93,7 @@ where ) { fn recurse<'a, Message, Renderer>( element: &mut overlay::Element<'_, Message, Renderer>, - mut layouts: impl Iterator>, + mut layouts: Peekable>>, renderer: &mut Renderer, theme: &::Theme, style: &renderer::Style, @@ -101,12 +102,33 @@ where Renderer: renderer::Renderer, { if let Some(layout) = layouts.next() { - renderer.with_layer(layout.bounds(), |renderer| { - element.draw(renderer, theme, style, layout, cursor); - }); + let is_over = cursor + .position() + .and_then(|cursor_position| { + layouts.peek().and_then(|nested_layout| { + element.overlay(layout, renderer).map(|overlay| { + overlay.is_over( + *nested_layout, + renderer, + cursor_position, + ) + }) + }) + }) + .unwrap_or_default(); renderer.with_layer(layout.bounds(), |renderer| { - element.draw(renderer, theme, style, layout, cursor); + element.draw( + renderer, + theme, + style, + layout, + if is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + ); }); if let Some(mut overlay) = element.overlay(layout, renderer) { @@ -123,7 +145,7 @@ where } self.overlay.with_element_mut(|element| { - let layouts = layout.children(); + let layouts = layout.children().peekable(); recurse(element, layouts, renderer, theme, style, cursor); }) -- cgit From 3e6f6eedcb51150b8a599530021cebdc629fddd2 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 16:08:05 -0800 Subject: Use layout with children for nesting --- runtime/src/user_interface/overlay.rs | 130 +++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 59 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs index 44f64c61..c495bb28 100644 --- a/runtime/src/user_interface/overlay.rs +++ b/runtime/src/user_interface/overlay.rs @@ -9,7 +9,6 @@ use crate::core::{ }; use std::cell::RefCell; -use std::iter::Peekable; /// An [`Overlay`] container that displays nested overlays #[allow(missing_debug_implementations)] @@ -55,7 +54,7 @@ where renderer: &Renderer, bounds: Size, position: Point, - ) -> Vec + ) -> layout::Node where Renderer: renderer::Renderer, { @@ -63,23 +62,23 @@ where let node = element.layout(renderer, bounds, translation); - if let Some(mut overlay) = + if let Some(mut nested) = element.overlay(Layout::new(&node), renderer) { - vec![node] - .into_iter() - .chain(recurse(&mut overlay, renderer, bounds, position)) - .collect() + layout::Node::with_children( + node.size(), + vec![ + node, + recurse(&mut nested, renderer, bounds, position), + ], + ) } else { - vec![node] + layout::Node::with_children(node.size(), vec![node]) } } self.overlay.with_element_mut(|element| { - layout::Node::with_children( - bounds, - recurse(element, renderer, bounds, position), - ) + recurse(element, renderer, bounds, position) }) } @@ -91,9 +90,9 @@ where layout: Layout<'_>, cursor: mouse::Cursor, ) { - fn recurse<'a, Message, Renderer>( + fn recurse( element: &mut overlay::Element<'_, Message, Renderer>, - mut layouts: Peekable>>, + layout: Layout<'_>, renderer: &mut Renderer, theme: &::Theme, style: &renderer::Style, @@ -101,18 +100,21 @@ where ) where Renderer: renderer::Renderer, { + let mut layouts = layout.children(); + if let Some(layout) = layouts.next() { + let nested_layout = layouts.next(); + let is_over = cursor .position() - .and_then(|cursor_position| { - layouts.peek().and_then(|nested_layout| { - element.overlay(layout, renderer).map(|overlay| { - overlay.is_over( - *nested_layout, - renderer, - cursor_position, - ) - }) + .zip(nested_layout) + .and_then(|(cursor_position, nested_layout)| { + element.overlay(layout, renderer).map(|nested| { + nested.is_over( + nested_layout, + renderer, + cursor_position, + ) }) }) .unwrap_or_default(); @@ -131,10 +133,12 @@ where ); }); - if let Some(mut overlay) = element.overlay(layout, renderer) { + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(nested_layout) + { recurse( - &mut overlay, - layouts, + &mut nested, + nested_layout, renderer, theme, style, @@ -145,9 +149,7 @@ where } self.overlay.with_element_mut(|element| { - let layouts = layout.children().peekable(); - - recurse(element, layouts, renderer, theme, style, cursor); + recurse(element, layout, renderer, theme, style, cursor); }) } @@ -157,26 +159,28 @@ where renderer: &Renderer, operation: &mut dyn widget::Operation, ) { - fn recurse<'a, Message, Renderer>( + fn recurse( element: &mut overlay::Element<'_, Message, Renderer>, - mut layouts: impl Iterator>, + layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) where Renderer: renderer::Renderer, { + let mut layouts = layout.children(); + if let Some(layout) = layouts.next() { element.operate(layout, renderer, operation); - if let Some(mut overlay) = element.overlay(layout, renderer) { - recurse(&mut overlay, layouts, renderer, operation); + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse(&mut nested, nested_layout, renderer, operation); } } } - let layouts = layout.children(); - - recurse(self.overlay.0.get_mut(), layouts, renderer, operation) + recurse(self.overlay.0.get_mut(), layout, renderer, operation) } fn on_event( @@ -188,9 +192,9 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - fn recurse<'a, Message, Renderer>( + fn recurse( element: &mut overlay::Element<'_, Message, Renderer>, - mut layouts: impl Iterator>, + layout: Layout<'_>, event: Event, cursor: mouse::Cursor, renderer: &Renderer, @@ -200,13 +204,15 @@ where where Renderer: renderer::Renderer, { + let mut layouts = layout.children(); + if let Some(layout) = layouts.next() { - let status = if let Some(mut overlay) = - element.overlay(layout, renderer) + let status = if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) { recurse( - &mut overlay, - layouts, + &mut nested, + nested_layout, event.clone(), cursor, renderer, @@ -229,11 +235,9 @@ where } } - let layouts = layout.children(); - recurse( self.overlay.0.get_mut(), - layouts, + layout, event, cursor, renderer, @@ -249,9 +253,9 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - fn recurse<'a, Message, Renderer>( + fn recurse( element: &mut overlay::Element<'_, Message, Renderer>, - mut layouts: impl Iterator>, + layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, @@ -259,6 +263,8 @@ where where Renderer: renderer::Renderer, { + let mut layouts = layout.children(); + let layout = layouts.next()?; let cursor_position = cursor.position()?; @@ -269,10 +275,11 @@ where Some( element .overlay(layout, renderer) - .and_then(|mut overlay| { + .zip(layouts.next()) + .and_then(|(mut overlay, layout)| { recurse( &mut overlay, - layouts, + layout, cursor, viewport, renderer, @@ -288,9 +295,7 @@ where self.overlay .with_element_mut(|element| { - let layouts = layout.children(); - - recurse(element, layouts, cursor, viewport, renderer) + recurse(element, layout, cursor, viewport, renderer) }) .unwrap_or_default() } @@ -301,22 +306,31 @@ where renderer: &Renderer, cursor_position: Point, ) -> bool { - fn recurse<'a, Message, Renderer>( + fn recurse( element: &mut overlay::Element<'_, Message, Renderer>, - mut layouts: impl Iterator>, + layout: Layout<'_>, renderer: &Renderer, cursor_position: Point, ) -> bool where Renderer: renderer::Renderer, { + let mut layouts = layout.children(); + if let Some(layout) = layouts.next() { if element.is_over(layout, renderer, cursor_position) { return true; } - if let Some(mut overlay) = element.overlay(layout, renderer) { - recurse(&mut overlay, layouts, renderer, cursor_position) + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + renderer, + cursor_position, + ) } else { false } @@ -326,9 +340,7 @@ where } self.overlay.with_element_mut(|element| { - let layouts = layout.children(); - - recurse(element, layouts, renderer, cursor_position) + recurse(element, layout, renderer, cursor_position) }) } -- cgit From 83140d6049c165020c2afc1db303b2556e40488e Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 19:03:52 -0800 Subject: Remove interior mutability Nested doesn't need to implement Overlay trait, it can be be used mutably in user interface so we don't need interior mutability. --- runtime/src/user_interface.rs | 37 +++++++-------- runtime/src/user_interface/overlay.rs | 85 ++++++++++------------------------- 2 files changed, 42 insertions(+), 80 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 8ae0363a..1d55970e 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -8,7 +8,7 @@ use crate::core::renderer; use crate::core::widget; use crate::core::window; use crate::core::{Clipboard, Point, Rectangle, Size}; -use crate::core::{Element, Layout, Overlay, Shell}; +use crate::core::{Element, Layout, Shell}; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -261,22 +261,23 @@ where } } - let base_cursor = manual_overlay - .as_ref() - .filter(|overlay| { - cursor - .position() - .map(|cursor_position| { - overlay.is_over( - Layout::new(&layout), - renderer, - cursor_position, - ) - }) - .unwrap_or_default() + let base_cursor = if manual_overlay + .as_mut() + .and_then(|overlay| { + cursor.position().map(|cursor_position| { + overlay.is_over( + Layout::new(&layout), + renderer, + cursor_position, + ) + }) }) - .map(|_| mouse::Cursor::Unavailable) - .unwrap_or(cursor); + .unwrap_or_default() + { + mouse::Cursor::Unavailable + } else { + cursor + }; self.overlay = Some(layout); @@ -434,7 +435,7 @@ where let viewport = Rectangle::with_size(self.bounds); - let base_cursor = if let Some(overlay) = self + let base_cursor = if let Some(mut overlay) = self .root .as_widget_mut() .overlay(&mut self.state, Layout::new(&self.base), renderer) @@ -503,7 +504,7 @@ where root.as_widget_mut() .overlay(&mut self.state, Layout::new(base), renderer) .map(overlay::Nested::new) - .map(|overlay| { + .map(|mut overlay| { let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), cursor, diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs index c495bb28..24ddb649 100644 --- a/runtime/src/user_interface/overlay.rs +++ b/runtime/src/user_interface/overlay.rs @@ -4,47 +4,25 @@ use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget; -use crate::core::{ - Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, -}; - -use std::cell::RefCell; +use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; /// An [`Overlay`] container that displays nested overlays #[allow(missing_debug_implementations)] pub struct Nested<'a, Message, Renderer> { - overlay: Inner<'a, Message, Renderer>, + overlay: overlay::Element<'a, Message, Renderer>, } -impl<'a, Message, Renderer> Nested<'a, Message, Renderer> { +impl<'a, Message, Renderer> Nested<'a, Message, Renderer> +where + Renderer: renderer::Renderer, +{ /// Creates a nested overlay from the provided [`overlay::Element`] pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self { - Self { - overlay: Inner(RefCell::new(element)), - } + Self { overlay: element } } -} - -struct Inner<'a, Message, Renderer>( - RefCell>, -); - -impl<'a, Message, Renderer> Inner<'a, Message, Renderer> { - fn with_element_mut( - &self, - mut f: impl FnMut(&mut overlay::Element<'_, Message, Renderer>) -> T, - ) -> T { - (f)(&mut self.0.borrow_mut()) - } -} -impl<'a, Message, Renderer> Overlay - for Nested<'a, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - fn layout( - &self, + pub fn layout( + &mut self, renderer: &Renderer, bounds: Size, position: Point, @@ -77,13 +55,11 @@ where } } - self.overlay.with_element_mut(|element| { - recurse(element, renderer, bounds, position) - }) + recurse(&mut self.overlay, renderer, bounds, position) } - fn draw( - &self, + pub fn draw( + &mut self, renderer: &mut Renderer, theme: &::Theme, style: &renderer::Style, @@ -148,12 +124,10 @@ where } } - self.overlay.with_element_mut(|element| { - recurse(element, layout, renderer, theme, style, cursor); - }) + recurse(&mut self.overlay, layout, renderer, theme, style, cursor); } - fn operate( + pub fn operate( &mut self, layout: Layout<'_>, renderer: &Renderer, @@ -180,10 +154,10 @@ where } } - recurse(self.overlay.0.get_mut(), layout, renderer, operation) + recurse(&mut self.overlay, layout, renderer, operation) } - fn on_event( + pub fn on_event( &mut self, event: Event, layout: Layout<'_>, @@ -236,7 +210,7 @@ where } recurse( - self.overlay.0.get_mut(), + &mut self.overlay, layout, event, cursor, @@ -246,8 +220,8 @@ where ) } - fn mouse_interaction( - &self, + pub fn mouse_interaction( + &mut self, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, @@ -293,15 +267,12 @@ where ) } - self.overlay - .with_element_mut(|element| { - recurse(element, layout, cursor, viewport, renderer) - }) + recurse(&mut self.overlay, layout, cursor, viewport, renderer) .unwrap_or_default() } - fn is_over( - &self, + pub fn is_over( + &mut self, layout: Layout<'_>, renderer: &Renderer, cursor_position: Point, @@ -339,16 +310,6 @@ where } } - self.overlay.with_element_mut(|element| { - recurse(element, layout, renderer, cursor_position) - }) - } - - fn overlay<'b>( - &'b mut self, - _layout: Layout<'_>, - _renderer: &Renderer, - ) -> Option> { - None + recurse(&mut self.overlay, layout, renderer, cursor_position) } } -- cgit From 4de6ee6fa18be5b8fa511bffe93bbec2c5811bfc Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sat, 18 Feb 2023 19:11:50 -0800 Subject: Cursor availability during on_event --- runtime/src/user_interface/overlay.rs | 73 ++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 23 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs index 24ddb649..1211d55b 100644 --- a/runtime/src/user_interface/overlay.rs +++ b/runtime/src/user_interface/overlay.rs @@ -174,42 +174,67 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status + ) -> (event::Status, bool) where Renderer: renderer::Renderer, { let mut layouts = layout.children(); if let Some(layout) = layouts.next() { - let status = if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) - { - recurse( - &mut nested, - nested_layout, - event.clone(), - cursor, - renderer, - clipboard, - shell, - ) - } else { - event::Status::Ignored - }; + let (nested_status, nested_is_over) = + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + event.clone(), + cursor, + renderer, + clipboard, + shell, + ) + } else { + (event::Status::Ignored, false) + }; - if matches!(status, event::Status::Ignored) { - element.on_event( - event, layout, cursor, renderer, clipboard, shell, + if matches!(nested_status, event::Status::Ignored) { + let is_over = nested_is_over + || cursor + .position() + .map(|cursor_position| { + element.is_over( + layout, + renderer, + cursor_position, + ) + }) + .unwrap_or_default(); + + ( + element.on_event( + event, + layout, + if nested_is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + renderer, + clipboard, + shell, + ), + is_over, ) } else { - status + (nested_status, nested_is_over) } } else { - event::Status::Ignored + (event::Status::Ignored, false) } } - recurse( + let (status, _) = recurse( &mut self.overlay, layout, event, @@ -217,7 +242,9 @@ where renderer, clipboard, shell, - ) + ); + + status } pub fn mouse_interaction( -- cgit From b0205e03d8e4794850e55e8c4bf83a40dd41aa9d Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Sun, 19 Feb 2023 17:43:13 -0800 Subject: Use nested for lazy widgets --- runtime/src/lib.rs | 1 + runtime/src/overlay.rs | 4 + runtime/src/overlay/nested.rs | 353 ++++++++++++++++++++++++++++++++++ runtime/src/user_interface.rs | 6 +- runtime/src/user_interface/overlay.rs | 342 -------------------------------- 5 files changed, 360 insertions(+), 346 deletions(-) create mode 100644 runtime/src/overlay.rs create mode 100644 runtime/src/overlay/nested.rs delete mode 100644 runtime/src/user_interface/overlay.rs (limited to 'runtime') diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 50abf7b2..4bbf9687 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -47,6 +47,7 @@ pub mod clipboard; pub mod command; pub mod font; pub mod keyboard; +pub mod overlay; pub mod program; pub mod system; pub mod user_interface; diff --git a/runtime/src/overlay.rs b/runtime/src/overlay.rs new file mode 100644 index 00000000..03390980 --- /dev/null +++ b/runtime/src/overlay.rs @@ -0,0 +1,4 @@ +//! Overlays for user interfaces. +mod nested; + +pub use nested::Nested; diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs new file mode 100644 index 00000000..5c5fafde --- /dev/null +++ b/runtime/src/overlay/nested.rs @@ -0,0 +1,353 @@ +use crate::core::event; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; + +/// An [`Overlay`] container that displays nested overlays +#[allow(missing_debug_implementations)] +pub struct Nested<'a, Message, Renderer> { + overlay: overlay::Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Nested<'a, Message, Renderer> +where + Renderer: renderer::Renderer, +{ + /// Creates a nested overlay from the provided [`overlay::Element`] + pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self { + Self { overlay: element } + } + + /// Returns the position of the [`Nested`] overlay. + pub fn position(&self) -> Point { + self.overlay.position() + } + + /// Returns the layout [`Node`] of the [`Nested`] overlay. + pub fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node + where + Renderer: renderer::Renderer, + { + let translation = position - element.position(); + + let node = element.layout(renderer, bounds, translation); + + if let Some(mut nested) = + element.overlay(Layout::new(&node), renderer) + { + layout::Node::with_children( + node.size(), + vec![ + node, + recurse(&mut nested, renderer, bounds, position), + ], + ) + } else { + layout::Node::with_children(node.size(), vec![node]) + } + } + + recurse(&mut self.overlay, renderer, bounds, position) + } + + /// Draws the [`Nested`] overlay using the associated `Renderer`. + pub fn draw( + &mut self, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + cursor: mouse::Cursor, + ) where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + let nested_layout = layouts.next(); + + let is_over = cursor + .position() + .zip(nested_layout) + .and_then(|(cursor_position, nested_layout)| { + element.overlay(layout, renderer).map(|nested| { + nested.is_over( + nested_layout, + renderer, + cursor_position, + ) + }) + }) + .unwrap_or_default(); + + renderer.with_layer(layout.bounds(), |renderer| { + element.draw( + renderer, + theme, + style, + layout, + if is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + ); + }); + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(nested_layout) + { + recurse( + &mut nested, + nested_layout, + renderer, + theme, + style, + cursor, + ); + } + } + } + + recurse(&mut self.overlay, layout, renderer, theme, style, cursor); + } + + /// Applies a [`widget::Operation`] to the [`Nested`] overlay. + pub fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + element.operate(layout, renderer, operation); + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse(&mut nested, nested_layout, renderer, operation); + } + } + } + + recurse(&mut self.overlay, layout, renderer, operation) + } + + /// Processes a runtime [`Event`]. + pub fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + event: Event, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, bool) + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + let (nested_status, nested_is_over) = + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + event.clone(), + cursor, + renderer, + clipboard, + shell, + ) + } else { + (event::Status::Ignored, false) + }; + + if matches!(nested_status, event::Status::Ignored) { + let is_over = nested_is_over + || cursor + .position() + .map(|cursor_position| { + element.is_over( + layout, + renderer, + cursor_position, + ) + }) + .unwrap_or_default(); + + ( + element.on_event( + event, + layout, + if nested_is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + renderer, + clipboard, + shell, + ), + is_over, + ) + } else { + (nested_status, nested_is_over) + } + } else { + (event::Status::Ignored, false) + } + } + + let (status, _) = recurse( + &mut self.overlay, + layout, + event, + cursor, + renderer, + clipboard, + shell, + ); + + status + } + + /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay. + pub fn mouse_interaction( + &mut self, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> Option + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + let layout = layouts.next()?; + let cursor_position = cursor.position()?; + + if !element.is_over(layout, renderer, cursor_position) { + return None; + } + + Some( + element + .overlay(layout, renderer) + .zip(layouts.next()) + .and_then(|(mut overlay, layout)| { + recurse( + &mut overlay, + layout, + cursor, + viewport, + renderer, + ) + }) + .unwrap_or_else(|| { + element.mouse_interaction( + layout, cursor, viewport, renderer, + ) + }), + ) + } + + recurse(&mut self.overlay, layout, cursor, viewport, renderer) + .unwrap_or_default() + } + + /// Returns true if the cursor is over the [`Nested`] overlay. + pub fn is_over( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + if element.is_over(layout, renderer, cursor_position) { + return true; + } + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + renderer, + cursor_position, + ) + } else { + false + } + } else { + false + } + } + + recurse(&mut self.overlay, layout, renderer, cursor_position) + } +} diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 1d55970e..619423fd 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -1,14 +1,12 @@ //! Implement your own event loop to drive a user interface. -mod overlay; - 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}; -use crate::core::{Element, Layout, Shell}; +use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. /// diff --git a/runtime/src/user_interface/overlay.rs b/runtime/src/user_interface/overlay.rs deleted file mode 100644 index 1211d55b..00000000 --- a/runtime/src/user_interface/overlay.rs +++ /dev/null @@ -1,342 +0,0 @@ -use crate::core::event; -use crate::core::layout; -use crate::core::mouse; -use crate::core::overlay; -use crate::core::renderer; -use crate::core::widget; -use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; - -/// An [`Overlay`] container that displays nested overlays -#[allow(missing_debug_implementations)] -pub struct Nested<'a, Message, Renderer> { - overlay: overlay::Element<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Nested<'a, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - /// Creates a nested overlay from the provided [`overlay::Element`] - pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self { - Self { overlay: element } - } - - pub fn layout( - &mut self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - fn recurse( - element: &mut overlay::Element<'_, Message, Renderer>, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node - where - Renderer: renderer::Renderer, - { - let translation = position - Point::ORIGIN; - - let node = element.layout(renderer, bounds, translation); - - if let Some(mut nested) = - element.overlay(Layout::new(&node), renderer) - { - layout::Node::with_children( - node.size(), - vec![ - node, - recurse(&mut nested, renderer, bounds, position), - ], - ) - } else { - layout::Node::with_children(node.size(), vec![node]) - } - } - - recurse(&mut self.overlay, renderer, bounds, position) - } - - pub fn draw( - &mut self, - renderer: &mut Renderer, - theme: &::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - ) { - fn recurse( - element: &mut overlay::Element<'_, Message, Renderer>, - layout: Layout<'_>, - renderer: &mut Renderer, - theme: &::Theme, - style: &renderer::Style, - cursor: mouse::Cursor, - ) where - Renderer: renderer::Renderer, - { - let mut layouts = layout.children(); - - if let Some(layout) = layouts.next() { - let nested_layout = layouts.next(); - - let is_over = cursor - .position() - .zip(nested_layout) - .and_then(|(cursor_position, nested_layout)| { - element.overlay(layout, renderer).map(|nested| { - nested.is_over( - nested_layout, - renderer, - cursor_position, - ) - }) - }) - .unwrap_or_default(); - - renderer.with_layer(layout.bounds(), |renderer| { - element.draw( - renderer, - theme, - style, - layout, - if is_over { - mouse::Cursor::Unavailable - } else { - cursor - }, - ); - }); - - if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(nested_layout) - { - recurse( - &mut nested, - nested_layout, - renderer, - theme, - style, - cursor, - ); - } - } - } - - recurse(&mut self.overlay, layout, renderer, theme, style, cursor); - } - - pub fn operate( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation, - ) { - fn recurse( - element: &mut overlay::Element<'_, Message, Renderer>, - layout: Layout<'_>, - renderer: &Renderer, - operation: &mut dyn widget::Operation, - ) where - Renderer: renderer::Renderer, - { - let mut layouts = layout.children(); - - if let Some(layout) = layouts.next() { - element.operate(layout, renderer, operation); - - if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) - { - recurse(&mut nested, nested_layout, renderer, operation); - } - } - } - - recurse(&mut self.overlay, layout, renderer, operation) - } - - pub fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - fn recurse( - element: &mut overlay::Element<'_, Message, Renderer>, - layout: Layout<'_>, - event: Event, - cursor: mouse::Cursor, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> (event::Status, bool) - where - Renderer: renderer::Renderer, - { - let mut layouts = layout.children(); - - if let Some(layout) = layouts.next() { - let (nested_status, nested_is_over) = - if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) - { - recurse( - &mut nested, - nested_layout, - event.clone(), - cursor, - renderer, - clipboard, - shell, - ) - } else { - (event::Status::Ignored, false) - }; - - if matches!(nested_status, event::Status::Ignored) { - let is_over = nested_is_over - || cursor - .position() - .map(|cursor_position| { - element.is_over( - layout, - renderer, - cursor_position, - ) - }) - .unwrap_or_default(); - - ( - element.on_event( - event, - layout, - if nested_is_over { - mouse::Cursor::Unavailable - } else { - cursor - }, - renderer, - clipboard, - shell, - ), - is_over, - ) - } else { - (nested_status, nested_is_over) - } - } else { - (event::Status::Ignored, false) - } - } - - let (status, _) = recurse( - &mut self.overlay, - layout, - event, - cursor, - renderer, - clipboard, - shell, - ); - - status - } - - pub fn mouse_interaction( - &mut self, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - fn recurse( - element: &mut overlay::Element<'_, Message, Renderer>, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - renderer: &Renderer, - ) -> Option - where - Renderer: renderer::Renderer, - { - let mut layouts = layout.children(); - - let layout = layouts.next()?; - let cursor_position = cursor.position()?; - - if !element.is_over(layout, renderer, cursor_position) { - return None; - } - - Some( - element - .overlay(layout, renderer) - .zip(layouts.next()) - .and_then(|(mut overlay, layout)| { - recurse( - &mut overlay, - layout, - cursor, - viewport, - renderer, - ) - }) - .unwrap_or_else(|| { - element.mouse_interaction( - layout, cursor, viewport, renderer, - ) - }), - ) - } - - recurse(&mut self.overlay, layout, cursor, viewport, renderer) - .unwrap_or_default() - } - - pub fn is_over( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - cursor_position: Point, - ) -> bool { - fn recurse( - element: &mut overlay::Element<'_, Message, Renderer>, - layout: Layout<'_>, - renderer: &Renderer, - cursor_position: Point, - ) -> bool - where - Renderer: renderer::Renderer, - { - let mut layouts = layout.children(); - - if let Some(layout) = layouts.next() { - if element.is_over(layout, renderer, cursor_position) { - return true; - } - - if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) - { - recurse( - &mut nested, - nested_layout, - renderer, - cursor_position, - ) - } else { - false - } - } else { - false - } - } - - recurse(&mut self.overlay, layout, renderer, cursor_position) - } -} -- cgit From 9803b276ad087846dfc3bb349113c5799ce00141 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 14 Jun 2023 11:27:42 +0200 Subject: Fix cursor availability in `overlay::Nested::draw` --- runtime/src/overlay/nested.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime') diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 5c5fafde..cd258ab6 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -94,7 +94,7 @@ where .and_then(|(cursor_position, nested_layout)| { element.overlay(layout, renderer).map(|nested| { nested.is_over( - nested_layout, + nested_layout.children().next().unwrap(), renderer, cursor_position, ) -- cgit From 716bf099233411b7c1f0bb8f8811f5d356992ef6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 21 Jun 2023 01:43:25 +0200 Subject: Fix `translation` in `layout` of `Nested` overlay --- runtime/src/overlay/nested.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime') diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index cd258ab6..b729f769 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -42,7 +42,7 @@ where where Renderer: renderer::Renderer, { - let translation = position - element.position(); + let translation = position - Point::ORIGIN; let node = element.layout(renderer, bounds, translation); -- cgit From 21a71b753d6da2233bce913f4e623ee14859ec23 Mon Sep 17 00:00:00 2001 From: Yiğit Özdemir Date: Wed, 21 Jun 2023 19:43:20 +0300 Subject: Add command to retrieve window size --- runtime/src/window/action.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'runtime') diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index a9d2a3d0..4ea9d474 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,3 +1,7 @@ + + +use iced_core::window::SizeType; + use crate::core::window::{Icon, Level, Mode, UserAttention}; use crate::futures::MaybeSend; @@ -20,6 +24,13 @@ pub enum Action { /// The new logical height of the window height: u32, }, + /// Fetch the current size of the window. + FetchSize { + /// Which size to fetch + size_type: SizeType, + /// Callback function + callback: Box T + 'static>, + }, /// Set the window to maximized or back Maximize(bool), /// Set the window to minimized or back @@ -104,6 +115,10 @@ impl Action { Self::Close => Action::Close, Self::Drag => Action::Drag, Self::Resize { width, height } => Action::Resize { width, height }, + Self::FetchSize { size_type, callback } => Action::FetchSize { + size_type: size_type, + callback: Box::new(move |s| f(callback(s))), + }, Self::Maximize(maximized) => Action::Maximize(maximized), Self::Minimize(minimized) => Action::Minimize(minimized), Self::Move { x, y } => Action::Move { x, y }, @@ -131,6 +146,7 @@ impl fmt::Debug for Action { f, "Action::Resize {{ widget: {width}, height: {height} }}" ), + Self::FetchSize { size_type, .. } => write!(f, "Action::FetchSize {{ size_type: {size_type:?} }}"), Self::Maximize(maximized) => { write!(f, "Action::Maximize({maximized})") } -- cgit From b394c84b37eacb266d45663d5d6626f1b616af7e Mon Sep 17 00:00:00 2001 From: Yiğit Özdemir Date: Thu, 22 Jun 2023 18:28:32 +0300 Subject: Add FetchSize command - apply the changes discussed at #water-cooler --- runtime/src/window/action.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) (limited to 'runtime') diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index 4ea9d474..551d0a01 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,7 +1,3 @@ - - -use iced_core::window::SizeType; - use crate::core::window::{Icon, Level, Mode, UserAttention}; use crate::futures::MaybeSend; @@ -25,12 +21,7 @@ pub enum Action { height: u32, }, /// Fetch the current size of the window. - FetchSize { - /// Which size to fetch - size_type: SizeType, - /// Callback function - callback: Box T + 'static>, - }, + FetchSize(Box T + 'static>), /// Set the window to maximized or back Maximize(bool), /// Set the window to minimized or back @@ -115,10 +106,7 @@ impl Action { Self::Close => Action::Close, Self::Drag => Action::Drag, Self::Resize { width, height } => Action::Resize { width, height }, - Self::FetchSize { size_type, callback } => Action::FetchSize { - size_type: size_type, - callback: Box::new(move |s| f(callback(s))), - }, + Self::FetchSize(o) => Action::FetchSize(Box::new(move |s| f(o(s)))), Self::Maximize(maximized) => Action::Maximize(maximized), Self::Minimize(minimized) => Action::Minimize(minimized), Self::Move { x, y } => Action::Move { x, y }, @@ -146,7 +134,7 @@ impl fmt::Debug for Action { f, "Action::Resize {{ widget: {width}, height: {height} }}" ), - Self::FetchSize { size_type, .. } => write!(f, "Action::FetchSize {{ size_type: {size_type:?} }}"), + Self::FetchSize(_) => write!(f, "Action::FetchSize"), Self::Maximize(maximized) => { write!(f, "Action::Maximize({maximized})") } -- cgit From 5ae726e02c4d6c9889ef7335d9bc80ef1992e34f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 27 Jun 2023 19:41:03 +0200 Subject: Move `Screenshot` inside `window` module --- runtime/src/lib.rs | 2 - runtime/src/screenshot.rs | 91 --------------------------------------- runtime/src/window.rs | 4 +- runtime/src/window/action.rs | 2 +- runtime/src/window/screenshot.rs | 92 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 95 deletions(-) delete mode 100644 runtime/src/screenshot.rs create mode 100644 runtime/src/window/screenshot.rs (limited to 'runtime') diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 32ed14d8..50abf7b2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -60,7 +60,6 @@ mod debug; #[cfg(not(feature = "debug"))] #[path = "debug/null.rs"] mod debug; -mod screenshot; pub use iced_core as core; pub use iced_futures as futures; @@ -69,5 +68,4 @@ pub use command::Command; pub use debug::Debug; pub use font::Font; pub use program::Program; -pub use screenshot::{CropError, Screenshot}; pub use user_interface::UserInterface; diff --git a/runtime/src/screenshot.rs b/runtime/src/screenshot.rs deleted file mode 100644 index b88f2c20..00000000 --- a/runtime/src/screenshot.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::core::{Rectangle, Size}; - -use std::fmt::{Debug, Formatter}; -use std::sync::Arc; - -/// Data of a screenshot, captured with `window::screenshot()`. -/// -/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. -#[derive(Clone)] -pub struct Screenshot { - /// The bytes of the [`Screenshot`]. - pub bytes: Arc>, - /// The size of the [`Screenshot`]. - pub size: Size, -} - -impl Debug for Screenshot { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Screenshot: {{ \n bytes: {}\n size: {:?} }}", - self.bytes.len(), - self.size - ) - } -} - -impl Screenshot { - /// Creates a new [`Screenshot`]. - pub fn new(bytes: Vec, size: Size) -> Self { - Self { - bytes: Arc::new(bytes), - size, - } - } - - /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the - /// top-left corner of the [`Screenshot`]. - pub fn crop(&self, region: Rectangle) -> Result { - if region.width == 0 || region.height == 0 { - return Err(CropError::Zero); - } - - if region.x + region.width > self.size.width - || region.y + region.height > self.size.height - { - return Err(CropError::OutOfBounds); - } - - // Image is always RGBA8 = 4 bytes per pixel - const PIXEL_SIZE: usize = 4; - - let bytes_per_row = self.size.width as usize * PIXEL_SIZE; - let row_range = region.y as usize..(region.y + region.height) as usize; - let column_range = region.x as usize * PIXEL_SIZE - ..(region.x + region.width) as usize * PIXEL_SIZE; - - let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold( - vec![], - |mut acc, (row, bytes)| { - if row_range.contains(&row) { - acc.extend(&bytes[column_range.clone()]); - } - - acc - }, - ); - - Ok(Self { - bytes: Arc::new(chopped), - size: Size::new(region.width, region.height), - }) - } -} - -impl AsRef<[u8]> for Screenshot { - fn as_ref(&self) -> &[u8] { - &self.bytes - } -} - -#[derive(Debug, thiserror::Error)] -/// Errors that can occur when cropping a [`Screenshot`]. -pub enum CropError { - #[error("The cropped region is out of bounds.")] - /// The cropped region's size is out of bounds. - OutOfBounds, - #[error("The cropped region is not visible.")] - /// The cropped region's size is zero. - Zero, -} diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 9b66cb0e..e448edef 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -1,13 +1,15 @@ //! Build window-based GUI applications. mod action; +pub mod screenshot; + pub use action::Action; +pub use screenshot::Screenshot; use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; use crate::futures::subscription::{self, Subscription}; -use crate::screenshot::Screenshot; /// Subscribes to the frames of the window of the running application. /// diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index cb430681..09be1810 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,7 +1,7 @@ use crate::core::window::{Icon, Level, Mode, UserAttention}; use crate::futures::MaybeSend; +use crate::window::Screenshot; -use crate::screenshot::Screenshot; use std::fmt; /// An operation to be performed on some window. diff --git a/runtime/src/window/screenshot.rs b/runtime/src/window/screenshot.rs new file mode 100644 index 00000000..c84286b6 --- /dev/null +++ b/runtime/src/window/screenshot.rs @@ -0,0 +1,92 @@ +//! Take screenshots of a window. +use crate::core::{Rectangle, Size}; + +use std::fmt::{Debug, Formatter}; +use std::sync::Arc; + +/// Data of a screenshot, captured with `window::screenshot()`. +/// +/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space. +#[derive(Clone)] +pub struct Screenshot { + /// The bytes of the [`Screenshot`]. + pub bytes: Arc>, + /// The size of the [`Screenshot`]. + pub size: Size, +} + +impl Debug for Screenshot { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Screenshot: {{ \n bytes: {}\n size: {:?} }}", + self.bytes.len(), + self.size + ) + } +} + +impl Screenshot { + /// Creates a new [`Screenshot`]. + pub fn new(bytes: Vec, size: Size) -> Self { + Self { + bytes: Arc::new(bytes), + size, + } + } + + /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the + /// top-left corner of the [`Screenshot`]. + pub fn crop(&self, region: Rectangle) -> Result { + if region.width == 0 || region.height == 0 { + return Err(CropError::Zero); + } + + if region.x + region.width > self.size.width + || region.y + region.height > self.size.height + { + return Err(CropError::OutOfBounds); + } + + // Image is always RGBA8 = 4 bytes per pixel + const PIXEL_SIZE: usize = 4; + + let bytes_per_row = self.size.width as usize * PIXEL_SIZE; + let row_range = region.y as usize..(region.y + region.height) as usize; + let column_range = region.x as usize * PIXEL_SIZE + ..(region.x + region.width) as usize * PIXEL_SIZE; + + let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold( + vec![], + |mut acc, (row, bytes)| { + if row_range.contains(&row) { + acc.extend(&bytes[column_range.clone()]); + } + + acc + }, + ); + + Ok(Self { + bytes: Arc::new(chopped), + size: Size::new(region.width, region.height), + }) + } +} + +impl AsRef<[u8]> for Screenshot { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +#[derive(Debug, thiserror::Error)] +/// Errors that can occur when cropping a [`Screenshot`]. +pub enum CropError { + #[error("The cropped region is out of bounds.")] + /// The cropped region's size is out of bounds. + OutOfBounds, + #[error("The cropped region is not visible.")] + /// The cropped region's size is zero. + Zero, +} -- cgit From 4b831a917d81def2320a5910ea8975acb2baaaf1 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 28 Jun 2023 18:51:14 +0200 Subject: runtime: Add `operate` method to `program::State` --- runtime/src/program/state.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'runtime') diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index d83e3f54..f57d2d8d 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,6 +1,7 @@ use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; +use crate::core::widget::operation::{Operation, Outcome}; use crate::core::{Clipboard, Size}; use crate::user_interface::{self, UserInterface}; use crate::{Command, Debug, Program}; @@ -173,6 +174,43 @@ where (uncaptured_events, command) } + + /// Applies [`widget::Operation`]s to the [`State`] + pub fn operate<'a>( + &mut self, + renderer: &mut P::Renderer, + operations: impl Iterator>, + bounds: Size, + debug: &mut Debug, + ) { + let mut user_interface = build_user_interface( + &mut self.program, + self.cache.take().unwrap(), + renderer, + bounds, + debug, + ); + + for operation in operations { + let mut owned_op; + let mut current_operation = Some(operation); + while let Some(operation) = current_operation.take() { + user_interface.operate(renderer, operation); + match operation.finish() { + Outcome::None => {} + Outcome::Some(message) => { + self.queued_messages.push(message) + } + Outcome::Chain(op) => { + owned_op = op; + current_operation = Some(owned_op.as_mut()); + } + }; + } + } + + self.cache = Some(user_interface.into_cache()); + } } fn build_user_interface<'a, P: Program>( -- cgit From 2128472c2a8afcb59927712497c4f613612e9dcc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jun 2023 01:04:07 +0200 Subject: Remove `layout` method from `core::Renderer` trait --- runtime/src/user_interface.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 619423fd..34b2ada0 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -95,8 +95,9 @@ where let Cache { mut state } = cache; state.diff(root.as_widget()); - let base = - renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); + let base = root + .as_widget() + .layout(renderer, &layout::Limits::new(Size::ZERO, bounds)); UserInterface { root, @@ -226,8 +227,8 @@ where if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); - self.base = renderer.layout( - &self.root, + self.base = self.root.as_widget().layout( + renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); @@ -322,8 +323,8 @@ where } shell.revalidate_layout(|| { - self.base = renderer.layout( - &self.root, + self.base = self.root.as_widget().layout( + renderer, &layout::Limits::new(Size::ZERO, self.bounds), ); -- cgit From ae2709f2c4ce30b2006471ef23589e12f41c9a80 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 08:14:44 +0200 Subject: Take `Box` instead of reference in `State::operate` --- runtime/src/program/state.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'runtime') diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index f57d2d8d..9ae504ee 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -176,10 +176,10 @@ where } /// Applies [`widget::Operation`]s to the [`State`] - pub fn operate<'a>( + pub fn operate( &mut self, renderer: &mut P::Renderer, - operations: impl Iterator>, + operations: impl Iterator>>, bounds: Size, debug: &mut Debug, ) { @@ -192,18 +192,18 @@ where ); for operation in operations { - let mut owned_op; let mut current_operation = Some(operation); - while let Some(operation) = current_operation.take() { - user_interface.operate(renderer, operation); + + while let Some(mut operation) = current_operation.take() { + user_interface.operate(renderer, operation.as_mut()); + match operation.finish() { Outcome::None => {} Outcome::Some(message) => { self.queued_messages.push(message) } - Outcome::Chain(op) => { - owned_op = op; - current_operation = Some(owned_op.as_mut()); + Outcome::Chain(next) => { + current_operation = Some(next); } }; } -- cgit From af4d65c42862190adc9266dd95d98195bb1107de Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 08:15:30 +0200 Subject: Keep imports consistent in `program::state` --- runtime/src/program/state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime') diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 9ae504ee..35df6078 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,7 +1,7 @@ use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; -use crate::core::widget::operation::{Operation, Outcome}; +use crate::core::widget::operation::{self, Operation}; use crate::core::{Clipboard, Size}; use crate::user_interface::{self, UserInterface}; use crate::{Command, Debug, Program}; @@ -198,11 +198,11 @@ where user_interface.operate(renderer, operation.as_mut()); match operation.finish() { - Outcome::None => {} - Outcome::Some(message) => { + operation::Outcome::None => {} + operation::Outcome::Some(message) => { self.queued_messages.push(message) } - Outcome::Chain(next) => { + operation::Outcome::Chain(next) => { current_operation = Some(next); } }; -- cgit From cdce03cf7f520ef0227aaec4eaed19332197f53b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 18:17:18 +0200 Subject: Revert "Remove `layout` method from `core::Renderer` trait" This reverts commit 2128472c2a8afcb59927712497c4f613612e9dcc. --- runtime/src/user_interface.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'runtime') diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 34b2ada0..619423fd 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -95,9 +95,8 @@ where let Cache { mut state } = cache; state.diff(root.as_widget()); - let base = root - .as_widget() - .layout(renderer, &layout::Limits::new(Size::ZERO, bounds)); + let base = + renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); UserInterface { root, @@ -227,8 +226,8 @@ where if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); - self.base = self.root.as_widget().layout( - renderer, + self.base = renderer.layout( + &self.root, &layout::Limits::new(Size::ZERO, self.bounds), ); @@ -323,8 +322,8 @@ where } shell.revalidate_layout(|| { - self.base = self.root.as_widget().layout( - renderer, + self.base = renderer.layout( + &self.root, &layout::Limits::new(Size::ZERO, self.bounds), ); -- cgit From cc32bd4de09ee58c15d1b3f2cec4a79dc65dd035 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 6 Jul 2023 06:41:28 +0200 Subject: Use `Size` in both `Resize` and `FetchSize` window actions --- runtime/src/window.rs | 5 +++-- runtime/src/window/action.rs | 17 +++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) (limited to 'runtime') diff --git a/runtime/src/window.rs b/runtime/src/window.rs index d4111293..094a713d 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -6,6 +6,7 @@ pub use action::Action; use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; +use crate::core::Size; use crate::futures::subscription::{self, Subscription}; /// Subscribes to the frames of the window of the running application. @@ -34,8 +35,8 @@ pub fn drag() -> Command { } /// 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 })) +pub fn resize(new_size: Size) -> Command { + Command::single(command::Action::Window(Action::Resize(new_size))) } /// Maximizes the window. diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index 551d0a01..d0137895 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,4 +1,5 @@ use crate::core::window::{Icon, Level, Mode, UserAttention}; +use crate::core::Size; use crate::futures::MaybeSend; use std::fmt; @@ -14,14 +15,9 @@ pub enum Action { /// 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, - }, + Resize(Size), /// Fetch the current size of the window. - FetchSize(Box T + 'static>), + FetchSize(Box) -> T + 'static>), /// Set the window to maximized or back Maximize(bool), /// Set the window to minimized or back @@ -105,7 +101,7 @@ impl Action { match self { Self::Close => Action::Close, Self::Drag => Action::Drag, - Self::Resize { width, height } => Action::Resize { width, height }, + Self::Resize(size) => Action::Resize(size), Self::FetchSize(o) => Action::FetchSize(Box::new(move |s| f(o(s)))), Self::Maximize(maximized) => Action::Maximize(maximized), Self::Minimize(minimized) => Action::Minimize(minimized), @@ -130,10 +126,7 @@ impl fmt::Debug for Action { 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::Resize(size) => write!(f, "Action::Resize({size:?})"), Self::FetchSize(_) => write!(f, "Action::FetchSize"), Self::Maximize(maximized) => { write!(f, "Action::Maximize({maximized})") -- cgit From f350a2f812487af9a43c36e28d9b904e76a66474 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 6 Jul 2023 06:44:09 +0200 Subject: Add `fetch_size` helper to `runtime::window` --- runtime/src/window.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'runtime') diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 094a713d..9356581a 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -39,6 +39,13 @@ pub fn resize(new_size: Size) -> Command { Command::single(command::Action::Window(Action::Resize(new_size))) } +/// Fetches the current window size in logical dimensions. +pub fn fetch_size( + f: impl FnOnce(Size) -> Message + 'static, +) -> Command { + Command::single(command::Action::Window(Action::FetchSize(Box::new(f)))) +} + /// Maximizes the window. pub fn maximize(maximized: bool) -> Command { Command::single(command::Action::Window(Action::Maximize(maximized))) -- cgit