summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
Diffstat (limited to 'widget')
-rw-r--r--widget/src/action.rs89
-rw-r--r--widget/src/button.rs136
-rw-r--r--widget/src/canvas.rs71
-rw-r--r--widget/src/canvas/event.rs21
-rw-r--r--widget/src/canvas/program.rs15
-rw-r--r--widget/src/checkbox.rs74
-rw-r--r--widget/src/column.rs43
-rw-r--r--widget/src/combo_box.rs46
-rw-r--r--widget/src/container.rs21
-rw-r--r--widget/src/helpers.rs98
-rw-r--r--widget/src/image/viewer.rs32
-rw-r--r--widget/src/keyed/column.rs43
-rw-r--r--widget/src/lazy.rs36
-rw-r--r--widget/src/lazy/component.rs88
-rw-r--r--widget/src/lazy/responsive.rs70
-rw-r--r--widget/src/lib.rs5
-rw-r--r--widget/src/mouse_area.rs162
-rw-r--r--widget/src/overlay/menu.rs71
-rw-r--r--widget/src/pane_grid.rs444
-rw-r--r--widget/src/pane_grid/content.rs53
-rw-r--r--widget/src/pane_grid/state.rs83
-rw-r--r--widget/src/pane_grid/title_bar.rs42
-rw-r--r--widget/src/pick_list.rs85
-rw-r--r--widget/src/pin.rs270
-rw-r--r--widget/src/progress_bar.rs6
-rw-r--r--widget/src/qr_code.rs2
-rw-r--r--widget/src/radio.rs59
-rw-r--r--widget/src/row.rs53
-rw-r--r--widget/src/rule.rs8
-rw-r--r--widget/src/scrollable.rs683
-rw-r--r--widget/src/shader.rs58
-rw-r--r--widget/src/shader/event.rs25
-rw-r--r--widget/src/shader/program.rs16
-rw-r--r--widget/src/slider.rs306
-rw-r--r--widget/src/stack.rs57
-rw-r--r--widget/src/svg.rs6
-rw-r--r--widget/src/text/rich.rs24
-rw-r--r--widget/src/text_editor.rs346
-rw-r--r--widget/src/text_input.rs272
-rw-r--r--widget/src/text_input/cursor.rs4
-rw-r--r--widget/src/themer.rs27
-rw-r--r--widget/src/toggler.rs63
-rw-r--r--widget/src/tooltip.rs25
-rw-r--r--widget/src/vertical_slider.rs25
44 files changed, 2481 insertions, 1682 deletions
diff --git a/widget/src/action.rs b/widget/src/action.rs
new file mode 100644
index 00000000..1dd3a787
--- /dev/null
+++ b/widget/src/action.rs
@@ -0,0 +1,89 @@
+use crate::core::event;
+use crate::core::time::Instant;
+use crate::core::window;
+
+/// A runtime action that can be performed by some widgets.
+#[derive(Debug, Clone)]
+pub struct Action<Message> {
+ message_to_publish: Option<Message>,
+ redraw_request: Option<window::RedrawRequest>,
+ event_status: event::Status,
+}
+
+impl<Message> Action<Message> {
+ fn new() -> Self {
+ Self {
+ message_to_publish: None,
+ redraw_request: None,
+ event_status: event::Status::Ignored,
+ }
+ }
+
+ /// Creates a new "capturing" [`Action`]. A capturing [`Action`]
+ /// will make other widgets consider it final and prevent further
+ /// processing.
+ ///
+ /// Prevents "event bubbling".
+ pub fn capture() -> Self {
+ Self {
+ event_status: event::Status::Captured,
+ ..Self::new()
+ }
+ }
+
+ /// Creates a new [`Action`] that publishes the given `Message` for
+ /// the application to handle.
+ ///
+ /// Publishing a `Message` always produces a redraw.
+ pub fn publish(message: Message) -> Self {
+ Self {
+ message_to_publish: Some(message),
+ ..Self::new()
+ }
+ }
+
+ /// Creates a new [`Action`] that requests a redraw to happen as
+ /// soon as possible; without publishing any `Message`.
+ pub fn request_redraw() -> Self {
+ Self {
+ redraw_request: Some(window::RedrawRequest::NextFrame),
+ ..Self::new()
+ }
+ }
+
+ /// Creates a new [`Action`] that requests a redraw to happen at
+ /// the given [`Instant`]; without publishing any `Message`.
+ ///
+ /// This can be useful to efficiently animate content, like a
+ /// blinking caret on a text input.
+ pub fn request_redraw_at(at: Instant) -> Self {
+ Self {
+ redraw_request: Some(window::RedrawRequest::At(at)),
+ ..Self::new()
+ }
+ }
+
+ /// Marks the [`Action`] as "capturing". See [`Self::capture`].
+ pub fn and_capture(mut self) -> Self {
+ self.event_status = event::Status::Captured;
+ self
+ }
+
+ /// Converts the [`Action`] into its internal parts.
+ ///
+ /// This method is meant to be used by runtimes, libraries, or internal
+ /// widget implementations.
+ pub fn into_inner(
+ self,
+ ) -> (
+ Option<Message>,
+ Option<window::RedrawRequest>,
+ event::Status,
+ ) {
+ (
+ self.message_to_publish,
+ self.redraw_request,
+ self.event_status,
+ )
+ }
+}
diff --git a/widget/src/button.rs b/widget/src/button.rs
index 552298bb..11839d5e 100644
--- a/widget/src/button.rs
+++ b/widget/src/button.rs
@@ -17,7 +17,6 @@
//! }
//! ```
use crate::core::border::{self, Border};
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
@@ -26,9 +25,10 @@ use crate::core::theme::palette;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation;
+use crate::core::window;
use crate::core::{
- Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle,
- Shadow, Shell, Size, Theme, Vector, Widget,
+ Background, Clipboard, Color, Element, Event, Layout, Length, Padding,
+ Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,
};
/// A generic widget that produces a message when pressed.
@@ -81,6 +81,7 @@ where
padding: Padding,
clip: bool,
class: Theme::Class<'a>,
+ status: Option<Status>,
}
enum OnPress<'a, Message> {
@@ -88,7 +89,7 @@ enum OnPress<'a, Message> {
Closure(Box<dyn Fn() -> Message + 'a>),
}
-impl<'a, Message: Clone> OnPress<'a, Message> {
+impl<Message: Clone> OnPress<'_, Message> {
fn get(&self) -> Message {
match self {
OnPress::Direct(message) => message.clone(),
@@ -117,6 +118,7 @@ where
padding: DEFAULT_PADDING,
clip: false,
class: Theme::default(),
+ status: None,
}
}
@@ -270,7 +272,7 @@ where
});
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -280,8 +282,8 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- if let event::Status::Captured = self.content.as_widget_mut().on_event(
+ ) {
+ self.content.as_widget_mut().update(
&mut tree.children[0],
event.clone(),
layout.children().next().unwrap(),
@@ -290,8 +292,10 @@ where
clipboard,
shell,
viewport,
- ) {
- return event::Status::Captured;
+ );
+
+ if shell.is_event_captured() {
+ return;
}
match event {
@@ -305,14 +309,13 @@ where
state.is_pressed = true;
- return event::Status::Captured;
+ shell.capture_event();
}
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
- if let Some(on_press) = self.on_press.as_ref().map(OnPress::get)
- {
+ if let Some(on_press) = &self.on_press {
let state = tree.state.downcast_mut::<State>();
if state.is_pressed {
@@ -321,10 +324,10 @@ where
let bounds = layout.bounds();
if cursor.is_over(bounds) {
- shell.publish(on_press);
+ shell.publish(on_press.get());
}
- return event::Status::Captured;
+ shell.capture_event();
}
}
}
@@ -336,7 +339,25 @@ where
_ => {}
}
- event::Status::Ignored
+ let current_status = if self.on_press.is_none() {
+ Status::Disabled
+ } else if cursor.is_over(layout.bounds()) {
+ let state = tree.state.downcast_ref::<State>();
+
+ if state.is_pressed {
+ Status::Pressed
+ } else {
+ Status::Hovered
+ }
+ } else {
+ Status::Active
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.status = Some(current_status);
+ } else if self.status.is_some_and(|status| status != current_status) {
+ shell.request_redraw();
+ }
}
fn draw(
@@ -351,23 +372,8 @@ where
) {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
- let is_mouse_over = cursor.is_over(bounds);
-
- let status = if self.on_press.is_none() {
- Status::Disabled
- } else if is_mouse_over {
- let state = tree.state.downcast_ref::<State>();
-
- if state.is_pressed {
- Status::Pressed
- } else {
- Status::Hovered
- }
- } else {
- Status::Active
- };
-
- let style = theme.style(&self.class, status);
+ let style =
+ theme.style(&self.class, self.status.unwrap_or(Status::Disabled));
if style.background.is_some()
|| style.border.width > 0.0
@@ -471,6 +477,9 @@ pub enum Status {
}
/// The style of a button.
+///
+/// If not specified with [`Button::style`]
+/// the theme will provide the style.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Background`] of the button.
@@ -505,6 +514,54 @@ impl Default for Style {
}
/// The theme catalog of a [`Button`].
+///
+/// All themes that can be used with [`Button`]
+/// must implement this trait.
+///
+/// # Example
+/// ```no_run
+/// # use iced_widget::core::{Color, Background};
+/// # use iced_widget::button::{Catalog, Status, Style};
+/// # struct MyTheme;
+/// #[derive(Debug, Default)]
+/// pub enum ButtonClass {
+/// #[default]
+/// Primary,
+/// Secondary,
+/// Danger
+/// }
+///
+/// impl Catalog for MyTheme {
+/// type Class<'a> = ButtonClass;
+///
+/// fn default<'a>() -> Self::Class<'a> {
+/// ButtonClass::default()
+/// }
+///
+///
+/// fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
+/// let mut style = Style::default();
+///
+/// match class {
+/// ButtonClass::Primary => {
+/// style.background = Some(Background::Color(Color::from_rgb(0.529, 0.808, 0.921)));
+/// },
+/// ButtonClass::Secondary => {
+/// style.background = Some(Background::Color(Color::WHITE));
+/// },
+/// ButtonClass::Danger => {
+/// style.background = Some(Background::Color(Color::from_rgb(0.941, 0.502, 0.502)));
+/// },
+/// }
+///
+/// style
+/// }
+/// }
+/// ```
+///
+/// Although, in order to use [`Button::style`]
+/// with `MyTheme`, [`Catalog::Class`] must implement
+/// `From<StyleFn<'_, MyTheme>>`.
pub trait Catalog {
/// The item class of the [`Catalog`].
type Class<'a>;
@@ -576,6 +633,21 @@ pub fn success(theme: &Theme, status: Status) -> Style {
}
}
+/// A warning button; denoting a risky action.
+pub fn warning(theme: &Theme, status: Status) -> Style {
+ let palette = theme.extended_palette();
+ let base = styled(palette.warning.base);
+
+ match status {
+ Status::Active | Status::Pressed => base,
+ Status::Hovered => Style {
+ background: Some(Background::Color(palette.warning.strong.color)),
+ ..base
+ },
+ Status::Disabled => disabled(base),
+ }
+}
+
/// A danger button; denoting a destructive action.
pub fn danger(theme: &Theme, status: Status) -> Style {
let palette = theme.extended_palette();
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 9fbccf82..23cc3f2b 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -48,24 +48,24 @@
//! canvas(Circle { radius: 50.0 }).into()
//! }
//! ```
-pub mod event;
-
mod program;
-pub use event::Event;
pub use program::Program;
+pub use crate::core::event::Event;
pub use crate::graphics::cache::Group;
pub use crate::graphics::geometry::{
fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
LineJoin, Path, Stroke, Style, Text,
};
+pub use crate::Action;
-use crate::core;
+use crate::core::event;
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,
};
@@ -148,6 +148,7 @@ where
message_: PhantomData<Message>,
theme_: PhantomData<Theme>,
renderer_: PhantomData<Renderer>,
+ last_mouse_interaction: Option<mouse::Interaction>,
}
impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer>
@@ -166,6 +167,7 @@ where
message_: PhantomData,
theme_: PhantomData,
renderer_: PhantomData,
+ last_mouse_interaction: None,
}
}
@@ -213,42 +215,63 @@ where
layout::atomic(limits, self.width, self.height)
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
- event: core::Event,
+ event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
- _renderer: &Renderer,
+ renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- _viewport: &Rectangle,
- ) -> event::Status {
+ viewport: &Rectangle,
+ ) {
let bounds = layout.bounds();
- let canvas_event = match event {
- core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
- core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
- core::Event::Keyboard(keyboard_event) => {
- Some(Event::Keyboard(keyboard_event))
- }
- core::Event::Window(_) => None,
- };
-
- if let Some(canvas_event) = canvas_event {
- let state = tree.state.downcast_mut::<P::State>();
+ let state = tree.state.downcast_mut::<P::State>();
+ let is_redraw_request = matches!(
+ event,
+ Event::Window(window::Event::RedrawRequested(_now)),
+ );
- let (event_status, message) =
- self.program.update(state, canvas_event, bounds, cursor);
+ if let Some(action) = self.program.update(state, event, bounds, cursor)
+ {
+ let (message, redraw_request, event_status) = action.into_inner();
if let Some(message) = message {
shell.publish(message);
}
- return event_status;
+ if let Some(redraw_request) = redraw_request {
+ match redraw_request {
+ window::RedrawRequest::NextFrame => {
+ shell.request_redraw();
+ }
+ window::RedrawRequest::At(at) => {
+ shell.request_redraw_at(at);
+ }
+ }
+ }
+
+ if event_status == event::Status::Captured {
+ shell.capture_event();
+ }
}
- event::Status::Ignored
+ if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) {
+ let mouse_interaction = self
+ .mouse_interaction(tree, layout, cursor, viewport, renderer);
+
+ if is_redraw_request {
+ self.last_mouse_interaction = Some(mouse_interaction);
+ } else if self.last_mouse_interaction.is_some_and(
+ |last_mouse_interaction| {
+ last_mouse_interaction != mouse_interaction
+ },
+ ) {
+ shell.request_redraw();
+ }
+ }
}
fn mouse_interaction(
diff --git a/widget/src/canvas/event.rs b/widget/src/canvas/event.rs
deleted file mode 100644
index a8eb47f7..00000000
--- a/widget/src/canvas/event.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-//! Handle events of a canvas.
-use crate::core::keyboard;
-use crate::core::mouse;
-use crate::core::touch;
-
-pub use crate::core::event::Status;
-
-/// A [`Canvas`] event.
-///
-/// [`Canvas`]: crate::Canvas
-#[derive(Debug, Clone, PartialEq)]
-pub enum Event {
- /// A mouse event.
- Mouse(mouse::Event),
-
- /// A touch event.
- Touch(touch::Event),
-
- /// A keyboard event.
- Keyboard(keyboard::Event),
-}
diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs
index a7ded0f4..c68b2830 100644
--- a/widget/src/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -1,8 +1,8 @@
-use crate::canvas::event::{self, Event};
use crate::canvas::mouse;
-use crate::canvas::Geometry;
+use crate::canvas::{Event, Geometry};
use crate::core::Rectangle;
use crate::graphics::geometry;
+use crate::Action;
/// The state and logic of a [`Canvas`].
///
@@ -22,8 +22,9 @@ where
/// When a [`Program`] is used in a [`Canvas`], the runtime will call this
/// method for each [`Event`].
///
- /// This method can optionally return a `Message` to notify an application
- /// of any meaningful interactions.
+ /// This method can optionally return an [`Action`] to either notify an
+ /// application of any meaningful interactions, capture the event, or
+ /// request a redraw.
///
/// By default, this method does and returns nothing.
///
@@ -34,8 +35,8 @@ where
_event: Event,
_bounds: Rectangle,
_cursor: mouse::Cursor,
- ) -> (event::Status, Option<Message>) {
- (event::Status::Ignored, None)
+ ) -> Option<Action<Message>> {
+ None
}
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
@@ -84,7 +85,7 @@ where
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
- ) -> (event::Status, Option<Message>) {
+ ) -> Option<Action<Message>> {
T::update(self, state, event, bounds, cursor)
}
diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs
index 4b2f6075..663bfad1 100644
--- a/widget/src/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -31,7 +31,6 @@
//! ```
//! ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
use crate::core::alignment;
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
@@ -40,9 +39,10 @@ use crate::core::theme::palette;
use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
- Background, Border, Clipboard, Color, Element, Layout, Length, Pixels,
- Rectangle, Shell, Size, Theme, Widget,
+ Background, Border, Clipboard, Color, Element, Event, Layout, Length,
+ Pixels, Rectangle, Shell, Size, Theme, Widget,
};
/// A box that can be checked.
@@ -100,6 +100,7 @@ pub struct Checkbox<
font: Option<Renderer::Font>,
icon: Icon<Renderer::Font>,
class: Theme::Class<'a>,
+ last_status: Option<Status>,
}
impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer>
@@ -139,6 +140,7 @@ where
shaping: text::Shaping::Basic,
},
class: Theme::default(),
+ last_status: None,
}
}
@@ -245,8 +247,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Checkbox<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Checkbox<'_, Message, Theme, Renderer>
where
Renderer: text::Renderer,
Theme: Catalog,
@@ -300,7 +302,7 @@ where
)
}
- fn on_event(
+ fn update(
&mut self,
_tree: &mut Tree,
event: Event,
@@ -310,7 +312,7 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
@@ -319,14 +321,35 @@ where
if mouse_over {
if let Some(on_toggle) = &self.on_toggle {
shell.publish((on_toggle)(!self.is_checked));
- return event::Status::Captured;
+ shell.capture_event();
}
}
}
_ => {}
}
- event::Status::Ignored
+ let current_status = {
+ let is_mouse_over = cursor.is_over(layout.bounds());
+ let is_disabled = self.on_toggle.is_none();
+ let is_checked = self.is_checked;
+
+ if is_disabled {
+ Status::Disabled { is_checked }
+ } else if is_mouse_over {
+ Status::Hovered { is_checked }
+ } else {
+ Status::Active { is_checked }
+ }
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.last_status = Some(current_status);
+ } else if self
+ .last_status
+ .is_some_and(|status| status != current_status)
+ {
+ shell.request_redraw();
+ }
}
fn mouse_interaction(
@@ -351,24 +374,17 @@ where
theme: &Theme,
defaults: &renderer::Style,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ _cursor: mouse::Cursor,
viewport: &Rectangle,
) {
- let is_mouse_over = cursor.is_over(layout.bounds());
- let is_disabled = self.on_toggle.is_none();
- let is_checked = self.is_checked;
-
let mut children = layout.children();
- let status = if is_disabled {
- Status::Disabled { is_checked }
- } else if is_mouse_over {
- Status::Hovered { is_checked }
- } else {
- Status::Active { is_checked }
- };
-
- let style = theme.style(&self.class, status);
+ let style = theme.style(
+ &self.class,
+ self.last_status.unwrap_or(Status::Disabled {
+ is_checked: self.is_checked,
+ }),
+ );
{
let layout = children.next().unwrap();
@@ -429,6 +445,16 @@ where
);
}
}
+
+ fn operate(
+ &self,
+ _state: &mut Tree,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ operation: &mut dyn widget::Operation,
+ ) {
+ operation.text(None, layout.bounds(), &self.label);
+ }
}
impl<'a, Message, Theme, Renderer> From<Checkbox<'a, Message, Theme, Renderer>>
@@ -481,7 +507,7 @@ pub enum Status {
}
/// The style of a checkbox.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Background`] of the checkbox.
pub background: Background,
diff --git a/widget/src/column.rs b/widget/src/column.rs
index 213f68fc..c729cbdb 100644
--- a/widget/src/column.rs
+++ b/widget/src/column.rs
@@ -1,14 +1,13 @@
//! Distribute content vertically.
use crate::core::alignment::{self, Alignment};
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{Operation, Tree};
use crate::core::{
- Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell,
- Size, Vector, Widget,
+ Clipboard, Element, Event, Layout, Length, Padding, Pixels, Rectangle,
+ Shell, Size, Vector, Widget,
};
/// A container that distributes its contents vertically.
@@ -174,7 +173,7 @@ where
}
}
-impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer>
+impl<Message, Renderer> Default for Column<'_, Message, Renderer>
where
Renderer: crate::core::Renderer,
{
@@ -196,8 +195,8 @@ impl<'a, Message, Theme, Renderer: crate::core::Renderer>
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Column<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Column<'_, Message, Theme, Renderer>
where
Renderer: crate::core::Renderer,
{
@@ -258,7 +257,7 @@ where
});
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -268,24 +267,24 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- self.children
+ ) {
+ for ((child, state), layout) in self
+ .children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
- .map(|((child, state), layout)| {
- child.as_widget_mut().on_event(
- state,
- event.clone(),
- layout,
- cursor,
- renderer,
- clipboard,
- shell,
- viewport,
- )
- })
- .fold(event::Status::Ignored, event::Status::merge)
+ {
+ child.as_widget_mut().update(
+ state,
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ );
+ }
}
fn mouse_interaction(
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index e300f1d0..500d2bec 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -54,7 +54,6 @@
//! }
//! }
//! ```
-use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key;
use crate::core::layout::{self, Layout};
@@ -64,8 +63,10 @@ use crate::core::renderer;
use crate::core::text;
use crate::core::time::Instant;
use crate::core::widget::{self, Widget};
+use crate::core::window;
use crate::core::{
- Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Theme, Vector,
+ Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,
+ Vector,
};
use crate::overlay::menu;
use crate::text::LineHeight;
@@ -458,8 +459,8 @@ enum TextInputEvent {
TextChanged(String),
}
-impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for ComboBox<'a, T, Message, Theme, Renderer>
+impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for ComboBox<'_, T, Message, Theme, Renderer>
where
T: Display + Clone + 'static,
Message: Clone,
@@ -509,7 +510,7 @@ where
vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _, _>)]
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut widget::Tree,
event: Event,
@@ -519,7 +520,7 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let menu = tree.state.downcast_mut::<Menu<T>>();
let started_focused = {
@@ -538,7 +539,7 @@ where
let mut local_shell = Shell::new(&mut local_messages);
// Provide it to the widget
- let mut event_status = self.text_input.on_event(
+ self.text_input.update(
&mut tree.children[0],
event.clone(),
layout,
@@ -549,13 +550,27 @@ where
viewport,
);
+ if local_shell.is_event_captured() {
+ shell.capture_event();
+ }
+
+ if let Some(redraw_request) = local_shell.redraw_request() {
+ match redraw_request {
+ window::RedrawRequest::NextFrame => {
+ shell.request_redraw();
+ }
+ window::RedrawRequest::At(at) => {
+ shell.request_redraw_at(at);
+ }
+ }
+ }
+
// Then finally react to them here
for message in local_messages {
let TextInputEvent::TextChanged(new_value) = message;
if let Some(on_input) = &self.on_input {
shell.publish((on_input)(new_value.clone()));
- published_message_to_shell = true;
}
// Couple the filtered options with the `ComboBox`
@@ -576,6 +591,7 @@ where
);
});
shell.invalidate_layout();
+ shell.request_redraw();
}
let is_focused = {
@@ -619,9 +635,9 @@ where
}
}
- event_status = event::Status::Captured;
+ shell.capture_event();
+ shell.request_redraw();
}
-
(key::Named::ArrowUp, _) | (key::Named::Tab, true) => {
if let Some(index) = &mut menu.hovered_option {
if *index == 0 {
@@ -656,7 +672,8 @@ where
}
}
- event_status = event::Status::Captured;
+ shell.capture_event();
+ shell.request_redraw();
}
(key::Named::ArrowDown, _)
| (key::Named::Tab, false)
@@ -703,7 +720,8 @@ where
}
}
- event_status = event::Status::Captured;
+ shell.capture_event();
+ shell.request_redraw();
}
_ => {}
}
@@ -724,7 +742,7 @@ where
published_message_to_shell = true;
// Unfocus the input
- let _ = self.text_input.on_event(
+ self.text_input.update(
&mut tree.children[0],
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
@@ -761,8 +779,6 @@ where
}
}
}
-
- event_status
}
fn mouse_interaction(
diff --git a/widget/src/container.rs b/widget/src/container.rs
index b256540c..a411a7d2 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -21,7 +21,6 @@
//! ```
use crate::core::alignment::{self, Alignment};
use crate::core::border::{self, Border};
-use crate::core::event::{self, Event};
use crate::core::gradient::{self, Gradient};
use crate::core::layout;
use crate::core::mouse;
@@ -30,7 +29,7 @@ use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Operation};
use crate::core::{
- self, color, Background, Clipboard, Color, Element, Layout, Length,
+ self, color, Background, Clipboard, Color, Element, Event, Layout, Length,
Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
Widget,
};
@@ -229,8 +228,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Container<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Container<'_, Message, Theme, Renderer>
where
Theme: Catalog,
Renderer: core::Renderer,
@@ -298,7 +297,7 @@ where
);
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -308,8 +307,8 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- self.content.as_widget_mut().on_event(
+ ) {
+ self.content.as_widget_mut().update(
tree,
event,
layout.children().next().unwrap(),
@@ -318,7 +317,7 @@ where
clipboard,
shell,
viewport,
- )
+ );
}
fn mouse_interaction(
@@ -494,11 +493,11 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
impl Operation<Option<Rectangle>> for VisibleBounds {
fn scrollable(
&mut self,
- _state: &mut dyn widget::operation::Scrollable,
_id: Option<&widget::Id>,
bounds: Rectangle,
_content_bounds: Rectangle,
translation: Vector,
+ _state: &mut dyn widget::operation::Scrollable,
) {
match self.scrollables.last() {
Some((last_translation, last_viewport, _depth)) => {
@@ -572,7 +571,7 @@ pub fn visible_bounds(id: Id) -> Task<Option<Rectangle>> {
}
/// The appearance of a container.
-#[derive(Debug, Clone, Copy, Default)]
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Style {
/// The text [`Color`] of the container.
pub text_color: Option<Color>,
@@ -651,7 +650,7 @@ pub trait Catalog {
/// A styling function for a [`Container`].
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
-impl<'a, Theme> From<Style> for StyleFn<'a, Theme> {
+impl<Theme> From<Style> for StyleFn<'_, Theme> {
fn from(style: Style) -> Self {
Box::new(move |_theme| style)
}
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 52290a54..cdfd2daf 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -24,7 +24,7 @@ use crate::text_input::{self, TextInput};
use crate::toggler::{self, Toggler};
use crate::tooltip::{self, Tooltip};
use crate::vertical_slider::{self, VerticalSlider};
-use crate::{Column, MouseArea, Row, Space, Stack, Themer};
+use crate::{Column, MouseArea, Pin, Row, Space, Stack, Themer};
use std::borrow::Borrow;
use std::ops::RangeInclusive;
@@ -249,6 +249,38 @@ where
container(content).center(Length::Fill)
}
+/// Creates a new [`Pin`] widget with the given content.
+///
+/// A [`Pin`] widget positions its contents at some fixed coordinates inside of its boundaries.
+///
+/// # Example
+/// ```no_run
+/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
+/// # pub type State = ();
+/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
+/// use iced::widget::pin;
+/// use iced::Fill;
+///
+/// enum Message {
+/// // ...
+/// }
+///
+/// fn view(state: &State) -> Element<'_, Message> {
+/// pin("This text is displayed at coordinates (50, 50)!")
+/// .x(50)
+/// .y(50)
+/// .into()
+/// }
+/// ```
+pub fn pin<'a, Message, Theme, Renderer>(
+ content: impl Into<Element<'a, Message, Theme, Renderer>>,
+) -> Pin<'a, Message, Theme, Renderer>
+where
+ Renderer: core::Renderer,
+{
+ Pin::new(content)
+}
+
/// Creates a new [`Column`] with the given children.
///
/// Columns distribute their children vertically.
@@ -363,19 +395,18 @@ where
Theme: 'a,
Renderer: core::Renderer + 'a,
{
- use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
- use crate::core::{Rectangle, Shell, Size};
+ use crate::core::{Event, Rectangle, Shell, Size};
struct Opaque<'a, Message, Theme, Renderer> {
content: Element<'a, Message, Theme, Renderer>,
}
- impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Opaque<'a, Message, Theme, Renderer>
+ impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Opaque<'_, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
@@ -439,7 +470,7 @@ where
.operate(state, layout, renderer, operation);
}
- fn on_event(
+ fn update(
&mut self,
state: &mut Tree,
event: Event,
@@ -449,25 +480,19 @@ where
clipboard: &mut dyn core::Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let is_mouse_press = matches!(
event,
core::Event::Mouse(mouse::Event::ButtonPressed(_))
);
- if let core::event::Status::Captured =
- self.content.as_widget_mut().on_event(
- state, event, layout, cursor, renderer, clipboard, shell,
- viewport,
- )
- {
- return event::Status::Captured;
- }
+ self.content.as_widget_mut().update(
+ state, event, layout, cursor, renderer, clipboard, shell,
+ viewport,
+ );
if is_mouse_press && cursor.is_over(layout.bounds()) {
- event::Status::Captured
- } else {
- event::Status::Ignored
+ shell.capture_event();
}
}
@@ -530,22 +555,22 @@ where
Theme: 'a,
Renderer: core::Renderer + 'a,
{
- use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
- use crate::core::{Rectangle, Shell, Size};
+ use crate::core::{Event, Rectangle, Shell, Size};
struct Hover<'a, Message, Theme, Renderer> {
base: Element<'a, Message, Theme, Renderer>,
top: Element<'a, Message, Theme, Renderer>,
is_top_focused: bool,
is_top_overlay_active: bool,
+ is_hovered: bool,
}
- impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Hover<'a, Message, Theme, Renderer>
+ impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Hover<'_, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
@@ -648,7 +673,7 @@ where
}
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -658,11 +683,13 @@ where
clipboard: &mut dyn core::Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let mut children = layout.children().zip(&mut tree.children);
let (base_layout, base_tree) = children.next().unwrap();
let (top_layout, top_tree) = children.next().unwrap();
+ let is_hovered = cursor.is_over(layout.bounds());
+
if matches!(event, Event::Window(window::Event::RedrawRequested(_)))
{
let mut count_focused = operation::focusable::count();
@@ -678,19 +705,23 @@ where
operation::Outcome::Some(count) => count.focused.is_some(),
_ => false,
};
+
+ self.is_hovered = is_hovered;
+ } else if is_hovered != self.is_hovered {
+ shell.request_redraw();
}
- let top_status = if matches!(
+ if matches!(
event,
Event::Mouse(
mouse::Event::CursorMoved { .. }
| mouse::Event::ButtonReleased(_)
)
- ) || cursor.is_over(layout.bounds())
+ ) || is_hovered
|| self.is_top_focused
|| self.is_top_overlay_active
{
- self.top.as_widget_mut().on_event(
+ self.top.as_widget_mut().update(
top_tree,
event.clone(),
top_layout,
@@ -699,16 +730,14 @@ where
clipboard,
shell,
viewport,
- )
- } else {
- event::Status::Ignored
+ );
};
- if top_status == event::Status::Captured {
- return top_status;
+ if shell.is_event_captured() {
+ return;
}
- self.base.as_widget_mut().on_event(
+ self.base.as_widget_mut().update(
base_tree,
event.clone(),
base_layout,
@@ -717,7 +746,7 @@ where
clipboard,
shell,
viewport,
- )
+ );
}
fn mouse_interaction(
@@ -777,6 +806,7 @@ where
top: top.into(),
is_top_focused: false,
is_top_overlay_active: false,
+ is_hovered: false,
})
}
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index b1aad22c..20a7955f 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -1,13 +1,12 @@
//! Zoom and pan on an image.
-use crate::core::event::{self, Event};
use crate::core::image::{self, FilterMethod};
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Clipboard, ContentFit, Element, Image, Layout, Length, Pixels, Point,
- Radians, Rectangle, Shell, Size, Vector, Widget,
+ Clipboard, ContentFit, Element, Event, Image, Layout, Length, Pixels,
+ Point, Radians, Rectangle, Shell, Size, Vector, Widget,
};
/// A frame that displays an image with the ability to zoom in/out and pan.
@@ -149,7 +148,7 @@ where
layout::Node::new(final_size)
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -157,15 +156,15 @@ where
cursor: mouse::Cursor,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- _shell: &mut Shell<'_, Message>,
+ shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let bounds = layout.bounds();
match event {
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
let Some(cursor_position) = cursor.position_over(bounds) else {
- return event::Status::Ignored;
+ return;
};
match delta {
@@ -216,29 +215,25 @@ where
}
}
- event::Status::Captured
+ shell.capture_event();
}
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let Some(cursor_position) = cursor.position_over(bounds) else {
- return event::Status::Ignored;
+ return;
};
let state = tree.state.downcast_mut::<State>();
state.cursor_grabbed_at = Some(cursor_position);
state.starting_offset = state.current_offset;
-
- event::Status::Captured
+ shell.capture_event();
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
let state = tree.state.downcast_mut::<State>();
if state.cursor_grabbed_at.is_some() {
state.cursor_grabbed_at = None;
-
- event::Status::Captured
- } else {
- event::Status::Ignored
+ shell.capture_event();
}
}
Event::Mouse(mouse::Event::CursorMoved { position }) => {
@@ -278,13 +273,10 @@ where
};
state.current_offset = Vector::new(x, y);
-
- event::Status::Captured
- } else {
- event::Status::Ignored
+ shell.capture_event();
}
}
- _ => event::Status::Ignored,
+ _ => {}
}
}
diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs
index 5852ede1..ab0b0bde 100644
--- a/widget/src/keyed/column.rs
+++ b/widget/src/keyed/column.rs
@@ -1,5 +1,4 @@
//! Keyed columns distribute content vertically while keeping continuity.
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
@@ -7,8 +6,8 @@ use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation;
use crate::core::{
- Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle,
- Shell, Size, Vector, Widget,
+ Alignment, Clipboard, Element, Event, Layout, Length, Padding, Pixels,
+ Rectangle, Shell, Size, Vector, Widget,
};
/// A container that distributes its contents vertically while keeping continuity.
@@ -186,7 +185,7 @@ where
}
}
-impl<'a, Key, Message, Renderer> Default for Column<'a, Key, Message, Renderer>
+impl<Key, Message, Renderer> Default for Column<'_, Key, Message, Renderer>
where
Key: Copy + PartialEq,
Renderer: crate::core::Renderer,
@@ -203,8 +202,8 @@ where
keys: Vec<Key>,
}
-impl<'a, Key, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Column<'a, Key, Message, Theme, Renderer>
+impl<Key, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Column<'_, Key, Message, Theme, Renderer>
where
Renderer: crate::core::Renderer,
Key: Copy + PartialEq + 'static,
@@ -298,7 +297,7 @@ where
});
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -308,24 +307,24 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- self.children
+ ) {
+ for ((child, state), layout) in self
+ .children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
- .map(|((child, state), layout)| {
- child.as_widget_mut().on_event(
- state,
- event.clone(),
- layout,
- cursor,
- renderer,
- clipboard,
- shell,
- viewport,
- )
- })
- .fold(event::Status::Ignored, event::Status::merge)
+ {
+ child.as_widget_mut().update(
+ state,
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ );
+ }
}
fn mouse_interaction(
diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs
index 6642c986..c6710e30 100644
--- a/widget/src/lazy.rs
+++ b/widget/src/lazy.rs
@@ -10,7 +10,6 @@ pub use responsive::Responsive;
mod cache;
-use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::overlay;
@@ -19,7 +18,7 @@ use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Widget};
use crate::core::Element;
use crate::core::{
- self, Clipboard, Length, Point, Rectangle, Shell, Size, Vector,
+ self, Clipboard, Event, Length, Point, Rectangle, Shell, Size, Vector,
};
use crate::runtime::overlay::Nested;
@@ -196,7 +195,7 @@ where
});
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -206,9 +205,9 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
self.with_element_mut(|element| {
- element.as_widget_mut().on_event(
+ element.as_widget_mut().update(
&mut tree.children[0],
event,
layout,
@@ -217,8 +216,8 @@ where
clipboard,
shell,
viewport,
- )
- })
+ );
+ });
}
fn mouse_interaction(
@@ -269,7 +268,7 @@ where
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
- ) -> Option<overlay::Element<'_, Message, Theme, Renderer>> {
+ ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let overlay = InnerBuilder {
cell: self.element.borrow().as_ref().unwrap().clone(),
element: self
@@ -322,16 +321,14 @@ struct Overlay<'a, Message, Theme, Renderer>(
Option<Inner<'a, Message, Theme, Renderer>>,
);
-impl<'a, Message, Theme, Renderer> Drop
- for Overlay<'a, Message, Theme, Renderer>
-{
+impl<Message, Theme, Renderer> Drop for Overlay<'_, Message, Theme, Renderer> {
fn drop(&mut self) {
let heads = self.0.take().unwrap().into_heads();
(*heads.cell.borrow_mut()) = Some(heads.element);
}
}
-impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> {
+impl<Message, Theme, Renderer> Overlay<'_, Message, Theme, Renderer> {
fn with_overlay_maybe<T>(
&self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
@@ -351,8 +348,8 @@ impl<'a, Message, Theme, Renderer> Overlay<'a, Message, Theme, Renderer> {
}
}
-impl<'a, Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
- for Overlay<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
+ for Overlay<'_, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
@@ -387,7 +384,7 @@ where
.unwrap_or_default()
}
- fn on_event(
+ fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@@ -395,11 +392,10 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- ) -> event::Status {
- self.with_overlay_mut_maybe(|overlay| {
- overlay.on_event(event, layout, cursor, renderer, clipboard, shell)
- })
- .unwrap_or(event::Status::Ignored)
+ ) {
+ let _ = self.with_overlay_mut_maybe(|overlay| {
+ overlay.update(event, layout, cursor, renderer, clipboard, shell);
+ });
}
fn is_over(
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index c7bc1264..15b8b62e 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -1,12 +1,12 @@
//! Build and reuse custom widgets using The Elm Architecture.
#![allow(deprecated)]
-use crate::core::event;
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
Widget,
@@ -141,8 +141,8 @@ struct State<'a, Message: 'a, Theme: 'a, Renderer: 'a, Event: 'a, S: 'a> {
element: Option<Element<'this, Event, Theme, Renderer>>,
}
-impl<'a, Message, Theme, Renderer, Event, S>
- Instance<'a, Message, Theme, Renderer, Event, S>
+impl<Message, Theme, Renderer, Event, S>
+ Instance<'_, Message, Theme, Renderer, Event, S>
where
S: Default + 'static,
Renderer: renderer::Renderer,
@@ -251,8 +251,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer, Event, S> Widget<Message, Theme, Renderer>
- for Instance<'a, Message, Theme, Renderer, Event, S>
+impl<Message, Theme, Renderer, Event, S> Widget<Message, Theme, Renderer>
+ for Instance<'_, Message, Theme, Renderer, Event, S>
where
S: 'static + Default,
Renderer: core::Renderer,
@@ -311,7 +311,7 @@ where
})
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: core::Event,
@@ -321,13 +321,13 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
- let event_status = self.with_element_mut(|element| {
- element.as_widget_mut().on_event(
+ self.with_element_mut(|element| {
+ element.as_widget_mut().update(
&mut t.borrow_mut().as_mut().unwrap().children[0],
event,
layout,
@@ -336,13 +336,24 @@ where
clipboard,
&mut local_shell,
viewport,
- )
+ );
});
+ if local_shell.is_event_captured() {
+ shell.capture_event();
+ }
+
local_shell.revalidate_layout(|| shell.invalidate_layout());
if let Some(redraw_request) = local_shell.redraw_request() {
- shell.request_redraw(redraw_request);
+ match redraw_request {
+ window::RedrawRequest::NextFrame => {
+ shell.request_redraw();
+ }
+ window::RedrawRequest::At(at) => {
+ shell.request_redraw_at(at);
+ }
+ }
}
if !local_messages.is_empty() {
@@ -369,8 +380,6 @@ where
shell.invalidate_layout();
}
-
- event_status
}
fn operate(
@@ -495,8 +504,8 @@ struct Overlay<'a, 'b, Message, Theme, Renderer, Event, S>(
Option<Inner<'a, 'b, Message, Theme, Renderer, Event, S>>,
);
-impl<'a, 'b, Message, Theme, Renderer, Event, S> Drop
- for Overlay<'a, 'b, Message, Theme, Renderer, Event, S>
+impl<Message, Theme, Renderer, Event, S> Drop
+ for Overlay<'_, '_, Message, Theme, Renderer, Event, S>
{
fn drop(&mut self) {
if let Some(heads) = self.0.take().map(Inner::into_heads) {
@@ -520,8 +529,8 @@ struct OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S> {
overlay: Option<Overlay<'a, 'b, Message, Theme, Renderer, Event, S>>,
}
-impl<'a, 'b, Message, Theme, Renderer, Event, S>
- OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S>
+impl<Message, Theme, Renderer, Event, S>
+ OverlayInstance<'_, '_, Message, Theme, Renderer, Event, S>
{
fn with_overlay_maybe<T>(
&self,
@@ -554,9 +563,9 @@ impl<'a, 'b, Message, Theme, Renderer, Event, S>
}
}
-impl<'a, 'b, Message, Theme, Renderer, Event, S>
+impl<Message, Theme, Renderer, Event, S>
overlay::Overlay<Message, Theme, Renderer>
- for OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S>
+ for OverlayInstance<'_, '_, Message, Theme, Renderer, Event, S>
where
Renderer: core::Renderer,
S: 'static + Default,
@@ -592,7 +601,7 @@ where
.unwrap_or_default()
}
- fn on_event(
+ fn update(
&mut self,
event: core::Event,
layout: Layout<'_>,
@@ -600,27 +609,36 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- ) -> event::Status {
+ ) {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
- let event_status = self
- .with_overlay_mut_maybe(|overlay| {
- overlay.on_event(
- event,
- layout,
- cursor,
- renderer,
- clipboard,
- &mut local_shell,
- )
- })
- .unwrap_or(event::Status::Ignored);
+ let _ = self.with_overlay_mut_maybe(|overlay| {
+ overlay.update(
+ event,
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ &mut local_shell,
+ );
+ });
+
+ if local_shell.is_event_captured() {
+ shell.capture_event();
+ }
local_shell.revalidate_layout(|| shell.invalidate_layout());
if let Some(redraw_request) = local_shell.redraw_request() {
- shell.request_redraw(redraw_request);
+ match redraw_request {
+ window::RedrawRequest::NextFrame => {
+ shell.request_redraw();
+ }
+ window::RedrawRequest::At(at) => {
+ shell.request_redraw_at(at);
+ }
+ }
}
if !local_messages.is_empty() {
@@ -658,8 +676,6 @@ where
shell.invalidate_layout();
}
-
- event_status
}
fn is_over(
diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs
index a7a99f56..8129336e 100644
--- a/widget/src/lazy/responsive.rs
+++ b/widget/src/lazy/responsive.rs
@@ -1,4 +1,3 @@
-use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::overlay;
@@ -6,8 +5,8 @@ use crate::core::renderer;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
- Widget,
+ self, Clipboard, Element, Event, Length, Point, Rectangle, Shell, Size,
+ Vector, Widget,
};
use crate::horizontal_space;
use crate::runtime::overlay::Nested;
@@ -83,15 +82,21 @@ where
new_size: Size,
view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
) {
- if self.size == new_size {
- return;
- }
+ if self.size != new_size {
+ self.element = view(new_size);
+ self.size = new_size;
+ self.layout = None;
- self.element = view(new_size);
- self.size = new_size;
- self.layout = None;
+ tree.diff(&self.element);
+ } else {
+ let is_tree_empty =
+ tree.tag == tree::Tag::stateless() && tree.children.is_empty();
- tree.diff(&self.element);
+ if is_tree_empty {
+ self.layout = None;
+ tree.diff(&self.element);
+ }
+ }
}
fn resolve<R, T>(
@@ -126,8 +131,8 @@ struct State {
tree: RefCell<Tree>,
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Responsive<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Responsive<'_, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
@@ -180,7 +185,7 @@ where
);
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -190,20 +195,20 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let state = tree.state.downcast_mut::<State>();
let mut content = self.content.borrow_mut();
let mut local_messages = vec![];
let mut local_shell = Shell::new(&mut local_messages);
- let status = content.resolve(
+ content.resolve(
&mut state.tree.borrow_mut(),
renderer,
layout,
&self.view,
|tree, renderer, layout, element| {
- element.as_widget_mut().on_event(
+ element.as_widget_mut().update(
tree,
event,
layout,
@@ -212,7 +217,7 @@ where
clipboard,
&mut local_shell,
viewport,
- )
+ );
},
);
@@ -221,8 +226,6 @@ where
}
shell.merge(local_shell, std::convert::identity);
-
- status
}
fn draw(
@@ -355,9 +358,7 @@ struct Overlay<'a, 'b, Message, Theme, Renderer> {
),
}
-impl<'a, 'b, Message, Theme, Renderer>
- Overlay<'a, 'b, Message, Theme, Renderer>
-{
+impl<Message, Theme, Renderer> Overlay<'_, '_, Message, Theme, Renderer> {
fn with_overlay_maybe<T>(
&self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
@@ -377,9 +378,8 @@ impl<'a, 'b, Message, Theme, Renderer>
}
}
-impl<'a, 'b, Message, Theme, Renderer>
- overlay::Overlay<Message, Theme, Renderer>
- for Overlay<'a, 'b, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
+ for Overlay<'_, '_, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
@@ -414,7 +414,7 @@ where
.unwrap_or_default()
}
- fn on_event(
+ fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@@ -422,28 +422,20 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- ) -> event::Status {
+ ) {
let mut is_layout_invalid = false;
- let event_status = self
- .with_overlay_mut_maybe(|overlay| {
- let event_status = overlay.on_event(
- event, layout, cursor, renderer, clipboard, shell,
- );
-
- is_layout_invalid = shell.is_layout_invalid();
+ let _ = self.with_overlay_mut_maybe(|overlay| {
+ overlay.update(event, layout, cursor, renderer, clipboard, shell);
- event_status
- })
- .unwrap_or(event::Status::Ignored);
+ is_layout_invalid = shell.is_layout_invalid();
+ });
if is_layout_invalid {
self.with_overlay_mut(|(_overlay, layout)| {
**layout = None;
});
}
-
- event_status
}
fn is_over(
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index a68720d6..38c9929a 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -8,8 +8,10 @@ pub use iced_renderer::graphics;
pub use iced_runtime as runtime;
pub use iced_runtime::core;
+mod action;
mod column;
mod mouse_area;
+mod pin;
mod row;
mod space;
mod stack;
@@ -62,6 +64,8 @@ pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
#[doc(no_inline)]
+pub use pin::Pin;
+#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;
@@ -131,4 +135,5 @@ pub use qr_code::QRCode;
pub mod markdown;
pub use crate::core::theme::{self, Theme};
+pub use action::Action;
pub use renderer::Renderer;
diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs
index c5a37ae3..bdc81bdf 100644
--- a/widget/src/mouse_area.rs
+++ b/widget/src/mouse_area.rs
@@ -1,5 +1,4 @@
//! A container for capturing mouse events.
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
@@ -7,8 +6,8 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::{tree, Operation, Tree};
use crate::core::{
- Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
- Widget,
+ Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size,
+ Vector, Widget,
};
/// Emit messages on mouse events.
@@ -164,8 +163,8 @@ impl<'a, Message, Theme, Renderer> MouseArea<'a, Message, Theme, Renderer> {
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for MouseArea<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for MouseArea<'_, Message, Theme, Renderer>
where
Renderer: renderer::Renderer,
Message: Clone,
@@ -216,7 +215,7 @@ where
);
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -226,8 +225,8 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- if let event::Status::Captured = self.content.as_widget_mut().on_event(
+ ) {
+ self.content.as_widget_mut().update(
&mut tree.children[0],
event.clone(),
layout,
@@ -236,11 +235,13 @@ where
clipboard,
shell,
viewport,
- ) {
- return event::Status::Captured;
+ );
+
+ if shell.is_event_captured() {
+ return;
}
- update(self, tree, event, layout, cursor, shell)
+ update(self, tree, event, layout, cursor, shell);
}
fn mouse_interaction(
@@ -329,7 +330,7 @@ fn update<Message: Clone, Theme, Renderer>(
layout: Layout<'_>,
cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
-) -> event::Status {
+) {
let state: &mut State = tree.state.downcast_mut();
let cursor_position = cursor.position();
@@ -363,104 +364,71 @@ fn update<Message: Clone, Theme, Renderer>(
}
if !cursor.is_over(layout.bounds()) {
- return event::Status::Ignored;
+ return;
}
- if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) = event
- {
- let mut captured = false;
-
- if let Some(message) = widget.on_press.as_ref() {
- captured = true;
- shell.publish(message.clone());
- }
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if let Some(message) = widget.on_press.as_ref() {
+ shell.publish(message.clone());
+ shell.capture_event();
+ }
- if let Some(position) = cursor_position {
- if let Some(message) = widget.on_double_click.as_ref() {
- let new_click = mouse::Click::new(
- position,
- mouse::Button::Left,
- state.previous_click,
- );
+ if let Some(position) = cursor_position {
+ if let Some(message) = widget.on_double_click.as_ref() {
+ let new_click = mouse::Click::new(
+ position,
+ mouse::Button::Left,
+ state.previous_click,
+ );
- if matches!(new_click.kind(), mouse::click::Kind::Double) {
- shell.publish(message.clone());
- }
+ if matches!(new_click.kind(), mouse::click::Kind::Double) {
+ shell.publish(message.clone());
+ }
- state.previous_click = Some(new_click);
+ state.previous_click = Some(new_click);
- // Even if this is not a double click, but the press is nevertheless
- // processed by us and should not be popup to parent widgets.
- captured = true;
+ // Even if this is not a double click, but the press is nevertheless
+ // processed by us and should not be popup to parent widgets.
+ shell.capture_event();
+ }
}
}
-
- if captured {
- return event::Status::Captured;
- }
- }
-
- if let Some(message) = widget.on_release.as_ref() {
- if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerLifted { .. }) = event
- {
- shell.publish(message.clone());
-
- return event::Status::Captured;
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. }) => {
+ if let Some(message) = widget.on_release.as_ref() {
+ shell.publish(message.clone());
+ }
}
- }
-
- if let Some(message) = widget.on_right_press.as_ref() {
- if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
- event
- {
- shell.publish(message.clone());
-
- return event::Status::Captured;
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
+ if let Some(message) = widget.on_right_press.as_ref() {
+ shell.publish(message.clone());
+ shell.capture_event();
+ }
}
- }
-
- if let Some(message) = widget.on_right_release.as_ref() {
- if let Event::Mouse(mouse::Event::ButtonReleased(
- mouse::Button::Right,
- )) = event
- {
- shell.publish(message.clone());
-
- return event::Status::Captured;
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => {
+ if let Some(message) = widget.on_right_release.as_ref() {
+ shell.publish(message.clone());
+ }
}
- }
-
- if let Some(message) = widget.on_middle_press.as_ref() {
- if let Event::Mouse(mouse::Event::ButtonPressed(
- mouse::Button::Middle,
- )) = event
- {
- shell.publish(message.clone());
-
- return event::Status::Captured;
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => {
+ if let Some(message) = widget.on_middle_press.as_ref() {
+ shell.publish(message.clone());
+ shell.capture_event();
+ }
}
- }
-
- if let Some(message) = widget.on_middle_release.as_ref() {
- if let Event::Mouse(mouse::Event::ButtonReleased(
- mouse::Button::Middle,
- )) = event
- {
- shell.publish(message.clone());
-
- return event::Status::Captured;
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => {
+ if let Some(message) = widget.on_middle_release.as_ref() {
+ shell.publish(message.clone());
+ }
}
- }
-
- if let Some(on_scroll) = widget.on_scroll.as_ref() {
- if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
- shell.publish(on_scroll(delta));
-
- return event::Status::Captured;
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ if let Some(on_scroll) = widget.on_scroll.as_ref() {
+ shell.publish(on_scroll(delta));
+ shell.capture_event();
+ }
}
+ _ => {}
}
-
- event::Status::Ignored
}
diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index f05ae40a..611476ce 100644
--- a/widget/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -1,17 +1,17 @@
//! Build and show dropdown menus.
use crate::core::alignment;
use crate::core::border::{self, Border};
-use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::text::{self, Text};
use crate::core::touch;
-use crate::core::widget::Tree;
+use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
- Background, Clipboard, Color, Length, Padding, Pixels, Point, Rectangle,
- Size, Theme, Vector,
+ Background, Clipboard, Color, Event, Length, Padding, Pixels, Point,
+ Rectangle, Size, Theme, Vector,
};
use crate::core::{Element, Shell, Widget};
use crate::scrollable::{self, Scrollable};
@@ -227,9 +227,8 @@ where
}
}
-impl<'a, 'b, Message, Theme, Renderer>
- crate::core::Overlay<Message, Theme, Renderer>
- for Overlay<'a, 'b, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> crate::core::Overlay<Message, Theme, Renderer>
+ for Overlay<'_, '_, Message, Theme, Renderer>
where
Theme: Catalog,
Renderer: text::Renderer,
@@ -262,7 +261,7 @@ where
})
}
- fn on_event(
+ fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@@ -270,13 +269,13 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- ) -> event::Status {
+ ) {
let bounds = layout.bounds();
- self.list.on_event(
+ self.list.update(
self.state, event, layout, cursor, renderer, clipboard, shell,
&bounds,
- )
+ );
}
fn mouse_interaction(
@@ -334,13 +333,25 @@ where
class: &'a <Theme as Catalog>::Class<'b>,
}
-impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for List<'a, 'b, T, Message, Theme, Renderer>
+struct ListState {
+ is_hovered: Option<bool>,
+}
+
+impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for List<'_, '_, T, Message, Theme, Renderer>
where
T: Clone + ToString,
Theme: Catalog,
Renderer: text::Renderer,
{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<Option<bool>>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(ListState { is_hovered: None })
+ }
+
fn size(&self) -> Size<Length> {
Size {
width: Length::Fill,
@@ -374,9 +385,9 @@ where
layout::Node::new(size)
}
- fn on_event(
+ fn update(
&mut self,
- _state: &mut Tree,
+ tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
@@ -384,14 +395,14 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if cursor.is_over(layout.bounds()) {
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
shell.publish((self.on_selected)(option.clone()));
- return event::Status::Captured;
+ shell.capture_event();
}
}
}
@@ -411,14 +422,18 @@ where
let new_hovered_option =
(cursor_position.y / option_height) as usize;
- if let Some(on_option_hovered) = self.on_option_hovered {
- if *self.hovered_option != Some(new_hovered_option) {
- if let Some(option) =
- self.options.get(new_hovered_option)
+ if *self.hovered_option != Some(new_hovered_option) {
+ if let Some(option) =
+ self.options.get(new_hovered_option)
+ {
+ if let Some(on_option_hovered) =
+ self.on_option_hovered
{
shell
.publish(on_option_hovered(option.clone()));
}
+
+ shell.request_redraw();
}
}
@@ -443,7 +458,7 @@ where
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
shell.publish((self.on_selected)(option.clone()));
- return event::Status::Captured;
+ shell.capture_event();
}
}
}
@@ -451,7 +466,15 @@ where
_ => {}
}
- event::Status::Ignored
+ let state = tree.state.downcast_mut::<ListState>();
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ state.is_hovered = Some(cursor.is_over(layout.bounds()));
+ } else if state.is_hovered.is_some_and(|is_hovered| {
+ is_hovered != cursor.is_over(layout.bounds())
+ }) {
+ shell.request_redraw();
+ }
}
fn mouse_interaction(
@@ -562,7 +585,7 @@ where
}
/// The appearance of a [`Menu`].
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Background`] of the menu.
pub background: Background,
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 9d4dda25..5c3b343c 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -79,7 +79,6 @@ pub use state::State;
pub use title_bar::TitleBar;
use crate::container;
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay::{self, Group};
@@ -87,8 +86,9 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
- self, Background, Border, Clipboard, Color, Element, Layout, Length,
+ self, Background, Border, Clipboard, Color, Element, Event, Layout, Length,
Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
@@ -157,7 +157,9 @@ pub struct PaneGrid<
Theme: Catalog,
Renderer: core::Renderer,
{
- contents: Contents<'a, Content<'a, Message, Theme, Renderer>>,
+ internal: &'a state::Internal,
+ panes: Vec<Pane>,
+ contents: Vec<Content<'a, Message, Theme, Renderer>>,
width: Length,
height: Length,
spacing: f32,
@@ -165,6 +167,7 @@ pub struct PaneGrid<
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
class: <Theme as Catalog>::Class<'a>,
+ last_mouse_interaction: Option<mouse::Interaction>,
}
impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer>
@@ -180,29 +183,19 @@ where
state: &'a State<T>,
view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>,
) -> Self {
- let contents = if let Some((pane, pane_state)) =
- state.maximized.and_then(|pane| {
- state.panes.get(&pane).map(|pane_state| (pane, pane_state))
- }) {
- Contents::Maximized(
- pane,
- view(pane, pane_state, true),
- Node::Pane(pane),
- )
- } else {
- Contents::All(
- state
- .panes
- .iter()
- .map(|(pane, pane_state)| {
- (*pane, view(*pane, pane_state, false))
- })
- .collect(),
- &state.internal,
- )
- };
+ let panes = state.panes.keys().copied().collect();
+ let contents = state
+ .panes
+ .iter()
+ .map(|(pane, pane_state)| match state.maximized() {
+ Some(p) if *pane == p => view(*pane, pane_state, true),
+ _ => view(*pane, pane_state, false),
+ })
+ .collect();
Self {
+ internal: &state.internal,
+ panes,
contents,
width: Length::Fill,
height: Length::Fill,
@@ -211,6 +204,7 @@ where
on_drag: None,
on_resize: None,
class: <Theme as Catalog>::default(),
+ last_mouse_interaction: None,
}
}
@@ -248,7 +242,9 @@ where
where
F: 'a + Fn(DragEvent) -> Message,
{
- self.on_drag = Some(Box::new(f));
+ if self.internal.maximized().is_none() {
+ self.on_drag = Some(Box::new(f));
+ }
self
}
@@ -265,7 +261,9 @@ where
where
F: 'a + Fn(ResizeEvent) -> Message,
{
- self.on_resize = Some((leeway.into().0, Box::new(f)));
+ if self.internal.maximized().is_none() {
+ self.on_resize = Some((leeway.into().0, Box::new(f)));
+ }
self
}
@@ -291,46 +289,114 @@ where
}
fn drag_enabled(&self) -> bool {
- (!self.contents.is_maximized())
+ self.internal
+ .maximized()
+ .is_none()
.then(|| self.on_drag.is_some())
.unwrap_or_default()
}
+
+ fn grid_interaction(
+ &self,
+ action: &state::Action,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ ) -> Option<mouse::Interaction> {
+ if action.picked_pane().is_some() {
+ return Some(mouse::Interaction::Grabbing);
+ }
+
+ let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway);
+ let node = self.internal.layout();
+
+ let resize_axis =
+ action.picked_split().map(|(_, axis)| axis).or_else(|| {
+ resize_leeway.and_then(|leeway| {
+ let cursor_position = cursor.position()?;
+ let bounds = layout.bounds();
+
+ let splits =
+ node.split_regions(self.spacing, bounds.size());
+
+ let relative_cursor = Point::new(
+ cursor_position.x - bounds.x,
+ cursor_position.y - bounds.y,
+ );
+
+ hovered_split(
+ splits.iter(),
+ self.spacing + leeway,
+ relative_cursor,
+ )
+ .map(|(_, axis, _)| axis)
+ })
+ });
+
+ if let Some(resize_axis) = resize_axis {
+ return Some(match resize_axis {
+ Axis::Horizontal => mouse::Interaction::ResizingVertically,
+ Axis::Vertical => mouse::Interaction::ResizingHorizontally,
+ });
+ }
+
+ None
+ }
+}
+
+#[derive(Default)]
+struct Memory {
+ action: state::Action,
+ order: Vec<Pane>,
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for PaneGrid<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for PaneGrid<'_, Message, Theme, Renderer>
where
Theme: Catalog,
Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
- tree::Tag::of::<state::Action>()
+ tree::Tag::of::<Memory>()
}
fn state(&self) -> tree::State {
- tree::State::new(state::Action::Idle)
+ tree::State::new(Memory::default())
}
fn children(&self) -> Vec<Tree> {
- self.contents
- .iter()
- .map(|(_, content)| content.state())
- .collect()
+ self.contents.iter().map(Content::state).collect()
}
fn diff(&self, tree: &mut Tree) {
- match &self.contents {
- Contents::All(contents, _) => tree.diff_children_custom(
- contents,
- |state, (_, content)| content.diff(state),
- |(_, content)| content.state(),
- ),
- Contents::Maximized(_, content, _) => tree.diff_children_custom(
- &[content],
- |state, content| content.diff(state),
- |content| content.state(),
- ),
- }
+ let Memory { order, .. } = tree.state.downcast_ref();
+
+ // `Pane` always increments and is iterated by Ord so new
+ // states are always added at the end. We can simply remove
+ // states which no longer exist and `diff_children` will
+ // diff the remaining values in the correct order and
+ // add new states at the end
+
+ let mut i = 0;
+ let mut j = 0;
+ tree.children.retain(|_| {
+ let retain = self.panes.get(i) == order.get(j);
+
+ if retain {
+ i += 1;
+ }
+ j += 1;
+
+ retain
+ });
+
+ tree.diff_children_custom(
+ &self.contents,
+ |state, content| content.diff(state),
+ Content::state,
+ );
+
+ let Memory { order, .. } = tree.state.downcast_mut();
+ order.clone_from(&self.panes);
}
fn size(&self) -> Size<Length> {
@@ -347,14 +413,23 @@ where
limits: &layout::Limits,
) -> layout::Node {
let size = limits.resolve(self.width, self.height, Size::ZERO);
- let node = self.contents.layout();
- let regions = node.pane_regions(self.spacing, size);
+ let regions = self.internal.layout().pane_regions(self.spacing, size);
let children = self
- .contents
+ .panes
.iter()
+ .copied()
+ .zip(&self.contents)
.zip(tree.children.iter_mut())
.filter_map(|((pane, content), tree)| {
+ if self
+ .internal
+ .maximized()
+ .is_some_and(|maximized| maximized != pane)
+ {
+ return Some(layout::Node::new(Size::ZERO));
+ }
+
let region = regions.get(&pane)?;
let size = Size::new(region.width, region.height);
@@ -379,17 +454,24 @@ where
operation: &mut dyn widget::Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
- self.contents
+ self.panes
.iter()
+ .copied()
+ .zip(&self.contents)
.zip(&mut tree.children)
.zip(layout.children())
- .for_each(|(((_pane, content), state), layout)| {
+ .filter(|(((pane, _), _), _)| {
+ self.internal
+ .maximized()
+ .map_or(true, |maximized| *pane == maximized)
+ })
+ .for_each(|(((_, content), state), layout)| {
content.operate(state, layout, renderer, operation);
});
});
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -399,11 +481,9 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- let mut event_status = event::Status::Ignored;
-
- let action = tree.state.downcast_mut::<state::Action>();
- let node = self.contents.layout();
+ ) {
+ let Memory { action, .. } = tree.state.downcast_mut();
+ let node = self.internal.layout();
let on_drag = if self.drag_enabled() {
&self.on_drag
@@ -411,13 +491,43 @@ where
&None
};
+ let picked_pane = action.picked_pane().map(|(pane, _)| pane);
+
+ for (((pane, content), tree), layout) in self
+ .panes
+ .iter()
+ .copied()
+ .zip(&mut self.contents)
+ .zip(&mut tree.children)
+ .zip(layout.children())
+ .filter(|(((pane, _), _), _)| {
+ self.internal
+ .maximized()
+ .map_or(true, |maximized| *pane == maximized)
+ })
+ {
+ let is_picked = picked_pane == Some(pane);
+
+ content.update(
+ tree,
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ is_picked,
+ );
+ }
+
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let bounds = layout.bounds();
if let Some(cursor_position) = cursor.position_over(bounds) {
- event_status = event::Status::Captured;
+ shell.capture_event();
match &self.on_resize {
Some((leeway, _)) => {
@@ -448,7 +558,10 @@ where
layout,
cursor_position,
shell,
- self.contents.iter(),
+ self.panes
+ .iter()
+ .copied()
+ .zip(&self.contents),
&self.on_click,
on_drag,
);
@@ -460,7 +573,7 @@ where
layout,
cursor_position,
shell,
- self.contents.iter(),
+ self.panes.iter().copied().zip(&self.contents),
&self.on_click,
on_drag,
);
@@ -486,8 +599,10 @@ where
}
} else {
let dropped_region = self
- .contents
+ .panes
.iter()
+ .copied()
+ .zip(&self.contents)
.zip(layout.children())
.find_map(|(target, layout)| {
layout_region(
@@ -513,13 +628,13 @@ where
};
shell.publish(on_drag(event));
+ } else {
+ shell.publish(on_drag(DragEvent::Canceled {
+ pane,
+ }));
}
}
}
-
- event_status = event::Status::Captured;
- } else if action.picked_split().is_some() {
- event_status = event::Status::Captured;
}
*action = state::Action::Idle;
@@ -561,37 +676,48 @@ where
ratio,
}));
- event_status = event::Status::Captured;
+ shell.capture_event();
}
}
+ } else if action.picked_pane().is_some() {
+ shell.request_redraw();
}
}
}
_ => {}
}
- let picked_pane = action.picked_pane().map(|(pane, _)| pane);
-
- self.contents
- .iter_mut()
- .zip(&mut tree.children)
- .zip(layout.children())
- .map(|(((pane, content), tree), layout)| {
- let is_picked = picked_pane == Some(pane);
-
- content.on_event(
- tree,
- event.clone(),
- layout,
- cursor,
- renderer,
- clipboard,
- shell,
- viewport,
- is_picked,
- )
- })
- .fold(event_status, event::Status::merge)
+ if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) {
+ let interaction = self
+ .grid_interaction(action, layout, cursor)
+ .or_else(|| {
+ self.panes
+ .iter()
+ .zip(&self.contents)
+ .zip(layout.children())
+ .filter(|((&pane, _content), _layout)| {
+ self.internal
+ .maximized()
+ .map_or(true, |maximized| pane == maximized)
+ })
+ .find_map(|((_pane, content), layout)| {
+ content.grid_interaction(
+ layout,
+ cursor,
+ on_drag.is_some(),
+ )
+ })
+ })
+ .unwrap_or(mouse::Interaction::None);
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.last_mouse_interaction = Some(interaction);
+ } else if self.last_mouse_interaction.is_some_and(
+ |last_mouse_interaction| last_mouse_interaction != interaction,
+ ) {
+ shell.request_redraw();
+ }
+ }
}
fn mouse_interaction(
@@ -602,50 +728,26 @@ where
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- let action = tree.state.downcast_ref::<state::Action>();
-
- if action.picked_pane().is_some() {
- return mouse::Interaction::Grabbing;
- }
-
- let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway);
- let node = self.contents.layout();
-
- let resize_axis =
- action.picked_split().map(|(_, axis)| axis).or_else(|| {
- resize_leeway.and_then(|leeway| {
- let cursor_position = cursor.position()?;
- let bounds = layout.bounds();
-
- let splits =
- node.split_regions(self.spacing, bounds.size());
-
- let relative_cursor = Point::new(
- cursor_position.x - bounds.x,
- cursor_position.y - bounds.y,
- );
-
- hovered_split(
- splits.iter(),
- self.spacing + leeway,
- relative_cursor,
- )
- .map(|(_, axis, _)| axis)
- })
- });
+ let Memory { action, .. } = tree.state.downcast_ref();
- if let Some(resize_axis) = resize_axis {
- return match resize_axis {
- Axis::Horizontal => mouse::Interaction::ResizingVertically,
- Axis::Vertical => mouse::Interaction::ResizingHorizontally,
- };
+ if let Some(grid_interaction) =
+ self.grid_interaction(action, layout, cursor)
+ {
+ return grid_interaction;
}
- self.contents
+ self.panes
.iter()
+ .copied()
+ .zip(&self.contents)
.zip(&tree.children)
.zip(layout.children())
- .map(|(((_pane, content), tree), layout)| {
+ .filter(|(((pane, _), _), _)| {
+ self.internal
+ .maximized()
+ .map_or(true, |maximized| *pane == maximized)
+ })
+ .map(|(((_, content), tree), layout)| {
content.mouse_interaction(
tree,
layout,
@@ -669,16 +771,10 @@ where
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
- let action = tree.state.downcast_ref::<state::Action>();
- let node = self.contents.layout();
+ let Memory { action, .. } = tree.state.downcast_ref();
+ let node = self.internal.layout();
let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway);
- let contents = self
- .contents
- .iter()
- .zip(&tree.children)
- .map(|((pane, content), tree)| (pane, (content, tree)));
-
let picked_pane = action.picked_pane().filter(|(_, origin)| {
cursor
.position()
@@ -747,8 +843,18 @@ where
let style = Catalog::style(theme, &self.class);
- for ((id, (content, tree)), pane_layout) in
- contents.zip(layout.children())
+ for (((id, content), tree), pane_layout) in self
+ .panes
+ .iter()
+ .copied()
+ .zip(&self.contents)
+ .zip(&tree.children)
+ .zip(layout.children())
+ .filter(|(((pane, _), _), _)| {
+ self.internal
+ .maximized()
+ .map_or(true, |maximized| maximized == *pane)
+ })
{
match picked_pane {
Some((dragging, origin)) if id == dragging => {
@@ -881,13 +987,23 @@ where
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
- ) -> Option<overlay::Element<'_, Message, Theme, Renderer>> {
+ ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let children = self
- .contents
- .iter_mut()
+ .panes
+ .iter()
+ .copied()
+ .zip(&mut self.contents)
.zip(&mut tree.children)
.zip(layout.children())
- .filter_map(|(((_, content), state), layout)| {
+ .filter_map(|(((pane, content), state), layout)| {
+ if self
+ .internal
+ .maximized()
+ .is_some_and(|maximized| maximized != pane)
+ {
+ return None;
+ }
+
content.overlay(state, layout, renderer, translation)
})
.collect::<Vec<_>>();
@@ -1136,52 +1252,6 @@ fn hovered_split<'a>(
})
}
-/// The visible contents of the [`PaneGrid`]
-#[derive(Debug)]
-pub enum Contents<'a, T> {
- /// All panes are visible
- All(Vec<(Pane, T)>, &'a state::Internal),
- /// A maximized pane is visible
- Maximized(Pane, T, Node),
-}
-
-impl<'a, T> Contents<'a, T> {
- /// Returns the layout [`Node`] of the [`Contents`]
- pub fn layout(&self) -> &Node {
- match self {
- Contents::All(_, state) => state.layout(),
- Contents::Maximized(_, _, layout) => layout,
- }
- }
-
- /// Returns an iterator over the values of the [`Contents`]
- pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> {
- match self {
- Contents::All(contents, _) => Box::new(
- contents.iter().map(|(pane, content)| (*pane, content)),
- ),
- Contents::Maximized(pane, content, _) => {
- Box::new(std::iter::once((*pane, content)))
- }
- }
- }
-
- fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> {
- match self {
- Contents::All(contents, _) => Box::new(
- contents.iter_mut().map(|(pane, content)| (*pane, content)),
- ),
- Contents::Maximized(pane, content, _) => {
- Box::new(std::iter::once((*pane, content)))
- }
- }
- }
-
- fn is_maximized(&self) -> bool {
- matches!(self, Self::Maximized(..))
- }
-}
-
/// The appearance of a [`PaneGrid`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs
index ec0676b1..be5e5066 100644
--- a/widget/src/pane_grid/content.rs
+++ b/widget/src/pane_grid/content.rs
@@ -1,12 +1,12 @@
use crate::container;
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{self, Tree};
use crate::core::{
- self, Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector,
+ self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size,
+ Vector,
};
use crate::pane_grid::{Draggable, TitleBar};
@@ -73,7 +73,7 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Content<'_, Message, Theme, Renderer>
where
Theme: container::Catalog,
Renderer: core::Renderer,
@@ -239,7 +239,7 @@ where
);
}
- pub(crate) fn on_event(
+ pub(crate) fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -250,13 +250,11 @@ where
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
is_picked: bool,
- ) -> 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(
+ title_bar.update(
&mut tree.children[1],
event.clone(),
children.next().unwrap(),
@@ -272,10 +270,8 @@ where
layout
};
- let body_status = if is_picked {
- event::Status::Ignored
- } else {
- self.body.as_widget_mut().on_event(
+ if !is_picked {
+ self.body.as_widget_mut().update(
&mut tree.children[0],
event,
body_layout,
@@ -284,10 +280,33 @@ where
clipboard,
shell,
viewport,
- )
- };
+ );
+ }
+ }
+
+ pub(crate) fn grid_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ drag_enabled: bool,
+ ) -> Option<mouse::Interaction> {
+ let title_bar = self.title_bar.as_ref()?;
+
+ let mut children = layout.children();
+ let title_bar_layout = children.next().unwrap();
+
+ let is_over_pick_area = cursor
+ .position()
+ .map(|cursor_position| {
+ title_bar.is_over_pick_area(title_bar_layout, cursor_position)
+ })
+ .unwrap_or_default();
+
+ if is_over_pick_area && drag_enabled {
+ return Some(mouse::Interaction::Grab);
+ }
- event_status.merge(body_status)
+ None
}
pub(crate) fn mouse_interaction(
@@ -382,8 +401,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Draggable
- for &Content<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Draggable
+ for &Content<'_, Message, Theme, Renderer>
where
Theme: container::Catalog,
Renderer: core::Renderer,
diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs
index c20c3b9c..2f8a64ea 100644
--- a/widget/src/pane_grid/state.rs
+++ b/widget/src/pane_grid/state.rs
@@ -6,7 +6,8 @@ use crate::pane_grid::{
Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target,
};
-use rustc_hash::FxHashMap;
+use std::borrow::Cow;
+use std::collections::BTreeMap;
/// The state of a [`PaneGrid`].
///
@@ -25,17 +26,12 @@ pub struct State<T> {
/// The panes of the [`PaneGrid`].
///
/// [`PaneGrid`]: super::PaneGrid
- pub panes: FxHashMap<Pane, T>,
+ pub panes: BTreeMap<Pane, T>,
/// The internal state of the [`PaneGrid`].
///
/// [`PaneGrid`]: super::PaneGrid
pub internal: Internal,
-
- /// The maximized [`Pane`] of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: super::PaneGrid
- pub(super) maximized: Option<Pane>,
}
impl<T> State<T> {
@@ -52,16 +48,12 @@ impl<T> State<T> {
/// Creates a new [`State`] with the given [`Configuration`].
pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {
- let mut panes = FxHashMap::default();
+ let mut panes = BTreeMap::default();
let internal =
Internal::from_configuration(&mut panes, config.into(), 0);
- State {
- panes,
- internal,
- maximized: None,
- }
+ State { panes, internal }
}
/// Returns the total amount of panes in the [`State`].
@@ -214,7 +206,7 @@ impl<T> State<T> {
}
let _ = self.panes.insert(new_pane, state);
- let _ = self.maximized.take();
+ let _ = self.internal.maximized.take();
Some((new_pane, new_split))
}
@@ -228,8 +220,11 @@ impl<T> State<T> {
) {
if let Some((state, _)) = self.close(pane) {
if let Some((new_pane, _)) = self.split(axis, target, state) {
+ // Ensure new node corresponds to original closed `Pane` for state continuity
+ self.relabel(new_pane, pane);
+
if swap {
- self.swap(target, new_pane);
+ self.swap(target, pane);
}
}
}
@@ -259,13 +254,27 @@ impl<T> State<T> {
&mut self,
axis: Axis,
pane: Pane,
- swap: bool,
+ inverse: bool,
) {
if let Some((state, _)) = self.close(pane) {
- let _ = self.split_node(axis, None, state, swap);
+ if let Some((new_pane, _)) =
+ self.split_node(axis, None, state, inverse)
+ {
+ // Ensure new node corresponds to original closed `Pane` for state continuity
+ self.relabel(new_pane, pane);
+ }
}
}
+ fn relabel(&mut self, target: Pane, label: Pane) {
+ self.swap(target, label);
+
+ let _ = self
+ .panes
+ .remove(&target)
+ .and_then(|state| self.panes.insert(label, state));
+ }
+
/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
@@ -303,8 +312,8 @@ impl<T> State<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 self.maximized == Some(pane) {
- let _ = self.maximized.take();
+ if self.internal.maximized == Some(pane) {
+ let _ = self.internal.maximized.take();
}
if let Some(sibling) = self.internal.layout.remove(pane) {
@@ -319,7 +328,7 @@ impl<T> State<T> {
///
/// [`PaneGrid`]: super::PaneGrid
pub fn maximize(&mut self, pane: Pane) {
- self.maximized = Some(pane);
+ self.internal.maximized = Some(pane);
}
/// Restore the currently maximized [`Pane`] to it's normal size. All panes
@@ -327,14 +336,14 @@ impl<T> State<T> {
///
/// [`PaneGrid`]: super::PaneGrid
pub fn restore(&mut self) {
- let _ = self.maximized.take();
+ let _ = self.internal.maximized.take();
}
/// Returns the maximized [`Pane`] of the [`PaneGrid`].
///
/// [`PaneGrid`]: super::PaneGrid
pub fn maximized(&self) -> Option<Pane> {
- self.maximized
+ self.internal.maximized
}
}
@@ -345,6 +354,7 @@ impl<T> State<T> {
pub struct Internal {
layout: Node,
last_id: usize,
+ maximized: Option<Pane>,
}
impl Internal {
@@ -353,7 +363,7 @@ impl Internal {
///
/// [`PaneGrid`]: super::PaneGrid
pub fn from_configuration<T>(
- panes: &mut FxHashMap<Pane, T>,
+ panes: &mut BTreeMap<Pane, T>,
content: Configuration<T>,
next_id: usize,
) -> Self {
@@ -390,18 +400,34 @@ impl Internal {
}
};
- Self { layout, last_id }
+ Self {
+ layout,
+ last_id,
+ maximized: None,
+ }
+ }
+
+ pub(super) fn layout(&self) -> Cow<'_, Node> {
+ match self.maximized {
+ Some(pane) => Cow::Owned(Node::Pane(pane)),
+ None => Cow::Borrowed(&self.layout),
+ }
+ }
+
+ pub(super) fn maximized(&self) -> Option<Pane> {
+ self.maximized
}
}
/// The current action of a [`PaneGrid`].
///
/// [`PaneGrid`]: super::PaneGrid
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Action {
/// The [`PaneGrid`] is idle.
///
/// [`PaneGrid`]: super::PaneGrid
+ #[default]
Idle,
/// A [`Pane`] in the [`PaneGrid`] is being dragged.
///
@@ -440,10 +466,3 @@ impl Action {
}
}
}
-
-impl Internal {
- /// The layout [`Node`] of the [`Internal`] state
- pub fn layout(&self) -> &Node {
- &self.layout
- }
-}
diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs
index 5002b4f7..4bd2c2f6 100644
--- a/widget/src/pane_grid/title_bar.rs
+++ b/widget/src/pane_grid/title_bar.rs
@@ -1,13 +1,12 @@
use crate::container;
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{self, Tree};
use crate::core::{
- self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
- Vector,
+ self, Clipboard, Element, Event, Layout, Padding, Point, Rectangle, Shell,
+ Size, Vector,
};
use crate::pane_grid::controls::Controls;
@@ -99,7 +98,7 @@ where
}
}
-impl<'a, Message, Theme, Renderer> TitleBar<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> TitleBar<'_, Message, Theme, Renderer>
where
Theme: container::Catalog,
Renderer: core::Renderer,
@@ -428,7 +427,7 @@ where
}
}
- pub(crate) fn on_event(
+ pub(crate) fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -438,7 +437,7 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let mut children = layout.children();
let padded = children.next().unwrap();
@@ -446,15 +445,16 @@ where
let title_layout = children.next().unwrap();
let mut show_title = true;
- let control_status = if let Some(controls) = &mut self.controls {
+ if let Some(controls) = &mut self.controls {
let controls_layout = children.next().unwrap();
+
if title_layout.bounds().width + controls_layout.bounds().width
> padded.bounds().width
{
if let Some(compact) = controls.compact.as_mut() {
let compact_layout = children.next().unwrap();
- compact.as_widget_mut().on_event(
+ compact.as_widget_mut().update(
&mut tree.children[2],
event.clone(),
compact_layout,
@@ -463,11 +463,11 @@ where
clipboard,
shell,
viewport,
- )
+ );
} else {
show_title = false;
- controls.full.as_widget_mut().on_event(
+ controls.full.as_widget_mut().update(
&mut tree.children[1],
event.clone(),
controls_layout,
@@ -476,10 +476,10 @@ where
clipboard,
shell,
viewport,
- )
+ );
}
} else {
- controls.full.as_widget_mut().on_event(
+ controls.full.as_widget_mut().update(
&mut tree.children[1],
event.clone(),
controls_layout,
@@ -488,14 +488,12 @@ where
clipboard,
shell,
viewport,
- )
+ );
}
- } else {
- event::Status::Ignored
- };
+ }
- let title_status = if show_title {
- self.content.as_widget_mut().on_event(
+ if show_title {
+ self.content.as_widget_mut().update(
&mut tree.children[0],
event,
title_layout,
@@ -504,12 +502,8 @@ where
clipboard,
shell,
viewport,
- )
- } else {
- event::Status::Ignored
- };
-
- control_status.merge(title_status)
+ );
+ }
}
pub(crate) fn mouse_interaction(
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs
index ff54fe8a..6708e7cd 100644
--- a/widget/src/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -61,7 +61,6 @@
//! }
//! ```
use crate::core::alignment;
-use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::layout;
use crate::core::mouse;
@@ -71,9 +70,10 @@ use crate::core::text::paragraph;
use crate::core::text::{self, Text};
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
- Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
- Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
+ Background, Border, Clipboard, Color, Element, Event, Layout, Length,
+ Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::overlay::menu::{self, Menu};
@@ -173,6 +173,7 @@ pub struct PickList<
handle: Handle<Renderer::Font>,
class: <Theme as Catalog>::Class<'a>,
menu_class: <Theme as menu::Catalog>::Class<'a>,
+ last_status: Option<Status>,
}
impl<'a, T, L, V, Message, Theme, Renderer>
@@ -208,6 +209,7 @@ where
handle: Handle::default(),
class: <Theme as Catalog>::default(),
menu_class: <Theme as Catalog>::default_menu(),
+ last_status: None,
}
}
@@ -425,7 +427,7 @@ where
layout::Node::new(size)
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -435,13 +437,12 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
+ let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
+
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- let state =
- tree.state.downcast_mut::<State<Renderer::Paragraph>>();
-
if state.is_open {
// Event wasn't processed by overlay, so cursor was clicked either outside its
// bounds or on the drop-down, either way we close the overlay.
@@ -451,7 +452,7 @@ where
shell.publish(on_close.clone());
}
- event::Status::Captured
+ shell.capture_event();
} else if cursor.is_over(layout.bounds()) {
let selected = self.selected.as_ref().map(Borrow::borrow);
@@ -466,17 +467,12 @@ where
shell.publish(on_open.clone());
}
- event::Status::Captured
- } else {
- event::Status::Ignored
+ shell.capture_event();
}
}
Event::Mouse(mouse::Event::WheelScrolled {
delta: mouse::ScrollDelta::Lines { y, .. },
}) => {
- let state =
- tree.state.downcast_mut::<State<Renderer::Paragraph>>();
-
if state.keyboard_modifiers.command()
&& cursor.is_over(layout.bounds())
&& !state.is_open
@@ -513,20 +509,34 @@ where
shell.publish((self.on_select)(next_option.clone()));
}
- event::Status::Captured
- } else {
- event::Status::Ignored
+ shell.capture_event();
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
- let state =
- tree.state.downcast_mut::<State<Renderer::Paragraph>>();
-
state.keyboard_modifiers = modifiers;
+ }
+ _ => {}
+ };
+
+ let status = {
+ let is_hovered = cursor.is_over(layout.bounds());
- event::Status::Ignored
+ if state.is_open {
+ Status::Opened { is_hovered }
+ } else if is_hovered {
+ Status::Hovered
+ } else {
+ Status::Active
}
- _ => event::Status::Ignored,
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.last_status = Some(status);
+ } else if self
+ .last_status
+ .is_some_and(|last_status| last_status != status)
+ {
+ shell.request_redraw();
}
}
@@ -555,7 +565,7 @@ where
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ _cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let font = self.font.unwrap_or_else(|| renderer.default_font());
@@ -563,18 +573,12 @@ where
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
let bounds = layout.bounds();
- let is_mouse_over = cursor.is_over(bounds);
- let is_selected = selected.is_some();
- let status = if state.is_open {
- Status::Opened
- } else if is_mouse_over {
- Status::Hovered
- } else {
- Status::Active
- };
-
- let style = Catalog::style(theme, &self.class, status);
+ let style = Catalog::style(
+ theme,
+ &self.class,
+ self.last_status.unwrap_or(Status::Active),
+ );
renderer.fill_quad(
renderer::Quad {
@@ -671,7 +675,7 @@ where
wrapping: text::Wrapping::default(),
},
Point::new(bounds.x + self.padding.left, bounds.center_y()),
- if is_selected {
+ if selected.is_some() {
style.text_color
} else {
style.placeholder_color
@@ -824,11 +828,14 @@ pub enum Status {
/// The [`PickList`] is being hovered.
Hovered,
/// The [`PickList`] is open.
- Opened,
+ Opened {
+ /// Whether the [`PickList`] is hovered, while open.
+ is_hovered: bool,
+ },
}
/// The appearance of a pick list.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The text [`Color`] of the pick list.
pub text_color: Color,
@@ -898,7 +905,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
match status {
Status::Active => active,
- Status::Hovered | Status::Opened => Style {
+ Status::Hovered | Status::Opened { .. } => Style {
border: Border {
color: palette.primary.strong.color,
..active.border
diff --git a/widget/src/pin.rs b/widget/src/pin.rs
new file mode 100644
index 00000000..7c1aca61
--- /dev/null
+++ b/widget/src/pin.rs
@@ -0,0 +1,270 @@
+//! A pin widget positions a widget at some fixed coordinates inside its boundaries.
+//!
+//! # Example
+//! ```no_run
+//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
+//! # pub type State = ();
+//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
+//! use iced::widget::pin;
+//! use iced::Fill;
+//!
+//! enum Message {
+//! // ...
+//! }
+//!
+//! fn view(state: &State) -> Element<'_, Message> {
+//! pin("This text is displayed at coordinates (50, 50)!")
+//! .x(50)
+//! .y(50)
+//! .into()
+//! }
+//! ```
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget;
+use crate::core::{
+ self, Clipboard, Element, Event, Layout, Length, Pixels, Point, Rectangle,
+ Shell, Size, Vector, Widget,
+};
+
+/// A widget that positions its contents at some fixed coordinates inside of its boundaries.
+///
+/// By default, a [`Pin`] widget will try to fill its parent.
+///
+/// # Example
+/// ```no_run
+/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::core::Length::Fill; }
+/// # pub type State = ();
+/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
+/// use iced::widget::pin;
+/// use iced::Fill;
+///
+/// enum Message {
+/// // ...
+/// }
+///
+/// fn view(state: &State) -> Element<'_, Message> {
+/// pin("This text is displayed at coordinates (50, 50)!")
+/// .x(50)
+/// .y(50)
+/// .into()
+/// }
+/// ```
+#[allow(missing_debug_implementations)]
+pub struct Pin<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
+where
+ Renderer: core::Renderer,
+{
+ content: Element<'a, Message, Theme, Renderer>,
+ width: Length,
+ height: Length,
+ position: Point,
+}
+
+impl<'a, Message, Theme, Renderer> Pin<'a, Message, Theme, Renderer>
+where
+ Renderer: core::Renderer,
+{
+ /// Creates a [`Pin`] widget with the given content.
+ pub fn new(
+ content: impl Into<Element<'a, Message, Theme, Renderer>>,
+ ) -> Self {
+ Self {
+ content: content.into(),
+ width: Length::Fill,
+ height: Length::Fill,
+ position: Point::ORIGIN,
+ }
+ }
+
+ /// Sets the width of the [`Pin`].
+ pub fn width(mut self, width: impl Into<Length>) -> Self {
+ self.width = width.into();
+ self
+ }
+
+ /// Sets the height of the [`Pin`].
+ pub fn height(mut self, height: impl Into<Length>) -> Self {
+ self.height = height.into();
+ self
+ }
+
+ /// Sets the position of the [`Pin`]; where the pinned widget will be displayed.
+ pub fn position(mut self, position: impl Into<Point>) -> Self {
+ self.position = position.into();
+ self
+ }
+
+ /// Sets the X coordinate of the [`Pin`].
+ pub fn x(mut self, x: impl Into<Pixels>) -> Self {
+ self.position.x = x.into().0;
+ self
+ }
+
+ /// Sets the Y coordinate of the [`Pin`].
+ pub fn y(mut self, y: impl Into<Pixels>) -> Self {
+ self.position.y = y.into().0;
+ self
+ }
+}
+
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Pin<'_, Message, Theme, Renderer>
+where
+ Renderer: core::Renderer,
+{
+ fn tag(&self) -> widget::tree::Tag {
+ self.content.as_widget().tag()
+ }
+
+ fn state(&self) -> widget::tree::State {
+ self.content.as_widget().state()
+ }
+
+ fn children(&self) -> Vec<widget::Tree> {
+ self.content.as_widget().children()
+ }
+
+ fn diff(&self, tree: &mut widget::Tree) {
+ self.content.as_widget().diff(tree);
+ }
+
+ fn size(&self) -> Size<Length> {
+ Size {
+ width: self.width,
+ height: self.height,
+ }
+ }
+
+ fn layout(
+ &self,
+ tree: &mut widget::Tree,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits.width(self.width).height(self.height);
+
+ let available =
+ limits.max() - Size::new(self.position.x, self.position.y);
+
+ let node = self
+ .content
+ .as_widget()
+ .layout(tree, renderer, &layout::Limits::new(Size::ZERO, available))
+ .move_to(self.position);
+
+ let size = limits.resolve(self.width, self.height, node.size());
+ layout::Node::with_children(size, vec![node])
+ }
+
+ fn operate(
+ &self,
+ tree: &mut widget::Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation,
+ ) {
+ self.content.as_widget().operate(
+ tree,
+ layout.children().next().unwrap(),
+ renderer,
+ operation,
+ );
+ }
+
+ fn update(
+ &mut self,
+ tree: &mut widget::Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
+ ) {
+ self.content.as_widget_mut().update(
+ tree,
+ event,
+ layout.children().next().unwrap(),
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ );
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &widget::Tree,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content.as_widget().mouse_interaction(
+ tree,
+ layout.children().next().unwrap(),
+ cursor,
+ viewport,
+ renderer,
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &widget::Tree,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+
+ if let Some(clipped_viewport) = bounds.intersection(viewport) {
+ self.content.as_widget().draw(
+ tree,
+ renderer,
+ theme,
+ style,
+ layout.children().next().unwrap(),
+ cursor,
+ &clipped_viewport,
+ );
+ }
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ tree: &'b mut widget::Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ translation: Vector,
+ ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
+ self.content.as_widget_mut().overlay(
+ tree,
+ layout.children().next().unwrap(),
+ renderer,
+ translation,
+ )
+ }
+}
+
+impl<'a, Message, Theme, Renderer> From<Pin<'a, Message, Theme, Renderer>>
+ for Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: 'a,
+ Renderer: core::Renderer + 'a,
+{
+ fn from(
+ pin: Pin<'a, Message, Theme, Renderer>,
+ ) -> Element<'a, Message, Theme, Renderer> {
+ Element::new(pin)
+ }
+}
diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs
index 8c665c8c..8f85dcf3 100644
--- a/widget/src/progress_bar.rs
+++ b/widget/src/progress_bar.rs
@@ -117,8 +117,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for ProgressBar<'a, Theme>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for ProgressBar<'_, Theme>
where
Theme: Catalog,
Renderer: core::Renderer,
@@ -208,7 +208,7 @@ where
}
/// The appearance of a progress bar.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Background`] of the progress bar.
pub background: Background,
diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs
index d1834465..458f3588 100644
--- a/widget/src/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -116,7 +116,7 @@ where
}
}
-impl<'a, Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'a, Theme>
+impl<Message, Theme> Widget<Message, Theme, Renderer> for QRCode<'_, Theme>
where
Theme: Catalog,
{
diff --git a/widget/src/radio.rs b/widget/src/radio.rs
index 300318fd..15c983df 100644
--- a/widget/src/radio.rs
+++ b/widget/src/radio.rs
@@ -58,7 +58,6 @@
//! ```
use crate::core::alignment;
use crate::core::border::{self, Border};
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
@@ -66,9 +65,10 @@ use crate::core::text;
use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
- Background, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle,
- Shell, Size, Theme, Widget,
+ Background, Clipboard, Color, Element, Event, Layout, Length, Pixels,
+ Rectangle, Shell, Size, Theme, Widget,
};
/// A circular button representing a choice.
@@ -147,6 +147,7 @@ where
text_wrapping: text::Wrapping,
font: Option<Renderer::Font>,
class: Theme::Class<'a>,
+ last_status: Option<Status>,
}
impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer>
@@ -192,6 +193,7 @@ where
text_wrapping: text::Wrapping::default(),
font: None,
class: Theme::default(),
+ last_status: None,
}
}
@@ -265,8 +267,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Radio<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Radio<'_, Message, Theme, Renderer>
where
Message: Clone,
Theme: Catalog,
@@ -321,7 +323,7 @@ where
)
}
- fn on_event(
+ fn update(
&mut self,
_state: &mut Tree,
event: Event,
@@ -331,20 +333,37 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if cursor.is_over(layout.bounds()) {
shell.publish(self.on_click.clone());
-
- return event::Status::Captured;
+ shell.capture_event();
}
}
_ => {}
}
- event::Status::Ignored
+ let current_status = {
+ let is_mouse_over = cursor.is_over(layout.bounds());
+ let is_selected = self.is_selected;
+
+ if is_mouse_over {
+ Status::Hovered { is_selected }
+ } else {
+ Status::Active { is_selected }
+ }
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.last_status = Some(current_status);
+ } else if self
+ .last_status
+ .is_some_and(|last_status| last_status != current_status)
+ {
+ shell.request_redraw();
+ }
}
fn mouse_interaction(
@@ -369,21 +388,17 @@ where
theme: &Theme,
defaults: &renderer::Style,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ _cursor: mouse::Cursor,
viewport: &Rectangle,
) {
- let is_mouse_over = cursor.is_over(layout.bounds());
- let is_selected = self.is_selected;
-
let mut children = layout.children();
- let status = if is_mouse_over {
- Status::Hovered { is_selected }
- } else {
- Status::Active { is_selected }
- };
-
- let style = theme.style(&self.class, status);
+ let style = theme.style(
+ &self.class,
+ self.last_status.unwrap_or(Status::Active {
+ is_selected: self.is_selected,
+ }),
+ );
{
let layout = children.next().unwrap();
@@ -471,7 +486,7 @@ pub enum Status {
}
/// The appearance of a radio button.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Background`] of the radio button.
pub background: Background,
diff --git a/widget/src/row.rs b/widget/src/row.rs
index 9c0fa97e..3b605f07 100644
--- a/widget/src/row.rs
+++ b/widget/src/row.rs
@@ -1,13 +1,12 @@
//! Distribute content horizontally.
use crate::core::alignment::{self, Alignment};
-use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{Operation, Tree};
use crate::core::{
- Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size,
+ Clipboard, Element, Event, Length, Padding, Pixels, Rectangle, Shell, Size,
Vector, Widget,
};
@@ -172,7 +171,7 @@ where
}
}
-impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer>
+impl<Message, Renderer> Default for Row<'_, Message, Renderer>
where
Renderer: crate::core::Renderer,
{
@@ -194,8 +193,8 @@ impl<'a, Message, Theme, Renderer: crate::core::Renderer>
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Row<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Row<'_, Message, Theme, Renderer>
where
Renderer: crate::core::Renderer,
{
@@ -254,7 +253,7 @@ where
});
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -264,24 +263,24 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- self.children
+ ) {
+ for ((child, state), layout) in self
+ .children
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
- .map(|((child, state), layout)| {
- child.as_widget_mut().on_event(
- state,
- event.clone(),
- layout,
- cursor,
- renderer,
- clipboard,
- shell,
- viewport,
- )
- })
- .fold(event::Status::Ignored, event::Status::merge)
+ {
+ child.as_widget_mut().update(
+ state,
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ );
+ }
}
fn mouse_interaction(
@@ -381,8 +380,8 @@ pub struct Wrapping<
row: Row<'a, Message, Theme, Renderer>,
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Wrapping<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Wrapping<'_, Message, Theme, Renderer>
where
Renderer: crate::core::Renderer,
{
@@ -493,7 +492,7 @@ where
self.row.operate(tree, layout, renderer, operation);
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -503,10 +502,10 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- self.row.on_event(
+ ) {
+ self.row.update(
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
- )
+ );
}
fn mouse_interaction(
diff --git a/widget/src/rule.rs b/widget/src/rule.rs
index 92199ca9..65c0a6dc 100644
--- a/widget/src/rule.rs
+++ b/widget/src/rule.rs
@@ -98,8 +98,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Rule<'a, Theme>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Rule<'_, Theme>
where
Renderer: core::Renderer,
Theme: Catalog,
@@ -187,7 +187,7 @@ where
}
/// The appearance of a rule.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The color of the rule.
pub color: Color,
@@ -200,7 +200,7 @@ pub struct Style {
}
/// The fill mode of a rule.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FillMode {
/// Fill the whole length of the container.
Full,
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 6d7f251e..b08d5d09 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -21,7 +21,6 @@
//! ```
use crate::container;
use crate::core::border::{self, Border};
-use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::layout;
use crate::core::mouse;
@@ -34,8 +33,8 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
use crate::core::{
- self, Background, Clipboard, Color, Element, Layout, Length, Padding,
- Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
+ self, Background, Clipboard, Color, Element, Event, Layout, Length,
+ Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::runtime::task::{self, Task};
use crate::runtime::Action;
@@ -81,6 +80,7 @@ pub struct Scrollable<
content: Element<'a, Message, Theme, Renderer>,
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
class: Theme::Class<'a>,
+ last_status: Option<Status>,
}
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
@@ -95,7 +95,7 @@ where
Self::with_direction(content, Direction::default())
}
- /// Creates a new vertical [`Scrollable`].
+ /// Creates a new [`Scrollable`] with the given [`Direction`].
pub fn with_direction(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
direction: impl Into<Direction>,
@@ -108,6 +108,7 @@ where
content: content.into(),
on_scroll: None,
class: Theme::default(),
+ last_status: None,
}
.validate()
}
@@ -136,7 +137,7 @@ where
self
}
- /// Creates a new [`Scrollable`] with the given [`Direction`].
+ /// Sets the [`Direction`] of the [`Scrollable`].
pub fn direction(mut self, direction: impl Into<Direction>) -> Self {
self.direction = direction.into();
self.validate()
@@ -384,8 +385,8 @@ pub enum Anchor {
End,
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Scrollable<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Scrollable<'_, Message, Theme, Renderer>
where
Theme: Catalog,
Renderer: core::Renderer,
@@ -486,11 +487,11 @@ where
state.translation(self.direction, bounds, content_bounds);
operation.scrollable(
- state,
self.id.as_ref().map(|id| &id.0),
bounds,
content_bounds,
translation,
+ state,
);
operation.container(
@@ -507,7 +508,7 @@ where
);
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -517,7 +518,7 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let state = tree.state.downcast_mut::<State>();
let bounds = layout.bounds();
let cursor_over_scrollable = cursor.position_over(bounds);
@@ -531,6 +532,8 @@ where
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
scrollbars.is_mouse_over(cursor);
+ let last_offsets = (state.offset_x, state.offset_y);
+
if let Some(last_scrolled) = state.last_scrolled {
let clear_transaction = match event {
Event::Mouse(
@@ -549,335 +552,387 @@ where
}
}
- if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
- match event {
- Event::Mouse(mouse::Event::CursorMoved { .. })
- | Event::Touch(touch::Event::FingerMoved { .. }) => {
- if let Some(scrollbar) = scrollbars.y {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
+ let mut update = || {
+ if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
+ match event {
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if let Some(scrollbar) = scrollbars.y {
+ let Some(cursor_position) = cursor.position()
+ else {
+ return;
+ };
- state.scroll_y_to(
- scrollbar.scroll_percentage_y(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
+ state.scroll_y_to(
+ scrollbar.scroll_percentage_y(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
- let _ = notify_scroll(
- state,
- &self.on_scroll,
- bounds,
- content_bounds,
- shell,
- );
+ let _ = notify_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
- return event::Status::Captured;
+ shell.capture_event();
+ }
}
+ _ => {}
}
- _ => {}
- }
- } else if mouse_over_y_scrollbar {
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(
- mouse::Button::Left,
- ))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
+ } else if mouse_over_y_scrollbar {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return;
+ };
- if let (Some(scroller_grabbed_at), Some(scrollbar)) = (
- scrollbars.grab_y_scroller(cursor_position),
- scrollbars.y,
- ) {
- state.scroll_y_to(
- scrollbar.scroll_percentage_y(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
+ if let (Some(scroller_grabbed_at), Some(scrollbar)) = (
+ scrollbars.grab_y_scroller(cursor_position),
+ scrollbars.y,
+ ) {
+ state.scroll_y_to(
+ scrollbar.scroll_percentage_y(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
- state.y_scroller_grabbed_at = Some(scroller_grabbed_at);
+ state.y_scroller_grabbed_at =
+ Some(scroller_grabbed_at);
- let _ = notify_scroll(
- state,
- &self.on_scroll,
- bounds,
- content_bounds,
- shell,
- );
- }
+ let _ = notify_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+ }
- return event::Status::Captured;
+ shell.capture_event();
+ }
+ _ => {}
}
- _ => {}
}
- }
- if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
- match event {
- Event::Mouse(mouse::Event::CursorMoved { .. })
- | Event::Touch(touch::Event::FingerMoved { .. }) => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
+ if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
+ match event {
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return;
+ };
- if let Some(scrollbar) = scrollbars.x {
- state.scroll_x_to(
- scrollbar.scroll_percentage_x(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
+ if let Some(scrollbar) = scrollbars.x {
+ state.scroll_x_to(
+ scrollbar.scroll_percentage_x(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
- let _ = notify_scroll(
- state,
- &self.on_scroll,
- bounds,
- content_bounds,
- shell,
- );
- }
+ let _ = notify_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+ }
- return event::Status::Captured;
+ shell.capture_event();
+ }
+ _ => {}
}
- _ => {}
- }
- } else if mouse_over_x_scrollbar {
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(
- mouse::Button::Left,
- ))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
+ } else if mouse_over_x_scrollbar {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return;
+ };
- if let (Some(scroller_grabbed_at), Some(scrollbar)) = (
- scrollbars.grab_x_scroller(cursor_position),
- scrollbars.x,
- ) {
- state.scroll_x_to(
- scrollbar.scroll_percentage_x(
- scroller_grabbed_at,
- cursor_position,
- ),
- bounds,
- content_bounds,
- );
+ if let (Some(scroller_grabbed_at), Some(scrollbar)) = (
+ scrollbars.grab_x_scroller(cursor_position),
+ scrollbars.x,
+ ) {
+ state.scroll_x_to(
+ scrollbar.scroll_percentage_x(
+ scroller_grabbed_at,
+ cursor_position,
+ ),
+ bounds,
+ content_bounds,
+ );
- state.x_scroller_grabbed_at = Some(scroller_grabbed_at);
+ state.x_scroller_grabbed_at =
+ Some(scroller_grabbed_at);
- let _ = notify_scroll(
- state,
- &self.on_scroll,
- bounds,
- content_bounds,
- shell,
- );
+ let _ = notify_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
- return event::Status::Captured;
+ shell.capture_event();
+ }
}
+ _ => {}
}
- _ => {}
}
- }
- let content_status = if state.last_scrolled.is_some()
- && matches!(event, Event::Mouse(mouse::Event::WheelScrolled { .. }))
- {
- event::Status::Ignored
- } else {
- let cursor = match cursor_over_scrollable {
- Some(cursor_position)
- if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
- {
- mouse::Cursor::Available(
- cursor_position
- + state.translation(
- self.direction,
- bounds,
- content_bounds,
- ),
- )
- }
- _ => mouse::Cursor::Unavailable,
- };
+ if state.last_scrolled.is_none()
+ || !matches!(
+ event,
+ Event::Mouse(mouse::Event::WheelScrolled { .. })
+ )
+ {
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar
+ || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(
+ cursor_position
+ + state.translation(
+ self.direction,
+ bounds,
+ content_bounds,
+ ),
+ )
+ }
+ _ => mouse::Cursor::Unavailable,
+ };
- let translation =
- state.translation(self.direction, bounds, content_bounds);
+ let translation =
+ state.translation(self.direction, bounds, content_bounds);
- self.content.as_widget_mut().on_event(
- &mut tree.children[0],
- event.clone(),
- content,
- cursor,
- renderer,
- clipboard,
- shell,
- &Rectangle {
- y: bounds.y + translation.y,
- x: bounds.x + translation.x,
- ..bounds
- },
- )
- };
+ self.content.as_widget_mut().update(
+ &mut tree.children[0],
+ event.clone(),
+ content,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ &Rectangle {
+ y: bounds.y + translation.y,
+ x: bounds.x + translation.x,
+ ..bounds
+ },
+ );
+ };
- if matches!(
- event,
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
- | Event::Touch(
- touch::Event::FingerLifted { .. }
- | touch::Event::FingerLost { .. }
- )
- ) {
- state.scroll_area_touched_at = None;
- state.x_scroller_grabbed_at = None;
- state.y_scroller_grabbed_at = None;
+ if matches!(
+ event,
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. }
+ )
+ ) {
+ state.scroll_area_touched_at = None;
+ state.x_scroller_grabbed_at = None;
+ state.y_scroller_grabbed_at = None;
- return content_status;
- }
+ return;
+ }
- if let event::Status::Captured = content_status {
- return event::Status::Captured;
- }
+ if shell.is_event_captured() {
+ return;
+ }
- if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) =
- event
- {
- state.keyboard_modifiers = modifiers;
+ if let Event::Keyboard(keyboard::Event::ModifiersChanged(
+ modifiers,
+ )) = event
+ {
+ state.keyboard_modifiers = modifiers;
- return event::Status::Ignored;
- }
+ return;
+ }
- match event {
- Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
- if cursor_over_scrollable.is_none() {
- return event::Status::Ignored;
- }
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ if cursor_over_scrollable.is_none() {
+ return;
+ }
- let delta = match delta {
- mouse::ScrollDelta::Lines { x, y } => {
- let is_shift_pressed = state.keyboard_modifiers.shift();
+ let delta = match delta {
+ mouse::ScrollDelta::Lines { x, y } => {
+ let is_shift_pressed =
+ state.keyboard_modifiers.shift();
- // macOS automatically inverts the axes when Shift is pressed
- let (x, y) =
- if cfg!(target_os = "macos") && is_shift_pressed {
+ // macOS automatically inverts the axes when Shift is pressed
+ let (x, y) = if cfg!(target_os = "macos")
+ && is_shift_pressed
+ {
(y, x)
} else {
(x, y)
};
- let is_vertical = match self.direction {
- Direction::Vertical(_) => true,
- Direction::Horizontal(_) => false,
- Direction::Both { .. } => !is_shift_pressed,
- };
-
- let movement = if is_vertical {
- Vector::new(x, y)
- } else {
- Vector::new(y, x)
- };
+ let is_vertical = match self.direction {
+ Direction::Vertical(_) => true,
+ Direction::Horizontal(_) => false,
+ Direction::Both { .. } => !is_shift_pressed,
+ };
- // TODO: Configurable speed/friction (?)
- -movement * 60.0
- }
- mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y),
- };
+ let movement = if is_vertical {
+ Vector::new(x, y)
+ } else {
+ Vector::new(y, x)
+ };
- state.scroll(
- self.direction.align(delta),
- bounds,
- content_bounds,
- );
+ // TODO: Configurable speed/friction (?)
+ -movement * 60.0
+ }
+ mouse::ScrollDelta::Pixels { x, y } => {
+ -Vector::new(x, y)
+ }
+ };
- let has_scrolled = notify_scroll(
- state,
- &self.on_scroll,
- bounds,
- content_bounds,
- shell,
- );
+ state.scroll(
+ self.direction.align(delta),
+ bounds,
+ content_bounds,
+ );
- let in_transaction = state.last_scrolled.is_some();
+ let has_scrolled = notify_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
- if has_scrolled || in_transaction {
- event::Status::Captured
- } else {
- event::Status::Ignored
- }
- }
- Event::Touch(event)
- if state.scroll_area_touched_at.is_some()
- || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
- {
- match event {
- touch::Event::FingerPressed { .. } => {
- let Some(cursor_position) = cursor.position() else {
- return event::Status::Ignored;
- };
+ let in_transaction = state.last_scrolled.is_some();
- state.scroll_area_touched_at = Some(cursor_position);
+ if has_scrolled || in_transaction {
+ shell.capture_event();
}
- touch::Event::FingerMoved { .. } => {
- if let Some(scroll_box_touched_at) =
- state.scroll_area_touched_at
- {
+ }
+ Event::Touch(event)
+ if state.scroll_area_touched_at.is_some()
+ || !mouse_over_y_scrollbar
+ && !mouse_over_x_scrollbar =>
+ {
+ match event {
+ touch::Event::FingerPressed { .. } => {
let Some(cursor_position) = cursor.position()
else {
- return event::Status::Ignored;
+ return;
};
- let delta = Vector::new(
- scroll_box_touched_at.x - cursor_position.x,
- scroll_box_touched_at.y - cursor_position.y,
- );
-
- state.scroll(
- self.direction.align(delta),
- bounds,
- content_bounds,
- );
-
state.scroll_area_touched_at =
Some(cursor_position);
-
- // TODO: bubble up touch movements if not consumed.
- let _ = notify_scroll(
- state,
- &self.on_scroll,
- bounds,
- content_bounds,
- shell,
- );
}
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ state.scroll_area_touched_at
+ {
+ let Some(cursor_position) = cursor.position()
+ else {
+ return;
+ };
+
+ let delta = Vector::new(
+ scroll_box_touched_at.x - cursor_position.x,
+ scroll_box_touched_at.y - cursor_position.y,
+ );
+
+ state.scroll(
+ self.direction.align(delta),
+ bounds,
+ content_bounds,
+ );
+
+ state.scroll_area_touched_at =
+ Some(cursor_position);
+
+ // TODO: bubble up touch movements if not consumed.
+ let _ = notify_scroll(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+ }
+ }
+ _ => {}
}
- _ => {}
- }
- event::Status::Captured
+ shell.capture_event();
+ }
+ Event::Window(window::Event::RedrawRequested(_)) => {
+ let _ = notify_viewport(
+ state,
+ &self.on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
+ }
+ _ => {}
}
- Event::Window(window::Event::RedrawRequested(_)) => {
- let _ = notify_viewport(
- state,
- &self.on_scroll,
- bounds,
- content_bounds,
- shell,
- );
+ };
- event::Status::Ignored
+ update();
+
+ let status = if state.y_scroller_grabbed_at.is_some()
+ || state.x_scroller_grabbed_at.is_some()
+ {
+ Status::Dragged {
+ is_horizontal_scrollbar_dragged: state
+ .x_scroller_grabbed_at
+ .is_some(),
+ is_vertical_scrollbar_dragged: state
+ .y_scroller_grabbed_at
+ .is_some(),
+ is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(),
+ is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(),
+ }
+ } else if cursor_over_scrollable.is_some() {
+ Status::Hovered {
+ is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar,
+ is_vertical_scrollbar_hovered: mouse_over_y_scrollbar,
+ is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(),
+ is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(),
+ }
+ } else {
+ Status::Active {
+ is_horizontal_scrollbar_disabled: scrollbars.is_x_disabled(),
+ is_vertical_scrollbar_disabled: scrollbars.is_y_disabled(),
}
- _ => event::Status::Ignored,
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.last_status = Some(status);
+ }
+
+ if last_offsets != (state.offset_x, state.offset_y)
+ || self
+ .last_status
+ .is_some_and(|last_status| last_status != status)
+ {
+ shell.request_redraw();
}
}
@@ -920,27 +975,13 @@ where
_ => mouse::Cursor::Unavailable,
};
- let status = if state.y_scroller_grabbed_at.is_some()
- || state.x_scroller_grabbed_at.is_some()
- {
- Status::Dragged {
- is_horizontal_scrollbar_dragged: state
- .x_scroller_grabbed_at
- .is_some(),
- is_vertical_scrollbar_dragged: state
- .y_scroller_grabbed_at
- .is_some(),
- }
- } else if cursor_over_scrollable.is_some() {
- Status::Hovered {
- is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar,
- is_vertical_scrollbar_hovered: mouse_over_y_scrollbar,
- }
- } else {
- Status::Active
- };
-
- let style = theme.style(&self.class, status);
+ let style = theme.style(
+ &self.class,
+ self.last_status.unwrap_or(Status::Active {
+ is_horizontal_scrollbar_disabled: false,
+ is_vertical_scrollbar_disabled: false,
+ }),
+ );
container::draw_background(renderer, &style.container, layout.bounds());
@@ -1323,7 +1364,7 @@ impl operation::Scrollable for State {
}
}
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
enum Offset {
Absolute(f32),
Relative(f32),
@@ -1632,6 +1673,7 @@ impl Scrollbars {
bounds: scrollbar_bounds,
scroller,
alignment: vertical.alignment,
+ disabled: content_bounds.height <= bounds.height,
})
} else {
None
@@ -1701,6 +1743,7 @@ impl Scrollbars {
bounds: scrollbar_bounds,
scroller,
alignment: horizontal.alignment,
+ disabled: content_bounds.width <= bounds.width,
})
} else {
None
@@ -1729,6 +1772,14 @@ impl Scrollbars {
}
}
+ fn is_y_disabled(&self) -> bool {
+ self.y.map(|y| y.disabled).unwrap_or(false)
+ }
+
+ fn is_x_disabled(&self) -> bool {
+ self.x.map(|x| x.disabled).unwrap_or(false)
+ }
+
fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> {
let scrollbar = self.y?;
let scroller = scrollbar.scroller?;
@@ -1775,6 +1826,7 @@ pub(super) mod internals {
pub bounds: Rectangle,
pub scroller: Option<Scroller>,
pub alignment: Anchor,
+ pub disabled: bool,
}
impl Scrollbar {
@@ -1838,13 +1890,22 @@ pub(super) mod internals {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
/// The [`Scrollable`] can be interacted with.
- Active,
+ Active {
+ /// Whether or not the horizontal scrollbar is disabled meaning the content isn't overflowing.
+ is_horizontal_scrollbar_disabled: bool,
+ /// Whether or not the vertical scrollbar is disabled meaning the content isn't overflowing.
+ is_vertical_scrollbar_disabled: bool,
+ },
/// The [`Scrollable`] is being hovered.
Hovered {
/// Indicates if the horizontal scrollbar is being hovered.
is_horizontal_scrollbar_hovered: bool,
/// Indicates if the vertical scrollbar is being hovered.
is_vertical_scrollbar_hovered: bool,
+ /// Whether or not the horizontal scrollbar is disabled meaning the content isn't overflowing.
+ is_horizontal_scrollbar_disabled: bool,
+ /// Whether or not the vertical scrollbar is disabled meaning the content isn't overflowing.
+ is_vertical_scrollbar_disabled: bool,
},
/// The [`Scrollable`] is being dragged.
Dragged {
@@ -1852,11 +1913,15 @@ pub enum Status {
is_horizontal_scrollbar_dragged: bool,
/// Indicates if the vertical scrollbar is being dragged.
is_vertical_scrollbar_dragged: bool,
+ /// Whether or not the horizontal scrollbar is disabled meaning the content isn't overflowing.
+ is_horizontal_scrollbar_disabled: bool,
+ /// Whether or not the vertical scrollbar is disabled meaning the content isn't overflowing.
+ is_vertical_scrollbar_disabled: bool,
},
}
/// The appearance of a scrollable.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`container::Style`] of a scrollable.
pub container: container::Style,
@@ -1869,7 +1934,7 @@ pub struct Style {
}
/// The appearance of the scrollbar of a scrollable.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rail {
/// The [`Background`] of a scrollbar.
pub background: Option<Background>,
@@ -1880,7 +1945,7 @@ pub struct Rail {
}
/// The appearance of the scroller of a scrollable.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Scroller {
/// The [`Color`] of the scroller.
pub color: Color,
@@ -1929,7 +1994,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
};
match status {
- Status::Active => Style {
+ Status::Active { .. } => Style {
container: container::Style::default(),
vertical_rail: scrollbar,
horizontal_rail: scrollbar,
@@ -1938,6 +2003,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
Status::Hovered {
is_horizontal_scrollbar_hovered,
is_vertical_scrollbar_hovered,
+ ..
} => {
let hovered_scrollbar = Rail {
scroller: Scroller {
@@ -1965,6 +2031,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
Status::Dragged {
is_horizontal_scrollbar_dragged,
is_vertical_scrollbar_dragged,
+ ..
} => {
let dragged_scrollbar = Rail {
scroller: Scroller {
diff --git a/widget/src/shader.rs b/widget/src/shader.rs
index fa692336..8ec57482 100644
--- a/widget/src/shader.rs
+++ b/widget/src/shader.rs
@@ -1,23 +1,22 @@
//! A custom shader widget for wgpu applications.
-mod event;
mod program;
-pub use event::Event;
pub use program::Program;
-use crate::core;
+use crate::core::event;
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Widget};
use crate::core::window;
-use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size};
+use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size};
use crate::renderer::wgpu::primitive;
use std::marker::PhantomData;
pub use crate::graphics::Viewport;
+pub use crate::Action;
pub use primitive::{Primitive, Storage};
/// A widget which can render custom shaders with Iced's `wgpu` backend.
@@ -87,7 +86,7 @@ where
layout::atomic(limits, self.width, self.height)
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: crate::core::Event,
@@ -97,40 +96,34 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let bounds = layout.bounds();
- let custom_shader_event = match event {
- core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
- core::Event::Keyboard(keyboard_event) => {
- Some(Event::Keyboard(keyboard_event))
- }
- core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
- core::Event::Window(window::Event::RedrawRequested(instant)) => {
- Some(Event::RedrawRequested(instant))
- }
- core::Event::Window(_) => None,
- };
-
- if let Some(custom_shader_event) = custom_shader_event {
- let state = tree.state.downcast_mut::<P::State>();
+ let state = tree.state.downcast_mut::<P::State>();
- let (event_status, message) = self.program.update(
- state,
- custom_shader_event,
- bounds,
- cursor,
- shell,
- );
+ if let Some(action) = self.program.update(state, event, bounds, cursor)
+ {
+ let (message, redraw_request, event_status) = action.into_inner();
if let Some(message) = message {
shell.publish(message);
}
- return event_status;
- }
+ if let Some(redraw_request) = redraw_request {
+ match redraw_request {
+ window::RedrawRequest::NextFrame => {
+ shell.request_redraw();
+ }
+ window::RedrawRequest::At(at) => {
+ shell.request_redraw_at(at);
+ }
+ }
+ }
- event::Status::Ignored
+ if event_status == event::Status::Captured {
+ shell.capture_event();
+ }
+ }
}
fn mouse_interaction(
@@ -194,9 +187,8 @@ where
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
- shell: &mut Shell<'_, Message>,
- ) -> (event::Status, Option<Message>) {
- T::update(self, state, event, bounds, cursor, shell)
+ ) -> Option<Action<Message>> {
+ T::update(self, state, event, bounds, cursor)
}
fn draw(
diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs
deleted file mode 100644
index 005c8725..00000000
--- a/widget/src/shader/event.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-//! Handle events of a custom shader widget.
-use crate::core::keyboard;
-use crate::core::mouse;
-use crate::core::time::Instant;
-use crate::core::touch;
-
-pub use crate::core::event::Status;
-
-/// A [`Shader`] event.
-///
-/// [`Shader`]: crate::Shader
-#[derive(Debug, Clone, PartialEq)]
-pub enum Event {
- /// A mouse event.
- Mouse(mouse::Event),
-
- /// A touch event.
- Touch(touch::Event),
-
- /// A keyboard event.
- Keyboard(keyboard::Event),
-
- /// A window requested a redraw.
- RedrawRequested(Instant),
-}
diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs
index 902c7c3b..0fc110af 100644
--- a/widget/src/shader/program.rs
+++ b/widget/src/shader/program.rs
@@ -1,8 +1,7 @@
-use crate::core::event;
use crate::core::mouse;
-use crate::core::{Rectangle, Shell};
+use crate::core::Rectangle;
use crate::renderer::wgpu::Primitive;
-use crate::shader;
+use crate::shader::{self, Action};
/// The state and logic of a [`Shader`] widget.
///
@@ -18,10 +17,10 @@ pub trait Program<Message> {
type Primitive: Primitive + 'static;
/// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes
- /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a
- /// redraw for the window, etc.
+ /// based on mouse & other events. You can return an [`Action`] to publish a message, request a
+ /// redraw, or capture the event.
///
- /// By default, this method does and returns nothing.
+ /// By default, this method returns `None`.
///
/// [`State`]: Self::State
fn update(
@@ -30,9 +29,8 @@ pub trait Program<Message> {
_event: shader::Event,
_bounds: Rectangle,
_cursor: mouse::Cursor,
- _shell: &mut Shell<'_, Message>,
- ) -> (event::Status, Option<Message>) {
- (event::Status::Ignored, None)
+ ) -> Option<Action<Message>> {
+ None
}
/// Draws the [`Primitive`].
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index 9477958d..52500854 100644
--- a/widget/src/slider.rs
+++ b/widget/src/slider.rs
@@ -29,7 +29,6 @@
//! }
//! ```
use crate::core::border::{self, Border};
-use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key::{self, Key};
use crate::core::layout;
@@ -37,9 +36,10 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
- self, Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
- Rectangle, Shell, Size, Theme, Widget,
+ self, Background, Clipboard, Color, Element, Event, Layout, Length, Pixels,
+ Point, Rectangle, Shell, Size, Theme, Widget,
};
use std::ops::RangeInclusive;
@@ -95,6 +95,7 @@ where
width: Length,
height: f32,
class: Theme::Class<'a>,
+ status: Option<Status>,
}
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
@@ -141,6 +142,7 @@ where
width: Length::Fill,
height: Self::DEFAULT_HEIGHT,
class: Theme::default(),
+ status: None,
}
}
@@ -208,8 +210,8 @@ where
}
}
-impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Slider<'a, T, Message, Theme>
+impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Slider<'_, T, Message, Theme>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
@@ -240,7 +242,7 @@ where
layout::atomic(limits, self.width, self.height)
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -250,188 +252,202 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let state = tree.state.downcast_mut::<State>();
- let is_dragging = state.is_dragging;
- let current_value = self.value;
+ let mut update = || {
+ let current_value = self.value;
- let locate = |cursor_position: Point| -> Option<T> {
- let bounds = layout.bounds();
- let new_value = if cursor_position.x <= bounds.x {
- Some(*self.range.start())
- } else if cursor_position.x >= bounds.x + bounds.width {
- Some(*self.range.end())
- } else {
- let step = if state.keyboard_modifiers.shift() {
- self.shift_step.unwrap_or(self.step)
+ let locate = |cursor_position: Point| -> Option<T> {
+ let bounds = layout.bounds();
+
+ let new_value = if cursor_position.x <= bounds.x {
+ Some(*self.range.start())
+ } else if cursor_position.x >= bounds.x + bounds.width {
+ Some(*self.range.end())
} else {
- self.step
- }
- .into();
+ let step = if state.keyboard_modifiers.shift() {
+ self.shift_step.unwrap_or(self.step)
+ } else {
+ self.step
+ }
+ .into();
+
+ let start = (*self.range.start()).into();
+ let end = (*self.range.end()).into();
- let start = (*self.range.start()).into();
- let end = (*self.range.end()).into();
+ let percent = f64::from(cursor_position.x - bounds.x)
+ / f64::from(bounds.width);
- let percent = f64::from(cursor_position.x - bounds.x)
- / f64::from(bounds.width);
+ let steps = (percent * (end - start) / step).round();
+ let value = steps * step + start;
- let steps = (percent * (end - start) / step).round();
- let value = steps * step + start;
+ T::from_f64(value.min(end))
+ };
- T::from_f64(value.min(end))
+ new_value
};
- new_value
- };
+ let increment = |value: T| -> Option<T> {
+ let step = if state.keyboard_modifiers.shift() {
+ self.shift_step.unwrap_or(self.step)
+ } else {
+ self.step
+ }
+ .into();
- let increment = |value: T| -> Option<T> {
- let step = if state.keyboard_modifiers.shift() {
- self.shift_step.unwrap_or(self.step)
- } else {
- self.step
- }
- .into();
+ let steps = (value.into() / step).round();
+ let new_value = step * (steps + 1.0);
- let steps = (value.into() / step).round();
- let new_value = step * (steps + 1.0);
+ if new_value > (*self.range.end()).into() {
+ return Some(*self.range.end());
+ }
- if new_value > (*self.range.end()).into() {
- return Some(*self.range.end());
- }
+ T::from_f64(new_value)
+ };
- T::from_f64(new_value)
- };
+ let decrement = |value: T| -> Option<T> {
+ let step = if state.keyboard_modifiers.shift() {
+ self.shift_step.unwrap_or(self.step)
+ } else {
+ self.step
+ }
+ .into();
- let decrement = |value: T| -> Option<T> {
- let step = if state.keyboard_modifiers.shift() {
- self.shift_step.unwrap_or(self.step)
- } else {
- self.step
- }
- .into();
+ let steps = (value.into() / step).round();
+ let new_value = step * (steps - 1.0);
- let steps = (value.into() / step).round();
- let new_value = step * (steps - 1.0);
+ if new_value < (*self.range.start()).into() {
+ return Some(*self.range.start());
+ }
- if new_value < (*self.range.start()).into() {
- return Some(*self.range.start());
- }
+ T::from_f64(new_value)
+ };
- T::from_f64(new_value)
- };
+ let change = |new_value: T| {
+ if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
+ shell.publish((self.on_change)(new_value));
- let change = |new_value: T| {
- if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
- shell.publish((self.on_change)(new_value));
+ self.value = new_value;
+ }
+ };
- self.value = new_value;
- }
- };
+ match &event {
+ Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if let Some(cursor_position) =
+ cursor.position_over(layout.bounds())
+ {
+ if state.keyboard_modifiers.command() {
+ let _ = self.default.map(change);
+ state.is_dragging = false;
+ } else {
+ let _ = locate(cursor_position).map(change);
+ state.is_dragging = true;
+ }
- match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerPressed { .. }) => {
- if let Some(cursor_position) =
- cursor.position_over(layout.bounds())
- {
- if state.keyboard_modifiers.command() {
- let _ = self.default.map(change);
- state.is_dragging = false;
- } else {
- let _ = locate(cursor_position).map(change);
- state.is_dragging = true;
+ shell.capture_event();
}
-
- return event::Status::Captured;
}
- }
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
- | Event::Touch(touch::Event::FingerLifted { .. })
- | Event::Touch(touch::Event::FingerLost { .. }) => {
- if is_dragging {
- if let Some(on_release) = self.on_release.clone() {
- shell.publish(on_release);
- }
- state.is_dragging = false;
+ Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Left,
+ ))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ if state.is_dragging {
+ if let Some(on_release) = self.on_release.clone() {
+ shell.publish(on_release);
+ }
+ state.is_dragging = false;
- return event::Status::Captured;
+ shell.capture_event();
+ }
}
- }
- Event::Mouse(mouse::Event::CursorMoved { .. })
- | Event::Touch(touch::Event::FingerMoved { .. }) => {
- if is_dragging {
- let _ = cursor.position().and_then(locate).map(change);
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if state.is_dragging {
+ let _ = cursor.position().and_then(locate).map(change);
- return event::Status::Captured;
- }
- }
- Event::Mouse(mouse::Event::WheelScrolled { delta })
- if state.keyboard_modifiers.control() =>
- {
- if cursor.is_over(layout.bounds()) {
- let delta = match delta {
- mouse::ScrollDelta::Lines { x: _, y } => y,
- mouse::ScrollDelta::Pixels { x: _, y } => y,
- };
-
- if delta < 0.0 {
- let _ = decrement(current_value).map(change);
- } else {
- let _ = increment(current_value).map(change);
+ shell.capture_event();
}
-
- return event::Status::Captured;
}
- }
- Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
- if cursor.is_over(layout.bounds()) {
- match key {
- Key::Named(key::Named::ArrowUp) => {
- let _ = increment(current_value).map(change);
- }
- Key::Named(key::Named::ArrowDown) => {
+ Event::Mouse(mouse::Event::WheelScrolled { delta })
+ if state.keyboard_modifiers.control() =>
+ {
+ if cursor.is_over(layout.bounds()) {
+ let delta = match delta {
+ mouse::ScrollDelta::Lines { x: _, y } => y,
+ mouse::ScrollDelta::Pixels { x: _, y } => y,
+ };
+
+ if *delta < 0.0 {
let _ = decrement(current_value).map(change);
+ } else {
+ let _ = increment(current_value).map(change);
}
- _ => (),
+
+ shell.capture_event();
}
+ }
+ Event::Keyboard(keyboard::Event::KeyPressed {
+ key, ..
+ }) => {
+ if cursor.is_over(layout.bounds()) {
+ match key {
+ Key::Named(key::Named::ArrowUp) => {
+ let _ = increment(current_value).map(change);
+ }
+ Key::Named(key::Named::ArrowDown) => {
+ let _ = decrement(current_value).map(change);
+ }
+ _ => (),
+ }
- return event::Status::Captured;
+ shell.capture_event();
+ }
}
+ Event::Keyboard(keyboard::Event::ModifiersChanged(
+ modifiers,
+ )) => {
+ state.keyboard_modifiers = *modifiers;
+ }
+ _ => {}
}
- Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
- state.keyboard_modifiers = modifiers;
- }
- _ => {}
- }
+ };
- event::Status::Ignored
+ update();
+
+ let current_status = if state.is_dragging {
+ Status::Dragged
+ } else if cursor.is_over(layout.bounds()) {
+ Status::Hovered
+ } else {
+ Status::Active
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.status = Some(current_status);
+ } else if self.status.is_some_and(|status| status != current_status) {
+ shell.request_redraw();
+ }
}
fn draw(
&self,
- tree: &Tree,
+ _tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
- let is_mouse_over = cursor.is_over(bounds);
- let style = theme.style(
- &self.class,
- if state.is_dragging {
- Status::Dragged
- } else if is_mouse_over {
- Status::Hovered
- } else {
- Status::Active
- },
- );
+ let style =
+ theme.style(&self.class, self.status.unwrap_or(Status::Active));
let (handle_width, handle_height, handle_border_radius) =
match style.handle.shape {
@@ -562,7 +578,7 @@ pub enum Status {
}
/// The appearance of a slider.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The colors of the rail of the slider.
pub rail: Rail,
@@ -582,7 +598,7 @@ impl Style {
}
/// The appearance of a slider rail
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rail {
/// The backgrounds of the rail of the slider.
pub backgrounds: (Background, Background),
@@ -593,7 +609,7 @@ pub struct Rail {
}
/// The appearance of the handle of a slider.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Handle {
/// The shape of the handle.
pub shape: HandleShape,
@@ -606,7 +622,7 @@ pub struct Handle {
}
/// The shape of the handle of a slider.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HandleShape {
/// A circular handle.
Circle {
diff --git a/widget/src/stack.rs b/widget/src/stack.rs
index 6a44c328..d2828c56 100644
--- a/widget/src/stack.rs
+++ b/widget/src/stack.rs
@@ -1,12 +1,12 @@
//! Display content on top of other content.
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{Operation, Tree};
use crate::core::{
- Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
+ Clipboard, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector,
+ Widget,
};
/// A container that displays children on top of each other.
@@ -116,7 +116,7 @@ where
}
}
-impl<'a, Message, Renderer> Default for Stack<'a, Message, Renderer>
+impl<Message, Renderer> Default for Stack<'_, Message, Renderer>
where
Renderer: crate::core::Renderer,
{
@@ -204,7 +204,7 @@ where
});
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -214,40 +214,41 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let is_over = cursor.is_over(layout.bounds());
- self.children
+ for ((child, state), layout) in self
+ .children
.iter_mut()
.rev()
.zip(tree.children.iter_mut().rev())
.zip(layout.children().rev())
- .map(|((child, state), layout)| {
- let status = child.as_widget_mut().on_event(
- state,
- event.clone(),
- layout,
- cursor,
- renderer,
- clipboard,
- shell,
- viewport,
+ {
+ child.as_widget_mut().update(
+ state,
+ event.clone(),
+ layout,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ viewport,
+ );
+
+ if is_over && cursor != mouse::Cursor::Unavailable {
+ let interaction = child.as_widget().mouse_interaction(
+ state, layout, cursor, viewport, renderer,
);
- if is_over && cursor != mouse::Cursor::Unavailable {
- let interaction = child.as_widget().mouse_interaction(
- state, layout, cursor, viewport, renderer,
- );
-
- if interaction != mouse::Interaction::None {
- cursor = mouse::Cursor::Unavailable;
- }
+ if interaction != mouse::Interaction::None {
+ cursor = mouse::Cursor::Unavailable;
}
+ }
- status
- })
- .find(|&status| status == event::Status::Captured)
- .unwrap_or(event::Status::Ignored)
+ if shell.is_event_captured() {
+ return;
+ }
+ }
}
fn mouse_interaction(
diff --git a/widget/src/svg.rs b/widget/src/svg.rs
index 8d57265a..72ead4f9 100644
--- a/widget/src/svg.rs
+++ b/widget/src/svg.rs
@@ -148,8 +148,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Svg<'a, Theme>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Svg<'_, Theme>
where
Renderer: svg::Renderer,
Theme: Catalog,
@@ -323,7 +323,7 @@ impl Catalog for Theme {
/// This is just a boxed closure: `Fn(&Theme, Status) -> Style`.
pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
-impl<'a, Theme> From<Style> for StyleFn<'a, Theme> {
+impl<Theme> From<Style> for StyleFn<'_, Theme> {
fn from(style: Style) -> Self {
Box::new(move |_theme, _status| style)
}
diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs
index 3d241375..a40f2b57 100644
--- a/widget/src/text/rich.rs
+++ b/widget/src/text/rich.rs
@@ -1,5 +1,4 @@
use crate::core::alignment;
-use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
@@ -181,8 +180,8 @@ struct State<Link, P: Paragraph> {
paragraph: P,
}
-impl<'a, Link, Theme, Renderer> Widget<Link, Theme, Renderer>
- for Rich<'a, Link, Theme, Renderer>
+impl<Link, Theme, Renderer> Widget<Link, Theme, Renderer>
+ for Rich<'_, Link, Theme, Renderer>
where
Link: Clone + 'static,
Theme: Catalog,
@@ -355,7 +354,7 @@ where
);
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -365,7 +364,7 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Link>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if let Some(position) = cursor.position_in(layout.bounds()) {
@@ -374,9 +373,16 @@ where
.downcast_mut::<State<Link, Renderer::Paragraph>>();
if let Some(span) = state.paragraph.hit_span(position) {
- state.span_pressed = Some(span);
-
- return event::Status::Captured;
+ if self
+ .spans
+ .as_ref()
+ .as_ref()
+ .get(span)
+ .is_some_and(|span| span.link.is_some())
+ {
+ state.span_pressed = Some(span);
+ shell.capture_event();
+ }
}
}
}
@@ -409,8 +415,6 @@ where
}
_ => {}
}
-
- event::Status::Ignored
}
fn mouse_interaction(
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index a9322474..ad852ce9 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -33,7 +33,6 @@
//! ```
use crate::core::alignment;
use crate::core::clipboard::{self, Clipboard};
-use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key;
use crate::core::layout::{self, Layout};
@@ -47,7 +46,7 @@ use crate::core::widget::operation;
use crate::core::widget::{self, Widget};
use crate::core::window;
use crate::core::{
- Background, Border, Color, Element, Length, Padding, Pixels, Point,
+ Background, Border, Color, Element, Event, Length, Padding, Pixels, Point,
Rectangle, Shell, Size, SmolStr, Theme, Vector,
};
@@ -120,6 +119,7 @@ pub struct TextEditor<
&Highlighter::Highlight,
&Theme,
) -> highlighter::Format<Renderer::Font>,
+ last_status: Option<Status>,
}
impl<'a, Message, Theme, Renderer>
@@ -147,6 +147,7 @@ where
highlighter_format: |_highlight, _theme| {
highlighter::Format::default()
},
+ last_status: None,
}
}
}
@@ -270,6 +271,7 @@ where
on_edit: self.on_edit,
highlighter_settings: settings,
highlighter_format: to_format,
+ last_status: self.last_status,
}
}
@@ -511,8 +513,8 @@ impl<Highlighter: text::Highlighter> operation::Focusable
}
}
-impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for TextEditor<'a, Highlighter, Message, Theme, Renderer>
+impl<Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for TextEditor<'_, Highlighter, Message, Theme, Renderer>
where
Highlighter: text::Highlighter,
Theme: Catalog,
@@ -596,7 +598,7 @@ where
}
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut widget::Tree,
event: Event,
@@ -606,12 +608,16 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let Some(on_edit) = self.on_edit.as_ref() else {
- return event::Status::Ignored;
+ return;
};
let state = tree.state.downcast_mut::<State<Highlighter>>();
+ let is_redraw = matches!(
+ event,
+ Event::Window(window::Event::RedrawRequested(_now)),
+ );
match event {
Event::Window(window::Event::Unfocused) => {
@@ -624,7 +630,7 @@ where
focus.is_window_focused = true;
focus.updated_at = Instant::now();
- shell.request_redraw(window::RedrawRequest::NextFrame);
+ shell.request_redraw();
}
}
Event::Window(window::Event::RedrawRequested(now)) => {
@@ -637,168 +643,193 @@ where
- (now - focus.updated_at).as_millis()
% Focus::CURSOR_BLINK_INTERVAL_MILLIS;
- shell.request_redraw(window::RedrawRequest::At(
+ shell.request_redraw_at(
now + Duration::from_millis(
millis_until_redraw as u64,
),
- ));
+ );
}
}
}
_ => {}
}
- let Some(update) = Update::from_event(
+ if let Some(update) = Update::from_event(
event,
state,
layout.bounds(),
self.padding,
cursor,
self.key_binding.as_deref(),
- ) else {
- return event::Status::Ignored;
- };
-
- match update {
- Update::Click(click) => {
- let action = match click.kind() {
- mouse::click::Kind::Single => {
- Action::Click(click.position())
- }
- mouse::click::Kind::Double => Action::SelectWord,
- mouse::click::Kind::Triple => Action::SelectLine,
- };
-
- state.focus = Some(Focus::now());
- state.last_click = Some(click);
- state.drag_click = Some(click.kind());
+ ) {
+ match update {
+ Update::Click(click) => {
+ let action = match click.kind() {
+ mouse::click::Kind::Single => {
+ Action::Click(click.position())
+ }
+ mouse::click::Kind::Double => Action::SelectWord,
+ mouse::click::Kind::Triple => Action::SelectLine,
+ };
- shell.publish(on_edit(action));
- }
- Update::Drag(position) => {
- shell.publish(on_edit(Action::Drag(position)));
- }
- Update::Release => {
- state.drag_click = None;
- }
- Update::Scroll(lines) => {
- let bounds = self.content.0.borrow().editor.bounds();
+ state.focus = Some(Focus::now());
+ state.last_click = Some(click);
+ state.drag_click = Some(click.kind());
- if bounds.height >= i32::MAX as f32 {
- return event::Status::Ignored;
+ shell.publish(on_edit(action));
+ shell.capture_event();
+ }
+ Update::Drag(position) => {
+ shell.publish(on_edit(Action::Drag(position)));
}
+ Update::Release => {
+ state.drag_click = None;
+ }
+ Update::Scroll(lines) => {
+ let bounds = self.content.0.borrow().editor.bounds();
+
+ if bounds.height >= i32::MAX as f32 {
+ return;
+ }
- let lines = lines + state.partial_scroll;
- state.partial_scroll = lines.fract();
+ let lines = lines + state.partial_scroll;
+ state.partial_scroll = lines.fract();
- shell.publish(on_edit(Action::Scroll {
- lines: lines as i32,
- }));
- }
- Update::Binding(binding) => {
- fn apply_binding<
- H: text::Highlighter,
- R: text::Renderer,
- Message,
- >(
- binding: Binding<Message>,
- content: &Content<R>,
- state: &mut State<H>,
- on_edit: &dyn Fn(Action) -> Message,
- clipboard: &mut dyn Clipboard,
- shell: &mut Shell<'_, Message>,
- ) {
- let mut publish = |action| shell.publish(on_edit(action));
-
- match binding {
- Binding::Unfocus => {
- state.focus = None;
- state.drag_click = None;
- }
- Binding::Copy => {
- if let Some(selection) = content.selection() {
- clipboard.write(
- clipboard::Kind::Standard,
- selection,
- );
+ shell.publish(on_edit(Action::Scroll {
+ lines: lines as i32,
+ }));
+ shell.capture_event();
+ }
+ Update::Binding(binding) => {
+ fn apply_binding<
+ H: text::Highlighter,
+ R: text::Renderer,
+ Message,
+ >(
+ binding: Binding<Message>,
+ content: &Content<R>,
+ state: &mut State<H>,
+ on_edit: &dyn Fn(Action) -> Message,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) {
+ let mut publish =
+ |action| shell.publish(on_edit(action));
+
+ match binding {
+ Binding::Unfocus => {
+ state.focus = None;
+ state.drag_click = None;
}
- }
- Binding::Cut => {
- if let Some(selection) = content.selection() {
- clipboard.write(
- clipboard::Kind::Standard,
- selection,
- );
-
+ Binding::Copy => {
+ if let Some(selection) = content.selection() {
+ clipboard.write(
+ clipboard::Kind::Standard,
+ selection,
+ );
+ }
+ }
+ Binding::Cut => {
+ if let Some(selection) = content.selection() {
+ clipboard.write(
+ clipboard::Kind::Standard,
+ selection,
+ );
+
+ publish(Action::Edit(Edit::Delete));
+ }
+ }
+ Binding::Paste => {
+ if let Some(contents) =
+ clipboard.read(clipboard::Kind::Standard)
+ {
+ publish(Action::Edit(Edit::Paste(
+ Arc::new(contents),
+ )));
+ }
+ }
+ Binding::Move(motion) => {
+ publish(Action::Move(motion));
+ }
+ Binding::Select(motion) => {
+ publish(Action::Select(motion));
+ }
+ Binding::SelectWord => {
+ publish(Action::SelectWord);
+ }
+ Binding::SelectLine => {
+ publish(Action::SelectLine);
+ }
+ Binding::SelectAll => {
+ publish(Action::SelectAll);
+ }
+ Binding::Insert(c) => {
+ publish(Action::Edit(Edit::Insert(c)));
+ }
+ Binding::Enter => {
+ publish(Action::Edit(Edit::Enter));
+ }
+ Binding::Backspace => {
+ publish(Action::Edit(Edit::Backspace));
+ }
+ Binding::Delete => {
publish(Action::Edit(Edit::Delete));
}
- }
- Binding::Paste => {
- if let Some(contents) =
- clipboard.read(clipboard::Kind::Standard)
- {
- publish(Action::Edit(Edit::Paste(Arc::new(
- contents,
- ))));
+ Binding::Sequence(sequence) => {
+ for binding in sequence {
+ apply_binding(
+ binding, content, state, on_edit,
+ clipboard, shell,
+ );
+ }
}
- }
- Binding::Move(motion) => {
- publish(Action::Move(motion));
- }
- Binding::Select(motion) => {
- publish(Action::Select(motion));
- }
- Binding::SelectWord => {
- publish(Action::SelectWord);
- }
- Binding::SelectLine => {
- publish(Action::SelectLine);
- }
- Binding::SelectAll => {
- publish(Action::SelectAll);
- }
- Binding::Insert(c) => {
- publish(Action::Edit(Edit::Insert(c)));
- }
- Binding::Enter => {
- publish(Action::Edit(Edit::Enter));
- }
- Binding::Backspace => {
- publish(Action::Edit(Edit::Backspace));
- }
- Binding::Delete => {
- publish(Action::Edit(Edit::Delete));
- }
- Binding::Sequence(sequence) => {
- for binding in sequence {
- apply_binding(
- binding, content, state, on_edit,
- clipboard, shell,
- );
+ Binding::Custom(message) => {
+ shell.publish(message);
}
}
- Binding::Custom(message) => {
- shell.publish(message);
- }
}
- }
- apply_binding(
- binding,
- self.content,
- state,
- on_edit,
- clipboard,
- shell,
- );
+ apply_binding(
+ binding,
+ self.content,
+ state,
+ on_edit,
+ clipboard,
+ shell,
+ );
+
+ if let Some(focus) = &mut state.focus {
+ focus.updated_at = Instant::now();
+ }
- if let Some(focus) = &mut state.focus {
- focus.updated_at = Instant::now();
+ shell.capture_event();
}
}
}
- event::Status::Captured
+ let status = {
+ let is_disabled = self.on_edit.is_none();
+ let is_hovered = cursor.is_over(layout.bounds());
+
+ if is_disabled {
+ Status::Disabled
+ } else if state.focus.is_some() {
+ Status::Focused { is_hovered }
+ } else if is_hovered {
+ Status::Hovered
+ } else {
+ Status::Active
+ }
+ };
+
+ if is_redraw {
+ self.last_status = Some(status);
+ } else if self
+ .last_status
+ .is_some_and(|last_status| status != last_status)
+ {
+ shell.request_redraw();
+ }
}
fn draw(
@@ -808,7 +839,7 @@ where
theme: &Theme,
_defaults: &renderer::Style,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -824,20 +855,8 @@ where
|highlight| (self.highlighter_format)(highlight, theme),
);
- let is_disabled = self.on_edit.is_none();
- let is_mouse_over = cursor.is_over(bounds);
-
- let status = if is_disabled {
- Status::Disabled
- } else if state.focus.is_some() {
- Status::Focused
- } else if is_mouse_over {
- Status::Hovered
- } else {
- Status::Active
- };
-
- let style = theme.style(&self.class, status);
+ let style = theme
+ .style(&self.class, self.last_status.unwrap_or(Status::Active));
renderer.fill_quad(
renderer::Quad {
@@ -952,13 +971,13 @@ where
fn operate(
&self,
tree: &mut widget::Tree,
- _layout: Layout<'_>,
+ layout: Layout<'_>,
_renderer: &Renderer,
operation: &mut dyn widget::Operation,
) {
let state = tree.state.downcast_mut::<State<Highlighter>>();
- operation.focusable(state, None);
+ operation.focusable(None, layout.bounds(), state);
}
}
@@ -1036,7 +1055,7 @@ impl<Message> Binding<Message> {
status,
} = event;
- if status != Status::Focused {
+ if !matches!(status, Status::Focused { .. }) {
return None;
}
@@ -1045,7 +1064,9 @@ impl<Message> Binding<Message> {
keyboard::Key::Named(key::Named::Backspace) => {
Some(Self::Backspace)
}
- keyboard::Key::Named(key::Named::Delete) if text.is_none() => {
+ keyboard::Key::Named(key::Named::Delete)
+ if text.is_none() || text.as_deref() == Some("\u{7f}") =>
+ {
Some(Self::Delete)
}
keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
@@ -1174,7 +1195,9 @@ impl<Message> Update<Message> {
..
}) => {
let status = if state.focus.is_some() {
- Status::Focused
+ Status::Focused {
+ is_hovered: cursor.is_over(bounds),
+ }
} else {
Status::Active
};
@@ -1220,13 +1243,16 @@ pub enum Status {
/// The [`TextEditor`] is being hovered.
Hovered,
/// The [`TextEditor`] is focused.
- Focused,
+ Focused {
+ /// Whether the [`TextEditor`] is hovered, while focused.
+ is_hovered: bool,
+ },
/// The [`TextEditor`] cannot be interacted with.
Disabled,
}
/// The appearance of a text input.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Background`] of the text input.
pub background: Background,
@@ -1295,7 +1321,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
},
..active
},
- Status::Focused => Style {
+ Status::Focused { .. } => Style {
border: Border {
color: palette.primary.strong.color,
..active.border
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 5bbf76f5..57ebe46a 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -42,7 +42,6 @@ use editor::Editor;
use crate::core::alignment;
use crate::core::clipboard::{self, Clipboard};
-use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key;
use crate::core::layout;
@@ -57,8 +56,8 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
use crate::core::{
- Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point,
- Rectangle, Shell, Size, Theme, Vector, Widget,
+ Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels,
+ Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::runtime::task::{self, Task};
use crate::runtime::Action;
@@ -120,6 +119,7 @@ pub struct TextInput<
on_submit: Option<Message>,
icon: Option<Icon<Renderer::Font>>,
class: Theme::Class<'a>,
+ last_status: Option<Status>,
}
/// The default [`Padding`] of a [`TextInput`].
@@ -150,6 +150,7 @@ where
on_submit: None,
icon: None,
class: Theme::default(),
+ last_status: None,
}
}
@@ -400,7 +401,7 @@ where
renderer: &mut Renderer,
theme: &Theme,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ _cursor: mouse::Cursor,
value: Option<&Value>,
viewport: &Rectangle,
) {
@@ -416,19 +417,8 @@ where
let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds();
- let is_mouse_over = cursor.is_over(bounds);
-
- let status = if is_disabled {
- Status::Disabled
- } else if state.is_focused() {
- Status::Focused
- } else if is_mouse_over {
- Status::Hovered
- } else {
- Status::Active
- };
-
- let style = theme.style(&self.class, status);
+ let style = theme
+ .style(&self.class, self.last_status.unwrap_or(Status::Disabled));
renderer.fill_quad(
renderer::Quad {
@@ -584,8 +574,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for TextInput<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for TextInput<'_, Message, Theme, Renderer>
where
Message: Clone,
Theme: Catalog,
@@ -627,17 +617,26 @@ where
fn operate(
&self,
tree: &mut Tree,
- _layout: Layout<'_>,
+ layout: Layout<'_>,
_renderer: &Renderer,
operation: &mut dyn Operation,
) {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
- operation.focusable(state, self.id.as_ref().map(|id| &id.0));
- operation.text_input(state, self.id.as_ref().map(|id| &id.0));
+ operation.focusable(
+ self.id.as_ref().map(|id| &id.0),
+ layout.bounds(),
+ state,
+ );
+
+ operation.text_input(
+ self.id.as_ref().map(|id| &id.0),
+ layout.bounds(),
+ state,
+ );
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -647,7 +646,7 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let update_cache = |state, value| {
replace_paragraph(
renderer,
@@ -660,22 +659,21 @@ where
);
};
- match event {
+ match &event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state::<Renderer>(tree);
+ let cursor_before = state.cursor;
let click_position = cursor.position_over(layout.bounds());
state.is_focused = if click_position.is_some() {
- state.is_focused.or_else(|| {
- let now = Instant::now();
-
- Some(Focus {
- updated_at: now,
- now,
- is_window_focused: true,
- })
+ let now = Instant::now();
+
+ Some(Focus {
+ updated_at: now,
+ now,
+ is_window_focused: true,
})
} else {
None
@@ -760,7 +758,11 @@ where
state.last_click = Some(click);
- return event::Status::Captured;
+ if cursor_before != state.cursor {
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
@@ -801,11 +803,21 @@ where
)
.unwrap_or(0);
+ let selection_before = state.cursor.selection(&value);
+
state
.cursor
.select_range(state.cursor.start(&value), position);
- return event::Status::Captured;
+ if let Some(focus) = &mut state.is_focused {
+ focus.updated_at = Instant::now();
+ }
+
+ if selection_before != state.cursor.selection(&value) {
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
}
Event::Keyboard(keyboard::Event::KeyPressed {
@@ -815,7 +827,6 @@ where
if let Some(focus) = &mut state.is_focused {
let modifiers = state.keyboard_modifiers;
- focus.updated_at = Instant::now();
match key.as_ref() {
keyboard::Key::Character("c")
@@ -831,14 +842,15 @@ where
);
}
- return event::Status::Captured;
+ shell.capture_event();
+ return;
}
keyboard::Key::Character("x")
if state.keyboard_modifiers.command()
&& !self.is_secure =>
{
let Some(on_input) = &self.on_input else {
- return event::Status::Ignored;
+ return;
};
if let Some((start, end)) =
@@ -856,17 +868,18 @@ where
let message = (on_input)(editor.contents());
shell.publish(message);
+ shell.capture_event();
+ focus.updated_at = Instant::now();
update_cache(state, &self.value);
-
- return event::Status::Captured;
+ return;
}
keyboard::Key::Character("v")
if state.keyboard_modifiers.command()
&& !state.keyboard_modifiers.alt() =>
{
let Some(on_input) = &self.on_input else {
- return event::Status::Ignored;
+ return;
};
let content = match state.is_pasting.take() {
@@ -885,7 +898,6 @@ where
let mut editor =
Editor::new(&mut self.value, &mut state.cursor);
-
editor.paste(content.clone());
let message = if let Some(paste) = &self.on_paste {
@@ -894,26 +906,35 @@ where
(on_input)(editor.contents())
};
shell.publish(message);
+ shell.capture_event();
state.is_pasting = Some(content);
-
+ focus.updated_at = Instant::now();
update_cache(state, &self.value);
-
- return event::Status::Captured;
+ return;
}
keyboard::Key::Character("a")
if state.keyboard_modifiers.command() =>
{
+ let cursor_before = state.cursor;
+
state.cursor.select_all(&self.value);
- return event::Status::Captured;
+ if cursor_before != state.cursor {
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
+ return;
}
_ => {}
}
if let Some(text) = text {
let Some(on_input) = &self.on_input else {
- return event::Status::Ignored;
+ return;
};
state.is_pasting = None;
@@ -928,12 +949,11 @@ where
let message = (on_input)(editor.contents());
shell.publish(message);
+ shell.capture_event();
focus.updated_at = Instant::now();
-
update_cache(state, &self.value);
-
- return event::Status::Captured;
+ return;
}
}
@@ -941,11 +961,12 @@ where
keyboard::Key::Named(key::Named::Enter) => {
if let Some(on_submit) = self.on_submit.clone() {
shell.publish(on_submit);
+ shell.capture_event();
}
}
keyboard::Key::Named(key::Named::Backspace) => {
let Some(on_input) = &self.on_input else {
- return event::Status::Ignored;
+ return;
};
if modifiers.jump()
@@ -968,12 +989,14 @@ where
let message = (on_input)(editor.contents());
shell.publish(message);
+ shell.capture_event();
+ focus.updated_at = Instant::now();
update_cache(state, &self.value);
}
keyboard::Key::Named(key::Named::Delete) => {
let Some(on_input) = &self.on_input else {
- return event::Status::Ignored;
+ return;
};
if modifiers.jump()
@@ -999,10 +1022,14 @@ where
let message = (on_input)(editor.contents());
shell.publish(message);
+ shell.capture_event();
+ focus.updated_at = Instant::now();
update_cache(state, &self.value);
}
keyboard::Key::Named(key::Named::Home) => {
+ let cursor_before = state.cursor;
+
if modifiers.shift() {
state.cursor.select_range(
state.cursor.start(&self.value),
@@ -1011,8 +1038,18 @@ where
} else {
state.cursor.move_to(0);
}
+
+ if cursor_before != state.cursor {
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
keyboard::Key::Named(key::Named::End) => {
+ let cursor_before = state.cursor;
+
if modifiers.shift() {
state.cursor.select_range(
state.cursor.start(&self.value),
@@ -1021,10 +1058,20 @@ where
} else {
state.cursor.move_to(self.value.len());
}
+
+ if cursor_before != state.cursor {
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
keyboard::Key::Named(key::Named::ArrowLeft)
if modifiers.macos_command() =>
{
+ let cursor_before = state.cursor;
+
if modifiers.shift() {
state.cursor.select_range(
state.cursor.start(&self.value),
@@ -1033,10 +1080,20 @@ where
} else {
state.cursor.move_to(0);
}
+
+ if cursor_before != state.cursor {
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
keyboard::Key::Named(key::Named::ArrowRight)
if modifiers.macos_command() =>
{
+ let cursor_before = state.cursor;
+
if modifiers.shift() {
state.cursor.select_range(
state.cursor.start(&self.value),
@@ -1045,8 +1102,18 @@ where
} else {
state.cursor.move_to(self.value.len());
}
+
+ if cursor_before != state.cursor {
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
keyboard::Key::Named(key::Named::ArrowLeft) => {
+ let cursor_before = state.cursor;
+
if modifiers.jump() && !self.is_secure {
if modifiers.shift() {
state
@@ -1062,8 +1129,18 @@ where
} else {
state.cursor.move_left(&self.value);
}
+
+ if cursor_before != state.cursor {
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
keyboard::Key::Named(key::Named::ArrowRight) => {
+ let cursor_before = state.cursor;
+
if modifiers.jump() && !self.is_secure {
if modifiers.shift() {
state
@@ -1079,6 +1156,14 @@ where
} else {
state.cursor.move_right(&self.value);
}
+
+ if cursor_before != state.cursor {
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw();
+ }
+
+ shell.capture_event();
}
keyboard::Key::Named(key::Named::Escape) => {
state.is_focused = None;
@@ -1087,39 +1172,22 @@ where
state.keyboard_modifiers =
keyboard::Modifiers::default();
- }
- keyboard::Key::Named(
- key::Named::Tab
- | key::Named::ArrowUp
- | key::Named::ArrowDown,
- ) => {
- return event::Status::Ignored;
+
+ shell.capture_event();
}
_ => {}
}
-
- return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
let state = state::<Renderer>(tree);
if state.is_focused.is_some() {
- match key.as_ref() {
- keyboard::Key::Character("v") => {
- state.is_pasting = None;
- }
- keyboard::Key::Named(
- key::Named::Tab
- | key::Named::ArrowUp
- | key::Named::ArrowDown,
- ) => {
- return event::Status::Ignored;
- }
- _ => {}
- }
+ if let keyboard::Key::Character("v") = key.as_ref() {
+ state.is_pasting = None;
- return event::Status::Captured;
+ shell.capture_event();
+ }
}
state.is_pasting = None;
@@ -1127,7 +1195,7 @@ where
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
let state = state::<Renderer>(tree);
- state.keyboard_modifiers = modifiers;
+ state.keyboard_modifiers = *modifiers;
}
Event::Window(window::Event::Unfocused) => {
let state = state::<Renderer>(tree);
@@ -1143,32 +1211,59 @@ where
focus.is_window_focused = true;
focus.updated_at = Instant::now();
- shell.request_redraw(window::RedrawRequest::NextFrame);
+ shell.request_redraw();
}
}
Event::Window(window::Event::RedrawRequested(now)) => {
let state = state::<Renderer>(tree);
if let Some(focus) = &mut state.is_focused {
- if focus.is_window_focused {
- focus.now = now;
+ if focus.is_window_focused
+ && matches!(
+ state.cursor.state(&self.value),
+ cursor::State::Index(_)
+ )
+ {
+ focus.now = *now;
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
- - (now - focus.updated_at).as_millis()
+ - (*now - focus.updated_at).as_millis()
% CURSOR_BLINK_INTERVAL_MILLIS;
- shell.request_redraw(window::RedrawRequest::At(
- now + Duration::from_millis(
+ shell.request_redraw_at(
+ *now + Duration::from_millis(
millis_until_redraw as u64,
),
- ));
+ );
}
}
}
_ => {}
}
- event::Status::Ignored
+ let state = state::<Renderer>(tree);
+ let is_disabled = self.on_input.is_none();
+
+ let status = if is_disabled {
+ Status::Disabled
+ } else if state.is_focused() {
+ Status::Focused {
+ is_hovered: cursor.is_over(layout.bounds()),
+ }
+ } else if cursor.is_over(layout.bounds()) {
+ Status::Hovered
+ } else {
+ Status::Active
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.last_status = Some(status);
+ } else if self
+ .last_status
+ .is_some_and(|last_status| status != last_status)
+ {
+ shell.request_redraw();
+ }
}
fn draw(
@@ -1535,13 +1630,16 @@ pub enum Status {
/// The [`TextInput`] is being hovered.
Hovered,
/// The [`TextInput`] is focused.
- Focused,
+ Focused {
+ /// Whether the [`TextInput`] is hovered, while focused.
+ is_hovered: bool,
+ },
/// The [`TextInput`] cannot be interacted with.
Disabled,
}
/// The appearance of a text input.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The [`Background`] of the text input.
pub background: Background,
@@ -1612,7 +1710,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
},
..active
},
- Status::Focused => Style {
+ Status::Focused { .. } => Style {
border: Border {
color: palette.primary.strong.color,
..active.border
diff --git a/widget/src/text_input/cursor.rs b/widget/src/text_input/cursor.rs
index f682b17d..a326fc8f 100644
--- a/widget/src/text_input/cursor.rs
+++ b/widget/src/text_input/cursor.rs
@@ -2,13 +2,13 @@
use crate::text_input::Value;
/// The cursor of a text input.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Cursor {
state: State,
}
/// The state of a [`Cursor`].
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum State {
/// Cursor without a selection
Index(usize),
diff --git a/widget/src/themer.rs b/widget/src/themer.rs
index 499a9fe8..769cc4ca 100644
--- a/widget/src/themer.rs
+++ b/widget/src/themer.rs
@@ -1,5 +1,4 @@
use crate::container;
-use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
@@ -7,8 +6,8 @@ use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation;
use crate::core::{
- Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
- Shell, Size, Vector, Widget,
+ Background, Clipboard, Color, Element, Event, Layout, Length, Point,
+ Rectangle, Shell, Size, Vector, Widget,
};
use std::marker::PhantomData;
@@ -64,8 +63,8 @@ where
}
}
-impl<'a, Message, Theme, NewTheme, F, Renderer> Widget<Message, Theme, Renderer>
- for Themer<'a, Message, Theme, NewTheme, F, Renderer>
+impl<Message, Theme, NewTheme, F, Renderer> Widget<Message, Theme, Renderer>
+ for Themer<'_, Message, Theme, NewTheme, F, Renderer>
where
F: Fn(&Theme) -> NewTheme,
Renderer: crate::core::Renderer,
@@ -111,7 +110,7 @@ where
.operate(tree, layout, renderer, operation);
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -121,10 +120,10 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
- self.content.as_widget_mut().on_event(
+ ) {
+ self.content.as_widget_mut().update(
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
- )
+ );
}
fn mouse_interaction(
@@ -188,9 +187,9 @@ where
content: overlay::Element<'a, Message, NewTheme, Renderer>,
}
- impl<'a, Message, Theme, NewTheme, Renderer>
+ impl<Message, Theme, NewTheme, Renderer>
overlay::Overlay<Message, Theme, Renderer>
- for Overlay<'a, Message, Theme, NewTheme, Renderer>
+ for Overlay<'_, Message, Theme, NewTheme, Renderer>
where
Renderer: crate::core::Renderer,
{
@@ -219,7 +218,7 @@ where
);
}
- fn on_event(
+ fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@@ -227,9 +226,9 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
- ) -> event::Status {
+ ) {
self.content
- .on_event(event, layout, cursor, renderer, clipboard, shell)
+ .update(event, layout, cursor, renderer, clipboard, shell);
}
fn operate(
diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs
index 3b412081..56c2be1f 100644
--- a/widget/src/toggler.rs
+++ b/widget/src/toggler.rs
@@ -31,7 +31,6 @@
//! }
//! ```
use crate::core::alignment;
-use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
@@ -39,6 +38,7 @@ use crate::core::text;
use crate::core::touch;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
Border, Clipboard, Color, Element, Event, Layout, Length, Pixels,
Rectangle, Shell, Size, Theme, Widget,
@@ -99,6 +99,7 @@ pub struct Toggler<
spacing: f32,
font: Option<Renderer::Font>,
class: Theme::Class<'a>,
+ last_status: Option<Status>,
}
impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer>
@@ -132,6 +133,7 @@ where
spacing: Self::DEFAULT_SIZE / 2.0,
font: None,
class: Theme::default(),
+ last_status: None,
}
}
@@ -243,8 +245,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Toggler<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Toggler<'_, Message, Theme, Renderer>
where
Theme: Catalog,
Renderer: text::Renderer,
@@ -304,7 +306,7 @@ where
)
}
- fn on_event(
+ fn update(
&mut self,
_state: &mut Tree,
event: Event,
@@ -314,9 +316,9 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let Some(on_toggle) = &self.on_toggle else {
- return event::Status::Ignored;
+ return;
};
match event {
@@ -326,13 +328,31 @@ where
if mouse_over {
shell.publish(on_toggle(!self.is_toggled));
-
- event::Status::Captured
- } else {
- event::Status::Ignored
+ shell.capture_event();
}
}
- _ => event::Status::Ignored,
+ _ => {}
+ }
+
+ let current_status = if self.on_toggle.is_none() {
+ Status::Disabled
+ } else if cursor.is_over(layout.bounds()) {
+ Status::Hovered {
+ is_toggled: self.is_toggled,
+ }
+ } else {
+ Status::Active {
+ is_toggled: self.is_toggled,
+ }
+ };
+
+ if let Event::Window(window::Event::RedrawRequested(_now)) = event {
+ self.last_status = Some(current_status);
+ } else if self
+ .last_status
+ .is_some_and(|status| status != current_status)
+ {
+ shell.request_redraw();
}
}
@@ -362,7 +382,7 @@ where
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor: mouse::Cursor,
+ _cursor: mouse::Cursor,
viewport: &Rectangle,
) {
/// Makes sure that the border radius of the toggler looks good at every size.
@@ -391,21 +411,8 @@ where
}
let bounds = toggler_layout.bounds();
- let is_mouse_over = cursor.is_over(layout.bounds());
-
- let status = if self.on_toggle.is_none() {
- Status::Disabled
- } else if is_mouse_over {
- Status::Hovered {
- is_toggled: self.is_toggled,
- }
- } else {
- Status::Active {
- is_toggled: self.is_toggled,
- }
- };
-
- let style = theme.style(&self.class, status);
+ let style = theme
+ .style(&self.class, self.last_status.unwrap_or(Status::Disabled));
let border_radius = bounds.height / BORDER_RADIUS_RATIO;
let space = SPACE_RATIO * bounds.height;
@@ -489,7 +496,7 @@ pub enum Status {
}
/// The appearance of a toggler.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The background [`Color`] of the toggler.
pub background: Color,
diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs
index e98f4da7..a0ffe392 100644
--- a/widget/src/tooltip.rs
+++ b/widget/src/tooltip.rs
@@ -22,7 +22,6 @@
//! }
//! ```
use crate::container;
-use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::overlay;
@@ -30,8 +29,8 @@ use crate::core::renderer;
use crate::core::text;
use crate::core::widget::{self, Widget};
use crate::core::{
- Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
- Vector,
+ Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle,
+ Shell, Size, Vector,
};
/// An element to display a widget over another.
@@ -143,8 +142,8 @@ where
}
}
-impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for Tooltip<'a, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Tooltip<'_, Message, Theme, Renderer>
where
Theme: container::Catalog,
Renderer: text::Renderer,
@@ -190,7 +189,7 @@ where
.layout(&mut tree.children[0], renderer, limits)
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut widget::Tree,
event: Event,
@@ -200,7 +199,7 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let state = tree.state.downcast_mut::<State>();
let was_idle = *state == State::Idle;
@@ -214,9 +213,12 @@ where
if was_idle != is_idle {
shell.invalidate_layout();
+ shell.request_redraw();
+ } else if !is_idle && self.position == Position::FollowCursor {
+ shell.request_redraw();
}
- self.content.as_widget_mut().on_event(
+ self.content.as_widget_mut().update(
&mut tree.children[0],
event,
layout,
@@ -225,7 +227,7 @@ where
clipboard,
shell,
viewport,
- )
+ );
}
fn mouse_interaction(
@@ -370,9 +372,8 @@ where
class: &'b Theme::Class<'a>,
}
-impl<'a, 'b, Message, Theme, Renderer>
- overlay::Overlay<Message, Theme, Renderer>
- for Overlay<'a, 'b, Message, Theme, Renderer>
+impl<Message, Theme, Renderer> overlay::Overlay<Message, Theme, Renderer>
+ for Overlay<'_, '_, Message, Theme, Renderer>
where
Theme: container::Catalog,
Renderer: text::Renderer,
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index 18633474..c1af142b 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -35,7 +35,6 @@ pub use crate::slider::{
};
use crate::core::border::Border;
-use crate::core::event::{self, Event};
use crate::core::keyboard;
use crate::core::keyboard::key::{self, Key};
use crate::core::layout::{self, Layout};
@@ -44,8 +43,8 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- self, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, Size,
- Widget,
+ self, Clipboard, Element, Event, Length, Pixels, Point, Rectangle, Shell,
+ Size, Widget,
};
/// An vertical bar and a handle that selects a single value from a range of
@@ -212,8 +211,8 @@ where
}
}
-impl<'a, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
- for VerticalSlider<'a, T, Message, Theme>
+impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for VerticalSlider<'_, T, Message, Theme>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Message: Clone,
@@ -244,7 +243,7 @@ where
layout::atomic(limits, self.width, self.height)
}
- fn on_event(
+ fn update(
&mut self,
tree: &mut Tree,
event: Event,
@@ -254,7 +253,7 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
- ) -> event::Status {
+ ) {
let state = tree.state.downcast_mut::<State>();
let is_dragging = state.is_dragging;
let current_value = self.value;
@@ -350,7 +349,7 @@ where
state.is_dragging = true;
}
- return event::Status::Captured;
+ shell.capture_event();
}
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
@@ -362,7 +361,7 @@ where
}
state.is_dragging = false;
- return event::Status::Captured;
+ shell.capture_event();
}
}
Event::Mouse(mouse::Event::CursorMoved { .. })
@@ -370,7 +369,7 @@ where
if is_dragging {
let _ = cursor.position().and_then(locate).map(change);
- return event::Status::Captured;
+ shell.capture_event();
}
}
Event::Mouse(mouse::Event::WheelScrolled { delta })
@@ -388,7 +387,7 @@ where
let _ = increment(current_value).map(change);
}
- return event::Status::Captured;
+ shell.capture_event();
}
}
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
@@ -403,7 +402,7 @@ where
_ => (),
}
- return event::Status::Captured;
+ shell.capture_event();
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
@@ -411,8 +410,6 @@ where
}
_ => {}
}
-
- event::Status::Ignored
}
fn draw(