diff options
author | 2020-05-28 21:52:34 +0200 | |
---|---|---|
committer | 2020-05-28 21:52:34 +0200 | |
commit | d3db055583f4cbef1441fd66d07da70424bd1200 (patch) | |
tree | 9f695bd26f688a5aaf3b8fa687a0e3ff096ffe11 /native | |
parent | ead4186870d1b46015986f702dd63382498060fc (diff) | |
parent | 709ed1f3f7ad8cf67a176763e394aaae4e808e93 (diff) | |
download | iced-d3db055583f4cbef1441fd66d07da70424bd1200.tar.gz iced-d3db055583f4cbef1441fd66d07da70424bd1200.tar.bz2 iced-d3db055583f4cbef1441fd66d07da70424bd1200.zip |
Merge pull request #354 from hecrj/feature/glow-renderer
OpenGL renderer and backend-agnostic graphics subcrate
Diffstat (limited to 'native')
-rw-r--r-- | native/Cargo.toml | 4 | ||||
-rw-r--r-- | native/src/debug/basic.rs | 216 | ||||
-rw-r--r-- | native/src/debug/null.rs | 47 | ||||
-rw-r--r-- | native/src/lib.rs | 18 | ||||
-rw-r--r-- | native/src/program.rs | 39 | ||||
-rw-r--r-- | native/src/program/state.rs | 185 | ||||
-rw-r--r-- | native/src/user_interface.rs | 4 | ||||
-rw-r--r-- | native/src/window.rs | 2 | ||||
-rw-r--r-- | native/src/window/backend.rs | 55 |
9 files changed, 506 insertions, 64 deletions
diff --git a/native/Cargo.toml b/native/Cargo.toml index 7e9c2a5a..75b4a56b 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -7,9 +7,11 @@ description = "A renderer-agnostic library for native GUIs" license = "MIT" repository = "https://github.com/hecrj/iced" +[features] +debug = [] + [dependencies] twox-hash = "1.5" -raw-window-handle = "0.3" unicode-segmentation = "1.6" [dependencies.iced_core] diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs new file mode 100644 index 00000000..5338d0d9 --- /dev/null +++ b/native/src/debug/basic.rs @@ -0,0 +1,216 @@ +#![allow(missing_docs)] +use std::{collections::VecDeque, time}; + +/// 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<String>, +} + +impl Debug { + /// Creates a new [`Debug`]. + /// + /// [`Debug`]: struct.Debug.html + 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<Message: std::fmt::Debug>(&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<String> { + if !self.is_enabled { + return Vec::new(); + } + + let mut lines = Vec::new(); + + fn key_value<T: std::fmt::Debug>(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| format!(" {}", msg)), + ); + + lines + } +} + +#[derive(Debug)] +struct TimeBuffer { + head: usize, + size: usize, + contents: Vec<time::Duration>, +} + +impl TimeBuffer { + fn new(capacity: usize) -> TimeBuffer { + TimeBuffer { + head: 0, + size: 0, + contents: vec![time::Duration::from_secs(0); capacity], + } + } + + fn push(&mut self, duration: time::Duration) { + self.head = (self.head + 1) % self.contents.len(); + self.contents[self.head] = duration; + self.size = (self.size + 1).min(self.contents.len()); + } + + fn average(&self) -> time::Duration { + let sum: time::Duration = if self.size == self.contents.len() { + self.contents[..].iter().sum() + } else { + self.contents[..self.size].iter().sum() + }; + + sum / self.size.max(1) as u32 + } +} diff --git a/native/src/debug/null.rs b/native/src/debug/null.rs new file mode 100644 index 00000000..60e6122d --- /dev/null +++ b/native/src/debug/null.rs @@ -0,0 +1,47 @@ +#![allow(missing_docs)] +#[derive(Debug)] +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<Message: std::fmt::Debug>( + &mut self, + _message: &Message, + ) { + } + + pub fn overlay(&self) -> Vec<String> { + Vec::new() + } +} diff --git a/native/src/lib.rs b/native/src/lib.rs index 9882803f..b67ff2a1 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -9,14 +9,11 @@ //! - Event handling for all the built-in widgets //! - A renderer-agnostic API //! -//! To achieve this, it introduces a bunch of reusable interfaces: +//! 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. -//! - A [`window::Backend`] 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. //! //! # Usage //! The strategy to use this crate depends on your particular use case. If you @@ -31,7 +28,6 @@ //! [`druid`]: https://github.com/xi-editor/druid //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle //! [`Widget`]: widget/trait.Widget.html -//! [`window::Backend`]: window/trait.Backend.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html #![deny(missing_docs)] @@ -42,6 +38,7 @@ pub mod keyboard; pub mod layout; pub mod mouse; +pub mod program; pub mod renderer; pub mod subscription; pub mod widget; @@ -54,6 +51,15 @@ mod hasher; mod runtime; mod user_interface; +// 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::{ Align, Background, Color, Font, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, @@ -64,10 +70,12 @@ pub use iced_futures::{executor, futures, Command}; pub use executor::Executor; pub use clipboard::Clipboard; +pub use debug::Debug; pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; +pub use program::Program; pub use renderer::Renderer; pub use runtime::Runtime; pub use subscription::Subscription; diff --git a/native/src/program.rs b/native/src/program.rs new file mode 100644 index 00000000..14afcd84 --- /dev/null +++ b/native/src/program.rs @@ -0,0 +1,39 @@ +//! Build interactive programs using The Elm Architecture. +use crate::{Command, 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`]. + /// + /// [`Program`]: trait.Program.html + type Renderer: Renderer; + + /// The type of __messages__ your [`Program`] will produce. + /// + /// [`Program`]: trait.Program.html + 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. + /// + /// [`Program`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command<Self::Message>; + + /// Returns the widgets to display in the [`Program`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Program`]: trait.Program.html + fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>; +} diff --git a/native/src/program/state.rs b/native/src/program/state.rs new file mode 100644 index 00000000..8716d8b9 --- /dev/null +++ b/native/src/program/state.rs @@ -0,0 +1,185 @@ +use crate::{ + Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size, + UserInterface, +}; + +/// The execution state of a [`Program`]. It leverages caching, event +/// processing, and rendering primitive storage. +/// +/// [`Program`]: trait.Program.html +#[allow(missing_debug_implementations)] +pub struct State<P> +where + P: Program + 'static, +{ + program: P, + cache: Option<Cache>, + primitive: <P::Renderer as Renderer>::Output, + queued_events: Vec<Event>, + queued_messages: Vec<P::Message>, +} + +impl<P> State<P> +where + P: Program + 'static, +{ + /// Creates a new [`State`] with the provided [`Program`], initializing its + /// primitive with the given logical bounds and renderer. + /// + /// [`State`]: struct.State.html + /// [`Program`]: trait.Program.html + pub fn new( + mut program: P, + bounds: Size, + renderer: &mut P::Renderer, + debug: &mut Debug, + ) -> Self { + let user_interface = build_user_interface( + &mut program, + Cache::default(), + renderer, + bounds, + debug, + ); + + debug.draw_started(); + let primitive = user_interface.draw(renderer); + debug.draw_finished(); + + let cache = Some(user_interface.into_cache()); + + State { + program, + cache, + primitive, + queued_events: Vec::new(), + queued_messages: Vec::new(), + } + } + + /// Returns a reference to the [`Program`] of the [`State`]. + /// + /// [`Program`]: trait.Program.html + /// [`State`]: struct.State.html + pub fn program(&self) -> &P { + &self.program + } + + /// Returns a reference to the current rendering primitive of the [`State`]. + /// + /// [`State`]: struct.State.html + pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output { + &self.primitive + } + + /// Queues an event in the [`State`] for processing during an [`update`]. + /// + /// [`State`]: struct.State.html + /// [`update`]: #method.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`]. + /// + /// [`State`]: struct.State.html + /// [`update`]: #method.update + pub fn queue_message(&mut self, message: P::Message) { + self.queued_messages.push(message); + } + + /// Processes all the queued events and messages, rebuilding and redrawing + /// the widgets of the linked [`Program`] if necessary. + /// + /// Returns the [`Command`] obtained from [`Program`] after updating it, + /// only if an update was necessary. + /// + /// [`Program`]: trait.Program.html + pub fn update( + &mut self, + clipboard: Option<&dyn Clipboard>, + bounds: Size, + renderer: &mut P::Renderer, + debug: &mut Debug, + ) -> Option<Command<P::Message>> { + if self.queued_events.is_empty() && self.queued_messages.is_empty() { + return None; + } + + let mut user_interface = build_user_interface( + &mut self.program, + self.cache.take().unwrap(), + renderer, + bounds, + debug, + ); + + debug.event_processing_started(); + let mut messages = user_interface.update( + self.queued_events.drain(..), + clipboard, + renderer, + ); + messages.extend(self.queued_messages.drain(..)); + debug.event_processing_finished(); + + if messages.is_empty() { + debug.draw_started(); + self.primitive = user_interface.draw(renderer); + 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 user_interface = build_user_interface( + &mut self.program, + temp_cache, + renderer, + bounds, + debug, + ); + + debug.draw_started(); + self.primitive = user_interface.draw(renderer); + debug.draw_finished(); + + self.cache = Some(user_interface.into_cache()); + + Some(commands) + } + } +} + +fn build_user_interface<'a, P: Program>( + program: &'a mut P, + cache: Cache, + renderer: &mut P::Renderer, + size: Size, + debug: &mut Debug, +) -> UserInterface<'a, P::Message, P::Renderer> { + debug.view_started(); + let view = program.view(); + debug.view_finished(); + + debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + debug.layout_finished(); + + user_interface +} diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 48cd6111..e963b601 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -102,7 +102,9 @@ where hasher.finish() }; - let layout = if hash == cache.hash && bounds == cache.bounds { + let layout_is_cached = hash == cache.hash && bounds == cache.bounds; + + let layout = if layout_is_cached { cache.layout } else { renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)) diff --git a/native/src/window.rs b/native/src/window.rs index 4dcae62f..220bb3be 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,6 +1,4 @@ //! Build window-based GUI applications. -mod backend; mod event; -pub use backend::Backend; pub use event::Event; diff --git a/native/src/window/backend.rs b/native/src/window/backend.rs deleted file mode 100644 index 892d4bb9..00000000 --- a/native/src/window/backend.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::mouse; - -use raw_window_handle::HasRawWindowHandle; - -/// A graphics backend that can render to windows. -pub trait Backend: Sized { - /// The settings of the backend. - type Settings: Default; - - /// The iced renderer of the backend. - type Renderer: crate::Renderer; - - /// The surface of the backend. - type Surface; - - /// The swap chain of the backend. - type SwapChain; - - /// Creates a new [`Backend`] and an associated iced renderer. - /// - /// [`Backend`]: trait.Backend.html - fn new(settings: Self::Settings) -> (Self, Self::Renderer); - - /// Crates a new [`Surface`] for the given window. - /// - /// [`Surface`]: #associatedtype.Surface - fn create_surface<W: HasRawWindowHandle>( - &mut self, - window: &W, - ) -> Self::Surface; - - /// Crates a new [`SwapChain`] for the given [`Surface`]. - /// - /// [`SwapChain`]: #associatedtype.SwapChain - /// [`Surface`]: #associatedtype.Surface - fn create_swap_chain( - &mut self, - surface: &Self::Surface, - width: u32, - height: u32, - ) -> Self::SwapChain; - - /// Draws the output primitives to the next frame of the given [`SwapChain`]. - /// - /// [`SwapChain`]: #associatedtype.SwapChain - /// [`Surface`]: #associatedtype.Surface - fn draw<T: AsRef<str>>( - &mut self, - renderer: &mut Self::Renderer, - swap_chain: &mut Self::SwapChain, - output: &<Self::Renderer as crate::Renderer>::Output, - scale_factor: f64, - overlay: &[T], - ) -> mouse::Interaction; -} |