summaryrefslogtreecommitdiffstats
path: root/native
diff options
context:
space:
mode:
Diffstat (limited to 'native')
-rw-r--r--native/Cargo.toml7
-rw-r--r--native/src/debug/basic.rs216
-rw-r--r--native/src/debug/null.rs47
-rw-r--r--native/src/element.rs2
-rw-r--r--native/src/event.rs5
-rw-r--r--native/src/input.rs7
-rw-r--r--native/src/input/button_state.rs9
-rw-r--r--native/src/input/keyboard.rs5
-rw-r--r--native/src/input/keyboard/event.rs26
-rw-r--r--native/src/input/mouse.rs9
-rw-r--r--native/src/input/mouse/button.rs15
-rw-r--r--native/src/input/mouse/event.rs61
-rw-r--r--native/src/keyboard.rs2
-rw-r--r--native/src/lib.rs23
-rw-r--r--native/src/mouse.rs6
-rw-r--r--native/src/mouse/click.rs (renamed from native/src/input/mouse/click.rs)0
-rw-r--r--native/src/mouse_cursor.rs36
-rw-r--r--native/src/program.rs39
-rw-r--r--native/src/program/state.rs191
-rw-r--r--native/src/renderer/null.rs12
-rw-r--r--native/src/user_interface.rs52
-rw-r--r--native/src/widget/button.rs33
-rw-r--r--native/src/widget/checkbox.rs20
-rw-r--r--native/src/widget/image.rs4
-rw-r--r--native/src/widget/pane_grid.rs291
-rw-r--r--native/src/widget/pane_grid/axis.rs206
-rw-r--r--native/src/widget/pane_grid/content.rs30
-rw-r--r--native/src/widget/pane_grid/node.rs187
-rw-r--r--native/src/widget/pane_grid/state.rs95
-rw-r--r--native/src/widget/radio.rs85
-rw-r--r--native/src/widget/scrollable.rs20
-rw-r--r--native/src/widget/slider.rs131
-rw-r--r--native/src/widget/text.rs64
-rw-r--r--native/src/widget/text_input.rs77
-rw-r--r--native/src/window.rs2
-rw-r--r--native/src/window/backend.rs55
36 files changed, 1431 insertions, 639 deletions
diff --git a/native/Cargo.toml b/native/Cargo.toml
index ca58d75c..13052a93 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -1,16 +1,19 @@
[package]
name = "iced_native"
-version = "0.2.1"
+version = "0.2.2"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
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"
+num-traits = "0.2"
[dependencies.iced_core]
version = "0.2"
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/element.rs b/native/src/element.rs
index f29580fc..73e39012 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -81,7 +81,7 @@ where
///
/// ```
/// # mod counter {
- /// # use iced_native::{text, Text};
+ /// # type Text = iced_native::Text<iced_native::renderer::Null>;
/// #
/// # #[derive(Debug, Clone, Copy)]
/// # pub enum Message {}
diff --git a/native/src/event.rs b/native/src/event.rs
index b2550ead..606a71d6 100644
--- a/native/src/event.rs
+++ b/native/src/event.rs
@@ -1,7 +1,4 @@
-use crate::{
- input::{keyboard, mouse},
- window,
-};
+use crate::{keyboard, mouse, window};
/// A user interface event.
///
diff --git a/native/src/input.rs b/native/src/input.rs
deleted file mode 100644
index 097fa730..00000000
--- a/native/src/input.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-//! Map your system events into input events that the runtime can understand.
-pub mod keyboard;
-pub mod mouse;
-
-mod button_state;
-
-pub use button_state::ButtonState;
diff --git a/native/src/input/button_state.rs b/native/src/input/button_state.rs
deleted file mode 100644
index 988043ba..00000000
--- a/native/src/input/button_state.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-/// The state of a button.
-#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
-pub enum ButtonState {
- /// The button is pressed.
- Pressed,
-
- /// The button is __not__ pressed.
- Released,
-}
diff --git a/native/src/input/keyboard.rs b/native/src/input/keyboard.rs
deleted file mode 100644
index 928bf492..00000000
--- a/native/src/input/keyboard.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-//! Build keyboard events.
-mod event;
-
-pub use event::Event;
-pub use iced_core::keyboard::{KeyCode, ModifiersState};
diff --git a/native/src/input/keyboard/event.rs b/native/src/input/keyboard/event.rs
deleted file mode 100644
index 862f30c4..00000000
--- a/native/src/input/keyboard/event.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use super::{KeyCode, ModifiersState};
-use crate::input::ButtonState;
-
-/// A keyboard event.
-///
-/// _**Note:** This type is largely incomplete! If you need to track
-/// additional events, feel free to [open an issue] and share your use case!_
-///
-/// [open an issue]: https://github.com/hecrj/iced/issues
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Event {
- /// A keyboard key was pressed or released.
- Input {
- /// The state of the key
- state: ButtonState,
-
- /// The key identifier
- key_code: KeyCode,
-
- /// The state of the modifier keys
- modifiers: ModifiersState,
- },
-
- /// A unicode character was received.
- CharacterReceived(char),
-}
diff --git a/native/src/input/mouse.rs b/native/src/input/mouse.rs
deleted file mode 100644
index 7198b233..00000000
--- a/native/src/input/mouse.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-//! Build mouse events.
-mod button;
-mod event;
-
-pub mod click;
-
-pub use button::Button;
-pub use click::Click;
-pub use event::{Event, ScrollDelta};
diff --git a/native/src/input/mouse/button.rs b/native/src/input/mouse/button.rs
deleted file mode 100644
index aeb8a55d..00000000
--- a/native/src/input/mouse/button.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-/// The button of a mouse.
-#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
-pub enum Button {
- /// The left mouse button.
- Left,
-
- /// The right mouse button.
- Right,
-
- /// The middle (wheel) button.
- Middle,
-
- /// Some other button.
- Other(u8),
-}
diff --git a/native/src/input/mouse/event.rs b/native/src/input/mouse/event.rs
deleted file mode 100644
index aafc4fe3..00000000
--- a/native/src/input/mouse/event.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use super::Button;
-use crate::input::ButtonState;
-
-/// A mouse event.
-///
-/// _**Note:** This type is largely incomplete! If you need to track
-/// additional events, feel free to [open an issue] and share your use case!_
-///
-/// [open an issue]: https://github.com/hecrj/iced/issues
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Event {
- /// The mouse cursor entered the window.
- CursorEntered,
-
- /// The mouse cursor left the window.
- CursorLeft,
-
- /// The mouse cursor was moved
- CursorMoved {
- /// The X coordinate of the mouse position
- x: f32,
-
- /// The Y coordinate of the mouse position
- y: f32,
- },
-
- /// A mouse button was pressed or released.
- Input {
- /// The state of the button
- state: ButtonState,
-
- /// The button identifier
- button: Button,
- },
-
- /// The mouse wheel was scrolled.
- WheelScrolled {
- /// The scroll movement.
- delta: ScrollDelta,
- },
-}
-
-/// A scroll movement.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum ScrollDelta {
- /// A line-based scroll movement
- Lines {
- /// The number of horizontal lines scrolled
- x: f32,
-
- /// The number of vertical lines scrolled
- y: f32,
- },
- /// A pixel-based scroll movement
- Pixels {
- /// The number of horizontal pixels scrolled
- x: f32,
- /// The number of vertical pixels scrolled
- y: f32,
- },
-}
diff --git a/native/src/keyboard.rs b/native/src/keyboard.rs
new file mode 100644
index 00000000..012538e3
--- /dev/null
+++ b/native/src/keyboard.rs
@@ -0,0 +1,2 @@
+//! Track keyboard events.
+pub use iced_core::keyboard::*;
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 89612391..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)]
@@ -39,8 +35,10 @@
#![deny(unused_results)]
#![forbid(unsafe_code)]
#![forbid(rust_2018_idioms)]
-pub mod input;
+pub mod keyboard;
pub mod layout;
+pub mod mouse;
+pub mod program;
pub mod renderer;
pub mod subscription;
pub mod widget;
@@ -50,10 +48,18 @@ mod clipboard;
mod element;
mod event;
mod hasher;
-mod mouse_cursor;
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,11 +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 mouse_cursor::MouseCursor;
+pub use program::Program;
pub use renderer::Renderer;
pub use runtime::Runtime;
pub use subscription::Subscription;
diff --git a/native/src/mouse.rs b/native/src/mouse.rs
new file mode 100644
index 00000000..9ee406cf
--- /dev/null
+++ b/native/src/mouse.rs
@@ -0,0 +1,6 @@
+//! Track mouse events.
+
+pub mod click;
+
+pub use click::Click;
+pub use iced_core::mouse::*;
diff --git a/native/src/input/mouse/click.rs b/native/src/mouse/click.rs
index d27bc67e..d27bc67e 100644
--- a/native/src/input/mouse/click.rs
+++ b/native/src/mouse/click.rs
diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs
deleted file mode 100644
index 0dad3edc..00000000
--- a/native/src/mouse_cursor.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-/// The state of the mouse cursor.
-#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)]
-pub enum MouseCursor {
- /// The cursor is out of the bounds of the user interface.
- OutOfBounds,
-
- /// The cursor is over a non-interactive widget.
- Idle,
-
- /// The cursor is over a clickable widget.
- Pointer,
-
- /// The cursor is over a busy widget.
- Working,
-
- /// The cursor is over a grabbable widget.
- Grab,
-
- /// The cursor is grabbing a widget.
- Grabbing,
-
- /// The cursor is over a text widget.
- Text,
-
- /// The cursor is resizing a widget horizontally.
- ResizingHorizontally,
-
- /// The cursor is resizing a widget vertically.
- ResizingVertically,
-}
-
-impl Default for MouseCursor {
- fn default() -> MouseCursor {
- MouseCursor::OutOfBounds
- }
-}
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..fdc42e8b
--- /dev/null
+++ b/native/src/program/state.rs
@@ -0,0 +1,191 @@
+use crate::{
+ Cache, Clipboard, Command, Debug, Event, Point, 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,
+ cursor_position: Point,
+ 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, cursor_position);
+ 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);
+ }
+
+ /// Returns whether the event queue of the [`State`] is empty or not.
+ ///
+ /// [`State`]: struct.State.html
+ pub fn is_queue_empty(&self) -> bool {
+ self.queued_events.is_empty() && self.queued_messages.is_empty()
+ }
+
+ /// 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,
+ bounds: Size,
+ cursor_position: Point,
+ clipboard: Option<&dyn Clipboard>,
+ renderer: &mut P::Renderer,
+ debug: &mut Debug,
+ ) -> Option<Command<P::Message>> {
+ 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(..),
+ cursor_position,
+ 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, 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 user_interface = build_user_interface(
+ &mut self.program,
+ temp_cache,
+ renderer,
+ bounds,
+ debug,
+ );
+
+ debug.draw_started();
+ self.primitive = user_interface.draw(renderer, cursor_position);
+ 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/renderer/null.rs b/native/src/renderer/null.rs
index 9033a7da..b8b0b996 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -47,7 +47,11 @@ impl row::Renderer for Null {
}
impl text::Renderer for Null {
- const DEFAULT_SIZE: u16 = 20;
+ type Font = Font;
+
+ fn default_size(&self) -> u16 {
+ 20
+ }
fn measure(
&self,
@@ -101,6 +105,7 @@ impl scrollable::Renderer for Null {
}
impl text_input::Renderer for Null {
+ type Font = Font;
type Style = ();
fn default_size(&self) -> u16 {
@@ -159,9 +164,8 @@ impl button::Renderer for Null {
impl radio::Renderer for Null {
type Style = ();
- fn default_size(&self) -> u32 {
- 20
- }
+ const DEFAULT_SIZE: u16 = 20;
+ const DEFAULT_SPACING: u16 = 15;
fn draw(
&mut self,
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 5d9221e9..b9646043 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,6 +1,4 @@
-use crate::{
- input::mouse, layout, Clipboard, Element, Event, Layout, Point, Size,
-};
+use crate::{layout, Clipboard, Element, Event, Layout, Point, Size};
use std::hash::Hasher;
@@ -25,7 +23,6 @@ pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
layout: layout::Node,
bounds: Size,
- cursor_position: Point,
}
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
@@ -104,7 +101,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))
@@ -115,7 +114,6 @@ where
root,
layout,
bounds,
- cursor_position: cache.cursor_position,
}
}
@@ -132,7 +130,7 @@ where
/// completing [the previous example](#example):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size};
+ /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -154,6 +152,7 @@ where
/// let mut cache = Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
+ /// let mut cursor_position = Point::default();
///
/// // Initialize our event storage
/// let mut events = Vec::new();
@@ -169,7 +168,12 @@ where
/// );
///
/// // Update the user interface
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// let messages = user_interface.update(
+ /// events.drain(..),
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// );
///
/// cache = user_interface.into_cache();
///
@@ -182,20 +186,17 @@ where
pub fn update(
&mut self,
events: impl IntoIterator<Item = Event>,
+ cursor_position: Point,
clipboard: Option<&dyn Clipboard>,
renderer: &Renderer,
) -> Vec<Message> {
let mut messages = Vec::new();
for event in events {
- if let Event::Mouse(mouse::Event::CursorMoved { x, y }) = event {
- self.cursor_position = Point::new(x, y);
- }
-
self.root.widget.on_event(
event,
Layout::new(&self.layout),
- self.cursor_position,
+ cursor_position,
&mut messages,
renderer,
clipboard,
@@ -219,7 +220,7 @@ where
/// [completing the last example](#example-1):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size};
+ /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -241,6 +242,7 @@ where
/// let mut cache = 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 events = Vec::new();
///
/// loop {
@@ -253,10 +255,15 @@ where
/// &mut renderer,
/// );
///
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// let messages = user_interface.update(
+ /// events.drain(..),
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// );
///
/// // Draw the user interface
- /// let mouse_cursor = user_interface.draw(&mut renderer);
+ /// let mouse_cursor = user_interface.draw(&mut renderer, cursor_position);
///
/// cache = user_interface.into_cache();
///
@@ -268,12 +275,16 @@ where
/// // Flush rendering operations...
/// }
/// ```
- pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ cursor_position: Point,
+ ) -> Renderer::Output {
self.root.widget.draw(
renderer,
&Renderer::Defaults::default(),
Layout::new(&self.layout),
- self.cursor_position,
+ cursor_position,
)
}
@@ -287,7 +298,6 @@ where
hash: self.hash,
layout: self.layout,
bounds: self.bounds,
- cursor_position: self.cursor_position,
}
}
}
@@ -300,7 +310,6 @@ pub struct Cache {
hash: u64,
layout: layout::Node,
bounds: Size,
- cursor_position: Point,
}
impl Cache {
@@ -316,7 +325,6 @@ impl Cache {
hash: 0,
layout: layout::Node::new(Size::new(0.0, 0.0)),
bounds: Size::ZERO,
- cursor_position: Point::new(-1.0, -1.0),
}
}
}
@@ -329,7 +337,7 @@ impl Default for Cache {
impl PartialEq for Cache {
fn eq(&self, other: &Cache) -> bool {
- self.hash == other.hash && self.cursor_position == other.cursor_position
+ self.hash == other.hash
}
}
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 3cf4f780..c932da2b 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -5,8 +5,7 @@
//! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html
use crate::{
- input::{mouse, ButtonState},
- layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
+ layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Rectangle, Widget,
};
use std::hash::Hash;
@@ -185,28 +184,24 @@ where
_clipboard: Option<&dyn Clipboard>,
) {
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state,
- }) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ if self.on_press.is_some() {
+ let bounds = layout.bounds();
+
+ self.state.is_pressed = bounds.contains(cursor_position);
+ }
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
if let Some(on_press) = self.on_press.clone() {
let bounds = layout.bounds();
- match state {
- ButtonState::Pressed => {
- self.state.is_pressed =
- bounds.contains(cursor_position);
- }
- ButtonState::Released => {
- let is_clicked = self.state.is_pressed
- && bounds.contains(cursor_position);
+ let is_clicked = self.state.is_pressed
+ && bounds.contains(cursor_position);
- self.state.is_pressed = false;
+ self.state.is_pressed = false;
- if is_clicked {
- messages.push(on_press);
- }
- }
+ if is_clicked {
+ messages.push(on_press);
}
}
}
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index d611993f..44962288 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -2,8 +2,7 @@
use std::hash::Hash;
use crate::{
- input::{mouse, ButtonState},
- layout, row, text, Align, Clipboard, Element, Event, Font, Hasher,
+ layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
VerticalAlignment, Widget,
};
@@ -33,7 +32,7 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
width: Length,
size: u16,
spacing: u16,
- text_size: u16,
+ text_size: Option<u16>,
style: Renderer::Style,
}
@@ -61,7 +60,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING,
- text_size: <Renderer as text::Renderer>::DEFAULT_SIZE,
+ text_size: None,
style: Renderer::Style::default(),
}
}
@@ -94,7 +93,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn text_size(mut self, text_size: u16) -> Self {
- self.text_size = text_size;
+ self.text_size = Some(text_size);
self
}
@@ -137,7 +136,7 @@ where
.push(
Text::new(&self.label)
.width(self.width)
- .size(self.text_size),
+ .size(self.text_size.unwrap_or(renderer.default_size())),
)
.layout(renderer, limits)
}
@@ -152,10 +151,7 @@ where
_clipboard: Option<&dyn Clipboard>,
) {
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Pressed,
- }) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
@@ -185,8 +181,8 @@ where
defaults,
label_layout.bounds(),
&self.label,
- self.text_size,
- Font::Default,
+ self.text_size.unwrap_or(renderer.default_size()),
+ Default::default(),
None,
HorizontalAlignment::Left,
VerticalAlignment::Center,
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 6bd0fd68..132f249d 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -123,6 +123,8 @@ pub struct Handle {
impl Handle {
/// Creates an image [`Handle`] pointing to the image of the given path.
///
+ /// Makes an educated guess about the image format by examining the data in the file.
+ ///
/// [`Handle`]: struct.Handle.html
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
Self::from_data(Data::Path(path.into()))
@@ -145,6 +147,8 @@ impl Handle {
/// Creates an image [`Handle`] containing the image data directly.
///
+ /// Makes an educated guess about the image format by examining the given data.
+ ///
/// This is useful if you already have your image loaded in-memory, maybe
/// because you downloaded or generated it procedurally.
///
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index f84775ed..2d21a968 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -9,6 +9,7 @@
//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
//! [`PaneGrid`]: struct.PaneGrid.html
mod axis;
+mod content;
mod direction;
mod node;
mod pane;
@@ -16,15 +17,16 @@ mod split;
mod state;
pub use axis::Axis;
+pub use content::Content;
pub use direction::Direction;
+pub use node::Node;
pub use pane::Pane;
pub use split::Split;
pub use state::{Focus, State};
use crate::{
- input::{keyboard, mouse, ButtonState},
- layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size,
- Widget,
+ keyboard, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length,
+ Point, Rectangle, Size, Widget,
};
/// A collection of panes distributed using either vertical or horizontal splits
@@ -74,7 +76,7 @@ use crate::{
/// }.into()
/// })
/// .on_drag(Message::PaneDragged)
-/// .on_resize(Message::PaneResized);
+/// .on_resize(10, Message::PaneResized);
/// ```
///
/// [`PaneGrid`]: struct.PaneGrid.html
@@ -89,7 +91,7 @@ pub struct PaneGrid<'a, Message, Renderer> {
spacing: u16,
modifier_keys: keyboard::ModifiersState,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
- on_resize: Option<Box<dyn Fn(ResizeEvent) -> Message + 'a>>,
+ on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>,
}
@@ -175,8 +177,8 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
/// Sets the modifier keys of the [`PaneGrid`].
///
- /// The modifier keys will need to be pressed to trigger dragging, resizing,
- /// and key events.
+ /// The modifier keys will need to be pressed to trigger dragging, and key
+ /// events.
///
/// The default modifier key is `Ctrl`.
///
@@ -206,14 +208,19 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
/// Enables the resize interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
///
- /// Panes can be resized using `Modifier keys + Right click`.
+ /// The `leeway` describes the amount of space around a split that can be
+ /// used to grab it.
+ ///
+ /// The grabbable area of a split will have a length of `spacing + leeway`,
+ /// properly centered. In other words, a length of
+ /// `(spacing + leeway) / 2.0` on either side of the split line.
///
/// [`PaneGrid`]: struct.PaneGrid.html
- pub fn on_resize<F>(mut self, f: F) -> Self
+ pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self
where
F: 'a + Fn(ResizeEvent) -> Message,
{
- self.on_resize = Some(Box::new(f));
+ self.on_resize = Some((leeway, Box::new(f)));
self
}
@@ -242,13 +249,42 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
self
}
+ fn click_pane(
+ &mut self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ messages: &mut Vec<Message>,
+ ) {
+ let mut clicked_region =
+ self.elements.iter().zip(layout.children()).filter(
+ |(_, layout)| layout.bounds().contains(cursor_position),
+ );
+
+ if let Some(((pane, _), _)) = clicked_region.next() {
+ match &self.on_drag {
+ Some(on_drag)
+ if self.pressed_modifiers.matches(self.modifier_keys) =>
+ {
+ self.state.pick_pane(pane);
+
+ messages.push(on_drag(DragEvent::Picked { pane: *pane }));
+ }
+ _ => {
+ self.state.focus(pane);
+ }
+ }
+ } else {
+ self.state.unfocus();
+ }
+ }
+
fn trigger_resize(
&mut self,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
- if let Some(on_resize) = &self.on_resize {
+ if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
@@ -405,40 +441,50 @@ where
clipboard: Option<&dyn Clipboard>,
) {
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state,
- }) => match state {
- ButtonState::Pressed => {
- let mut clicked_region =
- self.elements.iter().zip(layout.children()).filter(
- |(_, layout)| {
- layout.bounds().contains(cursor_position)
- },
- );
-
- if let Some(((pane, _), _)) = clicked_region.next() {
- match &self.on_drag {
- Some(on_drag)
- if self
- .pressed_modifiers
- .matches(self.modifier_keys) =>
- {
- self.state.pick_pane(pane);
-
- messages.push(on_drag(DragEvent::Picked {
- pane: *pane,
- }));
+ Event::Mouse(mouse_event) => match mouse_event {
+ mouse::Event::ButtonPressed(mouse::Button::Left) => {
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ match self.on_resize {
+ Some((leeway, _)) => {
+ let relative_cursor = Point::new(
+ cursor_position.x - bounds.x,
+ cursor_position.y - bounds.y,
+ );
+
+ let splits = self.state.splits(
+ f32::from(self.spacing),
+ Size::new(bounds.width, bounds.height),
+ );
+
+ let clicked_split = hovered_split(
+ splits.iter(),
+ f32::from(self.spacing + leeway),
+ relative_cursor,
+ );
+
+ if let Some((split, axis)) = clicked_split {
+ self.state.pick_split(&split, axis);
+ } else {
+ self.click_pane(
+ layout,
+ cursor_position,
+ messages,
+ );
+ }
}
- _ => {
- self.state.focus(pane);
+ None => {
+ self.click_pane(
+ layout,
+ cursor_position,
+ messages,
+ );
}
}
- } else {
- self.state.unfocus();
}
}
- ButtonState::Released => {
+ mouse::Event::ButtonReleased(mouse::Button::Left) => {
if let Some(pane) = self.state.picked_pane() {
self.state.focus(&pane);
@@ -463,99 +509,44 @@ where
messages.push(on_drag(event));
}
+ } else if self.state.picked_split().is_some() {
+ self.state.drop_split();
}
}
- },
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Right,
- state: ButtonState::Pressed,
- }) if self.on_resize.is_some()
- && self.state.picked_pane().is_none()
- && self.pressed_modifiers.matches(self.modifier_keys) =>
- {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
- let relative_cursor = Point::new(
- cursor_position.x - bounds.x,
- cursor_position.y - bounds.y,
- );
-
- let splits = self.state.splits(
- f32::from(self.spacing),
- Size::new(bounds.width, bounds.height),
- );
-
- let mut sorted_splits: Vec<_> = splits
- .iter()
- .filter(|(_, (axis, rectangle, _))| match axis {
- Axis::Horizontal => {
- relative_cursor.x > rectangle.x
- && relative_cursor.x
- < rectangle.x + rectangle.width
- }
- Axis::Vertical => {
- relative_cursor.y > rectangle.y
- && relative_cursor.y
- < rectangle.y + rectangle.height
- }
- })
- .collect();
-
- sorted_splits.sort_by_key(
- |(_, (axis, rectangle, ratio))| {
- let distance = match axis {
- Axis::Horizontal => (relative_cursor.y
- - (rectangle.y + rectangle.height * ratio))
- .abs(),
- Axis::Vertical => (relative_cursor.x
- - (rectangle.x + rectangle.width * ratio))
- .abs(),
- };
-
- distance.round() as u32
- },
- );
-
- if let Some((split, (axis, _, _))) = sorted_splits.first() {
- self.state.pick_split(split, *axis);
- self.trigger_resize(layout, cursor_position, messages);
- }
+ mouse::Event::CursorMoved { .. } => {
+ self.trigger_resize(layout, cursor_position, messages);
}
- }
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Right,
- state: ButtonState::Released,
- }) if self.state.picked_split().is_some() => {
- self.state.drop_split();
- }
- Event::Mouse(mouse::Event::CursorMoved { .. }) => {
- self.trigger_resize(layout, cursor_position, messages);
- }
- Event::Keyboard(keyboard::Event::Input {
- modifiers,
- key_code,
- state,
- }) => {
- if let Some(on_key_press) = &self.on_key_press {
- // TODO: Discard when event is captured
- if state == ButtonState::Pressed {
- if let Some(_) = self.state.active_pane() {
- if modifiers.matches(self.modifier_keys) {
- if let Some(message) =
- on_key_press(KeyPressEvent {
- key_code,
- modifiers,
- })
- {
- messages.push(message);
+ _ => {}
+ },
+ Event::Keyboard(keyboard_event) => {
+ match keyboard_event {
+ keyboard::Event::KeyPressed {
+ modifiers,
+ key_code,
+ } => {
+ if let Some(on_key_press) = &self.on_key_press {
+ // TODO: Discard when event is captured
+ if let Some(_) = self.state.active_pane() {
+ if modifiers.matches(self.modifier_keys) {
+ if let Some(message) =
+ on_key_press(KeyPressEvent {
+ key_code,
+ modifiers,
+ })
+ {
+ messages.push(message);
+ }
}
}
}
+
+ *self.pressed_modifiers = modifiers;
+ }
+ keyboard::Event::KeyReleased { modifiers, .. } => {
+ *self.pressed_modifiers = modifiers;
}
+ _ => {}
}
-
- *self.pressed_modifiers = modifiers;
}
_ => {}
}
@@ -585,11 +576,37 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
+ let picked_split = self
+ .state
+ .picked_split()
+ .or_else(|| match self.on_resize {
+ Some((leeway, _)) => {
+ let bounds = layout.bounds();
+
+ let relative_cursor = Point::new(
+ cursor_position.x - bounds.x,
+ cursor_position.y - bounds.y,
+ );
+
+ let splits = self
+ .state
+ .splits(f32::from(self.spacing), bounds.size());
+
+ hovered_split(
+ splits.iter(),
+ f32::from(self.spacing + leeway),
+ relative_cursor,
+ )
+ }
+ None => None,
+ })
+ .map(|(_, axis)| axis);
+
renderer.draw(
defaults,
&self.elements,
self.state.picked_pane(),
- self.state.picked_split().map(|(_, axis)| axis),
+ picked_split,
layout,
cursor_position,
)
@@ -653,3 +670,25 @@ where
Element::new(pane_grid)
}
}
+
+/*
+ * Helpers
+ */
+fn hovered_split<'a>(
+ splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>,
+ spacing: f32,
+ cursor_position: Point,
+) -> Option<(Split, Axis)> {
+ splits
+ .filter_map(|(split, (axis, region, ratio))| {
+ let bounds =
+ axis.split_line_bounds(*region, *ratio, f32::from(spacing));
+
+ if bounds.contains(cursor_position) {
+ Some((*split, *axis))
+ } else {
+ None
+ }
+ })
+ .next()
+}
diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs
index f0e3f362..2320cb7c 100644
--- a/native/src/widget/pane_grid/axis.rs
+++ b/native/src/widget/pane_grid/axis.rs
@@ -14,41 +14,225 @@ 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
},
)
}
}
}
+
+ pub(super) fn split_line_bounds(
+ &self,
+ rectangle: Rectangle,
+ ratio: f32,
+ spacing: f32,
+ ) -> Rectangle {
+ match self {
+ Axis::Horizontal => Rectangle {
+ x: rectangle.x,
+ y: (rectangle.y + rectangle.height * ratio - spacing / 2.0)
+ .round(),
+ width: rectangle.width,
+ height: spacing,
+ },
+ Axis::Vertical => Rectangle {
+ x: (rectangle.x + rectangle.width * ratio - spacing / 2.0)
+ .round(),
+ y: rectangle.y,
+ width: spacing,
+ height: rectangle.height,
+ },
+ }
+ }
+}
+
+#[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/content.rs b/native/src/widget/pane_grid/content.rs
new file mode 100644
index 00000000..8822083e
--- /dev/null
+++ b/native/src/widget/pane_grid/content.rs
@@ -0,0 +1,30 @@
+use crate::pane_grid::Axis;
+
+/// The content of a [`PaneGrid`].
+///
+/// [`PaneGrid`]: struct.PaneGrid.html
+#[derive(Debug, Clone)]
+pub enum Content<T> {
+ /// A split of the available space.
+ Split {
+ /// The direction of the split.
+ axis: Axis,
+
+ /// The ratio of the split in [0.0, 1.0].
+ ratio: f32,
+
+ /// The left/top [`Content`] of the split.
+ ///
+ /// [`Content`]: enum.Node.html
+ a: Box<Content<T>>,
+
+ /// The right/bottom [`Content`] of the split.
+ ///
+ /// [`Content`]: enum.Node.html
+ b: Box<Content<T>>,
+ },
+ /// A [`Pane`].
+ ///
+ /// [`Pane`]: struct.Pane.html
+ Pane(T),
+}
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index 4d5970b8..b13c5e26 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -5,20 +5,98 @@ use crate::{
use std::collections::HashMap;
-#[derive(Debug, Clone, Hash)]
+/// A layout node of a [`PaneGrid`].
+///
+/// [`PaneGrid`]: struct.PaneGrid.html
+#[derive(Debug, Clone)]
pub enum Node {
+ /// The region of this [`Node`] is split into two.
+ ///
+ /// [`Node`]: enum.Node.html
Split {
+ /// The [`Split`] of this [`Node`].
+ ///
+ /// [`Split`]: struct.Split.html
+ /// [`Node`]: enum.Node.html
id: Split,
+
+ /// The direction of the split.
axis: Axis,
- ratio: u32,
+
+ /// The ratio of the split in [0.0, 1.0].
+ ratio: f32,
+
+ /// The left/top [`Node`] of the split.
+ ///
+ /// [`Node`]: enum.Node.html
a: Box<Node>,
+
+ /// The right/bottom [`Node`] of the split.
+ ///
+ /// [`Node`]: enum.Node.html
b: Box<Node>,
},
+ /// The region of this [`Node`] is taken by a [`Pane`].
+ ///
+ /// [`Pane`]: struct.Pane.html
Pane(Pane),
}
impl Node {
- pub fn find(&mut self, pane: &Pane) -> Option<&mut Node> {
+ /// Returns the rectangular region for each [`Pane`] in the [`Node`] given
+ /// the spacing between panes and the total available space.
+ ///
+ /// [`Pane`]: struct.Pane.html
+ /// [`Node`]: enum.Node.html
+ pub fn regions(
+ &self,
+ spacing: f32,
+ size: Size,
+ ) -> HashMap<Pane, Rectangle> {
+ let mut regions = HashMap::new();
+
+ self.compute_regions(
+ spacing,
+ &Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: size.width,
+ height: size.height,
+ },
+ &mut regions,
+ );
+
+ regions
+ }
+
+ /// Returns the axis, rectangular region, and ratio for each [`Split`] in
+ /// the [`Node`] given the spacing between panes and the total available
+ /// space.
+ ///
+ /// [`Split`]: struct.Split.html
+ /// [`Node`]: enum.Node.html
+ pub fn splits(
+ &self,
+ spacing: f32,
+ size: Size,
+ ) -> HashMap<Split, (Axis, Rectangle, f32)> {
+ let mut splits = HashMap::new();
+
+ self.compute_splits(
+ spacing,
+ &Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: size.width,
+ height: size.height,
+ },
+ &mut splits,
+ );
+
+ splits
+ }
+
+ pub(crate) fn find(&mut self, pane: &Pane) -> Option<&mut Node> {
match self {
Node::Split { a, b, .. } => {
a.find(pane).or_else(move || b.find(pane))
@@ -33,17 +111,17 @@ impl Node {
}
}
- pub fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) {
+ pub(crate) fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) {
*self = Node::Split {
id,
axis,
- ratio: 500_000,
+ ratio: 0.5,
a: Box::new(self.clone()),
b: Box::new(Node::Pane(new_pane)),
};
}
- pub fn update(&mut self, f: &impl Fn(&mut Node)) {
+ pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {
match self {
Node::Split { a, b, .. } => {
a.update(f);
@@ -55,13 +133,13 @@ impl Node {
f(self);
}
- pub fn resize(&mut self, split: &Split, percentage: f32) -> bool {
+ pub(crate) fn resize(&mut self, split: &Split, percentage: f32) -> bool {
match self {
Node::Split {
id, ratio, a, b, ..
} => {
if id == split {
- *ratio = (percentage * 1_000_000.0).round() as u32;
+ *ratio = percentage;
true
} else if a.resize(split, percentage) {
@@ -74,7 +152,7 @@ impl Node {
}
}
- pub fn remove(&mut self, pane: &Pane) -> Option<Pane> {
+ pub(crate) fn remove(&mut self, pane: &Pane) -> Option<Pane> {
match self {
Node::Split { a, b, .. } => {
if a.pane() == Some(*pane) {
@@ -91,56 +169,14 @@ impl Node {
}
}
- pub fn regions(
- &self,
- spacing: f32,
- size: Size,
- ) -> HashMap<Pane, Rectangle> {
- let mut regions = HashMap::new();
-
- self.compute_regions(
- spacing / 2.0,
- &Rectangle {
- x: 0.0,
- y: 0.0,
- width: size.width,
- height: size.height,
- },
- &mut regions,
- );
-
- regions
- }
-
- pub fn splits(
- &self,
- spacing: f32,
- size: Size,
- ) -> HashMap<Split, (Axis, Rectangle, f32)> {
- let mut splits = HashMap::new();
-
- self.compute_splits(
- spacing / 2.0,
- &Rectangle {
- x: 0.0,
- y: 0.0,
- width: size.width,
- height: size.height,
- },
- &mut splits,
- );
-
- splits
- }
-
- pub fn pane(&self) -> Option<Pane> {
+ fn pane(&self) -> Option<Pane> {
match self {
Node::Split { .. } => None,
Node::Pane(pane) => Some(*pane),
}
}
- pub fn first_pane(&self) -> Pane {
+ fn first_pane(&self) -> Pane {
match self {
Node::Split { a, .. } => a.first_pane(),
Node::Pane(pane) => *pane,
@@ -149,7 +185,7 @@ impl Node {
fn compute_regions(
&self,
- halved_spacing: f32,
+ spacing: f32,
current: &Rectangle,
regions: &mut HashMap<Pane, Rectangle>,
) {
@@ -157,12 +193,10 @@ impl Node {
Node::Split {
axis, ratio, a, b, ..
} => {
- let ratio = *ratio as f32 / 1_000_000.0;
- 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);
@@ -172,7 +206,7 @@ impl Node {
fn compute_splits(
&self,
- halved_spacing: f32,
+ spacing: f32,
current: &Rectangle,
splits: &mut HashMap<Split, (Axis, Rectangle, f32)>,
) {
@@ -184,16 +218,37 @@ impl Node {
b,
id,
} => {
- let ratio = *ratio as f32 / 1_000_000.0;
- 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));
+ 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(_) => {}
}
}
}
+
+impl std::hash::Hash for Node {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ match self {
+ Node::Split {
+ id,
+ axis,
+ ratio,
+ a,
+ b,
+ } => {
+ id.hash(state);
+ axis.hash(state);
+ ((ratio * 100_000.0) as u32).hash(state);
+ a.hash(state);
+ b.hash(state);
+ }
+ Node::Pane(pane) => {
+ pane.hash(state);
+ }
+ }
+ }
+}
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index 0a8b8419..4b13fb8e 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -1,6 +1,6 @@
use crate::{
- input::keyboard,
- pane_grid::{node::Node, Axis, Direction, Pane, Split},
+ keyboard,
+ pane_grid::{Axis, Content, Direction, Node, Pane, Split},
Hasher, Point, Rectangle, Size,
};
@@ -21,7 +21,7 @@ use std::collections::HashMap;
/// [`Pane`]: struct.Pane.html
/// [`Split`]: struct.Split.html
/// [`State`]: struct.State.html
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct State<T> {
pub(super) panes: HashMap<Pane, T>,
pub(super) internal: Internal,
@@ -53,23 +53,28 @@ impl<T> State<T> {
/// [`State`]: struct.State.html
/// [`Pane`]: struct.Pane.html
pub fn new(first_pane_state: T) -> (Self, Pane) {
- let first_pane = Pane(0);
+ (Self::with_content(Content::Pane(first_pane_state)), Pane(0))
+ }
+ /// Creates a new [`State`] with the given [`Content`].
+ ///
+ /// [`State`]: struct.State.html
+ /// [`Content`]: enum.Content.html
+ pub fn with_content(content: impl Into<Content<T>>) -> Self {
let mut panes = HashMap::new();
- let _ = panes.insert(first_pane, first_pane_state);
-
- (
- State {
- panes,
- internal: Internal {
- layout: Node::Pane(first_pane),
- last_id: 0,
- action: Action::Idle { focus: None },
- },
- modifiers: keyboard::ModifiersState::default(),
+
+ let (layout, last_id) =
+ Self::distribute_content(&mut panes, content.into(), 0);
+
+ State {
+ panes,
+ internal: Internal {
+ layout,
+ last_id,
+ action: Action::Idle { focus: None },
},
- first_pane,
- )
+ modifiers: keyboard::ModifiersState::default(),
+ }
}
/// Returns the total amount of panes in the [`State`].
@@ -82,6 +87,14 @@ impl<T> State<T> {
/// Returns the internal state of the given [`Pane`], if it exists.
///
/// [`Pane`]: struct.Pane.html
+ pub fn get(&self, pane: &Pane) -> Option<&T> {
+ self.panes.get(pane)
+ }
+
+ /// Returns the internal state of the given [`Pane`] with mutability, if it
+ /// exists.
+ ///
+ /// [`Pane`]: struct.Pane.html
pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> {
self.panes.get_mut(pane)
}
@@ -102,6 +115,13 @@ impl<T> State<T> {
self.panes.iter_mut()
}
+ /// Returns the layout of the [`State`].
+ ///
+ /// [`State`]: struct.State.html
+ pub fn layout(&self) -> &Node {
+ &self.internal.layout
+ }
+
/// Returns the active [`Pane`] of the [`State`], if there is one.
///
/// A [`Pane`] is active if it is focused and is __not__ being dragged.
@@ -176,7 +196,12 @@ impl<T> State<T> {
///
/// [`Pane`]: struct.Pane.html
/// [`Axis`]: enum.Axis.html
- pub fn split(&mut self, axis: Axis, pane: &Pane, state: T) -> Option<Pane> {
+ pub fn split(
+ &mut self,
+ axis: Axis,
+ pane: &Pane,
+ state: T,
+ ) -> Option<(Pane, Split)> {
let node = self.internal.layout.find(pane)?;
let new_pane = {
@@ -196,7 +221,7 @@ impl<T> State<T> {
let _ = self.panes.insert(new_pane, state);
self.focus(&new_pane);
- Some(new_pane)
+ Some((new_pane, new_split))
}
/// Swaps the position of the provided panes in the [`State`].
@@ -246,9 +271,39 @@ impl<T> State<T> {
None
}
}
+
+ fn distribute_content(
+ panes: &mut HashMap<Pane, T>,
+ content: Content<T>,
+ next_id: usize,
+ ) -> (Node, usize) {
+ match content {
+ Content::Split { axis, ratio, a, b } => {
+ let (a, next_id) = Self::distribute_content(panes, *a, next_id);
+ let (b, next_id) = Self::distribute_content(panes, *b, next_id);
+
+ (
+ Node::Split {
+ id: Split(next_id),
+ axis,
+ ratio,
+ a: Box::new(a),
+ b: Box::new(b),
+ },
+ next_id + 1,
+ )
+ }
+ Content::Pane(state) => {
+ let id = Pane(next_id);
+ let _ = panes.insert(id, state);
+
+ (Node::Pane(id), next_id + 1)
+ }
+ }
+ }
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Internal {
layout: Node,
last_id: usize,
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 0ec621bf..5b8d00e9 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,7 +1,6 @@
//! Create choices using radio buttons.
use crate::{
- input::{mouse, ButtonState},
- layout, row, text, Align, Clipboard, Element, Event, Font, Hasher,
+ layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
VerticalAlignment, Widget,
};
@@ -35,14 +34,20 @@ use std::hash::Hash;
///
/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Radio<Message, Renderer: self::Renderer> {
+pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
is_selected: bool,
on_click: Message,
label: String,
+ width: Length,
+ size: u16,
+ spacing: u16,
+ text_size: Option<u16>,
style: Renderer::Style,
}
-impl<Message, Renderer: self::Renderer> Radio<Message, Renderer> {
+impl<Message, Renderer: self::Renderer + text::Renderer>
+ Radio<Message, Renderer>
+{
/// Creates a new [`Radio`] button.
///
/// It expects:
@@ -67,10 +72,46 @@ impl<Message, Renderer: self::Renderer> Radio<Message, Renderer> {
is_selected: Some(value) == selected,
on_click: f(value),
label: label.into(),
+ width: Length::Shrink,
+ size: <Renderer as self::Renderer>::DEFAULT_SIZE,
+ spacing: Renderer::DEFAULT_SPACING, //15
+ text_size: None,
style: Renderer::Style::default(),
}
}
+ /// Sets the size of the [`Radio`] button.
+ ///
+ /// [`Radio`]: struct.Radio.html
+ pub fn size(mut self, size: u16) -> Self {
+ self.size = size;
+ self
+ }
+
+ /// Sets the width of the [`Radio`] button.
+ ///
+ /// [`Radio`]: struct.Radio.html
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the spacing between the [`Radio`] button and the text.
+ ///
+ /// [`Radio`]: struct.Radio.html
+ pub fn spacing(mut self, spacing: u16) -> Self {
+ self.spacing = spacing;
+ self
+ }
+
+ /// Sets the text size of the [`Radio`] button.
+ ///
+ /// [`Radio`]: struct.Radio.html
+ pub fn text_size(mut self, text_size: u16) -> Self {
+ self.text_size = Some(text_size);
+ self
+ }
+
/// Sets the style of the [`Radio`] button.
///
/// [`Radio`]: struct.Radio.html
@@ -86,7 +127,7 @@ where
Message: Clone,
{
fn width(&self) -> Length {
- Length::Fill
+ self.width
}
fn height(&self) -> Length {
@@ -98,18 +139,20 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let size = self::Renderer::default_size(renderer);
-
Row::<(), Renderer>::new()
- .width(Length::Fill)
- .spacing(15)
+ .width(self.width)
+ .spacing(self.spacing)
.align_items(Align::Center)
.push(
Row::new()
- .width(Length::Units(size as u16))
- .height(Length::Units(size as u16)),
+ .width(Length::Units(self.size))
+ .height(Length::Units(self.size)),
+ )
+ .push(
+ Text::new(&self.label)
+ .width(self.width)
+ .size(self.text_size.unwrap_or(renderer.default_size())),
)
- .push(Text::new(&self.label))
.layout(renderer, limits)
}
@@ -123,10 +166,7 @@ where
_clipboard: Option<&dyn Clipboard>,
) {
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Pressed,
- }) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if layout.bounds().contains(cursor_position) {
messages.push(self.on_click.clone());
}
@@ -154,8 +194,8 @@ where
defaults,
label_layout.bounds(),
&self.label,
- <Renderer as text::Renderer>::DEFAULT_SIZE,
- Font::Default,
+ self.text_size.unwrap_or(renderer.default_size()),
+ Default::default(),
None,
HorizontalAlignment::Left,
VerticalAlignment::Center,
@@ -192,10 +232,15 @@ pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
- /// Returns the default size of a [`Radio`] button.
+ /// The default size of a [`Radio`] button.
+ ///
+ /// [`Radio`]: struct.Radio.html
+ const DEFAULT_SIZE: u16;
+
+ /// The default spacing of a [`Radio`] button.
///
/// [`Radio`]: struct.Radio.html
- fn default_size(&self) -> u32;
+ const DEFAULT_SPACING: u16;
/// Draws a [`Radio`] button.
///
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 393095a4..3c8e5e5b 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -1,9 +1,7 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::{
- column,
- input::{mouse, ButtonState},
- layout, Align, Clipboard, Column, Element, Event, Hasher, Layout, Length,
- Point, Rectangle, Size, Widget,
+ column, layout, mouse, Align, Clipboard, Column, Element, Event, Hasher,
+ Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{f32, hash::Hash, u32};
@@ -188,10 +186,9 @@ where
if self.state.is_scroller_grabbed() {
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Released,
- }) => {
+ Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Left,
+ )) => {
self.state.scroller_grabbed_at = None;
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
@@ -212,10 +209,9 @@ where
}
} else if is_mouse_over_scrollbar {
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Pressed,
- }) => {
+ Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Left,
+ )) => {
if let Some(scrollbar) = scrollbar {
if let Some(scroller_grabbed_at) =
scrollbar.grab_scroller(cursor_position)
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 1feb7825..70f2b6ac 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -5,8 +5,7 @@
//! [`Slider`]: struct.Slider.html
//! [`State`]: struct.State.html
use crate::{
- input::{mouse, ButtonState},
- layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
+ layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Rectangle, Size, Widget,
};
@@ -17,13 +16,16 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// A [`Slider`] will try to fill the horizontal space of its container.
///
+/// The [`Slider`] range of numeric values is generic and its step size defaults
+/// to 1 unit.
+///
/// [`Slider`]: struct.Slider.html
///
/// # Example
/// ```
/// # use iced_native::{slider, renderer::Null};
/// #
-/// # pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Null>;
+/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
/// pub enum Message {
/// SliderChanged(f32),
/// }
@@ -36,16 +38,22 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Slider<'a, Message, Renderer: self::Renderer> {
+pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
- on_change: Box<dyn Fn(f32) -> Message>,
+ range: RangeInclusive<T>,
+ step: T,
+ value: T,
+ on_change: Box<dyn Fn(T) -> Message>,
+ on_release: Option<Message>,
width: Length,
style: Renderer::Style,
}
-impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
+impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Renderer: self::Renderer,
+{
/// Creates a new [`Slider`].
///
/// It expects:
@@ -60,23 +68,50 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
/// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
+ range: RangeInclusive<T>,
+ value: T,
on_change: F,
) -> Self
where
- F: 'static + Fn(f32) -> Message,
+ F: 'static + Fn(T) -> Message,
{
+ let value = if value >= *range.start() {
+ value
+ } else {
+ *range.start()
+ };
+
+ let value = if value <= *range.end() {
+ value
+ } else {
+ *range.end()
+ };
+
Slider {
state,
- value: value.max(*range.start()).min(*range.end()),
+ value,
range,
+ step: T::from(1),
on_change: Box::new(on_change),
+ on_release: None,
width: Length::Fill,
style: Renderer::Style::default(),
}
}
+ /// Sets the release message of the [`Slider`].
+ /// This is called when the mouse is released from the slider.
+ ///
+ /// Typically, the user's interaction with the slider is finished when this message is produced.
+ /// This is useful if you need to spawn a long-running task from the slider's result, where
+ /// the default on_change message could create too many events.
+ ///
+ /// [`Slider`]: struct.Slider.html
+ pub fn on_release(mut self, on_release: Message) -> Self {
+ self.on_release = Some(on_release);
+ self
+ }
+
/// Sets the width of the [`Slider`].
///
/// [`Slider`]: struct.Slider.html
@@ -92,6 +127,14 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
self.style = style.into();
self
}
+
+ /// Sets the step size of the [`Slider`].
+ ///
+ /// [`Slider`]: struct.Slider.html
+ pub fn step(mut self, step: T) -> Self {
+ self.step = step;
+ self
+ }
}
/// The local state of a [`Slider`].
@@ -111,10 +154,12 @@ impl State {
}
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Slider<'a, Message, Renderer>
+impl<'a, T, Message, Renderer> Widget<Message, Renderer>
+ for Slider<'a, T, Message, Renderer>
where
+ T: Copy + Into<f64> + num_traits::FromPrimitive,
Renderer: self::Renderer,
+ Message: Clone,
{
fn width(&self) -> Length {
self.width
@@ -149,40 +194,50 @@ where
) {
let mut change = || {
let bounds = layout.bounds();
-
if cursor_position.x <= bounds.x {
messages.push((self.on_change)(*self.range.start()));
} else if cursor_position.x >= bounds.x + bounds.width {
messages.push((self.on_change)(*self.range.end()));
} else {
- let percent = (cursor_position.x - bounds.x) / bounds.width;
- let value = (self.range.end() - self.range.start()) * percent
- + self.range.start();
+ let step = self.step.into();
+ let start = (*self.range.start()).into();
+ let end = (*self.range.end()).into();
+
+ let percent = f64::from(cursor_position.x - bounds.x)
+ / f64::from(bounds.width);
+
+ let steps = (percent * (end - start) / step).round();
+ let value = steps * step + start;
- messages.push((self.on_change)(value));
+ if let Some(value) = T::from_f64(value) {
+ messages.push((self.on_change)(value));
+ }
}
};
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state,
- }) => match state {
- ButtonState::Pressed => {
+ Event::Mouse(mouse_event) => match mouse_event {
+ mouse::Event::ButtonPressed(mouse::Button::Left) => {
if layout.bounds().contains(cursor_position) {
change();
self.state.is_dragging = true;
}
}
- ButtonState::Released => {
- self.state.is_dragging = false;
+ mouse::Event::ButtonReleased(mouse::Button::Left) => {
+ if self.state.is_dragging {
+ if let Some(on_release) = self.on_release.clone() {
+ messages.push(on_release);
+ }
+ self.state.is_dragging = false;
+ }
}
- },
- Event::Mouse(mouse::Event::CursorMoved { .. }) => {
- if self.state.is_dragging {
- change();
+ mouse::Event::CursorMoved { .. } => {
+ if self.state.is_dragging {
+ change();
+ }
}
- }
+ _ => {}
+ },
_ => {}
}
}
@@ -194,11 +249,14 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
+ let start = *self.range.start();
+ let end = *self.range.end();
+
renderer.draw(
layout.bounds(),
cursor_position,
- self.range.clone(),
- self.value,
+ start.into() as f32..=end.into() as f32,
+ self.value.into() as f32,
self.state.is_dragging,
&self.style,
)
@@ -251,14 +309,15 @@ pub trait Renderer: crate::Renderer {
) -> Self::Output;
}
-impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>>
+impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
+ T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Renderer: 'a + self::Renderer,
- Message: 'a,
+ Message: 'a + Clone,
{
fn from(
- slider: Slider<'a, Message, Renderer>,
+ slider: Slider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index dc7c33ec..48a69e34 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -1,7 +1,7 @@
//! Write some text for your users to read.
use crate::{
- layout, Color, Element, Font, Hasher, HorizontalAlignment, Layout, Length,
- Point, Rectangle, Size, VerticalAlignment, Widget,
+ layout, Color, Element, Hasher, HorizontalAlignment, Layout, Length, Point,
+ Rectangle, Size, VerticalAlignment, Widget,
};
use std::hash::Hash;
@@ -11,7 +11,7 @@ use std::hash::Hash;
/// # Example
///
/// ```
-/// # use iced_native::Text;
+/// # type Text = iced_native::Text<iced_native::renderer::Null>;
/// #
/// Text::new("I <3 iced!")
/// .color([0.0, 0.0, 1.0])
@@ -19,19 +19,19 @@ use std::hash::Hash;
/// ```
///
/// ![Text drawn by `iced_wgpu`](https://github.com/hecrj/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text.png?raw=true)
-#[derive(Debug, Clone)]
-pub struct Text {
+#[derive(Debug)]
+pub struct Text<Renderer: self::Renderer> {
content: String,
size: Option<u16>,
color: Option<Color>,
- font: Font,
+ font: Renderer::Font,
width: Length,
height: Length,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
}
-impl Text {
+impl<Renderer: self::Renderer> Text<Renderer> {
/// Create a new fragment of [`Text`] with the given contents.
///
/// [`Text`]: struct.Text.html
@@ -40,7 +40,7 @@ impl Text {
content: label.into(),
size: None,
color: None,
- font: Font::Default,
+ font: Default::default(),
width: Length::Shrink,
height: Length::Shrink,
horizontal_alignment: HorizontalAlignment::Left,
@@ -69,8 +69,8 @@ impl Text {
///
/// [`Text`]: struct.Text.html
/// [`Font`]: ../../struct.Font.html
- pub fn font(mut self, font: Font) -> Self {
- self.font = font;
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.font = font.into();
self
}
@@ -112,7 +112,7 @@ impl Text {
}
}
-impl<Message, Renderer> Widget<Message, Renderer> for Text
+impl<Message, Renderer> Widget<Message, Renderer> for Text<Renderer>
where
Renderer: self::Renderer,
{
@@ -131,7 +131,7 @@ where
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
- let size = self.size.unwrap_or(Renderer::DEFAULT_SIZE);
+ let size = self.size.unwrap_or(renderer.default_size());
let bounds = limits.max();
@@ -154,7 +154,7 @@ where
defaults,
layout.bounds(),
&self.content,
- self.size.unwrap_or(Renderer::DEFAULT_SIZE),
+ self.size.unwrap_or(renderer.default_size()),
self.font,
self.color,
self.horizontal_alignment,
@@ -163,7 +163,8 @@ where
}
fn hash_layout(&self, state: &mut Hasher) {
- std::any::TypeId::of::<Text>().hash(state);
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
self.content.hash(state);
self.size.hash(state);
@@ -181,10 +182,15 @@ where
/// [renderer]: ../../renderer/index.html
/// [`UserInterface`]: ../../struct.UserInterface.html
pub trait Renderer: crate::Renderer {
- /// The default size of [`Text`].
+ /// The font type used for [`Text`].
///
/// [`Text`]: struct.Text.html
- const DEFAULT_SIZE: u16;
+ type Font: Default + Copy;
+
+ /// Returns the default size of [`Text`].
+ ///
+ /// [`Text`]: struct.Text.html
+ fn default_size(&self) -> u16;
/// Measures the [`Text`] in the given bounds and returns the minimum
/// boundaries that can fit the contents.
@@ -194,7 +200,7 @@ pub trait Renderer: crate::Renderer {
&self,
content: &str,
size: u16,
- font: Font,
+ font: Self::Font,
bounds: Size,
) -> (f32, f32);
@@ -217,18 +223,34 @@ pub trait Renderer: crate::Renderer {
bounds: Rectangle,
content: &str,
size: u16,
- font: Font,
+ font: Self::Font,
color: Option<Color>,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
) -> Self::Output;
}
-impl<'a, Message, Renderer> From<Text> for Element<'a, Message, Renderer>
+impl<'a, Message, Renderer> From<Text<Renderer>>
+ for Element<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: self::Renderer + 'a,
{
- fn from(text: Text) -> Element<'a, Message, Renderer> {
+ fn from(text: Text<Renderer>) -> Element<'a, Message, Renderer> {
Element::new(text)
}
}
+
+impl<Renderer: self::Renderer> Clone for Text<Renderer> {
+ fn clone(&self) -> Self {
+ Self {
+ content: self.content.clone(),
+ size: self.size,
+ color: self.color,
+ font: self.font,
+ width: self.width,
+ height: self.height,
+ horizontal_alignment: self.horizontal_alignment,
+ vertical_alignment: self.vertical_alignment,
+ }
+ }
+}
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 7d1a7415..24085606 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -15,13 +15,10 @@ pub use value::Value;
use editor::Editor;
use crate::{
- input::{
- keyboard,
- mouse::{self, click},
- ButtonState,
- },
- layout, Clipboard, Element, Event, Font, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ keyboard, layout,
+ mouse::{self, click},
+ Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
+ Widget,
};
use std::u32;
@@ -56,7 +53,7 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
placeholder: String,
value: Value,
is_secure: bool,
- font: Font,
+ font: Renderer::Font,
width: Length,
max_width: u32,
padding: u16,
@@ -91,7 +88,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
placeholder: String::from(placeholder),
value: Value::new(value),
is_secure: false,
- font: Font::Default,
+ font: Default::default(),
width: Length::Fill,
max_width: u32::MAX,
padding: 0,
@@ -114,7 +111,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
///
/// [`Text`]: struct.Text.html
/// [`Font`]: ../../struct.Font.html
- pub fn font(mut self, font: Font) -> Self {
+ pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
@@ -212,10 +209,7 @@ where
clipboard: Option<&dyn Clipboard>,
) {
match event {
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Pressed,
- }) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let is_clicked = layout.bounds().contains(cursor_position);
if is_clicked {
@@ -280,10 +274,7 @@ where
self.state.is_dragging = is_clicked;
self.state.is_focused = is_clicked;
}
- Event::Mouse(mouse::Event::Input {
- button: mouse::Button::Left,
- state: ButtonState::Released,
- }) => {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
self.state.is_dragging = false;
}
Event::Mouse(mouse::Event::CursorMoved { x, .. }) => {
@@ -327,9 +318,8 @@ where
let message = (self.on_change)(editor.contents());
messages.push(message);
}
- Event::Keyboard(keyboard::Event::Input {
+ Event::Keyboard(keyboard::Event::KeyPressed {
key_code,
- state: ButtonState::Pressed,
modifiers,
}) if self.state.is_focused => match key_code {
keyboard::KeyCode::Enter => {
@@ -473,10 +463,8 @@ where
}
_ => {}
},
- Event::Keyboard(keyboard::Event::Input {
- key_code,
- state: ButtonState::Released,
- ..
+ Event::Keyboard(keyboard::Event::KeyReleased {
+ key_code, ..
}) => match key_code {
keyboard::KeyCode::V => {
self.state.is_pasting = None;
@@ -544,6 +532,11 @@ where
/// [`TextInput`]: struct.TextInput.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer + Sized {
+ /// The font type used for [`TextInput`].
+ ///
+ /// [`TextInput`]: struct.TextInput.html
+ type Font: Default + Copy;
+
/// The style supported by this renderer.
type Style: Default;
@@ -555,7 +548,7 @@ pub trait Renderer: crate::Renderer + Sized {
/// Returns the width of the value of the [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
- fn measure_value(&self, value: &str, size: u16, font: Font) -> f32;
+ fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
/// Returns the current horizontal offset of the value of the
/// [`TextInput`].
@@ -568,7 +561,7 @@ pub trait Renderer: crate::Renderer + Sized {
fn offset(
&self,
text_bounds: Rectangle,
- font: Font,
+ font: Self::Font,
size: u16,
value: &Value,
state: &State,
@@ -592,7 +585,7 @@ pub trait Renderer: crate::Renderer + Sized {
bounds: Rectangle,
text_bounds: Rectangle,
cursor_position: Point,
- font: Font,
+ font: Self::Font,
size: u16,
placeholder: &str,
value: &Value,
@@ -607,7 +600,7 @@ pub trait Renderer: crate::Renderer + Sized {
fn find_cursor_position(
&self,
text_bounds: Rectangle,
- font: Font,
+ font: Self::Font,
size: Option<u16>,
value: &Value,
state: &State,
@@ -690,13 +683,37 @@ impl State {
pub fn cursor(&self) -> Cursor {
self.cursor
}
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
+ ///
+ /// [`Cursor`]: struct.Cursor.html
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn move_cursor_to_front(&mut self) {
+ self.cursor.move_to(0);
+ }
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
+ ///
+ /// [`Cursor`]: struct.Cursor.html
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn move_cursor_to_end(&mut self) {
+ self.cursor.move_to(usize::MAX);
+ }
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
+ ///
+ /// [`Cursor`]: struct.Cursor.html
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn move_cursor_to(&mut self, position: usize) {
+ self.cursor.move_to(position);
+ }
}
// TODO: Reduce allocations
fn find_cursor_position<Renderer: self::Renderer>(
renderer: &Renderer,
value: &Value,
- font: Font,
+ font: Renderer::Font,
size: u16,
target: f32,
start: usize,
@@ -749,7 +766,7 @@ fn find_cursor_position<Renderer: self::Renderer>(
}
mod platform {
- use crate::input::keyboard;
+ use crate::keyboard;
pub fn is_jump_modifier_pressed(
modifiers: keyboard::ModifiersState,
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 3bc691cd..00000000
--- a/native/src/window/backend.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use crate::MouseCursor;
-
-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],
- ) -> MouseCursor;
-}