Merge pull request #2662 from iced-rs/reactive-rendering
Reactive Rendering
This commit is contained in:
commit
a11fcf8f2d
60 changed files with 2212 additions and 1803 deletions
24
Cargo.toml
24
Cargo.toml
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug", "multi-window"] }
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
89
widget/src/action.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
//! ```
|
||||
//! 
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,10 @@ where
|
|||
..
|
||||
},
|
||||
..
|
||||
} => _debug.toggle(),
|
||||
} => {
|
||||
_debug.toggle();
|
||||
window.request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue