summaryrefslogtreecommitdiffstats
path: root/native/src
diff options
context:
space:
mode:
Diffstat (limited to 'native/src')
-rw-r--r--native/src/element.rs15
-rw-r--r--native/src/event.rs41
-rw-r--r--native/src/lib.rs2
-rw-r--r--native/src/overlay.rs7
-rw-r--r--native/src/overlay/element.rs12
-rw-r--r--native/src/overlay/menu.rs20
-rw-r--r--native/src/program/state.rs7
-rw-r--r--native/src/runtime.rs12
-rw-r--r--native/src/subscription.rs42
-rw-r--r--native/src/subscription/events.rs28
-rw-r--r--native/src/user_interface.rs99
-rw-r--r--native/src/widget.rs10
-rw-r--r--native/src/widget/button.rs27
-rw-r--r--native/src/widget/checkbox.rs16
-rw-r--r--native/src/widget/column.rs16
-rw-r--r--native/src/widget/container.rs8
-rw-r--r--native/src/widget/pane_grid.rs212
-rw-r--r--native/src/widget/pane_grid/content.rs17
-rw-r--r--native/src/widget/pane_grid/state.rs145
-rw-r--r--native/src/widget/pane_grid/title_bar.rs11
-rw-r--r--native/src/widget/pick_list.rs30
-rw-r--r--native/src/widget/radio.rs16
-rw-r--r--native/src/widget/row.rs16
-rw-r--r--native/src/widget/scrollable.rs100
-rw-r--r--native/src/widget/slider.rs16
-rw-r--r--native/src/widget/text_input.rs414
-rw-r--r--native/src/widget/text_input/value.rs9
27 files changed, 731 insertions, 617 deletions
diff --git a/native/src/element.rs b/native/src/element.rs
index 10e1b5fb..9703a7db 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -1,7 +1,8 @@
+use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Clipboard, Color, Event, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
};
/// A generic [`Widget`].
@@ -240,7 +241,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.widget.on_event(
event,
layout,
@@ -248,7 +249,7 @@ where
messages,
renderer,
clipboard,
- );
+ )
}
/// Draws the [`Element`] and its children using the given [`Layout`].
@@ -335,10 +336,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,
@@ -350,6 +351,8 @@ where
original_messages
.drain(..)
.for_each(|message| messages.push((self.mapper)(message)));
+
+ status
}
fn draw(
@@ -423,7 +426,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.element.widget.on_event(
event,
layout,
diff --git a/native/src/event.rs b/native/src/event.rs
index 606a71d6..9c079151 100644
--- a/native/src/event.rs
+++ b/native/src/event.rs
@@ -1,3 +1,4 @@
+//! Handle events of a user interface.
use crate::{keyboard, mouse, window};
/// A user interface event.
@@ -6,7 +7,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),
@@ -17,3 +18,41 @@ pub enum Event {
/// A window event
Window(window::Event),
}
+
+/// The status of an [`Event`] after being processed.
+///
+/// [`Event`]: enum.Event.html
+/// [`UserInterface`]: ../struct.UserInterface.html
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Status {
+ /// The [`Event`] was **NOT** handled by any widget.
+ ///
+ /// [`Event`]: enum.Event.html
+ Ignored,
+
+ /// The [`Event`] was handled and processed by a widget.
+ ///
+ /// [`Event`]: enum.Event.html
+ 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/lib.rs b/native/src/lib.rs
index 067e3c0a..d1252eaf 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -35,6 +35,7 @@
#![deny(unused_results)]
#![forbid(unsafe_code)]
#![forbid(rust_2018_idioms)]
+pub mod event;
pub mod keyboard;
pub mod layout;
pub mod mouse;
@@ -47,7 +48,6 @@ pub mod window;
mod clipboard;
mod element;
-mod event;
mod hasher;
mod runtime;
mod user_interface;
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index 7c3bec32..56d055d3 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -6,7 +6,9 @@ pub mod menu;
pub use element::Element;
pub use menu::Menu;
-use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size};
+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>
@@ -79,6 +81,7 @@ where
_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
index e1fd9b88..3f346695 100644
--- a/native/src/overlay/element.rs
+++ b/native/src/overlay/element.rs
@@ -1,6 +1,8 @@
pub use crate::Overlay;
-use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::{Clipboard, Hasher, Layout, Point, Size, Vector};
/// A generic [`Overlay`].
///
@@ -67,7 +69,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.overlay.on_event(
event,
layout,
@@ -136,10 +138,10 @@ where
messages: &mut Vec<B>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
let mut original_messages = Vec::new();
- self.content.on_event(
+ let event_status = self.content.on_event(
event,
layout,
cursor_position,
@@ -151,6 +153,8 @@ where
original_messages
.drain(..)
.for_each(|message| messages.push((self.mapper)(message)));
+
+ event_status
}
fn draw(
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index 4b392a8e..d99b5940 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -1,8 +1,14 @@
//! 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::{
- container, layout, mouse, overlay, scrollable, text, Clipboard, Container,
- Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size,
- Vector, Widget,
+ Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle,
+ Scrollable, Size, Vector, Widget,
};
/// A list of selectable options.
@@ -235,7 +241,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.container.on_event(
event.clone(),
layout,
@@ -243,7 +249,7 @@ where
messages,
renderer,
clipboard,
- );
+ )
}
fn draw(
@@ -336,7 +342,7 @@ where
_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();
@@ -364,6 +370,8 @@ where
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
diff --git a/native/src/program/state.rs b/native/src/program/state.rs
index 95e6b60c..76283e30 100644
--- a/native/src/program/state.rs
+++ b/native/src/program/state.rs
@@ -120,14 +120,17 @@ where
);
debug.event_processing_started();
- let mut messages = user_interface.update(
+ let mut messages = Vec::new();
+
+ let _ = user_interface.update(
&self.queued_events,
cursor_position,
clipboard,
renderer,
+ &mut messages,
);
- messages.extend(self.queued_messages.drain(..));
+ messages.extend(self.queued_messages.drain(..));
self.queued_events.clear();
debug.event_processing_finished();
diff --git a/native/src/runtime.rs b/native/src/runtime.rs
index 9fa031f4..bd814a0b 100644
--- a/native/src/runtime.rs
+++ b/native/src/runtime.rs
@@ -1,5 +1,6 @@
//! 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.
///
@@ -8,5 +9,10 @@ use crate::{Event, Hasher};
///
/// [`Command`]: ../struct.Command.html
/// [`Subscription`]: ../struct.Subscription.html
-pub type Runtime<Executor, Receiver, Message> =
- iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>;
+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..3cc04188 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.
@@ -15,19 +16,21 @@ use iced_futures::futures::stream::BoxStream;
///
/// [`Command`]: ../struct.Command.html
/// [`Subscription`]: struct.Subscription.html
-pub type Subscription<T> = iced_futures::Subscription<Hasher, Event, T>;
+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 +40,36 @@ use events::Events;
/// Returns a [`Subscription`] to all the runtime events.
///
-/// This subscription will notify your application of any [`Event`] handled by
-/// the runtime.
+/// This subscription will notify your application of any [`Event`] that was
+/// not captured by any widget.
///
/// [`Subscription`]: type.Subscription.html
/// [`Event`]: ../enum.Event.html
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.
+///
+/// [`Subscription`]: type.Subscription.html
+/// [`Event`]: ../enum.Event.html
+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/user_interface.rs b/native/src/user_interface.rs
index 59d91f42..31bb6b99 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,6 +1,7 @@
+use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
-use crate::{Clipboard, Element, Event, Layout, Point, Rectangle, Size};
+use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher;
@@ -169,9 +170,10 @@ where
///
/// // 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(),
@@ -181,17 +183,18 @@ where
/// );
///
/// // Update the user interface
- /// let messages = user_interface.update(
+ /// 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);
/// }
/// }
@@ -202,10 +205,9 @@ where
cursor_position: Point,
clipboard: Option<&dyn Clipboard>,
renderer: &Renderer,
- ) -> Vec<Message> {
- let mut messages = Vec::new();
-
- let base_cursor = if let Some(mut overlay) =
+ 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(
@@ -215,16 +217,20 @@ where
renderer,
);
- for event in events {
- overlay.on_event(
- event.clone(),
- Layout::new(&layer.layout),
- cursor_position,
- &mut messages,
- renderer,
- clipboard,
- );
- }
+ 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)
{
@@ -236,23 +242,28 @@ where
self.overlay = Some(layer);
- base_cursor
+ (base_cursor, event_statuses)
} else {
- cursor_position
+ (cursor_position, vec![event::Status::Ignored; events.len()])
};
- for event in events {
- self.root.widget.on_event(
- event.clone(),
- Layout::new(&self.base.layout),
- base_cursor,
- &mut messages,
- renderer,
- clipboard,
- );
- }
+ 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,
+ );
- messages
+ event_status.merge(overlay_status)
+ })
+ .collect()
}
/// Draws the [`UserInterface`] with the provided [`Renderer`].
@@ -293,9 +304,10 @@ where
/// 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(),
@@ -304,11 +316,13 @@ where
/// &mut renderer,
/// );
///
- /// let messages = user_interface.update(
+ /// // Update the user interface
+ /// let event_statuses = user_interface.update(
/// &events,
/// cursor_position,
/// None,
/// &renderer,
+ /// &mut messages
/// );
///
/// // Draw the user interface
@@ -316,7 +330,7 @@ where
///
/// cache = user_interface.into_cache();
///
- /// for message in messages {
+ /// for message in messages.drain(..) {
/// counter.update(message);
/// }
///
@@ -388,6 +402,23 @@ where
}
}
+ /// Relayouts and returns a new [`UserInterface`] using the provided
+ /// bounds.
+ ///
+ /// [`UserInterface`]: struct.UserInterface.html
+ 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,
+ )
+ }
+
/// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
/// process.
///
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 8687ce6f..d3ffe9c2 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -73,9 +73,10 @@ pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
-use crate::{
- layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point, Rectangle,
-};
+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.
///
@@ -182,7 +183,8 @@ where
_messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
+ event::Status::Ignored
}
/// Returns the overlay of the [`Element`], if there is any.
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 995ba7bc..466f6ac5 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -4,9 +4,11 @@
//!
//! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
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;
@@ -184,31 +186,38 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if self.on_press.is_some() {
let bounds = layout.bounds();
- self.state.is_pressed = bounds.contains(cursor_position);
+ if bounds.contains(cursor_position) {
+ self.state.is_pressed = true;
+
+ return event::Status::Captured;
+ }
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
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::Status::Ignored
}
fn draw(
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index e389427e..42e52aef 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -1,10 +1,14 @@
//! 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::{
- 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.
@@ -161,17 +165,21 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
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(
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index e874ad42..42a9e734 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -1,11 +1,11 @@
//! Distribute content vertically.
use std::hash::Hash;
+use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
- Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
};
use std::u32;
@@ -162,9 +162,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,
@@ -173,8 +175,8 @@ where
renderer,
clipboard,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 5b04d699..419060db 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, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
};
use std::u32;
@@ -174,7 +176,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
self.content.widget.on_event(
event,
layout.children().next().unwrap(),
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 276bfae3..acb43276 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -25,12 +25,19 @@ 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::{
- container, keyboard, layout, mouse, overlay, row, text, Clipboard, Element,
- Event, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
};
/// A collection of panes distributed using either vertical or horizontal splits
@@ -73,7 +80,7 @@ use crate::{
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
-/// PaneGrid::new(&mut state, |pane, state, focus| {
+/// 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"),
@@ -92,10 +99,9 @@ pub struct PaneGrid<'a, Message, Renderer: self::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<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
- on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
@@ -112,31 +118,13 @@ where
/// [`Pane`]: struct.Pane.html
pub fn new<T>(
state: &'a mut State<T>,
- view: impl Fn(
- Pane,
- &'a mut T,
- Option<Focus>,
- ) -> Content<'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()
};
@@ -146,13 +134,9 @@ where
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,
}
}
@@ -180,18 +164,16 @@ where
self
}
- /// Sets the modifier keys of the [`PaneGrid`].
- ///
- /// The modifier keys will need to be pressed to trigger key events.
- ///
- /// The default modifier key is `Ctrl`.
+ /// Sets the message that will be produced when a [`Pane`] of the
+ /// [`PaneGrid`] is clicked.
///
+ /// [`Pane`]: struct.Pane.html
/// [`PaneGrid`]: struct.PaneGrid.html
- pub fn modifier_keys(
- mut self,
- modifier_keys: keyboard::ModifiersState,
- ) -> Self {
- self.modifier_keys = modifier_keys;
+ pub fn on_click<F>(mut self, f: F) -> Self
+ where
+ F: 'a + Fn(Pane) -> Message,
+ {
+ self.on_click = Some(Box::new(f));
self
}
@@ -225,31 +207,6 @@ where
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>
@@ -268,24 +225,20 @@ where
);
if let Some(((pane, content), layout)) = clicked_region.next() {
- match &self.on_drag {
- Some(on_drag) => {
- if content.can_be_picked_at(layout, cursor_position) {
- let pane_position = layout.position();
+ if let Some(on_click) = &self.on_click {
+ messages.push(on_click(*pane));
+ }
- let origin = cursor_position
- - Vector::new(pane_position.x, pane_position.y);
+ if let Some(on_drag) = &self.on_drag {
+ if content.can_be_picked_at(layout, cursor_position) {
+ let pane_position = layout.position();
- self.state.pick_pane(pane, origin);
+ let origin = cursor_position
+ - Vector::new(pane_position.x, pane_position.y);
- messages
- .push(on_drag(DragEvent::Picked { pane: *pane }));
- } else {
- self.state.focus(pane);
- }
- }
- None => {
- self.state.focus(pane);
+ self.state.pick_pane(pane, origin);
+
+ messages.push(on_drag(DragEvent::Picked { pane: *pane }));
}
}
}
@@ -296,7 +249,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
- ) {
+ ) -> event::Status {
if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
@@ -323,9 +276,13 @@ where
};
messages.push(on_resize(ResizeEvent { split, ratio }));
+
+ return event::Status::Captured;
}
}
}
+
+ event::Status::Ignored
}
}
@@ -390,18 +347,6 @@ pub struct ResizeEvent {
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
@@ -452,13 +397,17 @@ 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 bounds = layout.bounds();
if bounds.contains(cursor_position) {
+ event_status = event::Status::Captured;
+
match self.on_resize {
Some((leeway, _)) => {
let relative_cursor = Point::new(
@@ -495,17 +444,10 @@ where
);
}
}
- } else {
- // TODO: Encode cursor availability in the type system
- if cursor_position.x > 0.0 && cursor_position.y > 0.0 {
- self.state.unfocus();
- }
}
}
mouse::Event::ButtonReleased(mouse::Button::Left) => {
if let Some((pane, _)) = self.state.picked_pane() {
- self.state.focus(&pane);
-
if let Some(on_drag) = &self.on_drag {
let mut dropped_region = self
.elements
@@ -527,58 +469,42 @@ where
messages.push(on_drag(event));
}
+
+ self.state.idle();
+
+ event_status = event::Status::Captured;
} else if self.state.picked_split().is_some() {
- self.state.drop_split();
+ self.state.idle();
+
+ event_status = event::Status::Captured;
}
}
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);
- }
- }
- }
- }
- }
- _ => {}
- }
- }
_ => {}
}
if self.state.picked_pane().is_none() {
- {
- self.elements.iter_mut().zip(layout.children()).for_each(
- |((_, pane), layout)| {
- pane.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
}
}
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index 1d339b75..2dac7060 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -1,8 +1,9 @@
use crate::container;
+use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::pane_grid::{self, TitleBar};
-use crate::{Clipboard, Element, Event, Hasher, Layout, Point, Size};
+use crate::{Clipboard, Element, Hasher, Layout, Point, Size};
/// The content of a [`Pane`].
///
@@ -41,9 +42,9 @@ where
self
}
- /// Sets the style of the [`TitleBar`].
+ /// Sets the style of the [`Content`].
///
- /// [`TitleBar`]: struct.TitleBar.html
+ /// [`Content`]: struct.Content.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -154,11 +155,13 @@ where
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();
- title_bar.on_event(
+ event_status = title_bar.on_event(
event.clone(),
children.next().unwrap(),
cursor_position,
@@ -172,7 +175,7 @@ where
layout
};
- self.body.on_event(
+ let body_status = self.body.on_event(
event,
body_layout,
cursor_position,
@@ -180,6 +183,8 @@ where
renderer,
clipboard,
);
+
+ event_status.merge(body_status)
}
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index fb59c846..7a51781e 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -26,22 +26,6 @@ pub struct State<T> {
pub(super) internal: Internal,
}
-/// 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> {
/// Creates a new [`State`], initializing the first pane with the provided
/// state.
@@ -72,7 +56,7 @@ impl<T> State<T> {
internal: Internal {
layout,
last_id,
- action: Action::Idle { focus: None },
+ action: Action::Idle,
},
}
}
@@ -122,45 +106,10 @@ impl<T> State<T> {
&self.internal.layout
}
- /// Returns the focused [`Pane`] of the [`State`], if there is one.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State`]: struct.State.html
- pub fn focused(&self) -> Option<Pane> {
- self.internal.focused_pane()
- }
-
- /// 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
@@ -194,20 +143,6 @@ 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);
- }
-
- /// Unfocuses the current focused [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn unfocus(&mut self) {
- self.internal.unfocus();
- }
-
/// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state.
///
@@ -236,7 +171,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))
}
@@ -277,13 +211,13 @@ impl<T> State<T> {
let _ = self.internal.layout.resize(split, ratio);
}
- /// Closes the given [`Pane`] and returns its internal state, if it exists.
+ /// Closes the given [`Pane`] and returns its internal state and its closest
+ /// sibling, if it exists.
///
/// [`Pane`]: struct.Pane.html
- pub fn close(&mut self, pane: &Pane) -> Option<T> {
+ 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
}
@@ -329,52 +263,12 @@ pub struct Internal {
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action {
- Idle {
- focus: Option<Pane>,
- },
- Dragging {
- pane: Pane,
- origin: Point,
- focus: Option<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 focused_pane(&self) -> Option<Pane> {
- match self.action {
- Action::Idle { focus } => focus,
- Action::Dragging { focus, .. } => focus,
- Action::Resizing { focus, .. } => focus,
- }
- }
-
- pub fn active_pane(&self) -> Option<Pane> {
- match self.action {
- Action::Idle { focus } => focus,
- _ => None,
- }
- }
-
pub fn picked_pane(&self) -> Option<(Pane, Point)> {
match self.action {
Action::Dragging { pane, origin, .. } => Some((pane, origin)),
@@ -405,17 +299,10 @@ impl Internal {
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, origin: Point) {
- let focus = self.focused_pane();
-
self.action = Action::Dragging {
pane: *pane,
origin,
- focus,
};
}
@@ -426,26 +313,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
index 9dfb9ae4..f8ff43eb 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -1,8 +1,7 @@
+use crate::event::{self, Event};
use crate::layout;
use crate::pane_grid;
-use crate::{
- Clipboard, Element, Event, Hasher, Layout, Point, Rectangle, Size,
-};
+use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
/// The title bar of a [`Pane`].
///
@@ -245,7 +244,7 @@ where
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();
@@ -261,7 +260,9 @@ where
messages,
renderer,
clipboard,
- );
+ )
+ } else {
+ event::Status::Ignored
}
}
}
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index e086e367..113197f7 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -1,9 +1,13 @@
//! 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::{
- layout, mouse, overlay,
- overlay::menu::{self, Menu},
- scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::borrow::Cow;
@@ -223,13 +227,15 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
- if *self.is_open {
+ 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();
@@ -238,15 +244,23 @@ where
.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,
}
}
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 06d3f846..781fffb1 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,8 +1,12 @@
//! Create choices using radio buttons.
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::row;
+use crate::text;
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;
@@ -166,15 +170,19 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if layout.bounds().contains(cursor_position) {
messages.push(self.on_click.clone());
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index bc8a3df1..6b09d0c8 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,9 +1,9 @@
//! Distribute content horizontally.
+use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
- Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
};
use std::hash::Hash;
@@ -162,9 +162,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,
@@ -173,8 +175,8 @@ where
renderer,
clipboard,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 60ec2d7d..92671ddd 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -1,7 +1,12 @@
//! 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::{
- column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event,
- Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ Align, Clipboard, Column, Element, Hasher, Layout, Length, Point,
+ Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
@@ -184,14 +189,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 }) => {
@@ -204,31 +251,21 @@ where
self.state.scroll(y, bounds, content_bounds);
}
}
+
+ return event::Status::Captured;
}
_ => {}
}
}
- 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);
-
if self.state.is_scroller_grabbed() {
match event {
Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Left,
)) => {
self.state.scroller_grabbed_at = None;
+
+ return event::Status::Captured;
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
if let (Some(scrollbar), Some(scroller_grabbed_at)) =
@@ -242,6 +279,8 @@ where
bounds,
content_bounds,
);
+
+ return event::Status::Captured;
}
}
_ => {}
@@ -266,6 +305,8 @@ where
self.state.scroller_grabbed_at =
Some(scroller_grabbed_at);
+
+ return event::Status::Captured;
}
}
}
@@ -273,28 +314,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(
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index d6e366aa..4e38fb86 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -4,9 +4,11 @@
//!
//! [`Slider`]: struct.Slider.html
//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
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};
@@ -202,7 +204,7 @@ 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 {
@@ -232,6 +234,8 @@ where
if layout.bounds().contains(cursor_position) {
change();
self.state.is_dragging = true;
+
+ return event::Status::Captured;
}
}
mouse::Event::ButtonReleased(mouse::Button::Left) => {
@@ -240,17 +244,23 @@ where
messages.push(on_release);
}
self.state.is_dragging = false;
+
+ return event::Status::Captured;
}
}
mouse::Event::CursorMoved { .. } => {
if self.state.is_dragging {
change();
+
+ return event::Status::Captured;
}
}
_ => {}
},
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 64182f2d..c067de77 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -14,11 +14,13 @@ 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::{
- keyboard, layout,
- mouse::{self, click},
- text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
- Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::u32;
@@ -163,6 +165,7 @@ where
/// Sets the style of the [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
+ /// [`State`]: struct.State.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -171,11 +174,63 @@ where
/// Returns the current [`State`] of the [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
+ /// [`State`]: struct.State.html
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.
+ ///
+ /// [`TextInput`]: struct.TextInput.html
+ /// [`Renderer`]: trait.Render.html
+ /// [`Value`]: struct.Value.html
+ 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
@@ -218,11 +273,13 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
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;
@@ -254,6 +311,8 @@ where
} else {
self.state.cursor.move_to(0);
}
+
+ self.state.is_dragging = true;
}
click::Kind::Double => {
if self.is_secure {
@@ -273,17 +332,19 @@ 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)) => {
self.state.is_dragging = false;
@@ -314,6 +375,8 @@ where
position,
);
}
+
+ return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::CharacterReceived(c))
@@ -328,167 +391,203 @@ 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(&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);
+ }) 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(&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(&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
+ 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 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,
+ );
- editor.paste(content.clone());
+ editor.paste(content.clone());
- let message = (self.on_change)(editor.contents());
- messages.push(message);
+ let message =
+ (self.on_change)(editor.contents());
+ messages.push(message);
- self.state.is_pasting = Some(content);
+ 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 platform::is_copy_paste_modifier_pressed(modifiers) {
+ 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;
+ }
+ _ => {}
}
- keyboard::KeyCode::Escape => {
- self.state.is_focused = false;
- self.state.is_dragging = false;
- self.state.is_pasting = None;
- }
- _ => {}
- },
+
+ 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::Status::Ignored
}
fn draw(
@@ -499,36 +598,7 @@ where
cursor_position: Point,
_viewport: &Rectangle,
) -> Renderer::Output {
- 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,
- &self.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,
- &self.value,
- &self.state,
- &self.style,
- )
- }
+ self.draw(renderer, layout, cursor_position, None)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -693,6 +763,20 @@ impl State {
self.cursor
}
+ /// Focuses the [`TextInput`].
+ ///
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn focus(&mut self) {
+ self.is_focused = true;
+ }
+
+ /// Unfocuses the [`TextInput`].
+ ///
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn unfocus(&mut self) {
+ self.is_focused = false;
+ }
+
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
///
/// [`Cursor`]: struct.Cursor.html
diff --git a/native/src/widget/text_input/value.rs b/native/src/widget/text_input/value.rs
index 1e9ba45b..8df74e0c 100644
--- a/native/src/widget/text_input/value.rs
+++ b/native/src/widget/text_input/value.rs
@@ -21,6 +21,15 @@ impl Value {
Self { graphemes }
}
+ /// Returns whether the [`Value`] is empty or not.
+ ///
+ /// A [`Value`] is empty when it contains no graphemes.
+ ///
+ /// [`Value`]: struct.Value.html
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
/// Returns the total amount of graphemes in the [`Value`].
///
/// [`Value`]: struct.Value.html