summaryrefslogtreecommitdiffstats
path: root/native
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--graphics/src/window/compositor.rs (renamed from native/src/window/backend.rs)16
-rw-r--r--native/Cargo.toml4
-rw-r--r--native/src/debug/basic.rs (renamed from winit/src/debug/basic.rs)5
-rw-r--r--native/src/debug/null.rs (renamed from winit/src/debug/null.rs)1
-rw-r--r--native/src/lib.rs18
-rw-r--r--native/src/program.rs39
-rw-r--r--native/src/program/state.rs185
-rw-r--r--native/src/user_interface.rs4
-rw-r--r--native/src/widget/pane_grid/axis.rs182
-rw-r--r--native/src/widget/pane_grid/node.rs22
-rw-r--r--native/src/window.rs2
11 files changed, 438 insertions, 40 deletions
diff --git a/native/src/window/backend.rs b/graphics/src/window/compositor.rs
index 892d4bb9..d5920c95 100644
--- a/native/src/window/backend.rs
+++ b/graphics/src/window/compositor.rs
@@ -1,14 +1,14 @@
-use crate::mouse;
-
+use crate::Viewport;
+use iced_native::mouse;
use raw_window_handle::HasRawWindowHandle;
-/// A graphics backend that can render to windows.
-pub trait Backend: Sized {
+/// A graphics compositor that can draw to windows.
+pub trait Compositor: Sized {
/// The settings of the backend.
type Settings: Default;
/// The iced renderer of the backend.
- type Renderer: crate::Renderer;
+ type Renderer: iced_native::Renderer;
/// The surface of the backend.
type Surface;
@@ -16,7 +16,7 @@ pub trait Backend: Sized {
/// The swap chain of the backend.
type SwapChain;
- /// Creates a new [`Backend`] and an associated iced renderer.
+ /// Creates a new [`Backend`].
///
/// [`Backend`]: trait.Backend.html
fn new(settings: Self::Settings) -> (Self, Self::Renderer);
@@ -48,8 +48,8 @@ pub trait Backend: Sized {
&mut self,
renderer: &mut Self::Renderer,
swap_chain: &mut Self::SwapChain,
- output: &<Self::Renderer as crate::Renderer>::Output,
- scale_factor: f64,
+ viewport: &Viewport,
+ output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;
}
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/winit/src/debug/basic.rs b/native/src/debug/basic.rs
index d46edba6..5338d0d9 100644
--- a/winit/src/debug/basic.rs
+++ b/native/src/debug/basic.rs
@@ -1,5 +1,7 @@
+#![allow(missing_docs)]
use std::{collections::VecDeque, time};
+/// A bunch of time measurements for debugging purposes.
#[derive(Debug)]
pub struct Debug {
is_enabled: bool,
@@ -30,6 +32,9 @@ pub struct Debug {
}
impl Debug {
+ /// Creates a new [`Debug`].
+ ///
+ /// [`Debug`]: struct.Debug.html
pub fn new() -> Self {
let now = time::Instant::now();
diff --git a/winit/src/debug/null.rs b/native/src/debug/null.rs
index 2a9430cd..60e6122d 100644
--- a/winit/src/debug/null.rs
+++ b/native/src/debug/null.rs
@@ -1,3 +1,4 @@
+#![allow(missing_docs)]
#[derive(Debug)]
pub struct Debug;
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/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs
index f0e3f362..b3a306d5 100644
--- a/native/src/widget/pane_grid/axis.rs
+++ b/native/src/widget/pane_grid/axis.rs
@@ -14,37 +14,39 @@ impl Axis {
&self,
rectangle: &Rectangle,
ratio: f32,
- halved_spacing: f32,
+ spacing: f32,
) -> (Rectangle, Rectangle) {
match self {
Axis::Horizontal => {
- let height_top = (rectangle.height * ratio).round();
- let height_bottom = rectangle.height - height_top;
+ let height_top =
+ (rectangle.height * ratio - spacing / 2.0).round();
+ let height_bottom = rectangle.height - height_top - spacing;
(
Rectangle {
- height: height_top - halved_spacing,
+ height: height_top,
..*rectangle
},
Rectangle {
- y: rectangle.y + height_top + halved_spacing,
- height: height_bottom - halved_spacing,
+ y: rectangle.y + height_top + spacing,
+ height: height_bottom,
..*rectangle
},
)
}
Axis::Vertical => {
- let width_left = (rectangle.width * ratio).round();
- let width_right = rectangle.width - width_left;
+ let width_left =
+ (rectangle.width * ratio - spacing / 2.0).round();
+ let width_right = rectangle.width - width_left - spacing;
(
Rectangle {
- width: width_left - halved_spacing,
+ width: width_left,
..*rectangle
},
Rectangle {
- x: rectangle.x + width_left + halved_spacing,
- width: width_right - halved_spacing,
+ x: rectangle.x + width_left + spacing,
+ width: width_right,
..*rectangle
},
)
@@ -52,3 +54,161 @@ impl Axis {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ enum Case {
+ Horizontal {
+ overall_height: f32,
+ spacing: f32,
+ top_height: f32,
+ bottom_y: f32,
+ bottom_height: f32,
+ },
+ Vertical {
+ overall_width: f32,
+ spacing: f32,
+ left_width: f32,
+ right_x: f32,
+ right_width: f32,
+ },
+ }
+
+ #[test]
+ fn split() {
+ let cases = vec![
+ // Even height, even spacing
+ Case::Horizontal {
+ overall_height: 10.0,
+ spacing: 2.0,
+ top_height: 4.0,
+ bottom_y: 6.0,
+ bottom_height: 4.0,
+ },
+ // Odd height, even spacing
+ Case::Horizontal {
+ overall_height: 9.0,
+ spacing: 2.0,
+ top_height: 4.0,
+ bottom_y: 6.0,
+ bottom_height: 3.0,
+ },
+ // Even height, odd spacing
+ Case::Horizontal {
+ overall_height: 10.0,
+ spacing: 1.0,
+ top_height: 5.0,
+ bottom_y: 6.0,
+ bottom_height: 4.0,
+ },
+ // Odd height, odd spacing
+ Case::Horizontal {
+ overall_height: 9.0,
+ spacing: 1.0,
+ top_height: 4.0,
+ bottom_y: 5.0,
+ bottom_height: 4.0,
+ },
+ // Even width, even spacing
+ Case::Vertical {
+ overall_width: 10.0,
+ spacing: 2.0,
+ left_width: 4.0,
+ right_x: 6.0,
+ right_width: 4.0,
+ },
+ // Odd width, even spacing
+ Case::Vertical {
+ overall_width: 9.0,
+ spacing: 2.0,
+ left_width: 4.0,
+ right_x: 6.0,
+ right_width: 3.0,
+ },
+ // Even width, odd spacing
+ Case::Vertical {
+ overall_width: 10.0,
+ spacing: 1.0,
+ left_width: 5.0,
+ right_x: 6.0,
+ right_width: 4.0,
+ },
+ // Odd width, odd spacing
+ Case::Vertical {
+ overall_width: 9.0,
+ spacing: 1.0,
+ left_width: 4.0,
+ right_x: 5.0,
+ right_width: 4.0,
+ },
+ ];
+ for case in cases {
+ match case {
+ Case::Horizontal {
+ overall_height,
+ spacing,
+ top_height,
+ bottom_y,
+ bottom_height,
+ } => {
+ let a = Axis::Horizontal;
+ let r = Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: 10.0,
+ height: overall_height,
+ };
+ let (top, bottom) = a.split(&r, 0.5, spacing);
+ assert_eq!(
+ top,
+ Rectangle {
+ height: top_height,
+ ..r
+ }
+ );
+ assert_eq!(
+ bottom,
+ Rectangle {
+ y: bottom_y,
+ height: bottom_height,
+ ..r
+ }
+ );
+ }
+ Case::Vertical {
+ overall_width,
+ spacing,
+ left_width,
+ right_x,
+ right_width,
+ } => {
+ let a = Axis::Vertical;
+ let r = Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: overall_width,
+ height: 10.0,
+ };
+ let (left, right) = a.split(&r, 0.5, spacing);
+ assert_eq!(
+ left,
+ Rectangle {
+ width: left_width,
+ ..r
+ }
+ );
+ assert_eq!(
+ right,
+ Rectangle {
+ x: right_x,
+ width: right_width,
+ ..r
+ }
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index 723ec393..b13c5e26 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -56,7 +56,7 @@ impl Node {
let mut regions = HashMap::new();
self.compute_regions(
- spacing / 2.0,
+ spacing,
&Rectangle {
x: 0.0,
y: 0.0,
@@ -83,7 +83,7 @@ impl Node {
let mut splits = HashMap::new();
self.compute_splits(
- spacing / 2.0,
+ spacing,
&Rectangle {
x: 0.0,
y: 0.0,
@@ -185,7 +185,7 @@ impl Node {
fn compute_regions(
&self,
- halved_spacing: f32,
+ spacing: f32,
current: &Rectangle,
regions: &mut HashMap<Pane, Rectangle>,
) {
@@ -193,11 +193,10 @@ impl Node {
Node::Split {
axis, ratio, a, b, ..
} => {
- let (region_a, region_b) =
- axis.split(current, *ratio, halved_spacing);
+ let (region_a, region_b) = axis.split(current, *ratio, spacing);
- a.compute_regions(halved_spacing, &region_a, regions);
- b.compute_regions(halved_spacing, &region_b, regions);
+ a.compute_regions(spacing, &region_a, regions);
+ b.compute_regions(spacing, &region_b, regions);
}
Node::Pane(pane) => {
let _ = regions.insert(*pane, *current);
@@ -207,7 +206,7 @@ impl Node {
fn compute_splits(
&self,
- halved_spacing: f32,
+ spacing: f32,
current: &Rectangle,
splits: &mut HashMap<Split, (Axis, Rectangle, f32)>,
) {
@@ -219,13 +218,12 @@ impl Node {
b,
id,
} => {
- let (region_a, region_b) =
- axis.split(current, *ratio, halved_spacing);
+ let (region_a, region_b) = axis.split(current, *ratio, spacing);
let _ = splits.insert(*id, (*axis, *current, *ratio));
- a.compute_splits(halved_spacing, &region_a, splits);
- b.compute_splits(halved_spacing, &region_b, splits);
+ a.compute_splits(spacing, &region_a, splits);
+ b.compute_splits(spacing, &region_b, splits);
}
Node::Pane(_) => {}
}
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;