summaryrefslogtreecommitdiffstats
path: root/native
diff options
context:
space:
mode:
Diffstat (limited to 'native')
-rw-r--r--native/Cargo.toml11
-rw-r--r--native/README.md6
-rw-r--r--native/src/clipboard.rs2
-rw-r--r--native/src/debug/basic.rs218
-rw-r--r--native/src/debug/null.rs47
-rw-r--r--native/src/element.rs81
-rw-r--r--native/src/event.rs42
-rw-r--r--native/src/layout.rs18
-rw-r--r--native/src/layout/debugger.rs10
-rw-r--r--native/src/layout/flex.rs2
-rw-r--r--native/src/layout/limits.rs33
-rw-r--r--native/src/layout/node.rs17
-rw-r--r--native/src/lib.rs29
-rw-r--r--native/src/mouse/click.rs5
-rw-r--r--native/src/overlay.rs76
-rw-r--r--native/src/overlay/element.rs156
-rw-r--r--native/src/overlay/menu.rs428
-rw-r--r--native/src/program.rs30
-rw-r--r--native/src/program/state.rs180
-rw-r--r--native/src/renderer.rs27
-rw-r--r--native/src/renderer/null.rs86
-rw-r--r--native/src/runtime.rs16
-rw-r--r--native/src/subscription.rs49
-rw-r--r--native/src/subscription/events.rs28
-rw-r--r--native/src/touch.rs23
-rw-r--r--native/src/user_interface.rs321
-rw-r--r--native/src/widget.rs58
-rw-r--r--native/src/widget/button.rs75
-rw-r--r--native/src/widget/checkbox.rs65
-rw-r--r--native/src/widget/column.rs68
-rw-r--r--native/src/widget/container.rs45
-rw-r--r--native/src/widget/image.rs31
-rw-r--r--native/src/widget/image/viewer.rs22
-rw-r--r--native/src/widget/pane_grid.rs558
-rw-r--r--native/src/widget/pane_grid/axis.rs206
-rw-r--r--native/src/widget/pane_grid/configuration.rs26
-rw-r--r--native/src/widget/pane_grid/content.rs235
-rw-r--r--native/src/widget/pane_grid/node.rs66
-rw-r--r--native/src/widget/pane_grid/pane.rs2
-rw-r--r--native/src/widget/pane_grid/split.rs2
-rw-r--r--native/src/widget/pane_grid/state.rs208
-rw-r--r--native/src/widget/pane_grid/title_bar.rs253
-rw-r--r--native/src/widget/pick_list.rs343
-rw-r--r--native/src/widget/progress_bar.rs16
-rw-r--r--native/src/widget/radio.rs58
-rw-r--r--native/src/widget/row.rs73
-rw-r--r--native/src/widget/rule.rs116
-rw-r--r--native/src/widget/scrollable.rs271
-rw-r--r--native/src/widget/slider.rs191
-rw-r--r--native/src/widget/space.rs13
-rw-r--r--native/src/widget/svg.rs35
-rw-r--r--native/src/widget/text.rs62
-rw-r--r--native/src/widget/text_input.rs543
-rw-r--r--native/src/widget/text_input/cursor.rs9
-rw-r--r--native/src/widget/text_input/editor.rs8
-rw-r--r--native/src/widget/text_input/value.rs25
-rw-r--r--native/src/window.rs2
-rw-r--r--native/src/window/backend.rs55
58 files changed, 4036 insertions, 1645 deletions
diff --git a/native/Cargo.toml b/native/Cargo.toml
index 7e9c2a5a..2c99638a 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -1,22 +1,25 @@
[package]
name = "iced_native"
-version = "0.2.2"
+version = "0.3.0"
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"
+version = "0.3"
path = "../core"
[dependencies.iced_futures]
-version = "0.1"
+version = "0.2"
path = "../futures"
features = ["thread-pool"]
diff --git a/native/README.md b/native/README.md
index 31c7db88..6323dd4f 100644
--- a/native/README.md
+++ b/native/README.md
@@ -14,7 +14,9 @@ To achieve this, it introduces a bunch of reusable interfaces:
- A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic.
- A `Windowed` trait, leveraging [`raw-window-handle`], which can be implemented by graphical renderers that target _windows_. Window-based shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic.
-![iced_native](../docs/graphs/native.png)
+<p align="center">
+ <img alt="The native target" src="../docs/graphs/native.png" width="80%">
+</p>
[documentation]: https://docs.rs/iced_native
[`iced_core`]: ../core
@@ -26,7 +28,7 @@ To achieve this, it introduces a bunch of reusable interfaces:
Add `iced_native` as a dependency in your `Cargo.toml`:
```toml
-iced_native = "0.2"
+iced_native = "0.3"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs
index 4c574590..ecdccabf 100644
--- a/native/src/clipboard.rs
+++ b/native/src/clipboard.rs
@@ -2,7 +2,5 @@
/// applications.
pub trait Clipboard {
/// Returns the current content of the [`Clipboard`] as text.
- ///
- /// [`Clipboard`]: trait.Clipboard.html
fn content(&self) -> Option<String>;
}
diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs
new file mode 100644
index 00000000..a42f66ea
--- /dev/null
+++ b/native/src/debug/basic.rs
@@ -0,0 +1,218 @@
+#![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 [`struct@Debug`].
+ pub fn new() -> Self {
+ let now = time::Instant::now();
+
+ Self {
+ is_enabled: false,
+ startup_start: now,
+ startup_duration: time::Duration::from_secs(0),
+
+ update_start: now,
+ update_durations: TimeBuffer::new(200),
+
+ view_start: now,
+ view_durations: TimeBuffer::new(200),
+
+ layout_start: now,
+ layout_durations: TimeBuffer::new(200),
+
+ event_start: now,
+ event_durations: TimeBuffer::new(200),
+
+ draw_start: now,
+ draw_durations: TimeBuffer::new(200),
+
+ render_start: now,
+ render_durations: TimeBuffer::new(50),
+
+ message_count: 0,
+ last_messages: VecDeque::new(),
+ }
+ }
+
+ pub fn toggle(&mut self) {
+ self.is_enabled = !self.is_enabled;
+ }
+
+ pub fn startup_started(&mut self) {
+ self.startup_start = time::Instant::now();
+ }
+
+ pub fn startup_finished(&mut self) {
+ self.startup_duration = time::Instant::now() - self.startup_start;
+ }
+
+ pub fn update_started(&mut self) {
+ self.update_start = time::Instant::now();
+ }
+
+ pub fn update_finished(&mut self) {
+ self.update_durations
+ .push(time::Instant::now() - self.update_start);
+ }
+
+ pub fn view_started(&mut self) {
+ self.view_start = time::Instant::now();
+ }
+
+ pub fn view_finished(&mut self) {
+ self.view_durations
+ .push(time::Instant::now() - self.view_start);
+ }
+
+ pub fn layout_started(&mut self) {
+ self.layout_start = time::Instant::now();
+ }
+
+ pub fn layout_finished(&mut self) {
+ self.layout_durations
+ .push(time::Instant::now() - self.layout_start);
+ }
+
+ pub fn event_processing_started(&mut self) {
+ self.event_start = time::Instant::now();
+ }
+
+ pub fn event_processing_finished(&mut self) {
+ self.event_durations
+ .push(time::Instant::now() - self.event_start);
+ }
+
+ pub fn draw_started(&mut self) {
+ self.draw_start = time::Instant::now();
+ }
+
+ pub fn draw_finished(&mut self) {
+ self.draw_durations
+ .push(time::Instant::now() - self.draw_start);
+ }
+
+ pub fn render_started(&mut self) {
+ self.render_start = time::Instant::now();
+ }
+
+ pub fn render_finished(&mut self) {
+ self.render_durations
+ .push(time::Instant::now() - self.render_start);
+ }
+
+ pub fn log_message<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| {
+ if msg.len() <= 100 {
+ format!(" {}", msg)
+ } else {
+ format!(" {:.100}...", 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 73e39012..d6e9639a 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -1,5 +1,8 @@
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, Clipboard, Color, Event, Hasher, Layout, Length, Point, Widget,
+ Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
};
/// A generic [`Widget`].
@@ -11,8 +14,6 @@ use crate::{
/// to turn it into an [`Element`].
///
/// [built-in widget]: widget/index.html#built-in-widgets
-/// [`Widget`]: widget/trait.Widget.html
-/// [`Element`]: struct.Element.html
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
pub(crate) widget: Box<dyn Widget<Message, Renderer> + 'a>,
@@ -22,10 +23,7 @@ impl<'a, Message, Renderer> Element<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
- /// Create a new [`Element`] containing the given [`Widget`].
- ///
- /// [`Element`]: struct.Element.html
- /// [`Widget`]: widget/trait.Widget.html
+ /// Creates a new [`Element`] containing the given [`Widget`].
pub fn new(
widget: impl Widget<Message, Renderer> + 'a,
) -> Element<'a, Message, Renderer> {
@@ -39,8 +37,6 @@ where
/// This method is useful when you want to decouple different parts of your
/// UI and make them __composable__.
///
- /// [`Element`]: struct.Element.html
- ///
/// # Example
/// Imagine we want to use [our counter](index.html#usage). But instead of
/// showing a single counter, we want to display many of them. We can reuse
@@ -186,8 +182,7 @@ where
/// The [`Renderer`] will explain the layout of the [`Element`] graphically.
/// This can be very useful for debugging your layout!
///
- /// [`Element`]: struct.Element.html
- /// [`Renderer`]: trait.Renderer.html
+ /// [`Renderer`]: crate::Renderer
pub fn explain<C: Into<Color>>(
self,
color: C,
@@ -202,23 +197,18 @@ where
}
/// Returns the width of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn width(&self) -> Length {
self.widget.width()
}
/// Returns the height of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn height(&self) -> Length {
self.widget.height()
}
/// Computes the layout of the [`Element`] in the given [`Limits`].
///
- /// [`Element`]: struct.Element.html
- /// [`Limits`]: layout/struct.Limits.html
+ /// [`Limits`]: layout::Limits
pub fn layout(
&self,
renderer: &Renderer,
@@ -228,8 +218,6 @@ where
}
/// Processes a runtime [`Event`].
- ///
- /// [`Event`]: enum.Event.html
pub fn on_event(
&mut self,
event: Event,
@@ -238,7 +226,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.widget.on_event(
event,
layout,
@@ -246,30 +234,34 @@ where
messages,
renderer,
clipboard,
- );
+ )
}
/// Draws the [`Element`] and its children using the given [`Layout`].
- ///
- /// [`Element`]: struct.Element.html
- /// [`Layout`]: layout/struct.Layout.html
pub fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
self.widget
- .draw(renderer, defaults, layout, cursor_position)
+ .draw(renderer, defaults, layout, cursor_position, viewport)
}
/// Computes the _layout_ hash of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn hash_layout(&self, state: &mut Hasher) {
self.widget.hash_layout(state);
}
+
+ /// Returns the overlay of the [`Element`], if there is any.
+ pub fn overlay<'b>(
+ &'b mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ self.widget.overlay(layout)
+ }
}
struct Map<'a, A, B, Renderer> {
@@ -294,7 +286,9 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
where
- Renderer: crate::Renderer,
+ Renderer: crate::Renderer + 'a,
+ A: 'static,
+ B: 'static,
{
fn width(&self) -> Length {
self.widget.width()
@@ -320,10 +314,10 @@ where
messages: &mut Vec<B>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
let mut original_messages = Vec::new();
- self.widget.on_event(
+ let status = self.widget.on_event(
event,
layout,
cursor_position,
@@ -335,6 +329,8 @@ where
original_messages
.drain(..)
.for_each(|message| messages.push((self.mapper)(message)));
+
+ status
}
fn draw(
@@ -343,14 +339,26 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
self.widget
- .draw(renderer, defaults, layout, cursor_position)
+ .draw(renderer, defaults, layout, cursor_position, viewport)
}
fn hash_layout(&self, state: &mut Hasher) {
self.widget.hash_layout(state);
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, B, Renderer>> {
+ let mapper = &self.mapper;
+
+ self.widget
+ .overlay(layout)
+ .map(move |overlay| overlay.map(mapper))
+ }
}
struct Explain<'a, Message, Renderer: crate::Renderer> {
@@ -396,7 +404,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.element.widget.on_event(
event,
layout,
@@ -413,12 +421,14 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
renderer.explain(
defaults,
self.element.widget.as_ref(),
layout,
cursor_position,
+ viewport,
self.color,
)
}
@@ -426,4 +436,11 @@ where
fn hash_layout(&self, state: &mut Hasher) {
self.element.widget.hash_layout(state);
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.element.overlay(layout)
+ }
}
diff --git a/native/src/event.rs b/native/src/event.rs
index 606a71d6..205bb797 100644
--- a/native/src/event.rs
+++ b/native/src/event.rs
@@ -1,4 +1,8 @@
-use crate::{keyboard, mouse, window};
+//! Handle events of a user interface.
+use crate::keyboard;
+use crate::mouse;
+use crate::touch;
+use crate::window;
/// A user interface event.
///
@@ -6,7 +10,7 @@ use crate::{keyboard, mouse, window};
/// additional events, feel free to [open an issue] and share your use case!_
///
/// [open an issue]: https://github.com/hecrj/iced/issues
-#[derive(PartialEq, Clone, Debug)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Event {
/// A keyboard event
Keyboard(keyboard::Event),
@@ -16,4 +20,38 @@ pub enum Event {
/// A window event
Window(window::Event),
+
+ /// A touch event
+ Touch(touch::Event),
+}
+
+/// The status of an [`Event`] after being processed.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Status {
+ /// The [`Event`] was **NOT** handled by any widget.
+ Ignored,
+
+ /// The [`Event`] was handled and processed by a widget.
+ Captured,
+}
+
+impl Status {
+ /// Merges two [`Status`] into one.
+ ///
+ /// `Captured` takes precedence over `Ignored`:
+ ///
+ /// ```
+ /// use iced_native::event::Status;
+ ///
+ /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored);
+ /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured);
+ /// assert_eq!(Status::Captured.merge(Status::Ignored), Status::Captured);
+ /// assert_eq!(Status::Captured.merge(Status::Captured), Status::Captured);
+ /// ```
+ pub fn merge(self, b: Self) -> Self {
+ match self {
+ Status::Ignored => b,
+ Status::Captured => Status::Captured,
+ }
+ }
}
diff --git a/native/src/layout.rs b/native/src/layout.rs
index 4a3ab94a..6d144902 100644
--- a/native/src/layout.rs
+++ b/native/src/layout.rs
@@ -12,8 +12,6 @@ pub use node::Node;
use crate::{Point, Rectangle, Vector};
/// The bounds of a [`Node`] and its children, using absolute coordinates.
-///
-/// [`Node`]: struct.Node.html
#[derive(Debug, Clone, Copy)]
pub struct Layout<'a> {
position: Point,
@@ -34,14 +32,15 @@ impl<'a> Layout<'a> {
}
}
- /// Gets the bounds of the [`Layout`].
+ /// Returns the position of the [`Layout`].
+ pub fn position(&self) -> Point {
+ self.position
+ }
+
+ /// Returns the bounds of the [`Layout`].
///
/// The returned [`Rectangle`] describes the position and size of a
/// [`Node`].
- ///
- /// [`Layout`]: struct.Layout.html
- /// [`Rectangle`]: struct.Rectangle.html
- /// [`Node`]: struct.Node.html
pub fn bounds(&self) -> Rectangle {
let bounds = self.node.bounds();
@@ -54,10 +53,7 @@ impl<'a> Layout<'a> {
}
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
- ///
- /// [`Layout`]: struct.Layout.html
- /// [`Node`]: struct.Node.html
- pub fn children(&'a self) -> impl Iterator<Item = Layout<'a>> {
+ pub fn children(self) -> impl Iterator<Item = Layout<'a>> {
self.node.children().iter().map(move |node| {
Layout::with_offset(
Vector::new(self.position.x, self.position.y),
diff --git a/native/src/layout/debugger.rs b/native/src/layout/debugger.rs
index e4b21609..0759613f 100644
--- a/native/src/layout/debugger.rs
+++ b/native/src/layout/debugger.rs
@@ -1,8 +1,6 @@
-use crate::{Color, Layout, Point, Renderer, Widget};
+use crate::{Color, Layout, Point, Rectangle, Renderer, Widget};
/// A renderer able to graphically explain a [`Layout`].
-///
-/// [`Layout`]: struct.Layout.html
pub trait Debugger: Renderer {
/// Explains the [`Layout`] of an [`Element`] for debugging purposes.
///
@@ -12,15 +10,15 @@ pub trait Debugger: Renderer {
/// A common approach consists in recursively rendering the bounds of the
/// [`Layout`] and its children.
///
- /// [`Layout`]: struct.Layout.html
- /// [`Element`]: ../struct.Element.html
- /// [`Element::explain`]: ../struct.Element.html#method.explain
+ /// [`Element`]: crate::Element
+ /// [`Element::explain`]: crate::Element::explain
fn explain<Message>(
&mut self,
defaults: &Self::Defaults,
widget: &dyn Widget<Message, Self>,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
color: Color,
) -> Self::Output;
}
diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs
index 9da75a21..4f6523fb 100644
--- a/native/src/layout/flex.rs
+++ b/native/src/layout/flex.rs
@@ -58,8 +58,6 @@ impl Axis {
/// padding and alignment to the items as needed.
///
/// It returns a new layout [`Node`].
-///
-/// [`Node`]: ../struct.Node.html
pub fn resolve<Message, Renderer>(
axis: Axis,
renderer: &Renderer,
diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs
index 664c881a..a7bb5c9c 100644
--- a/native/src/layout/limits.rs
+++ b/native/src/layout/limits.rs
@@ -17,9 +17,6 @@ impl Limits {
};
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub const fn new(min: Size, max: Size) -> Limits {
Limits {
min,
@@ -29,32 +26,21 @@ impl Limits {
}
/// Returns the minimum [`Size`] of the [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn min(&self) -> Size {
self.min
}
/// Returns the maximum [`Size`] of the [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn max(&self) -> Size {
self.max
}
/// Returns the fill [`Size`] of the [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn fill(&self) -> Size {
self.fill
}
/// Applies a width constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn width(mut self, width: Length) -> Limits {
match width {
Length::Shrink => {
@@ -77,8 +63,6 @@ impl Limits {
}
/// Applies a height constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn height(mut self, height: Length) -> Limits {
match height {
Length::Shrink => {
@@ -101,8 +85,6 @@ impl Limits {
}
/// Applies a minimum width constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn min_width(mut self, min_width: u32) -> Limits {
self.min.width =
self.min.width.max(min_width as f32).min(self.max.width);
@@ -111,8 +93,6 @@ impl Limits {
}
/// Applies a maximum width constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn max_width(mut self, max_width: u32) -> Limits {
self.max.width =
self.max.width.min(max_width as f32).max(self.min.width);
@@ -121,8 +101,6 @@ impl Limits {
}
/// Applies a minimum height constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn min_height(mut self, min_height: u32) -> Limits {
self.min.height =
self.min.height.max(min_height as f32).min(self.max.height);
@@ -131,8 +109,6 @@ impl Limits {
}
/// Applies a maximum height constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn max_height(mut self, max_height: u32) -> Limits {
self.max.height =
self.max.height.min(max_height as f32).max(self.min.height);
@@ -141,16 +117,11 @@ impl Limits {
}
/// Shrinks the current [`Limits`] to account for the given padding.
- ///
- /// [`Limits`]: struct.Limits.html
pub fn pad(&self, padding: f32) -> Limits {
self.shrink(Size::new(padding * 2.0, padding * 2.0))
}
/// Shrinks the current [`Limits`] by the given [`Size`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn shrink(&self, size: Size) -> Limits {
let min = Size::new(
(self.min().width - size.width).max(0.0),
@@ -171,8 +142,6 @@ impl Limits {
}
/// Removes the minimum width constraint for the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn loose(&self) -> Limits {
Limits {
min: Size::ZERO,
@@ -183,8 +152,6 @@ impl Limits {
/// Computes the resulting [`Size`] that fits the [`Limits`] given the
/// intrinsic size of some content.
- ///
- /// [`Limits`]: struct.Limits.html
pub fn resolve(&self, intrinsic_size: Size) -> Size {
Size::new(
intrinsic_size
diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs
index a265c46a..d7666f31 100644
--- a/native/src/layout/node.rs
+++ b/native/src/layout/node.rs
@@ -9,17 +9,11 @@ pub struct Node {
impl Node {
/// Creates a new [`Node`] with the given [`Size`].
- ///
- /// [`Node`]: struct.Node.html
- /// [`Size`]: ../struct.Size.html
pub const fn new(size: Size) -> Self {
Self::with_children(size, Vec::new())
}
/// Creates a new [`Node`] with the given [`Size`] and children.
- ///
- /// [`Node`]: struct.Node.html
- /// [`Size`]: ../struct.Size.html
pub const fn with_children(size: Size, children: Vec<Node>) -> Self {
Node {
bounds: Rectangle {
@@ -33,30 +27,21 @@ impl Node {
}
/// Returns the [`Size`] of the [`Node`].
- ///
- /// [`Node`]: struct.Node.html
- /// [`Size`]: ../struct.Size.html
pub fn size(&self) -> Size {
Size::new(self.bounds.width, self.bounds.height)
}
/// Returns the bounds of the [`Node`].
- ///
- /// [`Node`]: struct.Node.html
pub fn bounds(&self) -> Rectangle {
self.bounds
}
/// Returns the children of the [`Node`].
- ///
- /// [`Node`]: struct.Node.html
pub fn children(&self) -> &[Node] {
&self.children
}
/// Aligns the [`Node`] in the given space.
- ///
- /// [`Node`]: struct.Node.html
pub fn align(
&mut self,
horizontal_alignment: Align,
@@ -85,8 +70,6 @@ impl Node {
}
/// Moves the [`Node`] to the given position.
- ///
- /// [`Node`]: struct.Node.html
pub fn move_to(&mut self, position: Point) {
self.bounds.x = position.x;
self.bounds.y = position.y;
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 9882803f..0890785b 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -1,6 +1,6 @@
//! A renderer-agnostic native GUI runtime.
//!
-//! ![`iced_native` crate graph](https://github.com/hecrj/iced/blob/cae26cb7bc627f4a5b3bcf1cd023a0c552e8c65e/docs/graphs/native.png?raw=true)
+//! ![The native path of the Iced ecosystem](https://github.com/hecrj/iced/raw/improvement/update-ecosystem-and-roadmap/docs/graphs/native.png)
//!
//! `iced_native` takes [`iced_core`] and builds a native runtime on top of it,
//! featuring:
@@ -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
@@ -30,30 +27,39 @@
//! [`iced_winit`]: https://github.com/hecrj/iced/tree/master/winit
//! [`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
+//! [renderer]: crate::renderer
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
#![forbid(unsafe_code)]
#![forbid(rust_2018_idioms)]
+pub mod event;
pub mod keyboard;
pub mod layout;
pub mod mouse;
+pub mod overlay;
+pub mod program;
pub mod renderer;
pub mod subscription;
+pub mod touch;
pub mod widget;
pub mod window;
mod clipboard;
mod element;
-mod event;
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,13 @@ 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 overlay::Overlay;
+pub use program::Program;
pub use renderer::Renderer;
pub use runtime::Runtime;
pub use subscription::Subscription;
diff --git a/native/src/mouse/click.rs b/native/src/mouse/click.rs
index d27bc67e..6c8b61a5 100644
--- a/native/src/mouse/click.rs
+++ b/native/src/mouse/click.rs
@@ -36,8 +36,6 @@ impl Kind {
impl Click {
/// Creates a new [`Click`] with the given position and previous last
/// [`Click`].
- ///
- /// [`Click`]: struct.Click.html
pub fn new(position: Point, previous: Option<Click>) -> Click {
let time = Instant::now();
@@ -59,9 +57,6 @@ impl Click {
}
/// Returns the [`Kind`] of [`Click`].
- ///
- /// [`Kind`]: enum.Kind.html
- /// [`Click`]: struct.Click.html
pub fn kind(&self) -> Kind {
self.kind
}
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
new file mode 100644
index 00000000..ea8bb384
--- /dev/null
+++ b/native/src/overlay.rs
@@ -0,0 +1,76 @@
+//! Display interactive elements on top of other widgets.
+mod element;
+
+pub mod menu;
+
+pub use element::Element;
+pub use menu::Menu;
+
+use crate::event::{self, Event};
+use crate::layout;
+use crate::{Clipboard, Hasher, Layout, Point, Size};
+
+/// An interactive component that can be displayed on top of other widgets.
+pub trait Overlay<Message, Renderer>
+where
+ Renderer: crate::Renderer,
+{
+ /// Returns the layout [`Node`] of the [`Overlay`].
+ ///
+ /// This [`Node`] is used by the runtime to compute the [`Layout`] of the
+ /// user interface.
+ ///
+ /// [`Node`]: layout::Node
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node;
+
+ /// Draws the [`Overlay`] using the associated `Renderer`.
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Renderer::Output;
+
+ /// Computes the _layout_ hash of the [`Overlay`].
+ ///
+ /// The produced hash is used by the runtime to decide if the [`Layout`]
+ /// needs to be recomputed between frames. Therefore, to ensure maximum
+ /// efficiency, the hash should only be affected by the properties of the
+ /// [`Overlay`] that can affect layouting.
+ ///
+ /// For example, the [`Text`] widget does not hash its color property, as
+ /// its value cannot affect the overall [`Layout`] of the user interface.
+ ///
+ /// [`Text`]: crate::widget::Text
+ fn hash_layout(&self, state: &mut Hasher, position: Point);
+
+ /// Processes a runtime [`Event`].
+ ///
+ /// It receives:
+ /// * an [`Event`] describing user interaction
+ /// * the computed [`Layout`] of the [`Overlay`]
+ /// * the current cursor position
+ /// * a mutable `Message` list, allowing the [`Overlay`] to produce
+ /// new messages based on user interaction.
+ /// * the `Renderer`
+ /// * a [`Clipboard`], if available
+ ///
+ /// By default, it does nothing.
+ fn on_event(
+ &mut self,
+ _event: Event,
+ _layout: Layout<'_>,
+ _cursor_position: Point,
+ _messages: &mut Vec<Message>,
+ _renderer: &Renderer,
+ _clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ event::Status::Ignored
+ }
+}
diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs
new file mode 100644
index 00000000..0f44a781
--- /dev/null
+++ b/native/src/overlay/element.rs
@@ -0,0 +1,156 @@
+pub use crate::Overlay;
+
+use crate::event::{self, Event};
+use crate::layout;
+use crate::{Clipboard, Hasher, Layout, Point, Size, Vector};
+
+/// A generic [`Overlay`].
+#[allow(missing_debug_implementations)]
+pub struct Element<'a, Message, Renderer> {
+ position: Point,
+ overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
+}
+
+impl<'a, Message, Renderer> Element<'a, Message, Renderer>
+where
+ Renderer: crate::Renderer,
+{
+ /// Creates a new [`Element`] containing the given [`Overlay`].
+ pub fn new(
+ position: Point,
+ overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
+ ) -> Self {
+ Self { position, overlay }
+ }
+
+ /// Translates the [`Element`].
+ pub fn translate(mut self, translation: Vector) -> Self {
+ self.position = self.position + translation;
+ self
+ }
+
+ /// Applies a transformation to the produced message of the [`Element`].
+ pub fn map<B>(self, f: &'a dyn Fn(Message) -> B) -> Element<'a, B, Renderer>
+ where
+ Message: 'a,
+ Renderer: 'a,
+ B: 'static,
+ {
+ Element {
+ position: self.position,
+ overlay: Box::new(Map::new(self.overlay, f)),
+ }
+ }
+
+ /// Computes the layout of the [`Element`] in the given bounds.
+ pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node {
+ self.overlay.layout(renderer, bounds, self.position)
+ }
+
+ /// Processes a runtime [`Event`].
+ pub fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ messages: &mut Vec<Message>,
+ renderer: &Renderer,
+ clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ self.overlay.on_event(
+ event,
+ layout,
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ )
+ }
+
+ /// Draws the [`Element`] and its children using the given [`Layout`].
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Renderer::Output {
+ self.overlay
+ .draw(renderer, defaults, layout, cursor_position)
+ }
+
+ /// Computes the _layout_ hash of the [`Element`].
+ pub fn hash_layout(&self, state: &mut Hasher) {
+ self.overlay.hash_layout(state, self.position);
+ }
+}
+
+struct Map<'a, A, B, Renderer> {
+ content: Box<dyn Overlay<A, Renderer> + 'a>,
+ mapper: &'a dyn Fn(A) -> B,
+}
+
+impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
+ pub fn new(
+ content: Box<dyn Overlay<A, Renderer> + 'a>,
+ mapper: &'a dyn Fn(A) -> B,
+ ) -> Map<'a, A, B, Renderer> {
+ Map { content, mapper }
+ }
+}
+
+impl<'a, A, B, Renderer> Overlay<B, Renderer> for Map<'a, A, B, Renderer>
+where
+ Renderer: crate::Renderer,
+{
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ self.content.layout(renderer, bounds, position)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ messages: &mut Vec<B>,
+ renderer: &Renderer,
+ clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ let mut original_messages = Vec::new();
+
+ let event_status = self.content.on_event(
+ event,
+ layout,
+ cursor_position,
+ &mut original_messages,
+ renderer,
+ clipboard,
+ );
+
+ original_messages
+ .drain(..)
+ .for_each(|message| messages.push((self.mapper)(message)));
+
+ event_status
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Renderer::Output {
+ self.content
+ .draw(renderer, defaults, layout, cursor_position)
+ }
+
+ fn hash_layout(&self, state: &mut Hasher, position: Point) {
+ self.content.hash_layout(state, position);
+ }
+}
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
new file mode 100644
index 00000000..abac849f
--- /dev/null
+++ b/native/src/overlay/menu.rs
@@ -0,0 +1,428 @@
+//! Build and show dropdown menus.
+use crate::container;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::scrollable;
+use crate::text;
+use crate::{
+ Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle,
+ Scrollable, Size, Vector, Widget,
+};
+
+/// A list of selectable options.
+#[allow(missing_debug_implementations)]
+pub struct Menu<'a, T, Renderer: self::Renderer> {
+ state: &'a mut State,
+ options: &'a [T],
+ hovered_option: &'a mut Option<usize>,
+ last_selection: &'a mut Option<T>,
+ width: u16,
+ padding: u16,
+ text_size: Option<u16>,
+ font: Renderer::Font,
+ style: <Renderer as self::Renderer>::Style,
+}
+
+impl<'a, T, Renderer> Menu<'a, T, Renderer>
+where
+ T: ToString + Clone,
+ Renderer: self::Renderer + 'a,
+{
+ /// Creates a new [`Menu`] with the given [`State`], a list of options, and
+ /// the message to produced when an option is selected.
+ pub fn new(
+ state: &'a mut State,
+ options: &'a [T],
+ hovered_option: &'a mut Option<usize>,
+ last_selection: &'a mut Option<T>,
+ ) -> Self {
+ Menu {
+ state,
+ options,
+ hovered_option,
+ last_selection,
+ width: 0,
+ padding: 0,
+ text_size: None,
+ font: Default::default(),
+ style: Default::default(),
+ }
+ }
+
+ /// Sets the width of the [`Menu`].
+ pub fn width(mut self, width: u16) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the padding of the [`Menu`].
+ pub fn padding(mut self, padding: u16) -> Self {
+ self.padding = padding;
+ self
+ }
+
+ /// Sets the text size of the [`Menu`].
+ pub fn text_size(mut self, text_size: u16) -> Self {
+ self.text_size = Some(text_size);
+ self
+ }
+
+ /// Sets the font of the [`Menu`].
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
+ /// Sets the style of the [`Menu`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as self::Renderer>::Style>,
+ ) -> Self {
+ self.style = style.into();
+ self
+ }
+
+ /// Turns the [`Menu`] into an overlay [`Element`] at the given target
+ /// position.
+ ///
+ /// The `target_height` will be used to display the menu either on top
+ /// of the target or under it, depending on the screen position and the
+ /// dimensions of the [`Menu`].
+ pub fn overlay<Message: 'a>(
+ self,
+ position: Point,
+ target_height: f32,
+ ) -> overlay::Element<'a, Message, Renderer> {
+ overlay::Element::new(
+ position,
+ Box::new(Overlay::new(self, target_height)),
+ )
+ }
+}
+
+/// The local state of a [`Menu`].
+#[derive(Debug, Clone, Default)]
+pub struct State {
+ scrollable: scrollable::State,
+}
+
+impl State {
+ /// Creates a new [`State`] for a [`Menu`].
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+struct Overlay<'a, Message, Renderer: self::Renderer> {
+ container: Container<'a, Message, Renderer>,
+ width: u16,
+ target_height: f32,
+ style: <Renderer as self::Renderer>::Style,
+}
+
+impl<'a, Message, Renderer: self::Renderer> Overlay<'a, Message, Renderer>
+where
+ Message: 'a,
+ Renderer: 'a,
+{
+ pub fn new<T>(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self
+ where
+ T: Clone + ToString,
+ {
+ let Menu {
+ state,
+ options,
+ hovered_option,
+ last_selection,
+ width,
+ padding,
+ font,
+ text_size,
+ style,
+ } = menu;
+
+ let container =
+ Container::new(Scrollable::new(&mut state.scrollable).push(List {
+ options,
+ hovered_option,
+ last_selection,
+ font,
+ text_size,
+ padding,
+ style: style.clone(),
+ }))
+ .padding(1);
+
+ Self {
+ container,
+ width: width,
+ target_height,
+ style: style,
+ }
+ }
+}
+
+impl<'a, Message, Renderer> crate::Overlay<Message, Renderer>
+ for Overlay<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ let space_below = bounds.height - (position.y + self.target_height);
+ let space_above = position.y;
+
+ let limits = layout::Limits::new(
+ Size::ZERO,
+ Size::new(
+ bounds.width - position.x,
+ if space_below > space_above {
+ space_below
+ } else {
+ space_above
+ },
+ ),
+ )
+ .width(Length::Units(self.width));
+
+ let mut node = self.container.layout(renderer, &limits);
+
+ node.move_to(if space_below > space_above {
+ position + Vector::new(0.0, self.target_height)
+ } else {
+ position - Vector::new(0.0, node.size().height)
+ });
+
+ node
+ }
+
+ fn hash_layout(&self, state: &mut Hasher, position: Point) {
+ use std::hash::Hash;
+
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ (position.x as u32).hash(state);
+ (position.y as u32).hash(state);
+ self.container.hash_layout(state);
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ messages: &mut Vec<Message>,
+ renderer: &Renderer,
+ clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ self.container.on_event(
+ event.clone(),
+ layout,
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ )
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Renderer::Output {
+ let primitives = self.container.draw(
+ renderer,
+ defaults,
+ layout,
+ cursor_position,
+ &layout.bounds(),
+ );
+
+ renderer.decorate(
+ layout.bounds(),
+ cursor_position,
+ &self.style,
+ primitives,
+ )
+ }
+}
+
+struct List<'a, T, Renderer: self::Renderer> {
+ options: &'a [T],
+ hovered_option: &'a mut Option<usize>,
+ last_selection: &'a mut Option<T>,
+ padding: u16,
+ text_size: Option<u16>,
+ font: Renderer::Font,
+ style: <Renderer as self::Renderer>::Style,
+}
+
+impl<'a, T, Message, Renderer: self::Renderer> Widget<Message, Renderer>
+ for List<'a, T, Renderer>
+where
+ T: Clone + ToString,
+ Renderer: self::Renderer,
+{
+ fn width(&self) -> Length {
+ Length::Fill
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ use std::f32;
+
+ let limits = limits.width(Length::Fill).height(Length::Shrink);
+ let text_size = self.text_size.unwrap_or(renderer.default_size());
+
+ let size = {
+ let intrinsic = Size::new(
+ 0.0,
+ f32::from(text_size + self.padding * 2)
+ * self.options.len() as f32,
+ );
+
+ limits.resolve(intrinsic)
+ };
+
+ layout::Node::new(size)
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ use std::hash::Hash as _;
+
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.options.len().hash(state);
+ self.text_size.hash(state);
+ self.padding.hash(state);
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _messages: &mut Vec<Message>,
+ renderer: &Renderer,
+ _clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ if let Some(index) = *self.hovered_option {
+ if let Some(option) = self.options.get(index) {
+ *self.last_selection = Some(option.clone());
+ }
+ }
+ }
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. }) => {
+ let bounds = layout.bounds();
+ let text_size =
+ self.text_size.unwrap_or(renderer.default_size());
+
+ if bounds.contains(cursor_position) {
+ *self.hovered_option = Some(
+ ((cursor_position.y - bounds.y)
+ / f32::from(text_size + self.padding * 2))
+ as usize,
+ );
+ }
+ }
+ _ => {}
+ }
+
+ event::Status::Ignored
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ _defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> Renderer::Output {
+ self::Renderer::draw(
+ renderer,
+ layout.bounds(),
+ cursor_position,
+ viewport,
+ self.options,
+ *self.hovered_option,
+ self.padding,
+ self.text_size.unwrap_or(renderer.default_size()),
+ self.font,
+ &self.style,
+ )
+ }
+}
+
+/// The renderer of a [`Menu`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use a [`Menu`] in your user interface.
+///
+/// [renderer]: crate::renderer
+pub trait Renderer:
+ scrollable::Renderer + container::Renderer + text::Renderer
+{
+ /// The [`Menu`] style supported by this renderer.
+ type Style: Default + Clone;
+
+ /// Decorates a the list of options of a [`Menu`].
+ ///
+ /// This method can be used to draw a background for the [`Menu`].
+ fn decorate(
+ &mut self,
+ bounds: Rectangle,
+ cursor_position: Point,
+ style: &<Self as Renderer>::Style,
+ primitive: Self::Output,
+ ) -> Self::Output;
+
+ /// Draws the list of options of a [`Menu`].
+ fn draw<T: ToString>(
+ &mut self,
+ bounds: Rectangle,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ options: &[T],
+ hovered_option: Option<usize>,
+ padding: u16,
+ text_size: u16,
+ font: Self::Font,
+ style: &<Self as Renderer>::Style,
+ ) -> Self::Output;
+}
+
+impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
+ for List<'a, T, Renderer>
+where
+ T: ToString + Clone,
+ Message: 'a,
+ Renderer: 'a + self::Renderer,
+{
+ fn into(self) -> Element<'a, Message, Renderer> {
+ Element::new(self)
+ }
+}
diff --git a/native/src/program.rs b/native/src/program.rs
new file mode 100644
index 00000000..9ee72703
--- /dev/null
+++ b/native/src/program.rs
@@ -0,0 +1,30 @@
+//! 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`].
+ type Renderer: Renderer;
+
+ /// The type of __messages__ your [`Program`] will produce.
+ type Message: std::fmt::Debug + Send;
+
+ /// Handles a __message__ and updates the state of the [`Program`].
+ ///
+ /// This is where you define your __update logic__. All the __messages__,
+ /// produced by either user interactions or commands, will be handled by
+ /// this method.
+ ///
+ /// Any [`Command`] returned will be executed immediately in the
+ /// background by shells.
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+
+ /// Returns the widgets to display in the [`Program`].
+ ///
+ /// These widgets can produce __messages__ based on user interaction.
+ 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..e630890a
--- /dev/null
+++ b/native/src/program/state.rs
@@ -0,0 +1,180 @@
+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.
+#[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.
+ pub fn new(
+ mut program: P,
+ bounds: Size,
+ cursor_position: Point,
+ renderer: &mut P::Renderer,
+ debug: &mut Debug,
+ ) -> Self {
+ let mut 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`].
+ pub fn program(&self) -> &P {
+ &self.program
+ }
+
+ /// Returns a reference to the current rendering primitive of the [`State`].
+ pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output {
+ &self.primitive
+ }
+
+ /// Queues an event in the [`State`] for processing during an [`update`].
+ ///
+ /// [`update`]: Self::update
+ pub fn queue_event(&mut self, event: Event) {
+ self.queued_events.push(event);
+ }
+
+ /// Queues a message in the [`State`] for processing during an [`update`].
+ ///
+ /// [`update`]: Self::update
+ pub fn queue_message(&mut self, message: P::Message) {
+ self.queued_messages.push(message);
+ }
+
+ /// Returns whether the event queue of the [`State`] is empty or not.
+ pub fn is_queue_empty(&self) -> bool {
+ self.queued_events.is_empty() && self.queued_messages.is_empty()
+ }
+
+ /// 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.
+ 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 = Vec::new();
+
+ let _ = user_interface.update(
+ &self.queued_events,
+ cursor_position,
+ clipboard,
+ renderer,
+ &mut messages,
+ );
+
+ messages.extend(self.queued_messages.drain(..));
+ self.queued_events.clear();
+ 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 mut 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.rs b/native/src/renderer.rs
index a16df72b..39a6cff1 100644
--- a/native/src/renderer.rs
+++ b/native/src/renderer.rs
@@ -13,19 +13,19 @@
//! In the end, a __renderer__ satisfying all the constraints is
//! needed to build a [`UserInterface`].
//!
-//! [`Widget`]: ../widget/trait.Widget.html
-//! [`UserInterface`]: ../struct.UserInterface.html
-//! [`Text`]: ../widget/text/struct.Text.html
-//! [`text::Renderer`]: ../widget/text/trait.Renderer.html
-//! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html
-//! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html
+//! [`Widget`]: crate::Widget
+//! [`UserInterface`]: crate::UserInterface
+//! [`Text`]: crate::widget::Text
+//! [`text::Renderer`]: crate::widget::text::Renderer
+//! [`Checkbox`]: crate::widget::Checkbox
+//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
#[cfg(debug_assertions)]
mod null;
#[cfg(debug_assertions)]
pub use null::Null;
-use crate::{layout, Element};
+use crate::{layout, Element, Rectangle};
/// A component that can take the state of a user interface and produce an
/// output for its users.
@@ -34,15 +34,11 @@ pub trait Renderer: Sized {
///
/// If you are implementing a graphical renderer, your output will most
/// likely be a tree of visual primitives.
- ///
- /// [`Renderer`]: trait.Renderer.html
type Output;
/// The default styling attributes of the [`Renderer`].
///
/// This type can be leveraged to implement style inheritance.
- ///
- /// [`Renderer`]: trait.Renderer.html
type Defaults: Default;
/// Lays out the elements of a user interface.
@@ -56,4 +52,13 @@ pub trait Renderer: Sized {
) -> layout::Node {
element.layout(self, limits)
}
+
+ /// Overlays the `overlay` output with the given bounds on top of the `base`
+ /// output.
+ fn overlay(
+ &mut self,
+ base: Self::Output,
+ overlay: Self::Output,
+ overlay_bounds: Rectangle,
+ ) -> Self::Output;
}
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index 19689d30..91ee9a28 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -1,7 +1,8 @@
use crate::{
- button, checkbox, column, progress_bar, radio, row, scrollable, slider,
- text, text_input, Color, Element, Font, HorizontalAlignment, Layout, Point,
- Rectangle, Renderer, Size, VerticalAlignment,
+ button, checkbox, column, container, pane_grid, progress_bar, radio, row,
+ scrollable, slider, text, text_input, Color, Element, Font,
+ HorizontalAlignment, Layout, Point, Rectangle, Renderer, Size,
+ VerticalAlignment,
};
/// A renderer that does nothing.
@@ -12,8 +13,6 @@ pub struct Null;
impl Null {
/// Creates a new [`Null`] renderer.
- ///
- /// [`Null`]: struct.Null.html
pub fn new() -> Null {
Null
}
@@ -22,6 +21,9 @@ impl Null {
impl Renderer for Null {
type Output = ();
type Defaults = ();
+
+ fn overlay(&mut self, _base: (), _overlay: (), _overlay_bounds: Rectangle) {
+ }
}
impl column::Renderer for Null {
@@ -31,6 +33,7 @@ impl column::Renderer for Null {
_content: &[Element<'_, Message, Self>],
_layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) {
}
}
@@ -42,6 +45,7 @@ impl row::Renderer for Null {
_content: &[Element<'_, Message, Self>],
_layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) {
}
}
@@ -49,7 +53,9 @@ impl row::Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
- const DEFAULT_SIZE: u16 = 20;
+ fn default_size(&self) -> u16 {
+ 20
+ }
fn measure(
&self,
@@ -83,6 +89,9 @@ impl scrollable::Renderer for Null {
_bounds: Rectangle,
_content_bounds: Rectangle,
_offset: u32,
+ _scrollbar_width: u16,
+ _scrollbar_margin: u16,
+ _scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
None
}
@@ -105,10 +114,6 @@ impl scrollable::Renderer for Null {
impl text_input::Renderer for Null {
type Style = ();
- fn default_size(&self) -> u16 {
- 20
- }
-
fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
0.0
}
@@ -195,9 +200,7 @@ impl checkbox::Renderer for Null {
impl slider::Renderer for Null {
type Style = ();
- fn height(&self) -> u32 {
- 30
- }
+ const DEFAULT_HEIGHT: u16 = 30;
fn draw(
&mut self,
@@ -225,3 +228,60 @@ impl progress_bar::Renderer for Null {
) {
}
}
+
+impl container::Renderer for Null {
+ type Style = ();
+
+ fn draw<Message>(
+ &mut self,
+ _defaults: &Self::Defaults,
+ _bounds: Rectangle,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ _style: &Self::Style,
+ _content: &Element<'_, Message, Self>,
+ _content_layout: Layout<'_>,
+ ) {
+ }
+}
+
+impl pane_grid::Renderer for Null {
+ fn draw<Message>(
+ &mut self,
+ _defaults: &Self::Defaults,
+ _content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)],
+ _dragging: Option<(pane_grid::Pane, Point)>,
+ _resizing: Option<pane_grid::Axis>,
+ _layout: Layout<'_>,
+ _cursor_position: Point,
+ ) {
+ }
+
+ fn draw_pane<Message>(
+ &mut self,
+ _defaults: &Self::Defaults,
+ _bounds: Rectangle,
+ _style: &Self::Style,
+ _title_bar: Option<(
+ &pane_grid::TitleBar<'_, Message, Self>,
+ Layout<'_>,
+ )>,
+ _body: (&Element<'_, Message, Self>, Layout<'_>),
+ _cursor_position: Point,
+ ) {
+ }
+
+ fn draw_title_bar<Message>(
+ &mut self,
+ _defaults: &Self::Defaults,
+ _bounds: Rectangle,
+ _style: &Self::Style,
+ _title: &str,
+ _title_size: u16,
+ _title_font: Self::Font,
+ _title_bounds: Rectangle,
+ _controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
+ _cursor_position: Point,
+ ) {
+ }
+}
diff --git a/native/src/runtime.rs b/native/src/runtime.rs
index 9fa031f4..5b0a6925 100644
--- a/native/src/runtime.rs
+++ b/native/src/runtime.rs
@@ -1,12 +1,18 @@
//! Run commands and subscriptions.
-use crate::{Event, Hasher};
+use crate::event::{self, Event};
+use crate::Hasher;
/// A native runtime with a generic executor and receiver of results.
///
/// It can be used by shells to easily spawn a [`Command`] or track a
/// [`Subscription`].
///
-/// [`Command`]: ../struct.Command.html
-/// [`Subscription`]: ../struct.Subscription.html
-pub type Runtime<Executor, Receiver, Message> =
- iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>;
+/// [`Command`]: crate::Command
+/// [`Subscription`]: crate::Subscription
+pub type Runtime<Executor, Receiver, Message> = iced_futures::Runtime<
+ Hasher,
+ (Event, event::Status),
+ Executor,
+ Receiver,
+ Message,
+>;
diff --git a/native/src/subscription.rs b/native/src/subscription.rs
index 0d002c6c..ff954382 100644
--- a/native/src/subscription.rs
+++ b/native/src/subscription.rs
@@ -1,5 +1,6 @@
//! Listen to external events in your application.
-use crate::{Event, Hasher};
+use crate::event::{self, Event};
+use crate::Hasher;
use iced_futures::futures::stream::BoxStream;
/// A request to listen to external events.
@@ -13,21 +14,18 @@ use iced_futures::futures::stream::BoxStream;
/// For instance, you can use a [`Subscription`] to listen to a WebSocket
/// connection, keyboard presses, mouse events, time ticks, etc.
///
-/// [`Command`]: ../struct.Command.html
-/// [`Subscription`]: struct.Subscription.html
-pub type Subscription<T> = iced_futures::Subscription<Hasher, Event, T>;
+/// [`Command`]: crate::Command
+pub type Subscription<T> =
+ iced_futures::Subscription<Hasher, (Event, event::Status), T>;
/// A stream of runtime events.
///
/// It is the input of a [`Subscription`] in the native runtime.
-///
-/// [`Subscription`]: type.Subscription.html
-pub type EventStream = BoxStream<'static, Event>;
+pub type EventStream = BoxStream<'static, (Event, event::Status)>;
/// A native [`Subscription`] tracker.
-///
-/// [`Subscription`]: type.Subscription.html
-pub type Tracker = iced_futures::subscription::Tracker<Hasher, Event>;
+pub type Tracker =
+ iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>;
pub use iced_futures::subscription::Recipe;
@@ -37,11 +35,30 @@ use events::Events;
/// Returns a [`Subscription`] to all the runtime events.
///
-/// This subscription will notify your application of any [`Event`] handled by
-/// the runtime.
-///
-/// [`Subscription`]: type.Subscription.html
-/// [`Event`]: ../enum.Event.html
+/// This subscription will notify your application of any [`Event`] that was
+/// not captured by any widget.
pub fn events() -> Subscription<Event> {
- Subscription::from_recipe(Events)
+ Subscription::from_recipe(Events {
+ f: |event, status| match status {
+ event::Status::Ignored => Some(event),
+ event::Status::Captured => None,
+ },
+ })
+}
+
+/// Returns a [`Subscription`] that filters all the runtime events with the
+/// provided function, producing messages accordingly.
+///
+/// This subscription will call the provided function for every [`Event`]
+/// handled by the runtime. If the function:
+///
+/// - Returns `None`, the [`Event`] will be discarded.
+/// - Returns `Some` message, the `Message` will be produced.
+pub fn events_with<Message>(
+ f: fn(Event, event::Status) -> Option<Message>,
+) -> Subscription<Message>
+where
+ Message: 'static + Send,
+{
+ Subscription::from_recipe(Events { f })
}
diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs
index ceae467d..f689f3af 100644
--- a/native/src/subscription/events.rs
+++ b/native/src/subscription/events.rs
@@ -1,18 +1,26 @@
-use crate::{
- subscription::{EventStream, Recipe},
- Event, Hasher,
-};
+use crate::event::{self, Event};
+use crate::subscription::{EventStream, Recipe};
+use crate::Hasher;
+use iced_futures::futures::future;
+use iced_futures::futures::StreamExt;
use iced_futures::BoxStream;
-pub struct Events;
+pub struct Events<Message> {
+ pub(super) f: fn(Event, event::Status) -> Option<Message>,
+}
-impl Recipe<Hasher, Event> for Events {
- type Output = Event;
+impl<Message> Recipe<Hasher, (Event, event::Status)> for Events<Message>
+where
+ Message: 'static + Send,
+{
+ type Output = Message;
fn hash(&self, state: &mut Hasher) {
use std::hash::Hash;
- std::any::TypeId::of::<Self>().hash(state);
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+ self.f.hash(state);
}
fn stream(
@@ -20,5 +28,9 @@ impl Recipe<Hasher, Event> for Events {
event_stream: EventStream,
) -> BoxStream<Self::Output> {
event_stream
+ .filter_map(move |(event, status)| {
+ future::ready((self.f)(event, status))
+ })
+ .boxed()
}
}
diff --git a/native/src/touch.rs b/native/src/touch.rs
new file mode 100644
index 00000000..18120644
--- /dev/null
+++ b/native/src/touch.rs
@@ -0,0 +1,23 @@
+//! Build touch events.
+use crate::Point;
+
+/// A touch interaction.
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[allow(missing_docs)]
+pub enum Event {
+ /// A touch interaction was started.
+ FingerPressed { id: Finger, position: Point },
+
+ /// An on-going touch interaction was moved.
+ FingerMoved { id: Finger, position: Point },
+
+ /// A touch interaction was ended.
+ FingerLifted { id: Finger, position: Point },
+
+ /// A touch interaction was canceled.
+ FingerLost { id: Finger, position: Point },
+}
+
+/// A unique identifier representing a finger on a touch interaction.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Finger(pub u64);
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 48cd6111..7a64ac59 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,4 +1,7 @@
-use crate::{layout, mouse, Clipboard, Element, Event, Layout, Point, Size};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
+use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher;
@@ -9,21 +12,17 @@ use std::hash::Hasher;
/// Iced tries to avoid dictating how to write your event loop. You are in
/// charge of using this type in your system in any way you want.
///
-/// [`Layout`]: struct.Layout.html
-///
/// # Example
/// The [`integration` example] uses a [`UserInterface`] to integrate Iced in
/// an existing graphical application.
///
-/// [`integration` example]: https://github.com/hecrj/iced/tree/0.1/examples/integration
-/// [`UserInterface`]: struct.UserInterface.html
+/// [`integration` example]: https://github.com/hecrj/iced/tree/0.2/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
- hash: u64,
root: Element<'a, Message, Renderer>,
- layout: layout::Node,
+ base: Layer,
+ overlay: Option<Layer>,
bounds: Size,
- cursor_position: Point,
}
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
@@ -35,10 +34,6 @@ where
/// It is able to avoid expensive computations when using a [`Cache`]
/// obtained from a previous instance of a [`UserInterface`].
///
- /// [`Element`]: struct.Element.html
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
- ///
/// # Example
/// Imagine we want to build a [`UserInterface`] for
/// [the counter example that we previously wrote](index.html#usage). Here
@@ -95,25 +90,37 @@ where
) -> Self {
let root = root.into();
- let hash = {
- let hasher = &mut crate::Hasher::default();
- root.hash_layout(hasher);
+ let (base, overlay) = {
+ let hash = {
+ let hasher = &mut crate::Hasher::default();
+ root.hash_layout(hasher);
- hasher.finish()
- };
+ hasher.finish()
+ };
- let layout = if hash == cache.hash && bounds == cache.bounds {
- cache.layout
- } else {
- renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds))
+ let layout_is_cached =
+ hash == cache.base.hash && bounds == cache.bounds;
+
+ let (layout, overlay) = if layout_is_cached {
+ (cache.base.layout, cache.overlay)
+ } else {
+ (
+ renderer.layout(
+ &root,
+ &layout::Limits::new(Size::ZERO, bounds),
+ ),
+ None,
+ )
+ };
+
+ (Layer { layout, hash }, overlay)
};
UserInterface {
- hash,
root,
- layout,
+ base,
+ overlay,
bounds,
- cursor_position: cache.cursor_position,
}
}
@@ -122,15 +129,12 @@ where
/// It returns __messages__ that may have been produced as a result of user
/// interactions. You should feed these to your __update logic__.
///
- /// [`UserInterface`]: struct.UserInterface.html
- /// [`Event`]: enum.Event.html
- ///
/// # Example
/// Let's allow our [counter](index.html#usage) to change state by
/// 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 {
@@ -152,12 +156,14 @@ 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();
+ /// let mut messages = Vec::new();
///
/// loop {
- /// // Process system events...
+ /// // Obtain system events...
///
/// let mut user_interface = UserInterface::build(
/// counter.view(),
@@ -167,57 +173,103 @@ where
/// );
///
/// // Update the user interface
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// let event_statuses = user_interface.update(
+ /// &events,
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// &mut messages
+ /// );
///
/// cache = user_interface.into_cache();
///
/// // Process the produced messages
- /// for message in messages {
+ /// for message in messages.drain(..) {
/// counter.update(message);
/// }
/// }
/// ```
pub fn update(
&mut self,
- events: impl IntoIterator<Item = Event>,
+ events: &[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,
- &mut messages,
+ messages: &mut Vec<Message>,
+ ) -> Vec<event::Status> {
+ let (base_cursor, overlay_statuses) = if let Some(mut overlay) =
+ self.root.overlay(Layout::new(&self.base.layout))
+ {
+ let layer = Self::overlay_layer(
+ self.overlay.take(),
+ self.bounds,
+ &mut overlay,
renderer,
- clipboard,
);
- }
- messages
+ let event_statuses = events
+ .iter()
+ .cloned()
+ .map(|event| {
+ overlay.on_event(
+ event,
+ Layout::new(&layer.layout),
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ )
+ })
+ .collect();
+
+ let base_cursor = if layer.layout.bounds().contains(cursor_position)
+ {
+ // TODO: Type-safe cursor availability
+ Point::new(-1.0, -1.0)
+ } else {
+ cursor_position
+ };
+
+ self.overlay = Some(layer);
+
+ (base_cursor, event_statuses)
+ } else {
+ (cursor_position, vec![event::Status::Ignored; events.len()])
+ };
+
+ events
+ .iter()
+ .cloned()
+ .zip(overlay_statuses.into_iter())
+ .map(|(event, overlay_status)| {
+ let event_status = self.root.widget.on_event(
+ event,
+ Layout::new(&self.base.layout),
+ base_cursor,
+ messages,
+ renderer,
+ clipboard,
+ );
+
+ event_status.merge(overlay_status)
+ })
+ .collect()
}
/// Draws the [`UserInterface`] with the provided [`Renderer`].
///
- /// It returns the current state of the [`MouseCursor`]. You should update
- /// the icon of the mouse cursor accordingly in your system.
+ /// It returns the some [`Renderer::Output`]. You should update the icon of
+ /// the mouse cursor accordingly in your system.
///
- /// [`UserInterface`]: struct.UserInterface.html
- /// [`Renderer`]: trait.Renderer.html
- /// [`MouseCursor`]: enum.MouseCursor.html
+ /// [`Renderer`]: crate::Renderer
+ /// [`Renderer::Output`]: crate::Renderer::Output
///
/// # Example
/// We can finally draw our [counter](index.html#usage) by
/// [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 {
@@ -239,10 +291,12 @@ 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();
+ /// let mut messages = Vec::new();
///
/// loop {
- /// // Process system events...
+ /// // Obtain system events...
///
/// let mut user_interface = UserInterface::build(
/// counter.view(),
@@ -251,14 +305,21 @@ where
/// &mut renderer,
/// );
///
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// // Update the user interface
+ /// let event_statuses = user_interface.update(
+ /// &events,
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// &mut messages
+ /// );
///
/// // 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();
///
- /// for message in messages {
+ /// for message in messages.drain(..) {
/// counter.update(message);
/// }
///
@@ -266,39 +327,132 @@ where
/// // Flush rendering operations...
/// }
/// ```
- pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {
- self.root.widget.draw(
+ pub fn draw(
+ &mut self,
+ renderer: &mut Renderer,
+ cursor_position: Point,
+ ) -> Renderer::Output {
+ let viewport = Rectangle::with_size(self.bounds);
+
+ let overlay = if let Some(mut overlay) =
+ self.root.overlay(Layout::new(&self.base.layout))
+ {
+ let layer = Self::overlay_layer(
+ self.overlay.take(),
+ self.bounds,
+ &mut overlay,
+ renderer,
+ );
+
+ let overlay_bounds = layer.layout.bounds();
+
+ let overlay_primitives = overlay.draw(
+ renderer,
+ &Renderer::Defaults::default(),
+ Layout::new(&layer.layout),
+ cursor_position,
+ );
+
+ self.overlay = Some(layer);
+
+ Some((overlay_primitives, overlay_bounds))
+ } else {
+ None
+ };
+
+ if let Some((overlay_primitives, overlay_bounds)) = overlay {
+ let base_cursor = if overlay_bounds.contains(cursor_position) {
+ Point::new(-1.0, -1.0)
+ } else {
+ cursor_position
+ };
+
+ let base_primitives = self.root.widget.draw(
+ renderer,
+ &Renderer::Defaults::default(),
+ Layout::new(&self.base.layout),
+ base_cursor,
+ &viewport,
+ );
+
+ renderer.overlay(
+ base_primitives,
+ overlay_primitives,
+ overlay_bounds,
+ )
+ } else {
+ self.root.widget.draw(
+ renderer,
+ &Renderer::Defaults::default(),
+ Layout::new(&self.base.layout),
+ cursor_position,
+ &viewport,
+ )
+ }
+ }
+
+ /// Relayouts and returns a new [`UserInterface`] using the provided
+ /// bounds.
+ pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
+ Self::build(
+ self.root,
+ bounds,
+ Cache {
+ base: self.base,
+ overlay: self.overlay,
+ bounds: self.bounds,
+ },
renderer,
- &Renderer::Defaults::default(),
- Layout::new(&self.layout),
- self.cursor_position,
)
}
/// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
/// process.
- ///
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
pub fn into_cache(self) -> Cache {
Cache {
- hash: self.hash,
- layout: self.layout,
+ base: self.base,
+ overlay: self.overlay,
bounds: self.bounds,
- cursor_position: self.cursor_position,
+ }
+ }
+
+ fn overlay_layer(
+ cache: Option<Layer>,
+ bounds: Size,
+ overlay: &mut overlay::Element<'_, Message, Renderer>,
+ renderer: &Renderer,
+ ) -> Layer {
+ let new_hash = {
+ let hasher = &mut crate::Hasher::default();
+ overlay.hash_layout(hasher);
+
+ hasher.finish()
+ };
+
+ let layout = match cache {
+ Some(Layer { hash, layout }) if new_hash == hash => layout,
+ _ => overlay.layout(renderer, bounds),
+ };
+
+ Layer {
+ layout,
+ hash: new_hash,
}
}
}
+#[derive(Debug, Clone)]
+struct Layer {
+ layout: layout::Node,
+ hash: u64,
+}
+
/// Reusable data of a specific [`UserInterface`].
-///
-/// [`UserInterface`]: struct.UserInterface.html
#[derive(Debug, Clone)]
pub struct Cache {
- hash: u64,
- layout: layout::Node,
+ base: Layer,
+ overlay: Option<Layer>,
bounds: Size,
- cursor_position: Point,
}
impl Cache {
@@ -306,15 +460,14 @@ impl Cache {
///
/// You should use this to initialize a [`Cache`] before building your first
/// [`UserInterface`].
- ///
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
pub fn new() -> Cache {
Cache {
- hash: 0,
- layout: layout::Node::new(Size::new(0.0, 0.0)),
+ base: Layer {
+ layout: layout::Node::new(Size::new(0.0, 0.0)),
+ hash: 0,
+ },
+ overlay: None,
bounds: Size::ZERO,
- cursor_position: Point::new(-1.0, -1.0),
}
}
}
@@ -324,11 +477,3 @@ impl Default for Cache {
Cache::new()
}
}
-
-impl PartialEq for Cache {
- fn eq(&self, other: &Cache) -> bool {
- self.hash == other.hash && self.cursor_position == other.cursor_position
- }
-}
-
-impl Eq for Cache {}
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 4453145b..3677713a 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -18,17 +18,18 @@
//! use iced_native::{button, Button, Widget};
//! ```
//!
-//! [`Widget`]: trait.Widget.html
-//! [renderer]: ../renderer/index.html
+//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
pub mod image;
pub mod pane_grid;
+pub mod pick_list;
pub mod progress_bar;
pub mod radio;
pub mod row;
+pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod space;
@@ -49,12 +50,16 @@ pub use image::Image;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
+pub use pick_list::PickList;
+#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
#[doc(no_inline)]
pub use row::Row;
#[doc(no_inline)]
+pub use rule::Rule;
+#[doc(no_inline)]
pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;
@@ -67,16 +72,16 @@ pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
-use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Point};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
+use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// A component that displays information and allows interaction.
///
/// If you want to build your own widgets, you will need to implement this
/// trait.
///
-/// [`Widget`]: trait.Widget.html
-/// [`Element`]: ../struct.Element.html
-///
/// # Examples
/// The repository has some [examples] showcasing how to implement a custom
/// widget:
@@ -88,24 +93,20 @@ use crate::{layout, Clipboard, Event, Hasher, Layout, Length, Point};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
-/// [examples]: https://github.com/hecrj/iced/tree/0.1/examples
-/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.1/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.1/examples/custom_widget
-/// [`geometry`]: https://github.com/hecrj/iced/tree/0.1/examples/geometry
+/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples
+/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool
+/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget
+/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
-/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu
+/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.2/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Returns the width of the [`Widget`].
- ///
- /// [`Widget`]: trait.Widget.html
fn width(&self) -> Length;
/// Returns the height of the [`Widget`].
- ///
- /// [`Widget`]: trait.Widget.html
fn height(&self) -> Length;
/// Returns the [`Node`] of the [`Widget`].
@@ -113,9 +114,7 @@ where
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
///
- /// [`Node`]: ../layout/struct.Node.html
- /// [`Widget`]: trait.Widget.html
- /// [`Layout`]: ../layout/struct.Layout.html
+ /// [`Node`]: layout::Node
fn layout(
&self,
renderer: &Renderer,
@@ -123,14 +122,13 @@ where
) -> layout::Node;
/// Draws the [`Widget`] using the associated `Renderer`.
- ///
- /// [`Widget`]: trait.Widget.html
fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output;
/// Computes the _layout_ hash of the [`Widget`].
@@ -143,9 +141,7 @@ where
/// For example, the [`Text`] widget does not hash its color property, as
/// its value cannot affect the overall [`Layout`] of the user interface.
///
- /// [`Widget`]: trait.Widget.html
- /// [`Layout`]: ../layout/struct.Layout.html
- /// [`Text`]: text/struct.Text.html
+ /// [`Text`]: crate::widget::Text
fn hash_layout(&self, state: &mut Hasher);
/// Processes a runtime [`Event`].
@@ -160,11 +156,6 @@ where
/// * a [`Clipboard`], if available
///
/// By default, it does nothing.
- ///
- /// [`Event`]: ../enum.Event.html
- /// [`Widget`]: trait.Widget.html
- /// [`Layout`]: ../layout/struct.Layout.html
- /// [`Clipboard`]: ../trait.Clipboard.html
fn on_event(
&mut self,
_event: Event,
@@ -173,6 +164,15 @@ where
_messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
+ event::Status::Ignored
+ }
+
+ /// Returns the overlay of the [`Widget`], if there is any.
+ fn overlay(
+ &mut self,
+ _layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ None
}
}
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index c932da2b..8e2450de 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -1,12 +1,12 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: struct.Button.html
-//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::touch;
use crate::{
- layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
};
use std::hash::Hash;
@@ -18,6 +18,7 @@ use std::hash::Hash;
/// # type Button<'a, Message> =
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
/// #
+/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
@@ -41,13 +42,11 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
+ Message: Clone,
Renderer: self::Renderer,
{
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
- ///
- /// [`Button`]: struct.Button.html
- /// [`State`]: struct.State.html
pub fn new<E>(state: &'a mut State, content: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -66,56 +65,42 @@ where
}
/// Sets the width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the minimum width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_width(mut self, min_width: u32) -> Self {
self.min_width = min_width;
self
}
/// Sets the minimum height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_height(mut self, min_height: u32) -> Self {
self.min_height = min_height;
self
}
/// Sets the padding of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn padding(mut self, padding: u16) -> Self {
self.padding = padding;
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
- ///
- /// [`Button`]: struct.Button.html
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
}
/// Sets the style of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -123,8 +108,6 @@ where
}
/// The local state of a [`Button`].
-///
-/// [`Button`]: struct.Button.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_pressed: bool,
@@ -132,8 +115,6 @@ pub struct State {
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
@@ -142,8 +123,8 @@ impl State {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -182,31 +163,43 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if self.on_press.is_some() {
let bounds = layout.bounds();
- self.state.is_pressed = bounds.contains(cursor_position);
+ if bounds.contains(cursor_position) {
+ self.state.is_pressed = true;
+
+ return event::Status::Captured;
+ }
}
}
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) = self.on_press.clone() {
let bounds = layout.bounds();
- let is_clicked = self.state.is_pressed
- && bounds.contains(cursor_position);
+ if self.state.is_pressed {
+ self.state.is_pressed = false;
- self.state.is_pressed = false;
+ if bounds.contains(cursor_position) {
+ messages.push(on_press);
+ }
- if is_clicked {
- messages.push(on_press);
+ return event::Status::Captured;
}
}
}
+ Event::Touch(touch::Event::FingerLost { .. }) => {
+ self.state.is_pressed = false;
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -215,6 +208,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
@@ -242,20 +236,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Button`] in your user interface.
///
-/// [`Button`]: struct.Button.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// The default padding of a [`Button`].
- ///
- /// [`Button`]: struct.Button.html
const DEFAULT_PADDING: u16;
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`Button`].
- ///
- /// [`Button`]: struct.Button.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
@@ -272,8 +261,8 @@ pub trait Renderer: crate::Renderer + Sized {
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
button: Button<'a, Message, Renderer>,
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 5fb13290..77a82fad 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -1,10 +1,15 @@
//! Show toggle controls using checkboxes.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::row;
+use crate::text;
+use crate::touch;
use crate::{
- layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
- HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
- VerticalAlignment, Widget,
+ Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
+ Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
/// A box that can be checked.
@@ -32,7 +37,8 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
width: Length,
size: u16,
spacing: u16,
- text_size: u16,
+ text_size: Option<u16>,
+ font: Renderer::Font,
style: Renderer::Style,
}
@@ -47,8 +53,6 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// * a function that will be called when the [`Checkbox`] is toggled. It
/// will receive the new state of the [`Checkbox`] and must produce a
/// `Message`.
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self
where
F: 'static + Fn(bool) -> Message,
@@ -60,46 +64,45 @@ 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,
+ font: Renderer::Font::default(),
style: Renderer::Style::default(),
}
}
/// Sets the size of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn size(mut self, size: u16) -> Self {
self.size = size;
self
}
/// Sets the width of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the spacing between the [`Checkbox`] and the text.
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
/// Sets the text size of the [`Checkbox`].
- ///
- /// [`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
}
- /// Sets the style of the [`Checkbox`].
+ /// Sets the [`Font`] of the text of the [`Checkbox`].
///
- /// [`Checkbox`]: struct.Checkbox.html
+ /// [`Font`]: crate::widget::text::Renderer::Font
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
+ /// Sets the style of the [`Checkbox`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -135,8 +138,9 @@ where
)
.push(
Text::new(&self.label)
+ .font(self.font)
.width(self.width)
- .size(self.text_size),
+ .size(self.text_size.unwrap_or(renderer.default_size())),
)
.layout(renderer, limits)
}
@@ -149,17 +153,22 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
messages.push((self.on_toggle)(!self.is_checked));
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -168,6 +177,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let mut children = layout.children();
@@ -181,8 +191,8 @@ where
defaults,
label_layout.bounds(),
&self.label,
- self.text_size,
- Default::default(),
+ self.text_size.unwrap_or(renderer.default_size()),
+ self.font,
None,
HorizontalAlignment::Left,
VerticalAlignment::Center,
@@ -213,20 +223,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Checkbox`] in your user interface.
///
-/// [`Checkbox`]: struct.Checkbox.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::Renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default size of a [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
const DEFAULT_SIZE: u16;
/// The default spacing of a [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
const DEFAULT_SPACING: u16;
/// Draws a [`Checkbox`].
@@ -236,8 +241,6 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Checkbox`] is selected or not
/// * whether the mouse is over the [`Checkbox`] or not
/// * the drawn label of the [`Checkbox`]
- ///
- /// [`Checkbox`]: struct.Checkbox.html
fn draw(
&mut self,
bounds: Rectangle,
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 259a7e6e..e0e88d31 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -1,18 +1,16 @@
//! Distribute content vertically.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
};
use std::u32;
/// A container that distributes its contents vertically.
-///
-/// A [`Column`] will try to fill the horizontal space of its container.
-///
-/// [`Column`]: struct.Column.html
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message, Renderer> {
spacing: u16,
@@ -27,15 +25,11 @@ pub struct Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Creates an empty [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
- ///
- /// [`Column`]: struct.Column.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
@@ -62,56 +56,42 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
}
/// Sets the padding of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Column`] in pixels.
- ///
- /// [`Column`]: struct.Column.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
- ///
- /// [`Column`]: struct.Column.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -164,9 +144,11 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
- self.children.iter_mut().zip(layout.children()).for_each(
- |(child, layout)| {
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
@@ -175,8 +157,8 @@ where
renderer,
clipboard,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
@@ -185,8 +167,15 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
- renderer.draw(defaults, &self.children, layout, cursor_position)
+ renderer.draw(
+ defaults,
+ &self.children,
+ layout,
+ cursor_position,
+ viewport,
+ )
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -199,11 +188,23 @@ where
self.max_height.hash(state);
self.align_items.hash(state);
self.spacing.hash(state);
+ self.padding.hash(state);
for child in &self.children {
child.widget.hash_layout(state);
}
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .filter_map(|(child, layout)| child.widget.overlay(layout))
+ .next()
+ }
}
/// The renderer of a [`Column`].
@@ -211,8 +212,7 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Column`] in your user interface.
///
-/// [`Column`]: struct.Column.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Column`].
///
@@ -220,15 +220,13 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the children of the [`Column`]
/// - the [`Layout`] of the [`Column`] and its children
/// - the cursor position
- ///
- /// [`Column`]: struct.Column.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
}
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 2590fe3b..65764148 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -1,9 +1,11 @@
//! Decorate content and apply alignment.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
};
use std::u32;
@@ -29,8 +31,6 @@ where
Renderer: self::Renderer,
{
/// Creates an empty [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn new<T>(content: T) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
@@ -49,80 +49,60 @@ where
}
/// Sets the padding of the [`Container`].
- ///
- /// [`Container`]: struct.Column.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Container`] in pixels.
- ///
- /// [`Container`]: struct.Container.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn align_x(mut self, alignment: Align) -> Self {
self.horizontal_alignment = alignment;
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn align_y(mut self, alignment: Align) -> Self {
self.vertical_alignment = alignment;
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_x(mut self) -> Self {
self.horizontal_alignment = Align::Center;
self
}
/// Centers the contents in the vertical axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_y(mut self) -> Self {
self.vertical_alignment = Align::Center;
self
}
/// Sets the style of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -174,7 +154,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.content.widget.on_event(
event,
layout.children().next().unwrap(),
@@ -191,11 +171,13 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
layout.bounds(),
cursor_position,
+ viewport,
&self.style,
&self.content,
layout.children().next().unwrap(),
@@ -214,6 +196,13 @@ where
self.content.hash_layout(state);
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.content.overlay(layout.children().next().unwrap())
+ }
}
/// The renderer of a [`Container`].
@@ -221,20 +210,18 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Container`] in your user interface.
///
-/// [`Container`]: struct.Container.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`Container`].
- ///
- /// [`Container`]: struct.Container.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
cursor_position: Point,
+ viewport: &Rectangle,
style: &Self::Style,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 49905830..4d8e0a3f 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -2,7 +2,8 @@
pub mod viewer;
pub use viewer::Viewer;
-use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
+use crate::layout;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
@@ -30,8 +31,6 @@ pub struct Image {
impl Image {
/// Creates a new [`Image`] with the given path.
- ///
- /// [`Image`]: struct.Image.html
pub fn new<T: Into<Handle>>(handle: T) -> Self {
Image {
handle: handle.into(),
@@ -41,16 +40,12 @@ impl Image {
}
/// Sets the width of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -100,6 +95,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
}
@@ -115,8 +111,6 @@ where
}
/// An [`Image`] handle.
-///
-/// [`Image`]: struct.Image.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
@@ -127,8 +121,6 @@ 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()))
}
@@ -138,8 +130,6 @@ impl Handle {
/// pixels.
///
/// This is useful if you have already decoded your image.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
Self::from_data(Data::Pixels {
width,
@@ -154,8 +144,6 @@ impl Handle {
///
/// This is useful if you already have your image loaded in-memory, maybe
/// because you downloaded or generated it procedurally.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_memory(bytes: Vec<u8>) -> Handle {
Self::from_data(Data::Bytes(bytes))
}
@@ -171,15 +159,11 @@ impl Handle {
}
/// Returns the unique identifier of the [`Handle`].
- ///
- /// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the image [`Data`].
- ///
- /// [`Data`]: enum.Data.html
pub fn data(&self) -> &Data {
&self.data
}
@@ -201,8 +185,6 @@ impl Hash for Handle {
}
/// The data of an [`Image`].
-///
-/// [`Image`]: struct.Image.html
#[derive(Clone, Hash)]
pub enum Data {
/// File data
@@ -239,17 +221,12 @@ impl std::fmt::Debug for Data {
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Image`] in your user interface.
///
-/// [`Image`]: struct.Image.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an [`Image`] located on the given path.
- ///
- /// [`Image`]: struct.Image.html
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Image`].
- ///
- /// [`Image`]: struct.Image.html
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index b129924b..9544beab 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -1,7 +1,11 @@
//! Zoom and pan on an image.
+use crate::event::{self, Event};
+use crate::image;
+use crate::layout;
+use crate::mouse;
use crate::{
- image, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Rectangle, Size, Vector, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
};
use std::{f32, hash::Hash, u32};
@@ -216,7 +220,7 @@ where
_messages: &mut Vec<Message>,
renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
@@ -290,11 +294,16 @@ where
self.state.starting_cursor_pos = None
}
}
- Event::Mouse(mouse::Event::CursorMoved { x, y }) => {
+ Event::Mouse(mouse::Event::CursorMoved { position }) => {
if self.state.is_cursor_clicked() {
let image_bounds = self.image_bounds(renderer, bounds);
- self.state.pan(x, y, bounds, image_bounds);
+ self.state.pan(
+ position.x,
+ position.y,
+ bounds,
+ image_bounds,
+ );
}
}
_ => {}
@@ -305,6 +314,8 @@ where
self.state.starting_cursor_pos = None;
}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -313,6 +324,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 076ae76f..ff19cbc2 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -6,33 +6,43 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-//! [`PaneGrid`]: struct.PaneGrid.html
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
mod axis;
+mod configuration;
mod content;
mod direction;
mod node;
mod pane;
mod split;
mod state;
+mod title_bar;
pub use axis::Axis;
+pub use configuration::Configuration;
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};
-
+pub use state::State;
+pub use title_bar::TitleBar;
+
+use crate::container;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::row;
+use crate::text;
use crate::{
- keyboard, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
};
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
-/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
+/// [![Pane grid - Iced](https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif)](https://gfycat.com/frailfreshairedaleterrier)
///
/// This distribution of space is common in tiling window managers (like
/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even
@@ -69,134 +79,89 @@ use crate::{
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
-/// PaneGrid::new(&mut state, |pane, state, focus| {
-/// match state {
+/// PaneGrid::new(&mut state, |pane, state| {
+/// pane_grid::Content::new(match state {
/// PaneState::SomePane => Text::new("This is some pane"),
/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
-/// }.into()
+/// })
/// })
/// .on_drag(Message::PaneDragged)
-/// .on_resize(Message::PaneResized);
+/// .on_resize(10, Message::PaneResized);
/// ```
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [`State`]: struct.State.html
#[allow(missing_debug_implementations)]
-pub struct PaneGrid<'a, Message, Renderer> {
+pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
state: &'a mut state::Internal,
- pressed_modifiers: &'a mut keyboard::ModifiersState,
- elements: Vec<(Pane, Element<'a, Message, Renderer>)>,
+ elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
width: Length,
height: Length,
spacing: u16,
- modifier_keys: keyboard::ModifiersState,
+ on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
- on_resize: Option<Box<dyn Fn(ResizeEvent) -> Message + 'a>>,
- on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>,
+ on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
}
-impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
+impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
/// The view function will be called to display each [`Pane`] present in the
/// [`State`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`State`]: struct.State.html
- /// [`Pane`]: struct.Pane.html
pub fn new<T>(
state: &'a mut State<T>,
- view: impl Fn(
- Pane,
- &'a mut T,
- Option<Focus>,
- ) -> Element<'a, Message, Renderer>,
+ view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>,
) -> Self {
let elements = {
- let action = state.internal.action();
- let current_focus = action.focus();
-
state
.panes
.iter_mut()
- .map(move |(pane, pane_state)| {
- let focus = match current_focus {
- Some((focused_pane, focus))
- if *pane == focused_pane =>
- {
- Some(focus)
- }
- _ => None,
- };
-
- (*pane, view(*pane, pane_state, focus))
- })
+ .map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
.collect()
};
Self {
state: &mut state.internal,
- pressed_modifiers: &mut state.modifiers,
elements,
width: Length::Fill,
height: Length::Fill,
spacing: 0,
- modifier_keys: keyboard::ModifiersState {
- control: true,
- ..Default::default()
- },
+ on_click: None,
on_drag: None,
on_resize: None,
- on_key_press: None,
}
}
/// Sets the width of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the spacing _between_ the panes of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
- /// Sets the modifier keys of the [`PaneGrid`].
- ///
- /// The modifier keys will need to be pressed to trigger dragging, resizing,
- /// and key events.
- ///
- /// The default modifier key is `Ctrl`.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- pub fn modifier_keys(
- mut self,
- modifier_keys: keyboard::ModifiersState,
- ) -> Self {
- self.modifier_keys = modifier_keys;
+ /// Sets the message that will be produced when a [`Pane`] of the
+ /// [`PaneGrid`] is clicked.
+ pub fn on_click<F>(mut self, f: F) -> Self
+ where
+ F: 'a + Fn(Pane) -> Message,
+ {
+ self.on_click = Some(Box::new(f));
self
}
/// Enables the drag and drop interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
- ///
- /// Panes can be dragged using `Modifier keys + Left click`.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_drag<F>(mut self, f: F) -> Self
where
F: 'a + Fn(DragEvent) -> Message,
@@ -208,40 +173,54 @@ 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.
///
- /// [`PaneGrid`]: struct.PaneGrid.html
- pub fn on_resize<F>(mut self, f: F) -> Self
+ /// 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.
+ 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
}
+}
- /// Captures hotkey interactions with the [`PaneGrid`], using the provided
- /// function to produce messages.
- ///
- /// The function will be called when:
- /// - a [`Pane`] is focused
- /// - a key is pressed
- /// - all the modifier keys are pressed
- ///
- /// If the function returns `None`, the key press event will be discarded
- /// without producing any message.
- ///
- /// This method is particularly useful to implement hotkey interactions.
- /// For instance, you can use it to enable splitting, swapping, or resizing
- /// panes by pressing combinations of keys.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`Pane`]: struct.Pane.html
- pub fn on_key_press<F>(mut self, f: F) -> Self
- where
- F: 'a + Fn(KeyPressEvent) -> Option<Message>,
- {
- self.on_key_press = Some(Box::new(f));
- self
+impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ 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, content), layout)) = clicked_region.next() {
+ if let Some(on_click) = &self.on_click {
+ messages.push(on_click(*pane));
+ }
+
+ if let Some(on_drag) = &self.on_drag {
+ if content.can_be_picked_at(layout, cursor_position) {
+ let pane_position = layout.position();
+
+ let origin = cursor_position
+ - Vector::new(pane_position.x, pane_position.y);
+
+ self.state.pick_pane(pane, origin);
+
+ messages.push(on_drag(DragEvent::Picked { pane: *pane }));
+ }
+ }
+ }
}
fn trigger_resize(
@@ -249,12 +228,12 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
- ) {
- if let Some(on_resize) = &self.on_resize {
+ ) -> event::Status {
+ if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
- let splits = self.state.splits(
+ let splits = self.state.split_regions(
f32::from(self.spacing),
Size::new(bounds.width, bounds.height),
);
@@ -276,89 +255,59 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
};
messages.push(on_resize(ResizeEvent { split, ratio }));
+
+ return event::Status::Captured;
}
}
}
+
+ event::Status::Ignored
}
}
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub enum DragEvent {
/// A [`Pane`] was picked for dragging.
- ///
- /// [`Pane`]: struct.Pane.html
Picked {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
},
/// A [`Pane`] was dropped on top of another [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
Dropped {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
/// The [`Pane`] where the picked one was dropped on.
- ///
- /// [`Pane`]: struct.Pane.html
target: Pane,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
/// boundaries.
- ///
- /// [`Pane`]: struct.Pane.html
Canceled {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
},
}
/// An event produced during a resize interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
/// The [`Split`] that is being dragged for resizing.
- ///
- /// [`Split`]: struct.Split.html
pub split: Split,
/// The new ratio of the [`Split`].
///
/// The ratio is a value in [0, 1], representing the exact position of a
/// [`Split`] between two panes.
- ///
- /// [`Split`]: struct.Split.html
pub ratio: f32,
}
-/// An event produced during a key press interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
-#[derive(Debug, Clone, Copy)]
-pub struct KeyPressEvent {
- /// The key that was pressed.
- pub key_code: keyboard::KeyCode,
-
- /// The state of the modifier keys when the key was pressed.
- pub modifiers: keyboard::ModifiersState,
-}
-
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
- Renderer: self::Renderer,
+ Renderer: self::Renderer + container::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -376,7 +325,7 @@ where
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
- let regions = self.state.regions(f32::from(self.spacing), size);
+ let regions = self.state.pane_regions(f32::from(self.spacing), size);
let children = self
.elements
@@ -405,42 +354,57 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
+ let mut event_status = event::Status::Ignored;
+
match event {
Event::Mouse(mouse_event) => match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => {
- 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,
- }));
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ event_status = event::Status::Captured;
+
+ 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.split_regions(
+ 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();
}
}
mouse::Event::ButtonReleased(mouse::Button::Left) => {
- if let Some(pane) = self.state.picked_pane() {
- self.state.focus(&pane);
-
+ if let Some((pane, _)) = self.state.picked_pane() {
if let Some(on_drag) = &self.on_drag {
let mut dropped_region = self
.elements
@@ -462,131 +426,42 @@ where
messages.push(on_drag(event));
}
- }
- }
- mouse::Event::ButtonPressed(mouse::Button::Right)
- 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,
- );
- }
+ self.state.idle();
+
+ event_status = event::Status::Captured;
+ } else if self.state.picked_split().is_some() {
+ self.state.idle();
+
+ event_status = event::Status::Captured;
}
}
- mouse::Event::ButtonReleased(mouse::Button::Right)
- if self.state.picked_split().is_some() =>
- {
- self.state.drop_split();
- }
mouse::Event::CursorMoved { .. } => {
- self.trigger_resize(layout, cursor_position, messages);
+ event_status =
+ self.trigger_resize(layout, cursor_position, messages);
}
_ => {}
},
- 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;
- }
- _ => {}
- }
- }
_ => {}
}
if self.state.picked_pane().is_none() {
- {
- self.elements.iter_mut().zip(layout.children()).for_each(
- |((_, pane), layout)| {
- pane.widget.on_event(
- event.clone(),
- layout,
- cursor_position,
- messages,
- renderer,
- clipboard,
- )
- },
- );
- }
+ self.elements
+ .iter_mut()
+ .zip(layout.children())
+ .map(|((_, pane), layout)| {
+ pane.on_event(
+ event.clone(),
+ layout,
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ )
+ })
+ .fold(event_status, event::Status::merge)
+ } else {
+ event::Status::Captured
}
}
@@ -596,12 +471,40 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
- renderer.draw(
+ 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
+ .split_regions(f32::from(self.spacing), bounds.size());
+
+ hovered_split(
+ splits.iter(),
+ f32::from(self.spacing + leeway),
+ relative_cursor,
+ )
+ }
+ None => None,
+ })
+ .map(|(_, axis)| axis);
+
+ self::Renderer::draw(
+ renderer,
defaults,
&self.elements,
self.state.picked_pane(),
- self.state.picked_split().map(|(_, axis)| axis),
+ picked_split,
layout,
cursor_position,
)
@@ -609,6 +512,7 @@ where
fn hash_layout(&self, state: &mut Hasher) {
use std::hash::Hash;
+
struct Marker;
std::any::TypeId::of::<Marker>().hash(state);
@@ -620,6 +524,17 @@ where
element.hash_layout(state);
}
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.elements
+ .iter_mut()
+ .zip(layout.children())
+ .filter_map(|((_, pane), layout)| pane.overlay(layout))
+ .next()
+ }
}
/// The renderer of a [`PaneGrid`].
@@ -627,9 +542,10 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PaneGrid`] in your user interface.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer: crate::Renderer + Sized {
+/// [renderer]: crate::renderer
+pub trait Renderer:
+ crate::Renderer + container::Renderer + text::Renderer + Sized
+{
/// Draws a [`PaneGrid`].
///
/// It receives:
@@ -638,25 +554,59 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the [`Axis`] that is currently being resized
/// - the [`Layout`] of the [`PaneGrid`] and its elements
/// - the cursor position
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`Pane`]: struct.Pane.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
- content: &[(Pane, Element<'_, Message, Self>)],
- dragging: Option<Pane>,
+ content: &[(Pane, Content<'_, Message, Self>)],
+ dragging: Option<(Pane, Point)>,
resizing: Option<Axis>,
layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output;
+
+ /// Draws a [`Pane`].
+ ///
+ /// It receives:
+ /// - the [`TitleBar`] of the [`Pane`], if any
+ /// - the [`Content`] of the [`Pane`]
+ /// - the [`Layout`] of the [`Pane`] and its elements
+ /// - the cursor position
+ fn draw_pane<Message>(
+ &mut self,
+ defaults: &Self::Defaults,
+ bounds: Rectangle,
+ style: &Self::Style,
+ title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
+ body: (&Element<'_, Message, Self>, Layout<'_>),
+ cursor_position: Point,
+ ) -> Self::Output;
+
+ /// Draws a [`TitleBar`].
+ ///
+ /// It receives:
+ /// - the bounds, style of the [`TitleBar`]
+ /// - the style of the [`TitleBar`]
+ /// - the title of the [`TitleBar`] with its size, font, and bounds
+ /// - the controls of the [`TitleBar`] with their [`Layout`+, if any
+ /// - the cursor position
+ fn draw_title_bar<Message>(
+ &mut self,
+ defaults: &Self::Defaults,
+ bounds: Rectangle,
+ style: &Self::Style,
+ title: &str,
+ title_size: u16,
+ title_font: Self::Font,
+ title_bounds: Rectangle,
+ controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
+ cursor_position: Point,
+ ) -> Self::Output;
}
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
+ Renderer: 'a + self::Renderer + row::Renderer,
Message: 'a,
{
fn from(
@@ -665,3 +615,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/configuration.rs b/native/src/widget/pane_grid/configuration.rs
new file mode 100644
index 00000000..4c43826e
--- /dev/null
+++ b/native/src/widget/pane_grid/configuration.rs
@@ -0,0 +1,26 @@
+use crate::pane_grid::Axis;
+
+/// The arrangement of a [`PaneGrid`].
+///
+/// [`PaneGrid`]: crate::pane_grid::PaneGrid
+#[derive(Debug, Clone)]
+pub enum Configuration<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 [`Configuration`] of the split.
+ a: Box<Configuration<T>>,
+
+ /// The right/bottom [`Configuration`] of the split.
+ b: Box<Configuration<T>>,
+ },
+ /// A [`Pane`].
+ ///
+ /// [`Pane`]: crate::pane_grid::Pane
+ Pane(T),
+}
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index 8822083e..c9981903 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -1,30 +1,213 @@
-use crate::pane_grid::Axis;
+use crate::container;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
+use crate::pane_grid::{self, TitleBar};
+use crate::{Clipboard, Element, Hasher, Layout, Point, Size};
-/// The content of a [`PaneGrid`].
+/// The content of a [`Pane`].
///
-/// [`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`]: crate::widget::pane_grid::Pane
+#[allow(missing_debug_implementations)]
+pub struct Content<'a, Message, Renderer: pane_grid::Renderer> {
+ title_bar: Option<TitleBar<'a, Message, Renderer>>,
+ body: Element<'a, Message, Renderer>,
+ style: Renderer::Style,
+}
+
+impl<'a, Message, Renderer> Content<'a, Message, Renderer>
+where
+ Renderer: pane_grid::Renderer,
+{
+ /// Creates a new [`Content`] with the provided body.
+ pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
+ Self {
+ title_bar: None,
+ body: body.into(),
+ style: Renderer::Style::default(),
+ }
+ }
+
+ /// Sets the [`TitleBar`] of this [`Content`].
+ pub fn title_bar(
+ mut self,
+ title_bar: TitleBar<'a, Message, Renderer>,
+ ) -> Self {
+ self.title_bar = Some(title_bar);
+ self
+ }
+
+ /// Sets the style of the [`Content`].
+ pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+impl<'a, Message, Renderer> Content<'a, Message, Renderer>
+where
+ Renderer: pane_grid::Renderer,
+{
+ /// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Pane`]: struct.Pane.html
- Pane(T),
+ /// [`Renderer`]: crate::widget::pane_grid::Renderer
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> Renderer::Output {
+ if let Some(title_bar) = &self.title_bar {
+ let mut children = layout.children();
+ let title_bar_layout = children.next().unwrap();
+ let body_layout = children.next().unwrap();
+
+ renderer.draw_pane(
+ defaults,
+ layout.bounds(),
+ &self.style,
+ Some((title_bar, title_bar_layout)),
+ (&self.body, body_layout),
+ cursor_position,
+ )
+ } else {
+ renderer.draw_pane(
+ defaults,
+ layout.bounds(),
+ &self.style,
+ None,
+ (&self.body, layout),
+ cursor_position,
+ )
+ }
+ }
+
+ /// Returns whether the [`Content`] with the given [`Layout`] can be picked
+ /// at the provided cursor position.
+ pub fn can_be_picked_at(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> bool {
+ if let Some(title_bar) = &self.title_bar {
+ let mut children = layout.children();
+ let title_bar_layout = children.next().unwrap();
+
+ title_bar.is_over_pick_area(title_bar_layout, cursor_position)
+ } else {
+ false
+ }
+ }
+
+ pub(crate) fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ if let Some(title_bar) = &self.title_bar {
+ let max_size = limits.max();
+
+ let title_bar_layout = title_bar
+ .layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
+
+ let title_bar_size = title_bar_layout.size();
+
+ let mut body_layout = self.body.layout(
+ renderer,
+ &layout::Limits::new(
+ Size::ZERO,
+ Size::new(
+ max_size.width,
+ max_size.height - title_bar_size.height,
+ ),
+ ),
+ );
+
+ body_layout.move_to(Point::new(0.0, title_bar_size.height));
+
+ layout::Node::with_children(
+ max_size,
+ vec![title_bar_layout, body_layout],
+ )
+ } else {
+ self.body.layout(renderer, limits)
+ }
+ }
+
+ pub(crate) fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ messages: &mut Vec<Message>,
+ renderer: &Renderer,
+ clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ let mut event_status = event::Status::Ignored;
+
+ let body_layout = if let Some(title_bar) = &mut self.title_bar {
+ let mut children = layout.children();
+
+ event_status = title_bar.on_event(
+ event.clone(),
+ children.next().unwrap(),
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ );
+
+ children.next().unwrap()
+ } else {
+ layout
+ };
+
+ let body_status = self.body.on_event(
+ event,
+ body_layout,
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ );
+
+ event_status.merge(body_status)
+ }
+
+ pub(crate) fn hash_layout(&self, state: &mut Hasher) {
+ if let Some(title_bar) = &self.title_bar {
+ title_bar.hash_layout(state);
+ }
+
+ self.body.hash_layout(state);
+ }
+
+ pub(crate) fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ let body_layout = if self.title_bar.is_some() {
+ let mut children = layout.children();
+
+ // Overlays only allowed in the pane body, for now at least.
+ let _title_bar_layout = children.next();
+
+ children.next()?
+ } else {
+ layout
+ };
+
+ self.body.overlay(body_layout)
+ }
+}
+
+impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>
+where
+ T: Into<Element<'a, Message, Renderer>>,
+ Renderer: pane_grid::Renderer + container::Renderer,
+{
+ fn from(element: T) -> Self {
+ Self::new(element)
+ }
}
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index 723ec393..319936fc 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -7,17 +7,12 @@ use std::collections::HashMap;
/// A layout node of a [`PaneGrid`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
#[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.
@@ -27,28 +22,40 @@ pub enum Node {
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 {
+ /// Returns an iterator over each [`Split`] in this [`Node`].
+ pub fn splits(&self) -> impl Iterator<Item = &Split> {
+ let mut unvisited_nodes = vec![self];
+
+ std::iter::from_fn(move || {
+ while let Some(node) = unvisited_nodes.pop() {
+ match node {
+ Node::Split { id, a, b, .. } => {
+ unvisited_nodes.push(a);
+ unvisited_nodes.push(b);
+
+ return Some(id);
+ }
+ _ => {}
+ }
+ }
+
+ None
+ })
+ }
+
/// 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(
+ pub fn pane_regions(
&self,
spacing: f32,
size: Size,
@@ -56,7 +63,7 @@ impl Node {
let mut regions = HashMap::new();
self.compute_regions(
- spacing / 2.0,
+ spacing,
&Rectangle {
x: 0.0,
y: 0.0,
@@ -72,10 +79,7 @@ impl Node {
/// 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(
+ pub fn split_regions(
&self,
spacing: f32,
size: Size,
@@ -83,7 +87,7 @@ impl Node {
let mut splits = HashMap::new();
self.compute_splits(
- spacing / 2.0,
+ spacing,
&Rectangle {
x: 0.0,
y: 0.0,
@@ -185,7 +189,7 @@ impl Node {
fn compute_regions(
&self,
- halved_spacing: f32,
+ spacing: f32,
current: &Rectangle,
regions: &mut HashMap<Pane, Rectangle>,
) {
@@ -193,11 +197,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 +210,7 @@ impl Node {
fn compute_splits(
&self,
- halved_spacing: f32,
+ spacing: f32,
current: &Rectangle,
splits: &mut HashMap<Split, (Axis, Rectangle, f32)>,
) {
@@ -219,13 +222,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/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs
index f9866407..39d9f3ef 100644
--- a/native/src/widget/pane_grid/pane.rs
+++ b/native/src/widget/pane_grid/pane.rs
@@ -1,5 +1,5 @@
/// A rectangular region in a [`PaneGrid`] used to display widgets.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Pane(pub(super) usize);
diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs
index d020c510..16975abc 100644
--- a/native/src/widget/pane_grid/split.rs
+++ b/native/src/widget/pane_grid/split.rs
@@ -1,5 +1,5 @@
/// A divider that splits a region in a [`PaneGrid`] into two different panes.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Split(pub(super) usize);
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index 4b13fb8e..666e1ca0 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -1,6 +1,5 @@
use crate::{
- keyboard,
- pane_grid::{Axis, Content, Direction, Node, Pane, Split},
+ pane_grid::{Axis, Configuration, Direction, Node, Pane, Split},
Hasher, Point, Rectangle, Size,
};
@@ -16,32 +15,12 @@ use std::collections::HashMap;
/// provided to the view function of [`PaneGrid::new`] for displaying each
/// [`Pane`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [`PaneGrid::new`]: struct.PaneGrid.html#method.new
-/// [`Pane`]: struct.Pane.html
-/// [`Split`]: struct.Split.html
-/// [`State`]: struct.State.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
+/// [`PaneGrid::new`]: crate::widget::PaneGrid::new
#[derive(Debug, Clone)]
pub struct State<T> {
pub(super) panes: HashMap<Pane, T>,
pub(super) internal: Internal,
- pub(super) modifiers: keyboard::ModifiersState,
-}
-
-/// The current focus of a [`Pane`].
-///
-/// [`Pane`]: struct.Pane.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Focus {
- /// The [`Pane`] is just focused.
- ///
- /// [`Pane`]: struct.Pane.html
- Idle,
-
- /// The [`Pane`] is being dragged.
- ///
- /// [`Pane`]: struct.Pane.html
- Dragging,
}
impl<T> State<T> {
@@ -49,113 +28,70 @@ impl<T> State<T> {
/// state.
///
/// Alongside the [`State`], it returns the first [`Pane`] identifier.
- ///
- /// [`State`]: struct.State.html
- /// [`Pane`]: struct.Pane.html
pub fn new(first_pane_state: T) -> (Self, Pane) {
- (Self::with_content(Content::Pane(first_pane_state)), Pane(0))
+ (
+ Self::with_configuration(Configuration::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 {
+ /// Creates a new [`State`] with the given [`Configuration`].
+ pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {
let mut panes = HashMap::new();
let (layout, last_id) =
- Self::distribute_content(&mut panes, content.into(), 0);
+ Self::distribute_content(&mut panes, config.into(), 0);
State {
panes,
internal: Internal {
layout,
last_id,
- action: Action::Idle { focus: None },
+ action: Action::Idle,
},
- modifiers: keyboard::ModifiersState::default(),
}
}
/// Returns the total amount of panes in the [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn len(&self) -> usize {
self.panes.len()
}
/// 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)
}
/// Returns an iterator over all the panes of the [`State`], alongside its
/// internal state.
- ///
- /// [`State`]: struct.State.html
pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> {
self.panes.iter()
}
/// Returns a mutable iterator over all the panes of the [`State`],
/// alongside its internal state.
- ///
- /// [`State`]: struct.State.html
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut 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.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State`]: struct.State.html
- pub fn active(&self) -> Option<Pane> {
- self.internal.active_pane()
- }
-
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
/// direction, if there is one.
- ///
- /// ## Example
- /// You can combine this with [`State::active`] to find the pane that is
- /// adjacent to the current active one, and then swap them. For instance:
- ///
- /// ```
- /// # use iced_native::pane_grid;
- /// #
- /// # let (mut state, _) = pane_grid::State::new(());
- /// #
- /// if let Some(active) = state.active() {
- /// if let Some(adjacent) = state.adjacent(&active, pane_grid::Direction::Right) {
- /// state.swap(&active, &adjacent);
- /// }
- /// }
- /// ```
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State::active`]: struct.State.html#method.active
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
- let regions =
- self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0));
+ let regions = self
+ .internal
+ .layout
+ .pane_regions(0.0, Size::new(4096.0, 4096.0));
let current_region = regions.get(pane)?;
@@ -184,18 +120,8 @@ impl<T> State<T> {
Some(*pane)
}
- /// Focuses the given [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn focus(&mut self, pane: &Pane) {
- self.internal.focus(pane);
- }
-
/// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`Axis`]: enum.Axis.html
pub fn split(
&mut self,
axis: Axis,
@@ -219,7 +145,6 @@ impl<T> State<T> {
node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state);
- self.focus(&new_pane);
Some((new_pane, new_split))
}
@@ -229,9 +154,8 @@ impl<T> State<T> {
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
/// will need to call this method when handling a [`DragEvent`].
///
- /// [`State`]: struct.State.html
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`DragEvent`]: struct.DragEvent.html
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ /// [`DragEvent`]: crate::widget::pane_grid::DragEvent
pub fn swap(&mut self, a: &Pane, b: &Pane) {
self.internal.layout.update(&|node| match node {
Node::Split { .. } => {}
@@ -253,20 +177,17 @@ impl<T> State<T> {
/// If you want to enable resize interactions in your [`PaneGrid`], you will
/// need to call this method when handling a [`ResizeEvent`].
///
- /// [`Split`]: struct.Split.html
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`ResizeEvent`]: struct.ResizeEvent.html
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ /// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent
pub fn resize(&mut self, split: &Split, ratio: f32) {
let _ = self.internal.layout.resize(split, ratio);
}
- /// Closes the given [`Pane`] and returns its internal state, if it exists.
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn close(&mut self, pane: &Pane) -> Option<T> {
+ /// Closes the given [`Pane`] and returns its internal state and its closest
+ /// sibling, if it exists.
+ pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
if let Some(sibling) = self.internal.layout.remove(pane) {
- self.focus(&sibling);
- self.panes.remove(pane)
+ self.panes.remove(pane).map(|state| (state, sibling))
} else {
None
}
@@ -274,11 +195,11 @@ impl<T> State<T> {
fn distribute_content(
panes: &mut HashMap<Pane, T>,
- content: Content<T>,
+ content: Configuration<T>,
next_id: usize,
) -> (Node, usize) {
match content {
- Content::Split { axis, ratio, a, b } => {
+ Configuration::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);
@@ -293,7 +214,7 @@ impl<T> State<T> {
next_id + 1,
)
}
- Content::Pane(state) => {
+ Configuration::Pane(state) => {
let id = Pane(next_id);
let _ = panes.insert(id, state);
@@ -310,47 +231,17 @@ pub struct Internal {
action: Action,
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action {
- Idle {
- focus: Option<Pane>,
- },
- Dragging {
- pane: Pane,
- },
- Resizing {
- split: Split,
- axis: Axis,
- focus: Option<Pane>,
- },
-}
-
-impl Action {
- pub fn focus(&self) -> Option<(Pane, Focus)> {
- match self {
- Action::Idle { focus } | Action::Resizing { focus, .. } => {
- focus.map(|pane| (pane, Focus::Idle))
- }
- Action::Dragging { pane } => Some((*pane, Focus::Dragging)),
- }
- }
+ Idle,
+ Dragging { pane: Pane, origin: Point },
+ Resizing { split: Split, axis: Axis },
}
impl Internal {
- pub fn action(&self) -> Action {
- self.action
- }
-
- pub fn active_pane(&self) -> Option<Pane> {
+ pub fn picked_pane(&self) -> Option<(Pane, Point)> {
match self.action {
- Action::Idle { focus } => focus,
- _ => None,
- }
- }
-
- pub fn picked_pane(&self) -> Option<Pane> {
- match self.action {
- Action::Dragging { pane } => Some(pane),
+ Action::Dragging { pane, origin, .. } => Some((pane, origin)),
_ => None,
}
}
@@ -362,28 +253,27 @@ impl Internal {
}
}
- pub fn regions(
+ pub fn pane_regions(
&self,
spacing: f32,
size: Size,
) -> HashMap<Pane, Rectangle> {
- self.layout.regions(spacing, size)
+ self.layout.pane_regions(spacing, size)
}
- pub fn splits(
+ pub fn split_regions(
&self,
spacing: f32,
size: Size,
) -> HashMap<Split, (Axis, Rectangle, f32)> {
- self.layout.splits(spacing, size)
+ self.layout.split_regions(spacing, size)
}
- pub fn focus(&mut self, pane: &Pane) {
- self.action = Action::Idle { focus: Some(*pane) };
- }
-
- pub fn pick_pane(&mut self, pane: &Pane) {
- self.action = Action::Dragging { pane: *pane };
+ pub fn pick_pane(&mut self, pane: &Pane, origin: Point) {
+ self.action = Action::Dragging {
+ pane: *pane,
+ origin,
+ };
}
pub fn pick_split(&mut self, split: &Split, axis: Axis) {
@@ -393,26 +283,14 @@ impl Internal {
return;
}
- let focus = self.action.focus().map(|(pane, _)| pane);
-
self.action = Action::Resizing {
split: *split,
axis,
- focus,
};
}
- pub fn drop_split(&mut self) {
- match self.action {
- Action::Resizing { focus, .. } => {
- self.action = Action::Idle { focus };
- }
- _ => {}
- }
- }
-
- pub fn unfocus(&mut self) {
- self.action = Action::Idle { focus: None };
+ pub fn idle(&mut self) {
+ self.action = Action::Idle;
}
pub fn hash_layout(&self, hasher: &mut Hasher) {
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
new file mode 100644
index 00000000..475cb9ae
--- /dev/null
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -0,0 +1,253 @@
+use crate::event::{self, Event};
+use crate::layout;
+use crate::pane_grid;
+use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
+
+/// The title bar of a [`Pane`].
+///
+/// [`Pane`]: crate::widget::pane_grid::Pane
+#[allow(missing_debug_implementations)]
+pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
+ title: String,
+ title_size: Option<u16>,
+ controls: Option<Element<'a, Message, Renderer>>,
+ padding: u16,
+ always_show_controls: bool,
+ style: Renderer::Style,
+}
+
+impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
+where
+ Renderer: pane_grid::Renderer,
+{
+ /// Creates a new [`TitleBar`] with the given title.
+ pub fn new(title: impl Into<String>) -> Self {
+ Self {
+ title: title.into(),
+ title_size: None,
+ controls: None,
+ padding: 0,
+ always_show_controls: false,
+ style: Renderer::Style::default(),
+ }
+ }
+
+ /// Sets the size of the title of the [`TitleBar`].
+ pub fn title_size(mut self, size: u16) -> Self {
+ self.title_size = Some(size);
+ self
+ }
+
+ /// Sets the controls of the [`TitleBar`].
+ pub fn controls(
+ mut self,
+ controls: impl Into<Element<'a, Message, Renderer>>,
+ ) -> Self {
+ self.controls = Some(controls.into());
+ self
+ }
+
+ /// Sets the padding of the [`TitleBar`].
+ pub fn padding(mut self, units: u16) -> Self {
+ self.padding = units;
+ self
+ }
+
+ /// Sets the style of the [`TitleBar`].
+ pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
+
+ /// Sets whether or not the [`controls`] attached to this [`TitleBar`] are
+ /// always visible.
+ ///
+ /// By default, the controls are only visible when the [`Pane`] of this
+ /// [`TitleBar`] is hovered.
+ ///
+ /// [`controls`]: Self::controls
+ /// [`Pane`]: crate::widget::pane_grid::Pane
+ pub fn always_show_controls(mut self) -> Self {
+ self.always_show_controls = true;
+ self
+ }
+}
+
+impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
+where
+ Renderer: pane_grid::Renderer,
+{
+ /// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
+ ///
+ /// [`Renderer`]: crate::widget::pane_grid::Renderer
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ show_controls: bool,
+ ) -> Renderer::Output {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
+
+ if let Some(controls) = &self.controls {
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+ let controls_layout = children.next().unwrap();
+
+ let (title_bounds, controls) =
+ if show_controls || self.always_show_controls {
+ (title_layout.bounds(), Some((controls, controls_layout)))
+ } else {
+ (
+ Rectangle {
+ width: padded.bounds().width,
+ ..title_layout.bounds()
+ },
+ None,
+ )
+ };
+
+ renderer.draw_title_bar(
+ defaults,
+ layout.bounds(),
+ &self.style,
+ &self.title,
+ self.title_size.unwrap_or(renderer.default_size()),
+ Renderer::Font::default(),
+ title_bounds,
+ controls,
+ cursor_position,
+ )
+ } else {
+ renderer.draw_title_bar::<()>(
+ defaults,
+ layout.bounds(),
+ &self.style,
+ &self.title,
+ self.title_size.unwrap_or(renderer.default_size()),
+ Renderer::Font::default(),
+ padded.bounds(),
+ None,
+ cursor_position,
+ )
+ }
+ }
+
+ /// Returns whether the mouse cursor is over the pick area of the
+ /// [`TitleBar`] or not.
+ ///
+ /// The whole [`TitleBar`] is a pick area, except its controls.
+ pub fn is_over_pick_area(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) -> bool {
+ if layout.bounds().contains(cursor_position) {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
+
+ if self.controls.is_some() {
+ let mut children = padded.children();
+ let _ = children.next().unwrap();
+ let controls_layout = children.next().unwrap();
+
+ !controls_layout.bounds().contains(cursor_position)
+ } else {
+ true
+ }
+ } else {
+ false
+ }
+ }
+
+ pub(crate) fn hash_layout(&self, hasher: &mut Hasher) {
+ use std::hash::Hash;
+
+ self.title.hash(hasher);
+ self.title_size.hash(hasher);
+ self.padding.hash(hasher);
+ }
+
+ pub(crate) fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let padding = f32::from(self.padding);
+ let limits = limits.pad(padding);
+ let max_size = limits.max();
+
+ let title_size = self.title_size.unwrap_or(renderer.default_size());
+ let title_font = Renderer::Font::default();
+
+ let (title_width, title_height) = renderer.measure(
+ &self.title,
+ title_size,
+ title_font,
+ Size::new(f32::INFINITY, max_size.height),
+ );
+
+ let mut node = if let Some(controls) = &self.controls {
+ let mut controls_layout = controls
+ .layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
+
+ let controls_size = controls_layout.size();
+ let space_before_controls = max_size.width - controls_size.width;
+
+ let mut title_layout = layout::Node::new(Size::new(
+ title_width.min(space_before_controls),
+ title_height,
+ ));
+
+ let title_size = title_layout.size();
+ let height = title_size.height.max(controls_size.height);
+
+ title_layout
+ .move_to(Point::new(0.0, (height - title_size.height) / 2.0));
+ controls_layout.move_to(Point::new(space_before_controls, 0.0));
+
+ layout::Node::with_children(
+ Size::new(max_size.width, height),
+ vec![title_layout, controls_layout],
+ )
+ } else {
+ layout::Node::new(Size::new(max_size.width, title_height))
+ };
+
+ node.move_to(Point::new(padding, padding));
+
+ layout::Node::with_children(node.size().pad(padding), vec![node])
+ }
+
+ pub(crate) fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ messages: &mut Vec<Message>,
+ renderer: &Renderer,
+ clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ if let Some(controls) = &mut self.controls {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
+
+ let mut children = padded.children();
+ let _ = children.next();
+ let controls_layout = children.next().unwrap();
+
+ controls.on_event(
+ event,
+ controls_layout,
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ )
+ } else {
+ event::Status::Ignored
+ }
+ }
+}
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
new file mode 100644
index 00000000..58c0dfe1
--- /dev/null
+++ b/native/src/widget/pick_list.rs
@@ -0,0 +1,343 @@
+//! Display a dropdown list of selectable values.
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::overlay::menu::{self, Menu};
+use crate::scrollable;
+use crate::text;
+use crate::{
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+};
+use std::borrow::Cow;
+
+/// A widget for selecting a single value from a list of options.
+#[allow(missing_debug_implementations)]
+pub struct PickList<'a, T, Message, Renderer: self::Renderer>
+where
+ [T]: ToOwned<Owned = Vec<T>>,
+{
+ menu: &'a mut menu::State,
+ is_open: &'a mut bool,
+ hovered_option: &'a mut Option<usize>,
+ last_selection: &'a mut Option<T>,
+ on_selected: Box<dyn Fn(T) -> Message>,
+ options: Cow<'a, [T]>,
+ selected: Option<T>,
+ width: Length,
+ padding: u16,
+ text_size: Option<u16>,
+ font: Renderer::Font,
+ style: <Renderer as self::Renderer>::Style,
+}
+
+/// The local state of a [`PickList`].
+#[derive(Debug, Clone)]
+pub struct State<T> {
+ menu: menu::State,
+ is_open: bool,
+ hovered_option: Option<usize>,
+ last_selection: Option<T>,
+}
+
+impl<T> Default for State<T> {
+ fn default() -> Self {
+ Self {
+ menu: menu::State::default(),
+ is_open: bool::default(),
+ hovered_option: Option::default(),
+ last_selection: Option::default(),
+ }
+ }
+}
+
+impl<'a, T: 'a, Message, Renderer: self::Renderer>
+ PickList<'a, T, Message, Renderer>
+where
+ T: ToString + Eq,
+ [T]: ToOwned<Owned = Vec<T>>,
+{
+ /// Creates a new [`PickList`] with the given [`State`], a list of options,
+ /// the current selected value, and the message to produce when an option is
+ /// selected.
+ pub fn new(
+ state: &'a mut State<T>,
+ options: impl Into<Cow<'a, [T]>>,
+ selected: Option<T>,
+ on_selected: impl Fn(T) -> Message + 'static,
+ ) -> Self {
+ let State {
+ menu,
+ is_open,
+ hovered_option,
+ last_selection,
+ } = state;
+
+ Self {
+ menu,
+ is_open,
+ hovered_option,
+ last_selection,
+ on_selected: Box::new(on_selected),
+ options: options.into(),
+ selected,
+ width: Length::Shrink,
+ text_size: None,
+ padding: Renderer::DEFAULT_PADDING,
+ font: Default::default(),
+ style: Default::default(),
+ }
+ }
+
+ /// Sets the width of the [`PickList`].
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the padding of the [`PickList`].
+ pub fn padding(mut self, padding: u16) -> Self {
+ self.padding = padding;
+ self
+ }
+
+ /// Sets the text size of the [`PickList`].
+ pub fn text_size(mut self, size: u16) -> Self {
+ self.text_size = Some(size);
+ self
+ }
+
+ /// Sets the font of the [`PickList`].
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
+ /// Sets the style of the [`PickList`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as self::Renderer>::Style>,
+ ) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
+ for PickList<'a, T, Message, Renderer>
+where
+ T: Clone + ToString + Eq,
+ [T]: ToOwned<Owned = Vec<T>>,
+ Message: 'static,
+ Renderer: self::Renderer + scrollable::Renderer + 'a,
+{
+ fn width(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ use std::f32;
+
+ let limits = limits
+ .width(self.width)
+ .height(Length::Shrink)
+ .pad(f32::from(self.padding));
+
+ let text_size = self.text_size.unwrap_or(renderer.default_size());
+
+ let max_width = match self.width {
+ Length::Shrink => {
+ let labels = self.options.iter().map(ToString::to_string);
+
+ labels
+ .map(|label| {
+ let (width, _) = renderer.measure(
+ &label,
+ text_size,
+ Renderer::Font::default(),
+ Size::new(f32::INFINITY, f32::INFINITY),
+ );
+
+ width.round() as u32
+ })
+ .max()
+ .unwrap_or(100)
+ }
+ _ => 0,
+ };
+
+ let size = {
+ let intrinsic = Size::new(
+ max_width as f32
+ + f32::from(text_size)
+ + f32::from(self.padding),
+ f32::from(text_size),
+ );
+
+ limits.resolve(intrinsic).pad(f32::from(self.padding))
+ };
+
+ layout::Node::new(size)
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ use std::hash::Hash as _;
+
+ match self.width {
+ Length::Shrink => {
+ self.options
+ .iter()
+ .map(ToString::to_string)
+ .for_each(|label| label.hash(state));
+ }
+ _ => {
+ self.width.hash(state);
+ }
+ }
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ messages: &mut Vec<Message>,
+ _renderer: &Renderer,
+ _clipboard: Option<&dyn Clipboard>,
+ ) -> event::Status {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let event_status = if *self.is_open {
+ // TODO: Encode cursor availability in the type system
+ *self.is_open =
+ cursor_position.x < 0.0 || cursor_position.y < 0.0;
+
+ event::Status::Captured
+ } else if layout.bounds().contains(cursor_position) {
+ let selected = self.selected.as_ref();
+
+ *self.is_open = true;
+ *self.hovered_option = self
+ .options
+ .iter()
+ .position(|option| Some(option) == selected);
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ };
+
+ if let Some(last_selection) = self.last_selection.take() {
+ messages.push((self.on_selected)(last_selection));
+
+ *self.is_open = false;
+
+ event::Status::Captured
+ } else {
+ event_status
+ }
+ }
+ _ => event::Status::Ignored,
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ _defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ self::Renderer::draw(
+ renderer,
+ layout.bounds(),
+ cursor_position,
+ self.selected.as_ref().map(ToString::to_string),
+ self.padding,
+ self.text_size.unwrap_or(renderer.default_size()),
+ self.font,
+ &self.style,
+ )
+ }
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ if *self.is_open {
+ let bounds = layout.bounds();
+
+ let mut menu = Menu::new(
+ &mut self.menu,
+ &self.options,
+ &mut self.hovered_option,
+ &mut self.last_selection,
+ )
+ .width(bounds.width.round() as u16)
+ .padding(self.padding)
+ .font(self.font)
+ .style(Renderer::menu_style(&self.style));
+
+ if let Some(text_size) = self.text_size {
+ menu = menu.text_size(text_size);
+ }
+
+ Some(menu.overlay(layout.position(), bounds.height))
+ } else {
+ None
+ }
+ }
+}
+
+/// The renderer of a [`PickList`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use a [`PickList`] in your user interface.
+///
+/// [renderer]: crate::renderer
+pub trait Renderer: text::Renderer + menu::Renderer {
+ /// The default padding of a [`PickList`].
+ const DEFAULT_PADDING: u16;
+
+ /// The [`PickList`] style supported by this renderer.
+ type Style: Default;
+
+ /// Returns the style of the [`Menu`] of the [`PickList`].
+ fn menu_style(
+ style: &<Self as Renderer>::Style,
+ ) -> <Self as menu::Renderer>::Style;
+
+ /// Draws a [`PickList`].
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ cursor_position: Point,
+ selected: Option<String>,
+ padding: u16,
+ text_size: u16,
+ font: Self::Font,
+ style: &<Self as Renderer>::Style,
+ ) -> Self::Output;
+}
+
+impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
+ for PickList<'a, T, Message, Renderer>
+where
+ T: Clone + ToString + Eq,
+ [T]: ToOwned<Owned = Vec<T>>,
+ Renderer: self::Renderer + 'a,
+ Message: 'static,
+{
+ fn into(self) -> Element<'a, Message, Renderer> {
+ Element::new(self)
+ }
+}
diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs
index 5ab76d47..d294f198 100644
--- a/native/src/widget/progress_bar.rs
+++ b/native/src/widget/progress_bar.rs
@@ -33,8 +33,6 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`]
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
ProgressBar {
value: value.max(*range.start()).min(*range.end()),
@@ -46,24 +44,18 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
}
/// Sets the width of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn height(mut self, height: Length) -> Self {
self.height = Some(height);
self
}
/// Sets the style of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -104,6 +96,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
layout.bounds(),
@@ -127,15 +120,12 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`ProgressBar`] in your user interface.
///
-/// [`ProgressBar`]: struct.ProgressBar.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default height of a [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
const DEFAULT_HEIGHT: u16;
/// Draws a [`ProgressBar`].
@@ -146,8 +136,6 @@ pub trait Renderer: crate::Renderer {
/// * the current value of the [`ProgressBar`]
/// * maybe a specific background of the [`ProgressBar`]
/// * maybe a specific active color of the [`ProgressBar`]
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
fn draw(
&self,
bounds: Rectangle,
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index d07a9012..69952345 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,8 +1,13 @@
//! Create choices using radio buttons.
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::row;
+use crate::text;
+use crate::touch;
use crate::{
- layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
- HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
- VerticalAlignment, Widget,
+ Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
+ Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
use std::hash::Hash;
@@ -41,12 +46,14 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
width: Length,
size: u16,
spacing: u16,
- text_size: u16,
+ text_size: Option<u16>,
style: Renderer::Style,
}
impl<Message, Renderer: self::Renderer + text::Renderer>
Radio<Message, Renderer>
+where
+ Message: Clone,
{
/// Creates a new [`Radio`] button.
///
@@ -56,8 +63,6 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn new<F, V>(
value: V,
label: impl Into<String>,
@@ -75,46 +80,36 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, //15
- text_size: <Renderer as text::Renderer>::DEFAULT_SIZE,
+ 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 = text_size;
+ self.text_size = Some(text_size);
self
}
/// Sets the style of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -123,8 +118,8 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
where
- Renderer: self::Renderer + text::Renderer + row::Renderer,
Message: Clone,
+ Renderer: self::Renderer + text::Renderer + row::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -151,7 +146,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)
}
@@ -164,15 +159,20 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if layout.bounds().contains(cursor_position) {
messages.push(self.on_click.clone());
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -181,6 +181,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let mut children = layout.children();
@@ -194,7 +195,7 @@ where
defaults,
label_layout.bounds(),
&self.label,
- self.text_size,
+ self.text_size.unwrap_or(renderer.default_size()),
Default::default(),
None,
HorizontalAlignment::Left,
@@ -226,20 +227,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Radio`] button in your user interface.
///
-/// [`Radio`]: struct.Radio.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// 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
const DEFAULT_SPACING: u16;
/// Draws a [`Radio`] button.
@@ -249,8 +245,6 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Radio`] is selected or not
/// * whether the mouse is over the [`Radio`] or not
/// * the drawn label of the [`Radio`]
- ///
- /// [`Radio`]: struct.Radio.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -264,8 +258,8 @@ pub trait Renderer: crate::Renderer {
impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
{
fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(radio)
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 31f7472f..b71663bd 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,18 +1,15 @@
//! Distribute content horizontally.
-use std::hash::Hash;
-
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
};
+use std::hash::Hash;
use std::u32;
/// A container that distributes its contents horizontally.
-///
-/// A [`Row`] will try to fill the horizontal space of its container.
-///
-/// [`Row`]: struct.Row.html
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
spacing: u16,
@@ -27,15 +24,11 @@ pub struct Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Creates an empty [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
- ///
- /// [`Row`]: struct.Row.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
@@ -62,57 +55,42 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
}
/// Sets the padding of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
- ///
- /// [`Row`]: struct.Row.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
- ///
- /// [`Element`]: ../struct.Element.html
- /// [`Row`]: struct.Row.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -165,9 +143,11 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
- self.children.iter_mut().zip(layout.children()).for_each(
- |(child, layout)| {
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
@@ -176,8 +156,8 @@ where
renderer,
clipboard,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
@@ -186,8 +166,15 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
- renderer.draw(defaults, &self.children, layout, cursor_position)
+ renderer.draw(
+ defaults,
+ &self.children,
+ layout,
+ cursor_position,
+ viewport,
+ )
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -200,12 +187,23 @@ where
self.max_height.hash(state);
self.align_items.hash(state);
self.spacing.hash(state);
- self.spacing.hash(state);
+ self.padding.hash(state);
for child in &self.children {
child.widget.hash_layout(state);
}
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .filter_map(|(child, layout)| child.widget.overlay(layout))
+ .next()
+ }
}
/// The renderer of a [`Row`].
@@ -213,8 +211,7 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Row`] in your user interface.
///
-/// [`Row`]: struct.Row.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Row`].
///
@@ -222,15 +219,13 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the children of the [`Row`]
/// - the [`Layout`] of the [`Row`] and its children
/// - the cursor position
- ///
- /// [`Row`]: struct.Row.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
children: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
}
diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs
new file mode 100644
index 00000000..18c88658
--- /dev/null
+++ b/native/src/widget/rule.rs
@@ -0,0 +1,116 @@
+//! Display a horizontal or vertical rule for dividing content.
+
+use std::hash::Hash;
+
+use crate::{
+ layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+};
+
+/// Display a horizontal or vertical rule for dividing content.
+#[derive(Debug, Copy, Clone)]
+pub struct Rule<Renderer: self::Renderer> {
+ width: Length,
+ height: Length,
+ style: Renderer::Style,
+ is_horizontal: bool,
+}
+
+impl<Renderer: self::Renderer> Rule<Renderer> {
+ /// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
+ pub fn horizontal(spacing: u16) -> Self {
+ Rule {
+ width: Length::Fill,
+ height: Length::from(Length::Units(spacing)),
+ style: Renderer::Style::default(),
+ is_horizontal: true,
+ }
+ }
+
+ /// Creates a vertical [`Rule`] for dividing content by the given horizontal spacing.
+ pub fn vertical(spacing: u16) -> Self {
+ Rule {
+ width: Length::from(Length::Units(spacing)),
+ height: Length::Fill,
+ style: Renderer::Style::default(),
+ is_horizontal: false,
+ }
+ }
+
+ /// Sets the style of the [`Rule`].
+ pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+impl<Message, Renderer> Widget<Message, Renderer> for Rule<Renderer>
+where
+ Renderer: self::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits.width(self.width).height(self.height);
+
+ layout::Node::new(limits.resolve(Size::ZERO))
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ _defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.width.hash(state);
+ self.height.hash(state);
+ }
+}
+
+/// The renderer of a [`Rule`].
+pub trait Renderer: crate::Renderer {
+ /// The style supported by this renderer.
+ type Style: Default;
+
+ /// Draws a [`Rule`].
+ ///
+ /// It receives:
+ /// * the bounds of the [`Rule`]
+ /// * the style of the [`Rule`]
+ /// * whether the [`Rule`] is horizontal (true) or vertical (false)
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ style: &Self::Style,
+ is_horizontal: bool,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Rule<Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Renderer: 'a + self::Renderer,
+ Message: 'a,
+{
+ fn from(rule: Rule<Renderer>) -> Element<'a, Message, Renderer> {
+ Element::new(rule)
+ }
+}
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 3c8e5e5b..18cdf169 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -1,7 +1,13 @@
//! Navigate an endless amount of content with a scrollbar.
+use crate::column;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::touch;
use crate::{
- column, layout, mouse, Align, Clipboard, Column, Element, Event, Hasher,
- Layout, Length, Point, Rectangle, Size, Widget,
+ Align, Clipboard, Column, Element, Hasher, Layout, Length, Point,
+ Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
@@ -13,20 +19,23 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
content: Column<'a, Message, Renderer>,
style: Renderer::Style,
}
impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn new(state: &'a mut State) -> Self {
Scrollable {
state,
height: Length::Shrink,
max_height: u32::MAX,
+ scrollbar_width: 10,
+ scrollbar_margin: 0,
+ scroller_width: 10,
content: Column::new(),
style: Renderer::Style::default(),
}
@@ -43,64 +52,68 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the padding of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn padding(mut self, units: u16) -> Self {
self.content = self.content.padding(units);
self
}
/// Sets the width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn width(mut self, width: Length) -> Self {
self.content = self.content.width(width);
self
}
/// Sets the height of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.content = self.content.max_width(max_width);
self
}
/// Sets the maximum height of the [`Scrollable`] in pixels.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn align_items(mut self, align_items: Align) -> Self {
self.content = self.content.align_items(align_items);
self
}
+ /// Sets the scrollbar width of the [`Scrollable`] .
+ /// Silently enforces a minimum value of 1.
+ pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self {
+ self.scrollbar_width = scrollbar_width.max(1);
+ self
+ }
+
+ /// Sets the scrollbar margin of the [`Scrollable`] .
+ pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self {
+ self.scrollbar_margin = scrollbar_margin;
+ self
+ }
+
+ /// Sets the scroller width of the [`Scrollable`] .
+ /// Silently enforces a minimum value of 1.
+ pub fn scroller_width(mut self, scroller_width: u16) -> Self {
+ self.scroller_width = scroller_width.max(1);
+ self
+ }
+
/// Sets the style of the [`Scrollable`] .
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
/// Adds an element to the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -113,7 +126,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
- Renderer: self::Renderer + column::Renderer,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
Widget::<Message, Renderer>::width(&self.content)
@@ -152,14 +165,56 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
- // TODO: Event capture. Nested scrollables should capture scroll events.
+ let offset = self.state.offset(bounds, content_bounds);
+ let scrollbar = renderer.scrollbar(
+ bounds,
+ content_bounds,
+ offset,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ );
+ let is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ let event_status = {
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(
+ cursor_position.x,
+ cursor_position.y
+ + self.state.offset(bounds, content_bounds) as f32,
+ )
+ } else {
+ // TODO: Make `cursor_position` an `Option<Point>` so we can encode
+ // cursor availability.
+ // This will probably happen naturally once we add multi-window
+ // support.
+ Point::new(cursor_position.x, -1.0)
+ };
+
+ self.content.on_event(
+ event.clone(),
+ content,
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ )
+ };
+
+ if let event::Status::Captured = event_status {
+ return event::Status::Captured;
+ }
+
if is_mouse_over {
match event {
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
@@ -172,26 +227,57 @@ where
self.state.scroll(y, bounds, content_bounds);
}
}
+
+ return event::Status::Captured;
+ }
+ Event::Touch(event) => {
+ match event {
+ touch::Event::FingerPressed { .. } => {
+ self.state.scroll_box_touched_at =
+ Some(cursor_position);
+ }
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ self.state.scroll_box_touched_at
+ {
+ let delta =
+ cursor_position.y - scroll_box_touched_at.y;
+
+ self.state.scroll(
+ delta,
+ bounds,
+ content_bounds,
+ );
+
+ self.state.scroll_box_touched_at =
+ Some(cursor_position);
+ }
+ }
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. } => {
+ self.state.scroll_box_touched_at = None;
+ }
+ }
+
+ return event::Status::Captured;
}
_ => {}
}
}
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(bounds, content_bounds, offset);
- let is_mouse_over_scrollbar = scrollbar
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false);
-
if self.state.is_scroller_grabbed() {
match event {
Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Left,
- )) => {
+ ))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
self.state.scroller_grabbed_at = None;
+
+ return event::Status::Captured;
}
- Event::Mouse(mouse::Event::CursorMoved { .. }) => {
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
if let (Some(scrollbar), Some(scroller_grabbed_at)) =
(scrollbar, self.state.scroller_grabbed_at)
{
@@ -203,6 +289,8 @@ where
bounds,
content_bounds,
);
+
+ return event::Status::Captured;
}
}
_ => {}
@@ -211,7 +299,8 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
- )) => {
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if let Some(scrollbar) = scrollbar {
if let Some(scroller_grabbed_at) =
scrollbar.grab_scroller(cursor_position)
@@ -227,6 +316,8 @@ where
self.state.scroller_grabbed_at =
Some(scroller_grabbed_at);
+
+ return event::Status::Captured;
}
}
}
@@ -234,28 +325,7 @@ where
}
}
- let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
- Point::new(
- cursor_position.x,
- cursor_position.y
- + self.state.offset(bounds, content_bounds) as f32,
- )
- } else {
- // TODO: Make `cursor_position` an `Option<Point>` so we can encode
- // cursor availability.
- // This will probably happen naturally once we add multi-window
- // support.
- Point::new(cursor_position.x, -1.0)
- };
-
- self.content.on_event(
- event,
- content,
- cursor_position,
- messages,
- renderer,
- clipboard,
- )
+ event::Status::Ignored
}
fn draw(
@@ -264,12 +334,20 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(bounds, content_bounds, offset);
+ let scrollbar = renderer.scrollbar(
+ bounds,
+ content_bounds,
+ offset,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ );
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
@@ -289,6 +367,10 @@ where
defaults,
content_layout,
cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
)
};
@@ -315,30 +397,42 @@ where
self.content.hash_layout(state)
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ let Self { content, state, .. } = self;
+
+ content
+ .overlay(layout.children().next().unwrap())
+ .map(|overlay| {
+ let bounds = layout.bounds();
+ let content_layout = layout.children().next().unwrap();
+ let content_bounds = content_layout.bounds();
+ let offset = state.offset(bounds, content_bounds);
+
+ overlay.translate(Vector::new(0.0, -(offset as f32)))
+ })
+ }
}
/// The local state of a [`Scrollable`].
-///
-/// [`Scrollable`]: struct.Scrollable.html
#[derive(Debug, Clone, Copy, Default)]
pub struct State {
scroller_grabbed_at: Option<f32>,
+ scroll_box_touched_at: Option<Point>,
offset: f32,
}
impl State {
/// Creates a new [`State`] with the scrollbar located at the top.
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
State::default()
}
/// Apply a scrolling offset to the current [`State`], given the bounds of
/// the [`Scrollable`] and its contents.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn scroll(
&mut self,
delta_y: f32,
@@ -359,9 +453,6 @@ impl State {
///
/// `0` represents scrollbar at the top, while `1` represents scrollbar at
/// the bottom.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn scroll_to(
&mut self,
percentage: f32,
@@ -374,9 +465,6 @@ impl State {
/// Returns the current scrolling offset of the [`State`], given the bounds
/// of the [`Scrollable`] and its contents.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
let hidden_content =
(content_bounds.height - bounds.height).max(0.0).round() as u32;
@@ -388,31 +476,37 @@ impl State {
pub fn is_scroller_grabbed(&self) -> bool {
self.scroller_grabbed_at.is_some()
}
+
+ /// Returns whether the scroll box is currently touched or not.
+ pub fn is_scroll_box_touched(&self) -> bool {
+ self.scroll_box_touched_at.is_some()
+ }
}
/// The scrollbar of a [`Scrollable`].
-///
-/// [`Scrollable`]: struct.Scrollable.html
#[derive(Debug)]
pub struct Scrollbar {
+ /// The outer bounds of the scrollable, including the [`Scrollbar`] and
+ /// [`Scroller`].
+ pub outer_bounds: Rectangle,
+
/// The bounds of the [`Scrollbar`].
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
pub bounds: Rectangle,
+ /// The margin within the [`Scrollbar`].
+ pub margin: u16,
+
/// The bounds of the [`Scroller`].
- ///
- /// [`Scroller`]: struct.Scroller.html
pub scroller: Scroller,
}
impl Scrollbar {
fn is_mouse_over(&self, cursor_position: Point) -> bool {
- self.bounds.contains(cursor_position)
+ self.outer_bounds.contains(cursor_position)
}
fn grab_scroller(&self, cursor_position: Point) -> Option<f32> {
- if self.bounds.contains(cursor_position) {
+ if self.outer_bounds.contains(cursor_position) {
Some(if self.scroller.bounds.contains(cursor_position) {
(cursor_position.y - self.scroller.bounds.y)
/ self.scroller.bounds.height
@@ -437,13 +531,9 @@ impl Scrollbar {
}
/// The handle of a [`Scrollbar`].
-///
-/// [`Scrollbar`]: struct.Scrollbar.html
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
/// The bounds of the [`Scroller`].
- ///
- /// [`Scroller`]: struct.Scrollbar.html
pub bounds: Rectangle,
}
@@ -452,22 +542,21 @@ pub struct Scroller {
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Scrollable`] in your user interface.
///
-/// [`Scrollable`]: struct.Scrollable.html
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer: crate::Renderer + Sized {
+/// [renderer]: crate::renderer
+pub trait Renderer: column::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
/// Returns the [`Scrollbar`] given the bounds and content bounds of a
/// [`Scrollable`].
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
- /// [`Scrollable`]: struct.Scrollable.html
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
) -> Option<Scrollbar>;
/// Draws the [`Scrollable`].
@@ -481,10 +570,6 @@ pub trait Renderer: crate::Renderer + Sized {
/// - a optional [`Scrollbar`] to be rendered
/// - the scrolling offset
/// - the drawn content
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
fn draw(
&mut self,
scrollable: &State,
@@ -502,7 +587,7 @@ pub trait Renderer: crate::Renderer + Sized {
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + column::Renderer,
+ Renderer: 'a + self::Renderer,
Message: 'a,
{
fn from(
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 8cdfc3de..010c6e53 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -1,12 +1,12 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::touch;
use crate::{
- layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
@@ -16,13 +16,15 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// A [`Slider`] will try to fill the horizontal space of its container.
///
-/// [`Slider`]: struct.Slider.html
+/// The [`Slider`] range of numeric values is generic and its step size defaults
+/// to 1 unit.
///
/// # 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>;
+/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
@@ -35,16 +37,24 @@ 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,
+ height: u16,
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,
+ Message: Clone,
+ Renderer: self::Renderer,
+{
/// Creates a new [`Slider`].
///
/// It expects:
@@ -54,48 +64,77 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`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,
+ height: Renderer::DEFAULT_HEIGHT,
style: Renderer::Style::default(),
}
}
- /// Sets the width of the [`Slider`].
+ /// Sets the release message of the [`Slider`].
+ /// This is called when the mouse is released from the slider.
///
- /// [`Slider`]: struct.Slider.html
+ /// 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.
+ pub fn on_release(mut self, on_release: Message) -> Self {
+ self.on_release = Some(on_release);
+ self
+ }
+
+ /// Sets the width of the [`Slider`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
+ /// Sets the height of the [`Slider`].
+ pub fn height(mut self, height: u16) -> Self {
+ self.height = height;
+ self
+ }
+
/// Sets the style of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
+
+ /// Sets the step size of the [`Slider`].
+ pub fn step(mut self, step: T) -> Self {
+ self.step = step;
+ self
+ }
}
/// The local state of a [`Slider`].
-///
-/// [`Slider`]: struct.Slider.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_dragging: bool,
@@ -103,16 +142,16 @@ pub struct State {
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
}
-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,
+ Message: Clone,
Renderer: self::Renderer,
{
fn width(&self) -> Length {
@@ -125,12 +164,11 @@ where
fn layout(
&self,
- renderer: &Renderer,
+ _renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let limits = limits
- .width(self.width)
- .height(Length::Units(renderer.height() as u16));
+ let limits =
+ limits.width(self.width).height(Length::Units(self.height));
let size = limits.resolve(Size::ZERO);
@@ -145,43 +183,64 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
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);
- messages.push((self.on_change)(value));
+ let steps = (percent * (end - start) / step).round();
+ let value = steps * step + start;
+
+ if let Some(value) = T::from_f64(value) {
+ messages.push((self.on_change)(value));
+ }
}
};
match event {
- 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;
- }
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if layout.bounds().contains(cursor_position) {
+ change();
+ self.state.is_dragging = true;
+
+ return event::Status::Captured;
}
- mouse::Event::ButtonReleased(mouse::Button::Left) => {
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ if self.state.is_dragging {
+ if let Some(on_release) = self.on_release.clone() {
+ messages.push(on_release);
+ }
self.state.is_dragging = false;
+
+ return event::Status::Captured;
}
- mouse::Event::CursorMoved { .. } => {
- if self.state.is_dragging {
- change();
- }
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if self.state.is_dragging {
+ change();
+
+ return event::Status::Captured;
}
- _ => {}
- },
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -190,12 +249,16 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> 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,
)
@@ -214,16 +277,13 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Slider`] in your user interface.
///
-/// [`Slider`]: struct.Slider.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
- /// Returns the height of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
- fn height(&self) -> u32;
+ /// The default height of a [`Slider`].
+ const DEFAULT_HEIGHT: u16;
/// Draws a [`Slider`].
///
@@ -233,10 +293,6 @@ pub trait Renderer: crate::Renderer {
/// * the local state of the [`Slider`]
/// * the range of values of the [`Slider`]
/// * the current value of the [`Slider`]
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
- /// [`Class`]: enum.Class.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -248,14 +304,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,
+ Message: 'a + Clone,
Renderer: 'a + self::Renderer,
- Message: 'a,
{
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/space.rs b/native/src/widget/space.rs
index e56a8fe1..6b34ece8 100644
--- a/native/src/widget/space.rs
+++ b/native/src/widget/space.rs
@@ -16,15 +16,11 @@ pub struct Space {
impl Space {
/// Creates an amount of empty [`Space`] with the given width and height.
- ///
- /// [`Space`]: struct.Space.html
pub fn new(width: Length, height: Length) -> Self {
Space { width, height }
}
/// Creates an amount of horizontal [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_width(width: Length) -> Self {
Space {
width,
@@ -33,8 +29,6 @@ impl Space {
}
/// Creates an amount of vertical [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_height(height: Length) -> Self {
Space {
width: Length::Shrink,
@@ -43,7 +37,7 @@ impl Space {
}
}
-impl<'a, Message, Renderer> Widget<Message, Renderer> for Space
+impl<Message, Renderer> Widget<Message, Renderer> for Space
where
Renderer: self::Renderer,
{
@@ -71,6 +65,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(layout.bounds())
}
@@ -84,14 +79,10 @@ where
}
/// The renderer of an amount of [`Space`].
-///
-/// [`Space`]: struct.Space.html
pub trait Renderer: crate::Renderer {
/// Draws an amount of empty [`Space`].
///
/// You should most likely return an empty primitive here.
- ///
- /// [`Space`]: struct.Space.html
fn draw(&mut self, bounds: Rectangle) -> Self::Output;
}
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 114d5e41..9cd61918 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -1,5 +1,6 @@
//! Display vector graphics in your application.
-use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
+use crate::layout;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
@@ -13,8 +14,6 @@ use std::{
///
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Svg {
handle: Handle,
@@ -24,9 +23,6 @@ pub struct Svg {
impl Svg {
/// Creates a new [`Svg`] from the given [`Handle`].
- ///
- /// [`Svg`]: struct.Svg.html
- /// [`Handle`]: struct.Handle.html
pub fn new(handle: impl Into<Handle>) -> Self {
Svg {
handle: handle.into(),
@@ -37,23 +33,17 @@ impl Svg {
/// Creates a new [`Svg`] that will display the contents of the file at the
/// provided path.
- ///
- /// [`Svg`]: struct.Svg.html
pub fn from_path(path: impl Into<PathBuf>) -> Self {
Self::new(Handle::from_path(path))
}
/// Sets the width of the [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -103,6 +93,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
}
@@ -117,8 +108,6 @@ where
}
/// An [`Svg`] handle.
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
@@ -128,8 +117,6 @@ pub struct Handle {
impl Handle {
/// Creates an SVG [`Handle`] pointing to the vector image of the given
/// path.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_path(path: impl Into<PathBuf>) -> Handle {
Self::from_data(Data::Path(path.into()))
}
@@ -139,8 +126,6 @@ impl Handle {
///
/// This is useful if you already have your SVG data in-memory, maybe
/// because you downloaded or generated it procedurally.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
Self::from_data(Data::Bytes(bytes.into()))
}
@@ -156,15 +141,11 @@ impl Handle {
}
/// Returns the unique identifier of the [`Handle`].
- ///
- /// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the SVG [`Data`].
- ///
- /// [`Data`]: enum.Data.html
pub fn data(&self) -> &Data {
&self.data
}
@@ -177,8 +158,6 @@ impl Hash for Handle {
}
/// The data of an [`Svg`].
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Clone, Hash)]
pub enum Data {
/// File data
@@ -204,18 +183,12 @@ impl std::fmt::Debug for Data {
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Svg`] in your user interface.
///
-/// [`Svg`]: struct.Svg.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
- ///
- /// [`Svg`]: struct.Svg.html
- /// [`Handle`]: struct.Handle.html
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index d60aa468..6cc18e6c 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -19,7 +19,7 @@ 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)]
+#[derive(Debug)]
pub struct Text<Renderer: self::Renderer> {
content: String,
size: Option<u16>,
@@ -33,8 +33,6 @@ pub struct Text<Renderer: self::Renderer> {
impl<Renderer: self::Renderer> Text<Renderer> {
/// Create a new fragment of [`Text`] with the given contents.
- ///
- /// [`Text`]: struct.Text.html
pub fn new<T: Into<String>>(label: T) -> Self {
Text {
content: label.into(),
@@ -49,17 +47,12 @@ impl<Renderer: self::Renderer> Text<Renderer> {
}
/// Sets the size of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
}
/// Sets the [`Color`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`Color`]: ../../struct.Color.html
pub fn color<C: Into<Color>>(mut self, color: C) -> Self {
self.color = Some(color.into());
self
@@ -67,33 +60,25 @@ impl<Renderer: self::Renderer> Text<Renderer> {
/// Sets the [`Font`] of the [`Text`].
///
- /// [`Text`]: struct.Text.html
- /// [`Font`]: ../../struct.Font.html
+ /// [`Font`]: Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = font.into();
self
}
/// Sets the width of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the [`HorizontalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
pub fn horizontal_alignment(
mut self,
alignment: HorizontalAlignment,
@@ -103,9 +88,6 @@ impl<Renderer: self::Renderer> Text<Renderer> {
}
/// Sets the [`VerticalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`VerticalAlignment`]: enum.VerticalAlignment.html
pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
self.vertical_alignment = alignment;
self
@@ -131,7 +113,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();
@@ -149,12 +131,13 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
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,
@@ -176,26 +159,18 @@ where
/// The renderer of a [`Text`] fragment.
///
/// Your [renderer] will need to implement this trait before being
-/// able to use [`Text`] in your [`UserInterface`].
+/// able to use [`Text`] in your user interface.
///
-/// [`Text`]: struct.Text.html
-/// [renderer]: ../../renderer/index.html
-/// [`UserInterface`]: ../../struct.UserInterface.html
+/// [renderer]: crate::Renderer
pub trait Renderer: crate::Renderer {
/// The font type used for [`Text`].
- ///
- /// [`Text`]: struct.Text.html
type Font: Default + Copy;
- /// The default size of [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- const DEFAULT_SIZE: u16;
+ /// Returns the default size of [`Text`].
+ fn default_size(&self) -> u16;
/// Measures the [`Text`] in the given bounds and returns the minimum
/// boundaries that can fit the contents.
- ///
- /// [`Text`]: struct.Text.html
fn measure(
&self,
content: &str,
@@ -213,10 +188,6 @@ pub trait Renderer: crate::Renderer {
/// * the color of the [`Text`]
/// * the [`HorizontalAlignment`] of the [`Text`]
/// * the [`VerticalAlignment`] of the [`Text`]
- ///
- /// [`Text`]: struct.Text.html
- /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
- /// [`VerticalAlignment`]: enum.VerticalAlignment.html
fn draw(
&mut self,
defaults: &Self::Defaults,
@@ -239,3 +210,18 @@ where
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 1cdbe007..2fd9cec1 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -1,9 +1,6 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
mod editor;
mod value;
@@ -14,11 +11,14 @@ pub use value::Value;
use editor::Editor;
+use crate::event::{self, Event};
+use crate::keyboard;
+use crate::layout;
+use crate::mouse::{self, click};
+use crate::text;
+use crate::touch;
use crate::{
- keyboard, layout,
- mouse::{self, click},
- Clipboard, Element, Event, Font, Hasher, Layout, Length, Point, Rectangle,
- Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::u32;
@@ -53,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,
@@ -63,7 +63,11 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
style: Renderer::Style,
}
-impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
+impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
+where
+ Message: Clone,
+ Renderer: self::Renderer,
+{
/// Creates a new [`TextInput`].
///
/// It expects:
@@ -71,9 +75,6 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// - a placeholder
/// - the current value
/// - a function that produces a message when the [`TextInput`] changes
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
placeholder: &str,
@@ -88,7 +89,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,
@@ -100,8 +101,6 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
}
/// Converts the [`TextInput`] into a secure password input.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn password(mut self) -> Self {
self.is_secure = true;
self
@@ -109,39 +108,31 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// Sets the [`Font`] of the [`Text`].
///
- /// [`Text`]: struct.Text.html
- /// [`Font`]: ../../struct.Font.html
- pub fn font(mut self, font: Font) -> Self {
+ /// [`Font`]: crate::widget::text::Renderer::Font
+ /// [`Text`]: crate::widget::Text
+ pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
/// Sets the width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the maximum width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the padding of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the text size of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
@@ -149,27 +140,75 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// Sets the message that should be produced when the [`TextInput`] is
/// focused and the enter key is pressed.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn on_submit(mut self, message: Message) -> Self {
self.on_submit = Some(message);
self
}
/// Sets the style of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
+
+ /// Returns the current [`State`] of the [`TextInput`].
+ pub fn state(&self) -> &State {
+ self.state
+ }
+}
+
+impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
+ /// [`Value`] if provided.
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ value: Option<&Value>,
+ ) -> Renderer::Output {
+ let value = value.unwrap_or(&self.value);
+ let bounds = layout.bounds();
+ let text_bounds = layout.children().next().unwrap().bounds();
+
+ if self.is_secure {
+ self::Renderer::draw(
+ renderer,
+ bounds,
+ text_bounds,
+ cursor_position,
+ self.font,
+ self.size.unwrap_or(renderer.default_size()),
+ &self.placeholder,
+ &value.secure(),
+ &self.state,
+ &self.style,
+ )
+ } else {
+ self::Renderer::draw(
+ renderer,
+ bounds,
+ text_bounds,
+ cursor_position,
+ self.font,
+ self.size.unwrap_or(renderer.default_size()),
+ &self.placeholder,
+ value,
+ &self.state,
+ &self.style,
+ )
+ }
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -207,11 +246,14 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
let is_clicked = layout.bounds().contains(cursor_position);
+ self.state.is_focused = is_clicked;
+
if is_clicked {
let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x;
@@ -243,6 +285,8 @@ where
} else {
self.state.cursor.move_to(0);
}
+
+ self.state.is_dragging = true;
}
click::Kind::Double => {
if self.is_secure {
@@ -262,25 +306,30 @@ where
self.value.next_end_of_word(position),
);
}
+
+ self.state.is_dragging = false;
}
click::Kind::Triple => {
self.state.cursor.select_all(&self.value);
+ self.state.is_dragging = false;
}
}
self.state.last_click = Some(click);
- }
- self.state.is_dragging = is_clicked;
- self.state.is_focused = is_clicked;
+ return event::Status::Captured;
+ }
}
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
self.state.is_dragging = false;
}
- Event::Mouse(mouse::Event::CursorMoved { x, .. }) => {
+ Event::Mouse(mouse::Event::CursorMoved { position })
+ | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
if self.state.is_dragging {
let text_layout = layout.children().next().unwrap();
- let target = x - text_layout.bounds().x;
+ let target = position.x - text_layout.bounds().x;
if target > 0.0 {
let value = if self.is_secure {
@@ -303,11 +352,14 @@ where
position,
);
}
+
+ return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused
&& self.state.is_pasting.is_none()
+ && !self.state.keyboard_modifiers.is_command_pressed()
&& !c.is_control() =>
{
let mut editor =
@@ -317,162 +369,212 @@ where
let message = (self.on_change)(editor.contents());
messages.push(message);
+
+ return event::Status::Captured;
}
Event::Keyboard(keyboard::Event::KeyPressed {
- key_code,
- modifiers,
- }) if self.state.is_focused => match key_code {
- keyboard::KeyCode::Enter => {
- if let Some(on_submit) = self.on_submit.clone() {
- messages.push(on_submit);
- }
- }
- keyboard::KeyCode::Backspace => {
- if platform::is_jump_modifier_pressed(modifiers)
- && self.state.cursor.selection().is_none()
- {
- if self.is_secure {
- let cursor_pos = self.state.cursor.end(&self.value);
- self.state.cursor.select_range(0, cursor_pos);
- } else {
- self.state.cursor.select_left_by_words(&self.value);
+ key_code, ..
+ }) if self.state.is_focused => {
+ let modifiers = self.state.keyboard_modifiers;
+
+ match key_code {
+ keyboard::KeyCode::Enter => {
+ if let Some(on_submit) = self.on_submit.clone() {
+ messages.push(on_submit);
}
}
+ keyboard::KeyCode::Backspace => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && self
+ .state
+ .cursor
+ .selection(&self.value)
+ .is_none()
+ {
+ if self.is_secure {
+ let cursor_pos =
+ self.state.cursor.end(&self.value);
+ self.state.cursor.select_range(0, cursor_pos);
+ } else {
+ self.state
+ .cursor
+ .select_left_by_words(&self.value);
+ }
+ }
- let mut editor =
- Editor::new(&mut self.value, &mut self.state.cursor);
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
+ );
- editor.backspace();
+ editor.backspace();
- let message = (self.on_change)(editor.contents());
- messages.push(message);
- }
- keyboard::KeyCode::Delete => {
- if platform::is_jump_modifier_pressed(modifiers)
- && self.state.cursor.selection().is_none()
- {
- if self.is_secure {
- let cursor_pos = self.state.cursor.end(&self.value);
- self.state
- .cursor
- .select_range(cursor_pos, self.value.len());
- } else {
- self.state
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
+ }
+ keyboard::KeyCode::Delete => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && self
+ .state
.cursor
- .select_right_by_words(&self.value);
+ .selection(&self.value)
+ .is_none()
+ {
+ if self.is_secure {
+ let cursor_pos =
+ self.state.cursor.end(&self.value);
+ self.state
+ .cursor
+ .select_range(cursor_pos, self.value.len());
+ } else {
+ self.state
+ .cursor
+ .select_right_by_words(&self.value);
+ }
}
- }
- let mut editor =
- Editor::new(&mut self.value, &mut self.state.cursor);
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
+ );
- editor.delete();
+ editor.delete();
- let message = (self.on_change)(editor.contents());
- messages.push(message);
- }
- keyboard::KeyCode::Left => {
- if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
- {
- if modifiers.shift {
- self.state.cursor.select_left_by_words(&self.value);
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
+ }
+ keyboard::KeyCode::Left => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && !self.is_secure
+ {
+ if modifiers.shift {
+ self.state
+ .cursor
+ .select_left_by_words(&self.value);
+ } else {
+ self.state
+ .cursor
+ .move_left_by_words(&self.value);
+ }
+ } else if modifiers.shift {
+ self.state.cursor.select_left(&self.value)
} else {
- self.state.cursor.move_left_by_words(&self.value);
+ self.state.cursor.move_left(&self.value);
}
- } else if modifiers.shift {
- self.state.cursor.select_left(&self.value)
- } else {
- self.state.cursor.move_left(&self.value);
}
- }
- keyboard::KeyCode::Right => {
- if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
- {
- if modifiers.shift {
- self.state
- .cursor
- .select_right_by_words(&self.value);
+ keyboard::KeyCode::Right => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && !self.is_secure
+ {
+ if modifiers.shift {
+ self.state
+ .cursor
+ .select_right_by_words(&self.value);
+ } else {
+ self.state
+ .cursor
+ .move_right_by_words(&self.value);
+ }
+ } else if modifiers.shift {
+ self.state.cursor.select_right(&self.value)
} else {
- self.state.cursor.move_right_by_words(&self.value);
+ self.state.cursor.move_right(&self.value);
}
- } else if modifiers.shift {
- self.state.cursor.select_right(&self.value)
- } else {
- self.state.cursor.move_right(&self.value);
}
- }
- keyboard::KeyCode::Home => {
- if modifiers.shift {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- 0,
- );
- } else {
- self.state.cursor.move_to(0);
- }
- }
- keyboard::KeyCode::End => {
- if modifiers.shift {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- self.value.len(),
- );
- } else {
- self.state.cursor.move_to(self.value.len());
+ keyboard::KeyCode::Home => {
+ if modifiers.shift {
+ self.state.cursor.select_range(
+ self.state.cursor.start(&self.value),
+ 0,
+ );
+ } else {
+ self.state.cursor.move_to(0);
+ }
}
- }
- keyboard::KeyCode::V => {
- if platform::is_copy_paste_modifier_pressed(modifiers) {
- if let Some(clipboard) = clipboard {
- let content = match self.state.is_pasting.take() {
- Some(content) => content,
- None => {
- let content: String = clipboard
- .content()
- .unwrap_or(String::new())
- .chars()
- .filter(|c| !c.is_control())
- .collect();
-
- Value::new(&content)
- }
- };
-
- let mut editor = Editor::new(
- &mut self.value,
- &mut self.state.cursor,
+ keyboard::KeyCode::End => {
+ if modifiers.shift {
+ self.state.cursor.select_range(
+ self.state.cursor.start(&self.value),
+ self.value.len(),
);
+ } else {
+ self.state.cursor.move_to(self.value.len());
+ }
+ }
+ keyboard::KeyCode::V => {
+ if self.state.keyboard_modifiers.is_command_pressed() {
+ if let Some(clipboard) = clipboard {
+ let content = match self.state.is_pasting.take()
+ {
+ Some(content) => content,
+ None => {
+ let content: String = clipboard
+ .content()
+ .unwrap_or(String::new())
+ .chars()
+ .filter(|c| !c.is_control())
+ .collect();
+
+ Value::new(&content)
+ }
+ };
- editor.paste(content.clone());
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
+ );
- let message = (self.on_change)(editor.contents());
- messages.push(message);
+ editor.paste(content.clone());
- self.state.is_pasting = Some(content);
+ let message =
+ (self.on_change)(editor.contents());
+ messages.push(message);
+
+ self.state.is_pasting = Some(content);
+ }
+ } else {
+ self.state.is_pasting = None;
}
- } else {
- self.state.is_pasting = None;
}
- }
- keyboard::KeyCode::A => {
- if platform::is_copy_paste_modifier_pressed(modifiers) {
- self.state.cursor.select_all(&self.value);
+ keyboard::KeyCode::A => {
+ if self.state.keyboard_modifiers.is_command_pressed() {
+ self.state.cursor.select_all(&self.value);
+ }
+ }
+ keyboard::KeyCode::Escape => {
+ self.state.is_focused = false;
+ self.state.is_dragging = false;
+ self.state.is_pasting = None;
+
+ self.state.keyboard_modifiers =
+ keyboard::Modifiers::default();
}
+ _ => {}
}
- _ => {}
- },
+
+ return event::Status::Captured;
+ }
Event::Keyboard(keyboard::Event::KeyReleased {
key_code, ..
- }) => match key_code {
- keyboard::KeyCode::V => {
- self.state.is_pasting = None;
+ }) if self.state.is_focused => {
+ match key_code {
+ keyboard::KeyCode::V => {
+ self.state.is_pasting = None;
+ }
+ _ => {}
}
- _ => {}
- },
+
+ return event::Status::Captured;
+ }
+ Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers))
+ if self.state.is_focused =>
+ {
+ self.state.keyboard_modifiers = modifiers;
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -481,35 +583,9 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
- let bounds = layout.bounds();
- let text_bounds = layout.children().next().unwrap().bounds();
-
- if self.is_secure {
- renderer.draw(
- bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- &self.value.secure(),
- &self.state,
- &self.style,
- )
- } else {
- renderer.draw(
- bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- &self.value,
- &self.state,
- &self.style,
- )
- }
+ self.draw(renderer, layout, cursor_position, None)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -529,34 +605,23 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`TextInput`] in your user interface.
///
-/// [`TextInput`]: struct.TextInput.html
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer: crate::Renderer + Sized {
+/// [renderer]: crate::renderer
+pub trait Renderer: text::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
- /// Returns the default size of the text of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- fn default_size(&self) -> u16;
-
/// 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`].
///
/// This is the amount of horizontal scrolling applied when the [`Value`]
/// does not fit the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`Value`]: struct.Value.html
fn offset(
&self,
text_bounds: Rectangle,
- font: Font,
+ font: Self::Font,
size: u16,
value: &Value,
state: &State,
@@ -571,16 +636,12 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the placeholder to show when the value is empty
/// - the current [`Value`]
/// - the current [`State`]
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`Value`]: struct.Value.html
- /// [`State`]: struct.State.html
fn draw(
&mut self,
bounds: Rectangle,
text_bounds: Rectangle,
cursor_position: Point,
- font: Font,
+ font: Self::Font,
size: u16,
placeholder: &str,
value: &Value,
@@ -590,12 +651,10 @@ pub trait Renderer: crate::Renderer + Sized {
/// Computes the position of the text cursor at the given X coordinate of
/// a [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
fn find_cursor_position(
&self,
text_bounds: Rectangle,
- font: Font,
+ font: Self::Font,
size: Option<u16>,
value: &Value,
state: &State,
@@ -620,8 +679,8 @@ pub trait Renderer: crate::Renderer + Sized {
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
@@ -631,8 +690,6 @@ where
}
/// The state of a [`TextInput`].
-///
-/// [`TextInput`]: struct.TextInput.html
#[derive(Debug, Default, Clone)]
pub struct State {
is_focused: bool,
@@ -640,20 +697,17 @@ pub struct State {
is_pasting: Option<Value>,
last_click: Option<mouse::Click>,
cursor: Cursor,
+ keyboard_modifiers: keyboard::Modifiers,
// TODO: Add stateful horizontal scrolling offset
}
impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
Self::default()
}
/// Creates a new [`State`], representing a focused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn focused() -> Self {
Self {
is_focused: true,
@@ -661,30 +715,51 @@ impl State {
is_pasting: None,
last_click: None,
cursor: Cursor::default(),
+ keyboard_modifiers: keyboard::Modifiers::default(),
}
}
/// Returns whether the [`TextInput`] is currently focused or not.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn is_focused(&self) -> bool {
self.is_focused
}
/// Returns the [`Cursor`] of the [`TextInput`].
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn cursor(&self) -> Cursor {
self.cursor
}
+
+ /// Focuses the [`TextInput`].
+ pub fn focus(&mut self) {
+ self.is_focused = true;
+ }
+
+ /// Unfocuses the [`TextInput`].
+ pub fn unfocus(&mut self) {
+ self.is_focused = false;
+ }
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
+ 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.
+ pub fn move_cursor_to_end(&mut self) {
+ self.cursor.move_to(usize::MAX);
+ }
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
+ 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,
@@ -739,23 +814,11 @@ fn find_cursor_position<Renderer: self::Renderer>(
mod platform {
use crate::keyboard;
- pub fn is_jump_modifier_pressed(
- modifiers: keyboard::ModifiersState,
- ) -> bool {
+ pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
if cfg!(target_os = "macos") {
modifiers.alt
} else {
modifiers.control
}
}
-
- pub fn is_copy_paste_modifier_pressed(
- modifiers: keyboard::ModifiersState,
- ) -> bool {
- if cfg!(target_os = "macos") {
- modifiers.logo
- } else {
- modifiers.control
- }
- }
}
diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs
index 16e7a01b..e630e293 100644
--- a/native/src/widget/text_input/cursor.rs
+++ b/native/src/widget/text_input/cursor.rs
@@ -8,8 +8,6 @@ pub struct Cursor {
}
/// The state of a [`Cursor`].
-///
-/// [`Cursor`]: struct.Cursor.html
#[derive(Debug, Copy, Clone)]
pub enum State {
/// Cursor without a selection
@@ -34,9 +32,6 @@ impl Default for Cursor {
impl Cursor {
/// Returns the [`State`] of the [`Cursor`].
- ///
- /// [`State`]: struct.State.html
- /// [`Cursor`]: struct.Cursor.html
pub fn state(&self, value: &Value) -> State {
match self.state {
State::Index(index) => State::Index(index.min(value.len())),
@@ -166,8 +161,8 @@ impl Cursor {
end.min(value.len())
}
- pub(crate) fn selection(&self) -> Option<(usize, usize)> {
- match self.state {
+ pub(crate) fn selection(&self, value: &Value) -> Option<(usize, usize)> {
+ match self.state(value) {
State::Selection { start, end } => {
Some((start.min(end), start.max(end)))
}
diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs
index c9b9795d..20e42567 100644
--- a/native/src/widget/text_input/editor.rs
+++ b/native/src/widget/text_input/editor.rs
@@ -15,7 +15,7 @@ impl<'a> Editor<'a> {
}
pub fn insert(&mut self, character: char) {
- match self.cursor.selection() {
+ match self.cursor.selection(self.value) {
Some((left, right)) => {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
@@ -30,7 +30,7 @@ impl<'a> Editor<'a> {
pub fn paste(&mut self, content: Value) {
let length = content.len();
- match self.cursor.selection() {
+ match self.cursor.selection(self.value) {
Some((left, right)) => {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
@@ -44,7 +44,7 @@ impl<'a> Editor<'a> {
}
pub fn backspace(&mut self) {
- match self.cursor.selection() {
+ match self.cursor.selection(self.value) {
Some((start, end)) => {
self.cursor.move_left(self.value);
self.value.remove_many(start, end);
@@ -61,7 +61,7 @@ impl<'a> Editor<'a> {
}
pub fn delete(&mut self) {
- match self.cursor.selection() {
+ match self.cursor.selection(self.value) {
Some(_) => {
self.backspace();
}
diff --git a/native/src/widget/text_input/value.rs b/native/src/widget/text_input/value.rs
index 1e9ba45b..86be2790 100644
--- a/native/src/widget/text_input/value.rs
+++ b/native/src/widget/text_input/value.rs
@@ -2,7 +2,7 @@ use unicode_segmentation::UnicodeSegmentation;
/// The value of a [`TextInput`].
///
-/// [`TextInput`]: struct.TextInput.html
+/// [`TextInput`]: crate::widget::TextInput
// TODO: Reduce allocations, cache results (?)
#[derive(Debug, Clone)]
pub struct Value {
@@ -11,8 +11,6 @@ pub struct Value {
impl Value {
/// Creates a new [`Value`] from a string slice.
- ///
- /// [`Value`]: struct.Value.html
pub fn new(string: &str) -> Self {
let graphemes = UnicodeSegmentation::graphemes(string, true)
.map(String::from)
@@ -21,17 +19,20 @@ impl Value {
Self { graphemes }
}
- /// Returns the total amount of graphemes in the [`Value`].
+ /// Returns whether the [`Value`] is empty or not.
///
- /// [`Value`]: struct.Value.html
+ /// A [`Value`] is empty when it contains no graphemes.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Returns the total amount of graphemes in the [`Value`].
pub fn len(&self) -> usize {
self.graphemes.len()
}
/// Returns the position of the previous start of a word from the given
/// grapheme `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn previous_start_of_word(&self, index: usize) -> usize {
let previous_string =
&self.graphemes[..index.min(self.graphemes.len())].concat();
@@ -54,8 +55,6 @@ impl Value {
/// Returns the position of the next end of a word from the given grapheme
/// `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn next_end_of_word(&self, index: usize) -> usize {
let next_string = &self.graphemes[index..].concat();
@@ -76,8 +75,6 @@ impl Value {
/// Returns a new [`Value`] containing the graphemes until the given
/// `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn until(&self, index: usize) -> Self {
let graphemes = self.graphemes[..index.min(self.len())].to_vec();
@@ -85,8 +82,6 @@ impl Value {
}
/// Converts the [`Value`] into a `String`.
- ///
- /// [`Value`]: struct.Value.html
pub fn to_string(&self) -> String {
self.graphemes.concat()
}
@@ -109,8 +104,6 @@ impl Value {
}
/// Removes the grapheme at the given `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn remove(&mut self, index: usize) {
let _ = self.graphemes.remove(index);
}
@@ -122,8 +115,6 @@ impl Value {
/// Returns a new [`Value`] with all its graphemes replaced with the
/// dot ('•') character.
- ///
- /// [`Value`]: struct.Value.html
pub fn secure(&self) -> Self {
Self {
graphemes: std::iter::repeat(String::from("•"))
diff --git a/native/src/window.rs b/native/src/window.rs
index 4dcae62f..220bb3be 100644
--- a/native/src/window.rs
+++ b/native/src/window.rs
@@ -1,6 +1,4 @@
//! Build window-based GUI applications.
-mod backend;
mod event;
-pub use backend::Backend;
pub use event::Event;
diff --git a/native/src/window/backend.rs b/native/src/window/backend.rs
deleted file mode 100644
index 892d4bb9..00000000
--- a/native/src/window/backend.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use crate::mouse;
-
-use raw_window_handle::HasRawWindowHandle;
-
-/// A graphics backend that can render to windows.
-pub trait Backend: Sized {
- /// The settings of the backend.
- type Settings: Default;
-
- /// The iced renderer of the backend.
- type Renderer: crate::Renderer;
-
- /// The surface of the backend.
- type Surface;
-
- /// The swap chain of the backend.
- type SwapChain;
-
- /// Creates a new [`Backend`] and an associated iced renderer.
- ///
- /// [`Backend`]: trait.Backend.html
- fn new(settings: Self::Settings) -> (Self, Self::Renderer);
-
- /// Crates a new [`Surface`] for the given window.
- ///
- /// [`Surface`]: #associatedtype.Surface
- fn create_surface<W: HasRawWindowHandle>(
- &mut self,
- window: &W,
- ) -> Self::Surface;
-
- /// Crates a new [`SwapChain`] for the given [`Surface`].
- ///
- /// [`SwapChain`]: #associatedtype.SwapChain
- /// [`Surface`]: #associatedtype.Surface
- fn create_swap_chain(
- &mut self,
- surface: &Self::Surface,
- width: u32,
- height: u32,
- ) -> Self::SwapChain;
-
- /// Draws the output primitives to the next frame of the given [`SwapChain`].
- ///
- /// [`SwapChain`]: #associatedtype.SwapChain
- /// [`Surface`]: #associatedtype.Surface
- fn draw<T: AsRef<str>>(
- &mut self,
- renderer: &mut Self::Renderer,
- swap_chain: &mut Self::SwapChain,
- output: &<Self::Renderer as crate::Renderer>::Output,
- scale_factor: f64,
- overlay: &[T],
- ) -> mouse::Interaction;
-}