Merge pull request #2662 from iced-rs/reactive-rendering

Reactive Rendering
This commit is contained in:
Héctor 2024-11-13 17:08:26 +01:00 committed by GitHub
commit a11fcf8f2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 2212 additions and 1803 deletions

View file

@ -23,19 +23,19 @@ maintenance = { status = "actively-developed" }
[features]
default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"]
# Enable the `wgpu` GPU-accelerated renderer backend
# Enables the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"]
# Enable the `tiny-skia` software renderer backend
# Enables the `tiny-skia` software renderer backend
tiny-skia = ["iced_renderer/tiny-skia"]
# Enables the `Image` widget
# Enables the `image` widget
image = ["image-without-codecs", "image/default"]
# Enables the `Image` widget, without any built-in codecs of the `image` crate
# Enables the `image` widget, without any built-in codecs of the `image` crate
image-without-codecs = ["iced_widget/image", "dep:image"]
# Enables the `Svg` widget
# Enables the `svg` widget
svg = ["iced_widget/svg"]
# Enables the `Canvas` widget
# Enables the `canvas` widget
canvas = ["iced_widget/canvas"]
# Enables the `QRCode` widget
# Enables the `qr_code` widget
qr_code = ["iced_widget/qr_code"]
# Enables the `markdown` widget
markdown = ["iced_widget/markdown"]
@ -55,18 +55,18 @@ system = ["iced_winit/system"]
web-colors = ["iced_renderer/web-colors"]
# Enables the WebGL backend, replacing WebGPU
webgl = ["iced_renderer/webgl"]
# Enables the syntax `highlighter` module
# Enables syntax highligthing
highlighter = ["iced_highlighter", "iced_widget/highlighter"]
# Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"]
# Enables the advanced module
advanced = ["iced_core/advanced", "iced_widget/advanced"]
# Enables embedding Fira Sans as the default font on Wasm builds
# Embeds Fira Sans as the default font on Wasm builds
fira-sans = ["iced_renderer/fira-sans"]
# Enables auto-detecting light/dark mode for the built-in theme
# Auto-detects light/dark mode for the built-in theme
auto-detect-theme = ["iced_core/auto-detect-theme"]
# Enables strict assertions for debugging purposes at the expense of performance
strict-assertions = ["iced_renderer/strict-assertions"]
# Redraws on every runtime event, and not only when a widget requests it
unconditional-rendering = ["iced_winit/unconditional-rendering"]
[dependencies]
iced_core.workspace = true

View file

@ -1,4 +1,3 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
@ -6,8 +5,8 @@ use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{
Border, Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector,
Widget,
Border, Clipboard, Color, Event, Layout, Length, Rectangle, Shell, Size,
Vector, Widget,
};
use std::borrow::Borrow;
@ -309,7 +308,7 @@ where
self.widget.operate(tree, layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
@ -319,11 +318,11 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
viewport: &Rectangle,
) -> event::Status {
) {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
let status = self.widget.on_event(
self.widget.update(
tree,
event,
layout,
@ -335,8 +334,6 @@ where
);
shell.merge(local_shell, &self.mapper);
status
}
fn draw(
@ -447,7 +444,7 @@ where
.operate(state, layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
state: &mut Tree,
event: Event,
@ -457,10 +454,10 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
self.element.widget.on_event(
) {
self.element.widget.update(
state, event, layout, cursor, renderer, clipboard, shell, viewport,
)
);
}
fn draw(

View file

@ -79,6 +79,7 @@ where
let max_cross = axis.cross(limits.max());
let mut fill_main_sum = 0;
let mut some_fill_cross = false;
let (mut cross, cross_compress) = match axis {
Axis::Vertical if width == Length::Shrink => (0.0, true),
Axis::Horizontal if height == Length::Shrink => (0.0, true),
@ -90,6 +91,10 @@ where
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
nodes.resize(items.len(), Node::default());
// FIRST PASS
// We lay out non-fluid elements in the main axis.
// If we need to compress the cross axis, then we skip any of these elements
// that are also fluid in the cross axis.
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
@ -121,6 +126,41 @@ where
nodes[i] = layout;
} else {
fill_main_sum += fill_main_factor;
some_fill_cross = some_fill_cross || fill_cross_factor != 0;
}
}
// SECOND PASS (conditional)
// If we must compress the cross axis and there are fluid elements in the
// cross axis, we lay out any of these elements that are also non-fluid in
// the main axis (i.e. the ones we deliberately skipped in the first pass).
//
// We use the maximum cross length obtained in the first pass as the maximum
// cross limit.
if cross_compress && some_fill_cross {
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate()
{
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor == 0 && fill_cross_factor != 0 {
let (max_width, max_height) = axis.pack(available, cross);
let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
let layout =
child.as_widget().layout(tree, renderer, &child_limits);
let size = layout.size();
available -= axis.main(size);
cross = cross.max(axis.cross(size));
nodes[i] = layout;
}
}
}
@ -135,6 +175,9 @@ where
},
};
// THIRD PASS
// We only have the elements that are fluid in the main axis left.
// We use the remaining space to evenly allocate space based on fill factors.
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();
@ -142,10 +185,16 @@ where
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor != 0 || (cross_compress && fill_cross_factor != 0) {
if fill_main_factor != 0 {
let max_main =
remaining * fill_main_factor as f32 / fill_main_sum as f32;
let max_main = if max_main.is_nan() {
f32::INFINITY
} else {
max_main
};
let min_main = if max_main.is_infinite() {
0.0
} else {
@ -178,6 +227,8 @@ where
let pad = axis.pack(padding.left, padding.top);
let mut main = pad.0;
// FOURTH PASS
// We align all the laid out nodes in the cross axis, if needed.
for (i, node) in nodes.iter_mut().enumerate() {
if i > 0 {
main += spacing;

View file

@ -5,13 +5,12 @@ mod group;
pub use element::Element;
pub use group::Group;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::widget::Tree;
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size, Vector};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Theme, Renderer>
@ -57,7 +56,7 @@ where
/// * a [`Clipboard`], if available
///
/// By default, it does nothing.
fn on_event(
fn update(
&mut self,
_event: Event,
_layout: Layout<'_>,
@ -65,8 +64,7 @@ where
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
event::Status::Ignored
) {
}
/// Returns the current [`mouse::Interaction`] of the [`Overlay`].

View file

@ -1,11 +1,10 @@
pub use crate::Overlay;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size};
/// A generic [`Overlay`].
#[allow(missing_debug_implementations)]
@ -50,7 +49,7 @@ where
}
/// Processes a runtime [`Event`].
pub fn on_event(
pub fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -58,9 +57,9 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
) {
self.overlay
.on_event(event, layout, cursor, renderer, clipboard, shell)
.update(event, layout, cursor, renderer, clipboard, shell);
}
/// Returns the current [`mouse::Interaction`] of the [`Element`].
@ -149,7 +148,7 @@ where
self.content.operate(layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -157,11 +156,11 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
) -> event::Status {
) {
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
let event_status = self.content.on_event(
self.content.update(
event,
layout,
cursor,
@ -171,8 +170,6 @@ where
);
shell.merge(local_shell, self.mapper);
event_status
}
fn mouse_interaction(

View file

@ -1,4 +1,3 @@
use crate::event;
use crate::layout;
use crate::mouse;
use crate::overlay;
@ -73,7 +72,7 @@ where
)
}
fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -81,21 +80,17 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.children
.iter_mut()
.zip(layout.children())
.map(|(child, layout)| {
child.on_event(
event.clone(),
layout,
cursor,
renderer,
clipboard,
shell,
)
})
.fold(event::Status::Ignored, event::Status::merge)
) {
for (child, layout) in self.children.iter_mut().zip(layout.children()) {
child.update(
event.clone(),
layout,
cursor,
renderer,
clipboard,
shell,
);
}
}
fn draw(

View file

@ -1,3 +1,5 @@
use crate::event;
use crate::time::Instant;
use crate::window;
/// A connection to the state of a shell.
@ -9,6 +11,7 @@ use crate::window;
#[derive(Debug)]
pub struct Shell<'a, Message> {
messages: &'a mut Vec<Message>,
event_status: event::Status,
redraw_request: Option<window::RedrawRequest>,
is_layout_invalid: bool,
are_widgets_invalid: bool,
@ -19,6 +22,7 @@ impl<'a, Message> Shell<'a, Message> {
pub fn new(messages: &'a mut Vec<Message>) -> Self {
Self {
messages,
event_status: event::Status::Ignored,
redraw_request: None,
is_layout_invalid: false,
are_widgets_invalid: false,
@ -35,14 +39,37 @@ impl<'a, Message> Shell<'a, Message> {
self.messages.push(message);
}
/// Requests a new frame to be drawn.
pub fn request_redraw(&mut self, request: window::RedrawRequest) {
/// Marks the current event as captured. Prevents "event bubbling".
///
/// A widget should capture an event when no ancestor should
/// handle it.
pub fn capture_event(&mut self) {
self.event_status = event::Status::Captured;
}
/// Returns the current [`event::Status`] of the [`Shell`].
pub fn event_status(&self) -> event::Status {
self.event_status
}
/// Returns whether the current event has been captured.
pub fn is_event_captured(&self) -> bool {
self.event_status == event::Status::Captured
}
/// Requests a new frame to be drawn as soon as possible.
pub fn request_redraw(&mut self) {
self.redraw_request = Some(window::RedrawRequest::NextFrame);
}
/// Requests a new frame to be drawn at the given [`Instant`].
pub fn request_redraw_at(&mut self, at: Instant) {
match self.redraw_request {
None => {
self.redraw_request = Some(request);
self.redraw_request = Some(window::RedrawRequest::At(at));
}
Some(current) if request < current => {
self.redraw_request = Some(request);
Some(window::RedrawRequest::At(current)) if at < current => {
self.redraw_request = Some(window::RedrawRequest::At(at));
}
_ => {}
}
@ -95,8 +122,12 @@ impl<'a, Message> Shell<'a, Message> {
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
self.messages.extend(other.messages.drain(..).map(f));
if let Some(at) = other.redraw_request {
self.request_redraw(at);
if let Some(new) = other.redraw_request {
self.redraw_request = Some(
self.redraw_request
.map(|current| if current < new { current } else { new })
.unwrap_or(new),
);
}
self.is_layout_invalid =
@ -104,5 +135,7 @@ impl<'a, Message> Shell<'a, Message> {
self.are_widgets_invalid =
self.are_widgets_invalid || other.are_widgets_invalid;
self.event_status = self.event_status.merge(other.event_status);
}
}

View file

@ -10,12 +10,11 @@ pub use operation::Operation;
pub use text::Text;
pub use tree::Tree;
use crate::event::{self, Event};
use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::{Clipboard, Length, Rectangle, Shell, Size, Vector};
use crate::{Clipboard, Event, Length, Rectangle, Shell, Size, Vector};
/// A component that displays information and allows interaction.
///
@ -112,7 +111,7 @@ where
/// Processes a runtime [`Event`].
///
/// By default, it does nothing.
fn on_event(
fn update(
&mut self,
_state: &mut Tree,
_event: Event,
@ -122,8 +121,7 @@ where
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
event::Status::Ignored
) {
}
/// Returns the current [`mouse::Interaction`] of the [`Widget`].

View file

@ -57,8 +57,9 @@ impl Example {
mod bezier {
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
use iced::widget::canvas::{
self, Canvas, Event, Frame, Geometry, Path, Stroke,
};
use iced::{Element, Fill, Point, Rectangle, Renderer, Theme};
#[derive(Default)]
@ -96,48 +97,47 @@ mod bezier {
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (event::Status, Option<Curve>) {
let Some(cursor_position) = cursor.position_in(bounds) else {
return (event::Status::Ignored, None);
};
) -> Option<canvas::Action<Curve>> {
let cursor_position = cursor.position_in(bounds)?;
match event {
Event::Mouse(mouse_event) => {
let message = match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => {
match *state {
None => {
*state = Some(Pending::One {
from: cursor_position,
});
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
)) => Some(
match *state {
None => {
*state = Some(Pending::One {
from: cursor_position,
});
None
}
Some(Pending::One { from }) => {
*state = Some(Pending::Two {
from,
to: cursor_position,
});
None
}
Some(Pending::Two { from, to }) => {
*state = None;
Some(Curve {
from,
to,
control: cursor_position,
})
}
}
canvas::Action::request_redraw()
}
_ => None,
};
Some(Pending::One { from }) => {
*state = Some(Pending::Two {
from,
to: cursor_position,
});
(event::Status::Captured, message)
canvas::Action::request_redraw()
}
Some(Pending::Two { from, to }) => {
*state = None;
canvas::Action::publish(Curve {
from,
to,
control: cursor_position,
})
}
}
.and_capture(),
),
Event::Mouse(mouse::Event::CursorMoved { .. })
if state.is_some() =>
{
Some(canvas::Action::request_redraw())
}
_ => (event::Status::Ignored, None),
_ => None,
}
}

View file

@ -193,8 +193,9 @@ mod grid {
use iced::mouse;
use iced::touch;
use iced::widget::canvas;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
use iced::widget::canvas::{
Cache, Canvas, Event, Frame, Geometry, Path, Text,
};
use iced::{
Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector,
};
@ -383,14 +384,12 @@ mod grid {
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
) -> Option<canvas::Action<Message>> {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
*interaction = Interaction::None;
}
let Some(cursor_position) = cursor.position_in(bounds) else {
return (event::Status::Ignored, None);
};
let cursor_position = cursor.position_in(bounds)?;
let cell = Cell::at(self.project(cursor_position, bounds.size()));
let is_populated = self.state.contains(&cell);
@ -413,7 +412,12 @@ mod grid {
populate.or(unpopulate)
};
(event::Status::Captured, message)
Some(
message
.map(canvas::Action::publish)
.unwrap_or(canvas::Action::request_redraw())
.and_capture(),
)
}
Event::Mouse(mouse_event) => match mouse_event {
mouse::Event::ButtonPressed(button) => {
@ -438,7 +442,12 @@ mod grid {
_ => None,
};
(event::Status::Captured, message)
Some(
message
.map(canvas::Action::publish)
.unwrap_or(canvas::Action::request_redraw())
.and_capture(),
)
}
mouse::Event::CursorMoved { .. } => {
let message = match *interaction {
@ -454,12 +463,14 @@ mod grid {
Interaction::None => None,
};
let event_status = match interaction {
Interaction::None => event::Status::Ignored,
_ => event::Status::Captured,
};
let action = message
.map(canvas::Action::publish)
.unwrap_or(canvas::Action::request_redraw());
(event_status, message)
Some(match interaction {
Interaction::None => action,
_ => action.and_capture(),
})
}
mouse::Event::WheelScrolled { delta } => match delta {
mouse::ScrollDelta::Lines { y, .. }
@ -496,18 +507,21 @@ mod grid {
None
};
(
event::Status::Captured,
Some(Message::Scaled(scaling, translation)),
Some(
canvas::Action::publish(Message::Scaled(
scaling,
translation,
))
.and_capture(),
)
} else {
(event::Status::Captured, None)
Some(canvas::Action::capture())
}
}
},
_ => (event::Status::Ignored, None),
_ => None,
},
_ => (event::Status::Ignored, None),
_ => None,
}
}

View file

@ -3,11 +3,10 @@ use iced::advanced::layout;
use iced::advanced::renderer;
use iced::advanced::widget::tree::{self, Tree};
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
use iced::event;
use iced::mouse;
use iced::time::Instant;
use iced::widget::canvas;
use iced::window::{self, RedrawRequest};
use iced::window;
use iced::{
Background, Color, Element, Event, Length, Radians, Rectangle, Renderer,
Size, Vector,
@ -262,7 +261,7 @@ where
layout::atomic(limits, self.size, self.size)
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
@ -272,7 +271,7 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
) {
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
@ -283,10 +282,8 @@ where
);
state.cache.clear();
shell.request_redraw(RedrawRequest::NextFrame);
shell.request_redraw();
}
event::Status::Ignored
}
fn draw(

View file

@ -3,10 +3,9 @@ use iced::advanced::layout;
use iced::advanced::renderer::{self, Quad};
use iced::advanced::widget::tree::{self, Tree};
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
use iced::event;
use iced::mouse;
use iced::time::Instant;
use iced::window::{self, RedrawRequest};
use iced::window;
use iced::{Background, Color, Element, Event, Length, Rectangle, Size};
use super::easing::{self, Easing};
@ -176,7 +175,7 @@ where
layout::atomic(limits, self.width, self.height)
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
@ -186,16 +185,14 @@ where
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
) {
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
*state = state.timed_transition(self.cycle_duration, now);
shell.request_redraw(RedrawRequest::NextFrame);
shell.request_redraw();
}
event::Status::Ignored
}
fn draw(

View file

@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
iced = { path = "../..", features = ["debug", "multi-window"] }
iced = { path = "../..", features = ["debug"] }

View file

@ -3,9 +3,8 @@
//! computers like Microsoft Surface.
use iced::mouse;
use iced::touch;
use iced::widget::canvas::event;
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{self, Canvas, Geometry};
use iced::widget::canvas::{self, Canvas, Event, Geometry};
use iced::{Color, Element, Fill, Point, Rectangle, Renderer, Theme};
use std::collections::HashMap;
@ -56,25 +55,25 @@ impl canvas::Program<Message> for Multitouch {
fn update(
&self,
_state: &mut Self::State,
event: event::Event,
event: Event,
_bounds: Rectangle,
_cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
match event {
event::Event::Touch(touch_event) => match touch_event {
) -> Option<canvas::Action<Message>> {
let message = match event {
Event::Touch(
touch::Event::FingerPressed { id, position }
| touch::Event::FingerMoved { id, position } => (
event::Status::Captured,
Some(Message::FingerPressed { id, position }),
),
| touch::Event::FingerMoved { id, position },
) => Some(Message::FingerPressed { id, position }),
Event::Touch(
touch::Event::FingerLifted { id, .. }
| touch::Event::FingerLost { id, .. } => (
event::Status::Captured,
Some(Message::FingerLifted { id }),
),
},
_ => (event::Status::Ignored, None),
}
| touch::Event::FingerLost { id, .. },
) => Some(Message::FingerLifted { id }),
_ => None,
};
message
.map(canvas::Action::publish)
.map(canvas::Action::and_capture)
}
fn draw(

View file

@ -1,6 +1,5 @@
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{self, Canvas, Geometry};
use iced::widget::canvas::{self, Canvas, Event, Geometry};
use iced::widget::{column, row, slider, text};
use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme};
@ -80,26 +79,22 @@ impl canvas::Program<Message> for SierpinskiGraph {
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
let Some(cursor_position) = cursor.position_in(bounds) else {
return (event::Status::Ignored, None);
};
) -> Option<canvas::Action<Message>> {
let cursor_position = cursor.position_in(bounds)?;
match event {
Event::Mouse(mouse_event) => {
let message = match mouse_event {
iced::mouse::Event::ButtonPressed(
iced::mouse::Button::Left,
) => Some(Message::PointAdded(cursor_position)),
iced::mouse::Event::ButtonPressed(
iced::mouse::Button::Right,
) => Some(Message::PointRemoved),
_ => None,
};
(event::Status::Captured, message)
}
_ => (event::Status::Ignored, None),
Event::Mouse(mouse::Event::ButtonPressed(button)) => match button {
mouse::Button::Left => Some(canvas::Action::publish(
Message::PointAdded(cursor_position),
)),
mouse::Button::Right => {
Some(canvas::Action::publish(Message::PointRemoved))
}
_ => None,
},
_ => None,
}
.map(canvas::Action::and_capture)
}
fn draw(

View file

@ -169,7 +169,6 @@ mod toast {
use iced::advanced::renderer;
use iced::advanced::widget::{self, Operation, Tree};
use iced::advanced::{Clipboard, Shell, Widget};
use iced::event::{self, Event};
use iced::mouse;
use iced::theme;
use iced::widget::{
@ -177,8 +176,8 @@ mod toast {
};
use iced::window;
use iced::{
Alignment, Center, Element, Fill, Length, Point, Rectangle, Renderer,
Size, Theme, Vector,
Alignment, Center, Element, Event, Fill, Length, Point, Rectangle,
Renderer, Size, Theme, Vector,
};
pub const DEFAULT_TIMEOUT: u64 = 5;
@ -359,7 +358,7 @@ mod toast {
});
}
fn on_event(
fn update(
&mut self,
state: &mut Tree,
event: Event,
@ -369,8 +368,8 @@ mod toast {
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(
&mut state.children[0],
event,
layout,
@ -379,7 +378,7 @@ mod toast {
clipboard,
shell,
viewport,
)
);
}
fn draw(
@ -490,7 +489,7 @@ mod toast {
.translate(Vector::new(self.position.x, self.position.y))
}
fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -498,10 +497,8 @@ mod toast {
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
) {
if let Event::Window(window::Event::RedrawRequested(now)) = &event {
let mut next_redraw: Option<window::RedrawRequest> = None;
self.instants.iter_mut().enumerate().for_each(
|(index, maybe_instant)| {
if let Some(instant) = maybe_instant.as_mut() {
@ -512,55 +509,43 @@ mod toast {
if remaining == Duration::ZERO {
maybe_instant.take();
shell.publish((self.on_close)(index));
next_redraw =
Some(window::RedrawRequest::NextFrame);
} else {
let redraw_at =
window::RedrawRequest::At(*now + remaining);
next_redraw = next_redraw
.map(|redraw| redraw.min(redraw_at))
.or(Some(redraw_at));
shell.request_redraw_at(*now + remaining);
}
}
},
);
if let Some(redraw) = next_redraw {
shell.request_redraw(redraw);
}
}
let viewport = layout.bounds();
self.toasts
for (((child, state), layout), instant) in self
.toasts
.iter_mut()
.zip(self.state.iter_mut())
.zip(layout.children())
.zip(self.instants.iter_mut())
.map(|(((child, state), layout), instant)| {
let mut local_messages = vec![];
let mut local_shell = Shell::new(&mut local_messages);
{
let mut local_messages = vec![];
let mut local_shell = Shell::new(&mut local_messages);
let status = child.as_widget_mut().on_event(
state,
event.clone(),
layout,
cursor,
renderer,
clipboard,
&mut local_shell,
&viewport,
);
child.as_widget_mut().update(
state,
event.clone(),
layout,
cursor,
renderer,
clipboard,
&mut local_shell,
&viewport,
);
if !local_shell.is_empty() {
instant.take();
}
if !local_shell.is_empty() {
instant.take();
}
shell.merge(local_shell, std::convert::identity);
status
})
.fold(event::Status::Ignored, event::Status::merge)
shell.merge(local_shell, std::convert::identity);
}
}
fn draw(

View file

@ -158,7 +158,7 @@ where
}
/// Processes a runtime [`Event`].
pub fn on_event(
pub fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -166,7 +166,7 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
) {
fn recurse<Message, Theme, Renderer>(
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
layout: Layout<'_>,
@ -175,31 +175,30 @@ where
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> (event::Status, bool)
) -> bool
where
Renderer: renderer::Renderer,
{
let mut layouts = layout.children();
if let Some(layout) = layouts.next() {
let (nested_status, nested_is_over) =
if let Some((mut nested, nested_layout)) =
element.overlay(layout, renderer).zip(layouts.next())
{
recurse(
&mut nested,
nested_layout,
event.clone(),
cursor,
renderer,
clipboard,
shell,
)
} else {
(event::Status::Ignored, false)
};
let nested_is_over = if let Some((mut nested, nested_layout)) =
element.overlay(layout, renderer).zip(layouts.next())
{
recurse(
&mut nested,
nested_layout,
event.clone(),
cursor,
renderer,
clipboard,
shell,
)
} else {
false
};
if matches!(nested_status, event::Status::Ignored) {
if shell.event_status() == event::Status::Ignored {
let is_over = nested_is_over
|| cursor
.position()
@ -212,30 +211,29 @@ where
})
.unwrap_or_default();
(
element.on_event(
event,
layout,
if nested_is_over {
mouse::Cursor::Unavailable
} else {
cursor
},
renderer,
clipboard,
shell,
),
is_over,
)
element.update(
event,
layout,
if nested_is_over {
mouse::Cursor::Unavailable
} else {
cursor
},
renderer,
clipboard,
shell,
);
is_over
} else {
(nested_status, nested_is_over)
nested_is_over
}
} else {
(event::Status::Ignored, false)
false
}
}
let (status, _) = recurse(
let _ = recurse(
&mut self.overlay,
layout,
event,
@ -244,8 +242,6 @@ where
clipboard,
shell,
);
status
}
/// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay.

View file

@ -210,7 +210,7 @@ where
for event in events.iter().cloned() {
let mut shell = Shell::new(messages);
let event_status = overlay.on_event(
overlay.update(
event,
Layout::new(&layout),
cursor,
@ -219,7 +219,7 @@ where
&mut shell,
);
event_statuses.push(event_status);
event_statuses.push(shell.event_status());
match (redraw_request, shell.redraw_request()) {
(None, Some(at)) => {
@ -308,7 +308,7 @@ where
let mut shell = Shell::new(messages);
let event_status = self.root.as_widget_mut().on_event(
self.root.as_widget_mut().update(
&mut self.state,
event,
Layout::new(&self.base),
@ -319,7 +319,7 @@ where
&viewport,
);
if matches!(event_status, event::Status::Captured) {
if shell.event_status() == event::Status::Captured {
self.overlay = None;
}
@ -347,7 +347,7 @@ where
outdated = true;
}
event_status.merge(overlay_status)
shell.event_status().merge(overlay_status)
})
.collect();

89
widget/src/action.rs Normal file
View file

@ -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,
)
}
}

View file

@ -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> {
@ -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,7 +309,7 @@ where
state.is_pressed = true;
return event::Status::Captured;
shell.capture_event();
}
}
}
@ -324,7 +328,7 @@ where
shell.publish(on_press);
}
return event::Status::Captured;
shell.capture_event();
}
}
}
@ -336,7 +340,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 +373,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

View file

@ -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,
};
let state = tree.state.downcast_mut::<P::State>();
let is_redraw_request = matches!(
event,
Event::Window(window::Event::RedrawRequested(_now)),
);
if let Some(canvas_event) = canvas_event {
let state = tree.state.downcast_mut::<P::State>();
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(

View file

@ -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),
}

View file

@ -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)
}

View file

@ -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,
}
}
@ -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();

View file

@ -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.
@ -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(

View file

@ -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;
@ -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(

View file

@ -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,
};
@ -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(

View file

@ -363,12 +363,11 @@ 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>,
@ -439,7 +438,7 @@ where
.operate(state, layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
state: &mut Tree,
event: Event,
@ -449,25 +448,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,18 +523,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 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>
@ -648,7 +641,7 @@ where
}
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
@ -658,11 +651,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 +673,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 +698,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 +714,7 @@ where
clipboard,
shell,
viewport,
)
);
}
fn mouse_interaction(
@ -777,6 +774,7 @@ where
top: top.into(),
is_top_focused: false,
is_top_overlay_active: false,
is_hovered: false,
})
}

View file

@ -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,
_ => {}
}
}

View file

@ -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.
@ -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(

View file

@ -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(
@ -387,7 +386,7 @@ where
.unwrap_or_default()
}
fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -395,11 +394,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(

View file

@ -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,
@ -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(
@ -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(

View file

@ -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,18 +82,21 @@ where
new_size: Size,
view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
) {
let is_tree_empty =
tree.tag == tree::Tag::stateless() && tree.children.is_empty();
if self.size != new_size {
self.element = view(new_size);
self.size = new_size;
self.layout = None;
if !is_tree_empty && self.size == new_size {
return;
tree.diff(&self.element);
} else {
let is_tree_empty =
tree.tag == tree::Tag::stateless() && tree.children.is_empty();
if is_tree_empty {
self.layout = None;
tree.diff(&self.element);
}
}
self.element = view(new_size);
self.size = new_size;
self.layout = None;
tree.diff(&self.element);
}
fn resolve<R, T>(
@ -183,7 +185,7 @@ where
);
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
@ -193,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,
@ -215,7 +217,7 @@ where
clipboard,
&mut local_shell,
viewport,
)
);
},
);
@ -224,8 +226,6 @@ where
}
shell.merge(local_shell, std::convert::identity);
status
}
fn draw(
@ -417,7 +417,7 @@ where
.unwrap_or_default()
}
fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -425,28 +425,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,
);
let _ = self.with_overlay_mut_maybe(|overlay| {
overlay.update(event, layout, cursor, renderer, clipboard, shell);
is_layout_invalid = shell.is_layout_invalid();
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(

View file

@ -8,6 +8,7 @@ pub use iced_renderer::graphics;
pub use iced_runtime as runtime;
pub use iced_runtime::core;
mod action;
mod column;
mod mouse_area;
mod row;
@ -131,4 +132,5 @@ pub use qr_code::QRCode;
pub mod markdown;
pub use crate::core::theme::{self, Theme};
pub use action::Action;
pub use renderer::Renderer;

View file

@ -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.
@ -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;
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(message) = widget.on_press.as_ref() {
captured = true;
shell.publish(message.clone());
}
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);
// 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();
}
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;
}
}
if captured {
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_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::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_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::ButtonReleased(mouse::Button::Right)) => {
if let Some(message) = widget.on_right_release.as_ref() {
shell.publish(message.clone());
}
}
}
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::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_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::ButtonReleased(mouse::Button::Middle)) => {
if let Some(message) = widget.on_middle_release.as_ref() {
shell.publish(message.clone());
}
}
}
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::WheelScrolled { delta }) => {
if let Some(on_scroll) = widget.on_scroll.as_ref() {
shell.publish(on_scroll(delta));
shell.capture_event();
}
}
_ => {}
}
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::Status::Ignored
}

View file

@ -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};
@ -262,7 +262,7 @@ where
})
}
fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
@ -270,13 +270,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,6 +334,10 @@ where
class: &'a <Theme as Catalog>::Class<'b>,
}
struct ListState {
is_hovered: Option<bool>,
}
impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for List<'a, 'b, T, Message, Theme, Renderer>
where
@ -341,6 +345,14 @@ where
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 +386,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 +396,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 +423,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 +459,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 +467,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(

View file

@ -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,
};
@ -167,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>
@ -203,6 +204,7 @@ where
on_drag: None,
on_resize: None,
class: <Theme as Catalog>::default(),
last_mouse_interaction: None,
}
}
@ -293,6 +295,52 @@ where
.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)]
@ -423,7 +471,7 @@ where
});
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
@ -433,9 +481,7 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
let mut event_status = event::Status::Ignored;
) {
let Memory { action, .. } = tree.state.downcast_mut();
let node = self.internal.layout();
@ -445,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, _)) => {
@ -555,10 +631,6 @@ where
}
}
}
event_status = event::Status::Captured;
} else if action.picked_split().is_some() {
event_status = event::Status::Captured;
}
*action = state::Action::Idle;
@ -600,44 +672,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);
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);
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)
})
.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 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(
@ -650,41 +726,10 @@ where
) -> mouse::Interaction {
let Memory { action, .. } = tree.state.downcast_ref();
if action.picked_pane().is_some() {
return 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 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.panes

View file

@ -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};
@ -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,
)
};
);
}
}
event_status.merge(body_status)
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);
}
None
}
pub(crate) fn mouse_interaction(

View file

@ -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;
@ -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(

View file

@ -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;
event::Status::Ignored
}
_ => event::Status::Ignored,
_ => {}
};
let status = {
let is_hovered = cursor.is_over(layout.bounds());
if state.is_open {
Status::Opened { is_hovered }
} else if is_hovered {
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| 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,7 +828,10 @@ 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.
@ -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

View file

@ -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,
}
}
@ -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();

View file

@ -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,
};
@ -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(
@ -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(

View file

@ -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>
@ -108,6 +108,7 @@ where
content: content.into(),
on_scroll: None,
class: Theme::default(),
last_status: None,
}
.validate()
}
@ -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,309 +552,65 @@ 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;
};
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,
);
return event::Status::Captured;
}
}
_ => {}
}
} 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;
};
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);
let _ = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
}
return event::Status::Captured;
}
_ => {}
}
}
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(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,
);
}
return event::Status::Captured;
}
_ => {}
}
} 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;
};
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);
let _ = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
return event::Status::Captured;
}
}
_ => {}
}
}
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,
};
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
},
)
};
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;
}
if let event::Status::Captured = content_status {
return event::Status::Captured;
}
if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) =
event
{
state.keyboard_modifiers = modifiers;
return event::Status::Ignored;
}
match event {
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
if cursor_over_scrollable.is_none() {
return event::Status::Ignored;
}
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 {
(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)
};
// TODO: Configurable speed/friction (?)
-movement * 60.0
}
mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y),
};
state.scroll(
self.direction.align(delta),
bounds,
content_bounds,
);
let has_scrolled = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
let in_transaction = state.last_scrolled.is_some();
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 =>
{
let mut update = || {
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
match event {
touch::Event::FingerPressed { .. } => {
let Some(cursor_position) = cursor.position() else {
return event::Status::Ignored;
};
state.scroll_area_touched_at = Some(cursor_position);
}
touch::Event::FingerMoved { .. } => {
if let Some(scroll_box_touched_at) =
state.scroll_area_touched_at
{
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;
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),
state.scroll_y_to(
scrollbar.scroll_percentage_y(
scroller_grabbed_at,
cursor_position,
),
bounds,
content_bounds,
);
state.scroll_area_touched_at =
Some(cursor_position);
let _ = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
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;
};
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);
// TODO: bubble up touch movements if not consumed.
let _ = notify_scroll(
state,
&self.on_scroll,
@ -860,24 +619,313 @@ where
shell,
);
}
shell.capture_event();
}
_ => {}
}
event::Status::Captured
}
Event::Window(window::Event::RedrawRequested(_)) => {
let _ = notify_viewport(
state,
&self.on_scroll,
bounds,
content_bounds,
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,
);
let _ = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
}
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;
};
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);
let _ = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
shell.capture_event();
}
}
_ => {}
}
}
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);
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
},
);
};
event::Status::Ignored
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;
}
_ => event::Status::Ignored,
if shell.is_event_captured() {
return;
}
if let Event::Keyboard(keyboard::Event::ModifiersChanged(
modifiers,
)) = event
{
state.keyboard_modifiers = modifiers;
return;
}
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();
// 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)
};
// TODO: Configurable speed/friction (?)
-movement * 60.0
}
mouse::ScrollDelta::Pixels { x, y } => {
-Vector::new(x, y)
}
};
state.scroll(
self.direction.align(delta),
bounds,
content_bounds,
);
let has_scrolled = notify_scroll(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
let in_transaction = state.last_scrolled.is_some();
if has_scrolled || in_transaction {
shell.capture_event();
}
}
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;
};
state.scroll_area_touched_at =
Some(cursor_position);
}
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,
);
}
}
_ => {}
}
shell.capture_event();
}
Event::Window(window::Event::RedrawRequested(_)) => {
let _ = notify_viewport(
state,
&self.on_scroll,
bounds,
content_bounds,
shell,
);
}
_ => {}
}
};
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(),
}
} 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
};
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 +968,8 @@ 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));
container::draw_background(renderer, &style.container, layout.bounds());
@ -1323,7 +1352,7 @@ impl operation::Scrollable for State {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum Offset {
Absolute(f32),
Relative(f32),

View file

@ -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,
};
let state = tree.state.downcast_mut::<P::State>();
if let Some(custom_shader_event) = custom_shader_event {
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(

View file

@ -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),
}

View file

@ -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`].

View file

@ -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,
}
}
@ -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,19 +252,43 @@ 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 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)
} else {
self.step
}
.into();
let start = (*self.range.start()).into();
let end = (*self.range.end()).into();
let percent = f64::from(cursor_position.x - bounds.x)
/ f64::from(bounds.width);
let steps = (percent * (end - start) / step).round();
let value = steps * step + start;
T::from_f64(value.min(end))
};
new_value
};
let increment = |value: T| -> Option<T> {
let step = if state.keyboard_modifiers.shift() {
self.shift_step.unwrap_or(self.step)
} else {
@ -270,168 +296,158 @@ where
}
.into();
let start = (*self.range.start()).into();
let end = (*self.range.end()).into();
let steps = (value.into() / step).round();
let new_value = step * (steps + 1.0);
let percent = f64::from(cursor_position.x - bounds.x)
/ f64::from(bounds.width);
if new_value > (*self.range.end()).into() {
return Some(*self.range.end());
}
let steps = (percent * (end - start) / step).round();
let value = steps * step + start;
T::from_f64(value.min(end))
T::from_f64(new_value)
};
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 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.start()).into() {
return Some(*self.range.start());
}
if new_value > (*self.range.end()).into() {
return Some(*self.range.end());
}
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 decrement = |value: T| -> Option<T> {
let step = if state.keyboard_modifiers.shift() {
self.shift_step.unwrap_or(self.step)
} else {
self.step
}
.into();
self.value = new_value;
}
};
let steps = (value.into() / step).round();
let new_value = step * (steps - 1.0);
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;
}
if new_value < (*self.range.start()).into() {
return Some(*self.range.start());
}
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));
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);
shell.capture_event();
}
}
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;
} 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);
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if state.is_dragging {
let _ = cursor.position().and_then(locate).map(change);
shell.capture_event();
}
state.is_dragging = false;
return event::Status::Captured;
}
}
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::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,
};
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);
}
return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
if cursor.is_over(layout.bounds()) {
match key {
Key::Named(key::Named::ArrowUp) => {
if *delta < 0.0 {
let _ = decrement(current_value).map(change);
} else {
let _ = increment(current_value).map(change);
}
Key::Named(key::Named::ArrowDown) => {
let _ = decrement(current_value).map(change);
}
_ => (),
shell.capture_event();
}
return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
state.keyboard_modifiers = modifiers;
}
_ => {}
}
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);
}
_ => (),
}
event::Status::Ignored
shell.capture_event();
}
}
Event::Keyboard(keyboard::Event::ModifiersChanged(
modifiers,
)) => {
state.keyboard_modifiers = *modifiers;
}
_ => {}
}
};
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 {

View file

@ -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.
@ -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(

View file

@ -1,5 +1,4 @@
use crate::core::alignment;
use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::renderer;
@ -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(

View file

@ -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,
}
}
@ -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,
};
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());
state.focus = Some(Focus::now());
state.last_click = Some(click);
state.drag_click = Some(click.kind());
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();
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();
let lines = lines + state.partial_scroll;
state.partial_scroll = lines.fract();
if bounds.height >= i32::MAX as f32 {
return;
}
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));
let lines = lines + state.partial_scroll;
state.partial_scroll = lines.fract();
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 {
@ -1036,7 +1055,7 @@ impl<Message> Binding<Message> {
status,
} = event;
if status != Status::Focused {
if !matches!(status, Status::Focused { .. }) {
return None;
}
@ -1176,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
};
@ -1222,7 +1243,10 @@ 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,
}
@ -1297,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

View file

@ -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 {
@ -637,7 +627,7 @@ where
operation.text_input(state, self.id.as_ref().map(|id| &id.0));
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
@ -647,7 +637,7 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
) {
let update_cache = |state, value| {
replace_paragraph(
renderer,
@ -660,22 +650,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();
let now = Instant::now();
Some(Focus {
updated_at: now,
now,
is_window_focused: true,
})
Some(Focus {
updated_at: now,
now,
is_window_focused: true,
})
} else {
None
@ -760,7 +749,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 +794,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 +818,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 +833,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 +859,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 +889,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 +897,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 +940,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 +952,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 +980,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 +1013,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 +1029,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 +1049,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 +1071,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 +1093,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 +1120,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 +1147,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 +1163,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 +1186,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 +1202,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,7 +1621,10 @@ 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,
}
@ -1612,7 +1701,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

View file

@ -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),

View file

@ -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;
@ -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(
@ -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(

View file

@ -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,
}
}
@ -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;

View file

@ -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.
@ -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;
@ -216,7 +215,7 @@ where
shell.invalidate_layout();
}
self.content.as_widget_mut().on_event(
self.content.as_widget_mut().update(
&mut tree.children[0],
event,
layout,
@ -225,7 +224,7 @@ where
clipboard,
shell,
viewport,
)
);
}
fn mouse_interaction(

View file

@ -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
@ -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(

View file

@ -22,7 +22,7 @@ x11 = ["winit/x11"]
wayland = ["winit/wayland"]
wayland-dlopen = ["winit/wayland-dlopen"]
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
multi-window = ["iced_runtime/multi-window"]
unconditional-rendering = []
[dependencies]
iced_futures.workspace = true

View file

@ -758,14 +758,25 @@ async fn run_instance<P, C>(
}
Event::EventLoopAwakened(event) => {
match event {
event::Event::NewEvents(
event::StartCause::Init
| event::StartCause::ResumeTimeReached { .. },
) => {
event::Event::NewEvents(event::StartCause::Init) => {
for (_id, window) in window_manager.iter_mut() {
window.raw.request_redraw();
}
}
event::Event::NewEvents(
event::StartCause::ResumeTimeReached { .. },
) => {
let now = Instant::now();
for (_id, window) in window_manager.iter_mut() {
if let Some(redraw_at) = window.redraw_at {
if redraw_at <= now {
window.raw.request_redraw();
window.redraw_at = None;
}
}
}
}
event::Event::PlatformSpecific(
event::PlatformSpecific::MacOS(
event::MacOS::ReceivedUrl(url),
@ -807,11 +818,39 @@ async fn run_instance<P, C>(
continue;
};
// TODO: Avoid redrawing all the time by forcing widgets to
// request redraws on state changes
//
// Then, we can use the `interface_state` here to decide if a redraw
// is needed right away, or simply wait until a specific time.
let physical_size = window.state.physical_size();
if physical_size.width == 0 || physical_size.height == 0
{
continue;
}
if window.viewport_version
!= window.state.viewport_version()
{
let logical_size = window.state.logical_size();
debug.layout_started();
let ui = user_interfaces
.remove(&id)
.expect("Remove user interface");
let _ = user_interfaces.insert(
id,
ui.relayout(logical_size, &mut window.renderer),
);
debug.layout_finished();
compositor.configure_surface(
&mut window.surface,
physical_size.width,
physical_size.height,
);
window.viewport_version =
window.state.viewport_version();
}
let redraw_event = core::Event::Window(
window::Event::RedrawRequested(Instant::now()),
);
@ -857,81 +896,18 @@ async fn run_instance<P, C>(
status: core::event::Status::Ignored,
});
let _ = control_sender.start_send(Control::ChangeFlow(
match ui_state {
user_interface::State::Updated {
redraw_request: Some(redraw_request),
} => match redraw_request {
window::RedrawRequest::NextFrame => {
window.raw.request_redraw();
ControlFlow::Wait
}
window::RedrawRequest::At(at) => {
ControlFlow::WaitUntil(at)
}
},
_ => ControlFlow::Wait,
},
));
let physical_size = window.state.physical_size();
if physical_size.width == 0 || physical_size.height == 0
if let user_interface::State::Updated {
redraw_request: Some(redraw_request),
} = ui_state
{
continue;
}
if window.viewport_version
!= window.state.viewport_version()
{
let logical_size = window.state.logical_size();
debug.layout_started();
let ui = user_interfaces
.remove(&id)
.expect("Remove user interface");
let _ = user_interfaces.insert(
id,
ui.relayout(logical_size, &mut window.renderer),
);
debug.layout_finished();
debug.draw_started();
let new_mouse_interaction = user_interfaces
.get_mut(&id)
.expect("Get user interface")
.draw(
&mut window.renderer,
window.state.theme(),
&renderer::Style {
text_color: window.state.text_color(),
},
window.state.cursor(),
);
debug.draw_finished();
if new_mouse_interaction != window.mouse_interaction
{
window.raw.set_cursor(
conversion::mouse_interaction(
new_mouse_interaction,
),
);
window.mouse_interaction =
new_mouse_interaction;
match redraw_request {
window::RedrawRequest::NextFrame => {
window.raw.request_redraw();
}
window::RedrawRequest::At(at) => {
window.redraw_at = Some(at);
}
}
compositor.configure_surface(
&mut window.surface,
physical_size.width,
physical_size.height,
);
window.viewport_version =
window.state.viewport_version();
}
debug.render_started();
@ -993,6 +969,13 @@ async fn run_instance<P, C>(
continue;
};
if matches!(
window_event,
winit::event::WindowEvent::Resized(_)
) {
window.raw.request_redraw();
}
if matches!(
window_event,
winit::event::WindowEvent::CloseRequested
@ -1031,7 +1014,10 @@ async fn run_instance<P, C>(
}
}
event::Event::AboutToWait => {
if events.is_empty() && messages.is_empty() {
if events.is_empty()
&& messages.is_empty()
&& window_manager.is_idle()
{
continue;
}
@ -1065,13 +1051,27 @@ async fn run_instance<P, C>(
&mut messages,
);
#[cfg(feature = "unconditional-rendering")]
window.raw.request_redraw();
if !uis_stale {
uis_stale = matches!(
ui_state,
user_interface::State::Outdated
);
match ui_state {
#[cfg(not(
feature = "unconditional-rendering"
))]
user_interface::State::Updated {
redraw_request: Some(redraw_request),
} => match redraw_request {
window::RedrawRequest::NextFrame => {
window.raw.request_redraw();
}
window::RedrawRequest::At(at) => {
window.redraw_at = Some(at);
}
},
user_interface::State::Outdated => {
uis_stale = true;
}
user_interface::State::Updated { .. } => {}
}
for (event, status) in window_events
@ -1139,6 +1139,17 @@ async fn run_instance<P, C>(
actions = 0;
}
}
if let Some(redraw_at) = window_manager.redraw_at() {
let _ =
control_sender.start_send(Control::ChangeFlow(
ControlFlow::WaitUntil(redraw_at),
));
} else {
let _ = control_sender.start_send(
Control::ChangeFlow(ControlFlow::Wait),
);
}
}
_ => {}
}

View file

@ -190,7 +190,10 @@ where
..
},
..
} => _debug.toggle(),
} => {
_debug.toggle();
window.request_redraw();
}
_ => {}
}
}

View file

@ -1,4 +1,5 @@
use crate::core::mouse;
use crate::core::time::Instant;
use crate::core::window::Id;
use crate::core::{Point, Size};
use crate::graphics::Compositor;
@ -62,6 +63,7 @@ where
surface,
renderer,
mouse_interaction: mouse::Interaction::None,
redraw_at: None,
},
);
@ -74,6 +76,19 @@ where
self.entries.is_empty()
}
pub fn is_idle(&self) -> bool {
self.entries
.values()
.all(|window| window.redraw_at.is_none())
}
pub fn redraw_at(&self) -> Option<Instant> {
self.entries
.values()
.filter_map(|window| window.redraw_at)
.min()
}
pub fn first(&self) -> Option<&Window<P, C>> {
self.entries.first_key_value().map(|(_id, window)| window)
}
@ -138,6 +153,7 @@ where
pub mouse_interaction: mouse::Interaction,
pub surface: C::Surface,
pub renderer: P::Renderer,
pub redraw_at: Option<Instant>,
}
impl<P, C> Window<P, C>