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]
|
[features]
|
||||||
default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"]
|
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"]
|
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"]
|
tiny-skia = ["iced_renderer/tiny-skia"]
|
||||||
# Enables the `Image` widget
|
# Enables the `image` widget
|
||||||
image = ["image-without-codecs", "image/default"]
|
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"]
|
image-without-codecs = ["iced_widget/image", "dep:image"]
|
||||||
# Enables the `Svg` widget
|
# Enables the `svg` widget
|
||||||
svg = ["iced_widget/svg"]
|
svg = ["iced_widget/svg"]
|
||||||
# Enables the `Canvas` widget
|
# Enables the `canvas` widget
|
||||||
canvas = ["iced_widget/canvas"]
|
canvas = ["iced_widget/canvas"]
|
||||||
# Enables the `QRCode` widget
|
# Enables the `qr_code` widget
|
||||||
qr_code = ["iced_widget/qr_code"]
|
qr_code = ["iced_widget/qr_code"]
|
||||||
# Enables the `markdown` widget
|
# Enables the `markdown` widget
|
||||||
markdown = ["iced_widget/markdown"]
|
markdown = ["iced_widget/markdown"]
|
||||||
|
|
@ -55,18 +55,18 @@ system = ["iced_winit/system"]
|
||||||
web-colors = ["iced_renderer/web-colors"]
|
web-colors = ["iced_renderer/web-colors"]
|
||||||
# Enables the WebGL backend, replacing WebGPU
|
# Enables the WebGL backend, replacing WebGPU
|
||||||
webgl = ["iced_renderer/webgl"]
|
webgl = ["iced_renderer/webgl"]
|
||||||
# Enables the syntax `highlighter` module
|
# Enables syntax highligthing
|
||||||
highlighter = ["iced_highlighter", "iced_widget/highlighter"]
|
highlighter = ["iced_highlighter", "iced_widget/highlighter"]
|
||||||
# Enables experimental multi-window support.
|
|
||||||
multi-window = ["iced_winit/multi-window"]
|
|
||||||
# Enables the advanced module
|
# Enables the advanced module
|
||||||
advanced = ["iced_core/advanced", "iced_widget/advanced"]
|
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"]
|
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"]
|
auto-detect-theme = ["iced_core/auto-detect-theme"]
|
||||||
# Enables strict assertions for debugging purposes at the expense of performance
|
# Enables strict assertions for debugging purposes at the expense of performance
|
||||||
strict-assertions = ["iced_renderer/strict-assertions"]
|
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]
|
[dependencies]
|
||||||
iced_core.workspace = true
|
iced_core.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::event::{self, Event};
|
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
|
@ -6,8 +5,8 @@ use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::tree::{self, Tree};
|
use crate::widget::tree::{self, Tree};
|
||||||
use crate::{
|
use crate::{
|
||||||
Border, Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector,
|
Border, Clipboard, Color, Event, Layout, Length, Rectangle, Shell, Size,
|
||||||
Widget,
|
Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
@ -309,7 +308,7 @@ where
|
||||||
self.widget.operate(tree, layout, renderer, operation);
|
self.widget.operate(tree, layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -319,11 +318,11 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, B>,
|
shell: &mut Shell<'_, B>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut local_messages = Vec::new();
|
let mut local_messages = Vec::new();
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
let status = self.widget.on_event(
|
self.widget.update(
|
||||||
tree,
|
tree,
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -335,8 +334,6 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
shell.merge(local_shell, &self.mapper);
|
shell.merge(local_shell, &self.mapper);
|
||||||
|
|
||||||
status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -447,7 +444,7 @@ where
|
||||||
.operate(state, layout, renderer, operation);
|
.operate(state, layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut Tree,
|
state: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -457,10 +454,10 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.element.widget.on_event(
|
self.element.widget.update(
|
||||||
state, event, layout, cursor, renderer, clipboard, shell, viewport,
|
state, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ where
|
||||||
let max_cross = axis.cross(limits.max());
|
let max_cross = axis.cross(limits.max());
|
||||||
|
|
||||||
let mut fill_main_sum = 0;
|
let mut fill_main_sum = 0;
|
||||||
|
let mut some_fill_cross = false;
|
||||||
let (mut cross, cross_compress) = match axis {
|
let (mut cross, cross_compress) = match axis {
|
||||||
Axis::Vertical if width == Length::Shrink => (0.0, true),
|
Axis::Vertical if width == Length::Shrink => (0.0, true),
|
||||||
Axis::Horizontal if height == 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());
|
let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
|
||||||
nodes.resize(items.len(), Node::default());
|
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() {
|
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
|
||||||
let (fill_main_factor, fill_cross_factor) = {
|
let (fill_main_factor, fill_cross_factor) = {
|
||||||
let size = child.as_widget().size();
|
let size = child.as_widget().size();
|
||||||
|
|
@ -121,6 +126,41 @@ where
|
||||||
nodes[i] = layout;
|
nodes[i] = layout;
|
||||||
} else {
|
} else {
|
||||||
fill_main_sum += fill_main_factor;
|
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() {
|
for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
|
||||||
let (fill_main_factor, fill_cross_factor) = {
|
let (fill_main_factor, fill_cross_factor) = {
|
||||||
let size = child.as_widget().size();
|
let size = child.as_widget().size();
|
||||||
|
|
@ -142,10 +185,16 @@ where
|
||||||
axis.pack(size.width.fill_factor(), size.height.fill_factor())
|
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 =
|
let max_main =
|
||||||
remaining * fill_main_factor as f32 / fill_main_sum as f32;
|
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() {
|
let min_main = if max_main.is_infinite() {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -178,6 +227,8 @@ where
|
||||||
let pad = axis.pack(padding.left, padding.top);
|
let pad = axis.pack(padding.left, padding.top);
|
||||||
let mut main = pad.0;
|
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() {
|
for (i, node) in nodes.iter_mut().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
main += spacing;
|
main += spacing;
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,12 @@ mod group;
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
pub use group::Group;
|
pub use group::Group;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::widget::Tree;
|
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.
|
/// An interactive component that can be displayed on top of other widgets.
|
||||||
pub trait Overlay<Message, Theme, Renderer>
|
pub trait Overlay<Message, Theme, Renderer>
|
||||||
|
|
@ -57,7 +56,7 @@ where
|
||||||
/// * a [`Clipboard`], if available
|
/// * a [`Clipboard`], if available
|
||||||
///
|
///
|
||||||
/// By default, it does nothing.
|
/// By default, it does nothing.
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_event: Event,
|
_event: Event,
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
|
|
@ -65,8 +64,7 @@ where
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
_shell: &mut Shell<'_, Message>,
|
_shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current [`mouse::Interaction`] of the [`Overlay`].
|
/// Returns the current [`mouse::Interaction`] of the [`Overlay`].
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
pub use crate::Overlay;
|
pub use crate::Overlay;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::renderer;
|
use crate::renderer;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size};
|
||||||
|
|
||||||
/// A generic [`Overlay`].
|
/// A generic [`Overlay`].
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
|
|
@ -50,7 +49,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes a runtime [`Event`].
|
/// Processes a runtime [`Event`].
|
||||||
pub fn on_event(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -58,9 +57,9 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.overlay
|
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`].
|
/// Returns the current [`mouse::Interaction`] of the [`Element`].
|
||||||
|
|
@ -149,7 +148,7 @@ where
|
||||||
self.content.operate(layout, renderer, operation);
|
self.content.operate(layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -157,11 +156,11 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, B>,
|
shell: &mut Shell<'_, B>,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut local_messages = Vec::new();
|
let mut local_messages = Vec::new();
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
let event_status = self.content.on_event(
|
self.content.update(
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
|
|
@ -171,8 +170,6 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
shell.merge(local_shell, self.mapper);
|
shell.merge(local_shell, self.mapper);
|
||||||
|
|
||||||
event_status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::event;
|
|
||||||
use crate::layout;
|
use crate::layout;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
|
@ -73,7 +72,7 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -81,21 +80,17 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.children
|
for (child, layout) in self.children.iter_mut().zip(layout.children()) {
|
||||||
.iter_mut()
|
child.update(
|
||||||
.zip(layout.children())
|
event.clone(),
|
||||||
.map(|(child, layout)| {
|
layout,
|
||||||
child.on_event(
|
cursor,
|
||||||
event.clone(),
|
renderer,
|
||||||
layout,
|
clipboard,
|
||||||
cursor,
|
shell,
|
||||||
renderer,
|
);
|
||||||
clipboard,
|
}
|
||||||
shell,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::event;
|
||||||
|
use crate::time::Instant;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
|
|
||||||
/// A connection to the state of a shell.
|
/// A connection to the state of a shell.
|
||||||
|
|
@ -9,6 +11,7 @@ use crate::window;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Shell<'a, Message> {
|
pub struct Shell<'a, Message> {
|
||||||
messages: &'a mut Vec<Message>,
|
messages: &'a mut Vec<Message>,
|
||||||
|
event_status: event::Status,
|
||||||
redraw_request: Option<window::RedrawRequest>,
|
redraw_request: Option<window::RedrawRequest>,
|
||||||
is_layout_invalid: bool,
|
is_layout_invalid: bool,
|
||||||
are_widgets_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 {
|
pub fn new(messages: &'a mut Vec<Message>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
messages,
|
messages,
|
||||||
|
event_status: event::Status::Ignored,
|
||||||
redraw_request: None,
|
redraw_request: None,
|
||||||
is_layout_invalid: false,
|
is_layout_invalid: false,
|
||||||
are_widgets_invalid: false,
|
are_widgets_invalid: false,
|
||||||
|
|
@ -35,14 +39,37 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
self.messages.push(message);
|
self.messages.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests a new frame to be drawn.
|
/// Marks the current event as captured. Prevents "event bubbling".
|
||||||
pub fn request_redraw(&mut self, request: window::RedrawRequest) {
|
///
|
||||||
|
/// 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 {
|
match self.redraw_request {
|
||||||
None => {
|
None => {
|
||||||
self.redraw_request = Some(request);
|
self.redraw_request = Some(window::RedrawRequest::At(at));
|
||||||
}
|
}
|
||||||
Some(current) if request < current => {
|
Some(window::RedrawRequest::At(current)) if at < current => {
|
||||||
self.redraw_request = Some(request);
|
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) {
|
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
|
||||||
self.messages.extend(other.messages.drain(..).map(f));
|
self.messages.extend(other.messages.drain(..).map(f));
|
||||||
|
|
||||||
if let Some(at) = other.redraw_request {
|
if let Some(new) = other.redraw_request {
|
||||||
self.request_redraw(at);
|
self.redraw_request = Some(
|
||||||
|
self.redraw_request
|
||||||
|
.map(|current| if current < new { current } else { new })
|
||||||
|
.unwrap_or(new),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_layout_invalid =
|
self.is_layout_invalid =
|
||||||
|
|
@ -104,5 +135,7 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
|
|
||||||
self.are_widgets_invalid =
|
self.are_widgets_invalid =
|
||||||
self.are_widgets_invalid || other.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 text::Text;
|
||||||
pub use tree::Tree;
|
pub use tree::Tree;
|
||||||
|
|
||||||
use crate::event::{self, Event};
|
|
||||||
use crate::layout::{self, Layout};
|
use crate::layout::{self, Layout};
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
use crate::renderer;
|
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.
|
/// A component that displays information and allows interaction.
|
||||||
///
|
///
|
||||||
|
|
@ -112,7 +111,7 @@ where
|
||||||
/// Processes a runtime [`Event`].
|
/// Processes a runtime [`Event`].
|
||||||
///
|
///
|
||||||
/// By default, it does nothing.
|
/// By default, it does nothing.
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: &mut Tree,
|
_state: &mut Tree,
|
||||||
_event: Event,
|
_event: Event,
|
||||||
|
|
@ -122,8 +121,7 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
_shell: &mut Shell<'_, Message>,
|
_shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
|
/// Returns the current [`mouse::Interaction`] of the [`Widget`].
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,9 @@ impl Example {
|
||||||
|
|
||||||
mod bezier {
|
mod bezier {
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::widget::canvas::event::{self, Event};
|
use iced::widget::canvas::{
|
||||||
use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
|
self, Canvas, Event, Frame, Geometry, Path, Stroke,
|
||||||
|
};
|
||||||
use iced::{Element, Fill, Point, Rectangle, Renderer, Theme};
|
use iced::{Element, Fill, Point, Rectangle, Renderer, Theme};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -96,48 +97,47 @@ mod bezier {
|
||||||
event: Event,
|
event: Event,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> (event::Status, Option<Curve>) {
|
) -> Option<canvas::Action<Curve>> {
|
||||||
let Some(cursor_position) = cursor.position_in(bounds) else {
|
let cursor_position = cursor.position_in(bounds)?;
|
||||||
return (event::Status::Ignored, None);
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse_event) => {
|
Event::Mouse(mouse::Event::ButtonPressed(
|
||||||
let message = match mouse_event {
|
mouse::Button::Left,
|
||||||
mouse::Event::ButtonPressed(mouse::Button::Left) => {
|
)) => Some(
|
||||||
match *state {
|
match *state {
|
||||||
None => {
|
None => {
|
||||||
*state = Some(Pending::One {
|
*state = Some(Pending::One {
|
||||||
from: cursor_position,
|
from: cursor_position,
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
canvas::Action::request_redraw()
|
||||||
}
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => 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::mouse;
|
||||||
use iced::touch;
|
use iced::touch;
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas;
|
||||||
use iced::widget::canvas::event::{self, Event};
|
use iced::widget::canvas::{
|
||||||
use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
|
Cache, Canvas, Event, Frame, Geometry, Path, Text,
|
||||||
|
};
|
||||||
use iced::{
|
use iced::{
|
||||||
Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector,
|
Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector,
|
||||||
};
|
};
|
||||||
|
|
@ -383,14 +384,12 @@ mod grid {
|
||||||
event: Event,
|
event: Event,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> (event::Status, Option<Message>) {
|
) -> Option<canvas::Action<Message>> {
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
|
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
|
||||||
*interaction = Interaction::None;
|
*interaction = Interaction::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(cursor_position) = cursor.position_in(bounds) else {
|
let cursor_position = cursor.position_in(bounds)?;
|
||||||
return (event::Status::Ignored, None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let cell = Cell::at(self.project(cursor_position, bounds.size()));
|
let cell = Cell::at(self.project(cursor_position, bounds.size()));
|
||||||
let is_populated = self.state.contains(&cell);
|
let is_populated = self.state.contains(&cell);
|
||||||
|
|
@ -413,7 +412,12 @@ mod grid {
|
||||||
populate.or(unpopulate)
|
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 {
|
Event::Mouse(mouse_event) => match mouse_event {
|
||||||
mouse::Event::ButtonPressed(button) => {
|
mouse::Event::ButtonPressed(button) => {
|
||||||
|
|
@ -438,7 +442,12 @@ mod grid {
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
(event::Status::Captured, message)
|
Some(
|
||||||
|
message
|
||||||
|
.map(canvas::Action::publish)
|
||||||
|
.unwrap_or(canvas::Action::request_redraw())
|
||||||
|
.and_capture(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
mouse::Event::CursorMoved { .. } => {
|
mouse::Event::CursorMoved { .. } => {
|
||||||
let message = match *interaction {
|
let message = match *interaction {
|
||||||
|
|
@ -454,12 +463,14 @@ mod grid {
|
||||||
Interaction::None => None,
|
Interaction::None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let event_status = match interaction {
|
let action = message
|
||||||
Interaction::None => event::Status::Ignored,
|
.map(canvas::Action::publish)
|
||||||
_ => event::Status::Captured,
|
.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::Event::WheelScrolled { delta } => match delta {
|
||||||
mouse::ScrollDelta::Lines { y, .. }
|
mouse::ScrollDelta::Lines { y, .. }
|
||||||
|
|
@ -496,18 +507,21 @@ mod grid {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
Some(
|
||||||
event::Status::Captured,
|
canvas::Action::publish(Message::Scaled(
|
||||||
Some(Message::Scaled(scaling, translation)),
|
scaling,
|
||||||
|
translation,
|
||||||
|
))
|
||||||
|
.and_capture(),
|
||||||
)
|
)
|
||||||
} else {
|
} 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::renderer;
|
||||||
use iced::advanced::widget::tree::{self, Tree};
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
||||||
use iced::event;
|
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::time::Instant;
|
use iced::time::Instant;
|
||||||
use iced::widget::canvas;
|
use iced::widget::canvas;
|
||||||
use iced::window::{self, RedrawRequest};
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Background, Color, Element, Event, Length, Radians, Rectangle, Renderer,
|
Background, Color, Element, Event, Length, Radians, Rectangle, Renderer,
|
||||||
Size, Vector,
|
Size, Vector,
|
||||||
|
|
@ -262,7 +261,7 @@ where
|
||||||
layout::atomic(limits, self.size, self.size)
|
layout::atomic(limits, self.size, self.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -272,7 +271,7 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
|
|
@ -283,10 +282,8 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
state.cache.clear();
|
state.cache.clear();
|
||||||
shell.request_redraw(RedrawRequest::NextFrame);
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,9 @@ use iced::advanced::layout;
|
||||||
use iced::advanced::renderer::{self, Quad};
|
use iced::advanced::renderer::{self, Quad};
|
||||||
use iced::advanced::widget::tree::{self, Tree};
|
use iced::advanced::widget::tree::{self, Tree};
|
||||||
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
|
||||||
use iced::event;
|
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::time::Instant;
|
use iced::time::Instant;
|
||||||
use iced::window::{self, RedrawRequest};
|
use iced::window;
|
||||||
use iced::{Background, Color, Element, Event, Length, Rectangle, Size};
|
use iced::{Background, Color, Element, Event, Length, Rectangle, Size};
|
||||||
|
|
||||||
use super::easing::{self, Easing};
|
use super::easing::{self, Easing};
|
||||||
|
|
@ -176,7 +175,7 @@ where
|
||||||
layout::atomic(limits, self.width, self.height)
|
layout::atomic(limits, self.width, self.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -186,16 +185,14 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
*state = state.timed_transition(self.cycle_duration, now);
|
*state = state.timed_transition(self.cycle_duration, now);
|
||||||
|
|
||||||
shell.request_redraw(RedrawRequest::NextFrame);
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["debug", "multi-window"] }
|
iced = { path = "../..", features = ["debug"] }
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
//! computers like Microsoft Surface.
|
//! computers like Microsoft Surface.
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::touch;
|
use iced::touch;
|
||||||
use iced::widget::canvas::event;
|
|
||||||
use iced::widget::canvas::stroke::{self, Stroke};
|
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 iced::{Color, Element, Fill, Point, Rectangle, Renderer, Theme};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -56,25 +55,25 @@ impl canvas::Program<Message> for Multitouch {
|
||||||
fn update(
|
fn update(
|
||||||
&self,
|
&self,
|
||||||
_state: &mut Self::State,
|
_state: &mut Self::State,
|
||||||
event: event::Event,
|
event: Event,
|
||||||
_bounds: Rectangle,
|
_bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> (event::Status, Option<Message>) {
|
) -> Option<canvas::Action<Message>> {
|
||||||
match event {
|
let message = match event {
|
||||||
event::Event::Touch(touch_event) => match touch_event {
|
Event::Touch(
|
||||||
touch::Event::FingerPressed { id, position }
|
touch::Event::FingerPressed { id, position }
|
||||||
| touch::Event::FingerMoved { id, position } => (
|
| touch::Event::FingerMoved { id, position },
|
||||||
event::Status::Captured,
|
) => Some(Message::FingerPressed { id, position }),
|
||||||
Some(Message::FingerPressed { id, position }),
|
Event::Touch(
|
||||||
),
|
|
||||||
touch::Event::FingerLifted { id, .. }
|
touch::Event::FingerLifted { id, .. }
|
||||||
| touch::Event::FingerLost { id, .. } => (
|
| touch::Event::FingerLost { id, .. },
|
||||||
event::Status::Captured,
|
) => Some(Message::FingerLifted { id }),
|
||||||
Some(Message::FingerLifted { id }),
|
_ => None,
|
||||||
),
|
};
|
||||||
},
|
|
||||||
_ => (event::Status::Ignored, None),
|
message
|
||||||
}
|
.map(canvas::Action::publish)
|
||||||
|
.map(canvas::Action::and_capture)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::widget::canvas::event::{self, Event};
|
use iced::widget::canvas::{self, Canvas, Event, Geometry};
|
||||||
use iced::widget::canvas::{self, Canvas, Geometry};
|
|
||||||
use iced::widget::{column, row, slider, text};
|
use iced::widget::{column, row, slider, text};
|
||||||
use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme};
|
use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme};
|
||||||
|
|
||||||
|
|
@ -80,26 +79,22 @@ impl canvas::Program<Message> for SierpinskiGraph {
|
||||||
event: Event,
|
event: Event,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> (event::Status, Option<Message>) {
|
) -> Option<canvas::Action<Message>> {
|
||||||
let Some(cursor_position) = cursor.position_in(bounds) else {
|
let cursor_position = cursor.position_in(bounds)?;
|
||||||
return (event::Status::Ignored, None);
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse_event) => {
|
Event::Mouse(mouse::Event::ButtonPressed(button)) => match button {
|
||||||
let message = match mouse_event {
|
mouse::Button::Left => Some(canvas::Action::publish(
|
||||||
iced::mouse::Event::ButtonPressed(
|
Message::PointAdded(cursor_position),
|
||||||
iced::mouse::Button::Left,
|
)),
|
||||||
) => Some(Message::PointAdded(cursor_position)),
|
mouse::Button::Right => {
|
||||||
iced::mouse::Event::ButtonPressed(
|
Some(canvas::Action::publish(Message::PointRemoved))
|
||||||
iced::mouse::Button::Right,
|
}
|
||||||
) => Some(Message::PointRemoved),
|
_ => None,
|
||||||
_ => None,
|
},
|
||||||
};
|
_ => None,
|
||||||
(event::Status::Captured, message)
|
|
||||||
}
|
|
||||||
_ => (event::Status::Ignored, None),
|
|
||||||
}
|
}
|
||||||
|
.map(canvas::Action::and_capture)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,6 @@ mod toast {
|
||||||
use iced::advanced::renderer;
|
use iced::advanced::renderer;
|
||||||
use iced::advanced::widget::{self, Operation, Tree};
|
use iced::advanced::widget::{self, Operation, Tree};
|
||||||
use iced::advanced::{Clipboard, Shell, Widget};
|
use iced::advanced::{Clipboard, Shell, Widget};
|
||||||
use iced::event::{self, Event};
|
|
||||||
use iced::mouse;
|
use iced::mouse;
|
||||||
use iced::theme;
|
use iced::theme;
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
|
|
@ -177,8 +176,8 @@ mod toast {
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Alignment, Center, Element, Fill, Length, Point, Rectangle, Renderer,
|
Alignment, Center, Element, Event, Fill, Length, Point, Rectangle,
|
||||||
Size, Theme, Vector,
|
Renderer, Size, Theme, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DEFAULT_TIMEOUT: u64 = 5;
|
pub const DEFAULT_TIMEOUT: u64 = 5;
|
||||||
|
|
@ -359,7 +358,7 @@ mod toast {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut Tree,
|
state: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -369,8 +368,8 @@ mod toast {
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.content.as_widget_mut().on_event(
|
self.content.as_widget_mut().update(
|
||||||
&mut state.children[0],
|
&mut state.children[0],
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -379,7 +378,7 @@ mod toast {
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -490,7 +489,7 @@ mod toast {
|
||||||
.translate(Vector::new(self.position.x, self.position.y))
|
.translate(Vector::new(self.position.x, self.position.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -498,10 +497,8 @@ mod toast {
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = &event {
|
if let Event::Window(window::Event::RedrawRequested(now)) = &event {
|
||||||
let mut next_redraw: Option<window::RedrawRequest> = None;
|
|
||||||
|
|
||||||
self.instants.iter_mut().enumerate().for_each(
|
self.instants.iter_mut().enumerate().for_each(
|
||||||
|(index, maybe_instant)| {
|
|(index, maybe_instant)| {
|
||||||
if let Some(instant) = maybe_instant.as_mut() {
|
if let Some(instant) = maybe_instant.as_mut() {
|
||||||
|
|
@ -512,55 +509,43 @@ mod toast {
|
||||||
if remaining == Duration::ZERO {
|
if remaining == Duration::ZERO {
|
||||||
maybe_instant.take();
|
maybe_instant.take();
|
||||||
shell.publish((self.on_close)(index));
|
shell.publish((self.on_close)(index));
|
||||||
next_redraw =
|
|
||||||
Some(window::RedrawRequest::NextFrame);
|
|
||||||
} else {
|
} else {
|
||||||
let redraw_at =
|
shell.request_redraw_at(*now + remaining);
|
||||||
window::RedrawRequest::At(*now + remaining);
|
|
||||||
next_redraw = next_redraw
|
|
||||||
.map(|redraw| redraw.min(redraw_at))
|
|
||||||
.or(Some(redraw_at));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(redraw) = next_redraw {
|
|
||||||
shell.request_redraw(redraw);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewport = layout.bounds();
|
let viewport = layout.bounds();
|
||||||
|
|
||||||
self.toasts
|
for (((child, state), layout), instant) in self
|
||||||
|
.toasts
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(self.state.iter_mut())
|
.zip(self.state.iter_mut())
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.zip(self.instants.iter_mut())
|
.zip(self.instants.iter_mut())
|
||||||
.map(|(((child, state), layout), instant)| {
|
{
|
||||||
let mut local_messages = vec![];
|
let mut local_messages = vec![];
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
let status = child.as_widget_mut().on_event(
|
child.as_widget_mut().update(
|
||||||
state,
|
state,
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
&mut local_shell,
|
&mut local_shell,
|
||||||
&viewport,
|
&viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
if !local_shell.is_empty() {
|
if !local_shell.is_empty() {
|
||||||
instant.take();
|
instant.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.merge(local_shell, std::convert::identity);
|
shell.merge(local_shell, std::convert::identity);
|
||||||
|
}
|
||||||
status
|
|
||||||
})
|
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes a runtime [`Event`].
|
/// Processes a runtime [`Event`].
|
||||||
pub fn on_event(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -166,7 +166,7 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
fn recurse<Message, Theme, Renderer>(
|
fn recurse<Message, Theme, Renderer>(
|
||||||
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
|
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -175,31 +175,30 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> (event::Status, bool)
|
) -> bool
|
||||||
where
|
where
|
||||||
Renderer: renderer::Renderer,
|
Renderer: renderer::Renderer,
|
||||||
{
|
{
|
||||||
let mut layouts = layout.children();
|
let mut layouts = layout.children();
|
||||||
|
|
||||||
if let Some(layout) = layouts.next() {
|
if let Some(layout) = layouts.next() {
|
||||||
let (nested_status, nested_is_over) =
|
let nested_is_over = if let Some((mut nested, nested_layout)) =
|
||||||
if let Some((mut nested, nested_layout)) =
|
element.overlay(layout, renderer).zip(layouts.next())
|
||||||
element.overlay(layout, renderer).zip(layouts.next())
|
{
|
||||||
{
|
recurse(
|
||||||
recurse(
|
&mut nested,
|
||||||
&mut nested,
|
nested_layout,
|
||||||
nested_layout,
|
event.clone(),
|
||||||
event.clone(),
|
cursor,
|
||||||
cursor,
|
renderer,
|
||||||
renderer,
|
clipboard,
|
||||||
clipboard,
|
shell,
|
||||||
shell,
|
)
|
||||||
)
|
} else {
|
||||||
} else {
|
false
|
||||||
(event::Status::Ignored, false)
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(nested_status, event::Status::Ignored) {
|
if shell.event_status() == event::Status::Ignored {
|
||||||
let is_over = nested_is_over
|
let is_over = nested_is_over
|
||||||
|| cursor
|
|| cursor
|
||||||
.position()
|
.position()
|
||||||
|
|
@ -212,30 +211,29 @@ where
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
(
|
element.update(
|
||||||
element.on_event(
|
event,
|
||||||
event,
|
layout,
|
||||||
layout,
|
if nested_is_over {
|
||||||
if nested_is_over {
|
mouse::Cursor::Unavailable
|
||||||
mouse::Cursor::Unavailable
|
} else {
|
||||||
} else {
|
cursor
|
||||||
cursor
|
},
|
||||||
},
|
renderer,
|
||||||
renderer,
|
clipboard,
|
||||||
clipboard,
|
shell,
|
||||||
shell,
|
);
|
||||||
),
|
|
||||||
is_over,
|
is_over
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(nested_status, nested_is_over)
|
nested_is_over
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(event::Status::Ignored, false)
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (status, _) = recurse(
|
let _ = recurse(
|
||||||
&mut self.overlay,
|
&mut self.overlay,
|
||||||
layout,
|
layout,
|
||||||
event,
|
event,
|
||||||
|
|
@ -244,8 +242,6 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
);
|
);
|
||||||
|
|
||||||
status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay.
|
/// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay.
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ where
|
||||||
for event in events.iter().cloned() {
|
for event in events.iter().cloned() {
|
||||||
let mut shell = Shell::new(messages);
|
let mut shell = Shell::new(messages);
|
||||||
|
|
||||||
let event_status = overlay.on_event(
|
overlay.update(
|
||||||
event,
|
event,
|
||||||
Layout::new(&layout),
|
Layout::new(&layout),
|
||||||
cursor,
|
cursor,
|
||||||
|
|
@ -219,7 +219,7 @@ where
|
||||||
&mut shell,
|
&mut shell,
|
||||||
);
|
);
|
||||||
|
|
||||||
event_statuses.push(event_status);
|
event_statuses.push(shell.event_status());
|
||||||
|
|
||||||
match (redraw_request, shell.redraw_request()) {
|
match (redraw_request, shell.redraw_request()) {
|
||||||
(None, Some(at)) => {
|
(None, Some(at)) => {
|
||||||
|
|
@ -308,7 +308,7 @@ where
|
||||||
|
|
||||||
let mut shell = Shell::new(messages);
|
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,
|
&mut self.state,
|
||||||
event,
|
event,
|
||||||
Layout::new(&self.base),
|
Layout::new(&self.base),
|
||||||
|
|
@ -319,7 +319,7 @@ where
|
||||||
&viewport,
|
&viewport,
|
||||||
);
|
);
|
||||||
|
|
||||||
if matches!(event_status, event::Status::Captured) {
|
if shell.event_status() == event::Status::Captured {
|
||||||
self.overlay = None;
|
self.overlay = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,7 +347,7 @@ where
|
||||||
outdated = true;
|
outdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_status.merge(overlay_status)
|
shell.event_status().merge(overlay_status)
|
||||||
})
|
})
|
||||||
.collect();
|
.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::border::{self, Border};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -26,9 +25,10 @@ use crate::core::theme::palette;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::Operation;
|
use crate::core::widget::Operation;
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle,
|
Background, Clipboard, Color, Element, Event, Layout, Length, Padding,
|
||||||
Shadow, Shell, Size, Theme, Vector, Widget,
|
Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A generic widget that produces a message when pressed.
|
/// A generic widget that produces a message when pressed.
|
||||||
|
|
@ -81,6 +81,7 @@ where
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
clip: bool,
|
clip: bool,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OnPress<'a, Message> {
|
enum OnPress<'a, Message> {
|
||||||
|
|
@ -117,6 +118,7 @@ where
|
||||||
padding: DEFAULT_PADDING,
|
padding: DEFAULT_PADDING,
|
||||||
clip: false,
|
clip: false,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,7 +272,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -280,8 +282,8 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
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],
|
&mut tree.children[0],
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout.children().next().unwrap(),
|
layout.children().next().unwrap(),
|
||||||
|
|
@ -290,8 +292,10 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
) {
|
);
|
||||||
return event::Status::Captured;
|
|
||||||
|
if shell.is_event_captured() {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -305,7 +309,7 @@ where
|
||||||
|
|
||||||
state.is_pressed = true;
|
state.is_pressed = true;
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +328,7 @@ where
|
||||||
shell.publish(on_press);
|
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(
|
fn draw(
|
||||||
|
|
@ -351,23 +373,8 @@ where
|
||||||
) {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let content_layout = layout.children().next().unwrap();
|
let content_layout = layout.children().next().unwrap();
|
||||||
let is_mouse_over = cursor.is_over(bounds);
|
let style =
|
||||||
|
theme.style(&self.class, self.status.unwrap_or(Status::Disabled));
|
||||||
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);
|
|
||||||
|
|
||||||
if style.background.is_some()
|
if style.background.is_some()
|
||||||
|| style.border.width > 0.0
|
|| style.border.width > 0.0
|
||||||
|
|
|
||||||
|
|
@ -48,24 +48,24 @@
|
||||||
//! canvas(Circle { radius: 50.0 }).into()
|
//! canvas(Circle { radius: 50.0 }).into()
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
pub mod event;
|
|
||||||
|
|
||||||
mod program;
|
mod program;
|
||||||
|
|
||||||
pub use event::Event;
|
|
||||||
pub use program::Program;
|
pub use program::Program;
|
||||||
|
|
||||||
|
pub use crate::core::event::Event;
|
||||||
pub use crate::graphics::cache::Group;
|
pub use crate::graphics::cache::Group;
|
||||||
pub use crate::graphics::geometry::{
|
pub use crate::graphics::geometry::{
|
||||||
fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
|
fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
|
||||||
LineJoin, Path, Stroke, Style, Text,
|
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::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,
|
Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
@ -148,6 +148,7 @@ where
|
||||||
message_: PhantomData<Message>,
|
message_: PhantomData<Message>,
|
||||||
theme_: PhantomData<Theme>,
|
theme_: PhantomData<Theme>,
|
||||||
renderer_: PhantomData<Renderer>,
|
renderer_: PhantomData<Renderer>,
|
||||||
|
last_mouse_interaction: Option<mouse::Interaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer>
|
impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer>
|
||||||
|
|
@ -166,6 +167,7 @@ where
|
||||||
message_: PhantomData,
|
message_: PhantomData,
|
||||||
theme_: PhantomData,
|
theme_: PhantomData,
|
||||||
renderer_: PhantomData,
|
renderer_: PhantomData,
|
||||||
|
last_mouse_interaction: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,42 +215,63 @@ where
|
||||||
layout::atomic(limits, self.width, self.height)
|
layout::atomic(limits, self.width, self.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: core::Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
_renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let canvas_event = match event {
|
let state = tree.state.downcast_mut::<P::State>();
|
||||||
core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
|
let is_redraw_request = matches!(
|
||||||
core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
|
event,
|
||||||
core::Event::Keyboard(keyboard_event) => {
|
Event::Window(window::Event::RedrawRequested(_now)),
|
||||||
Some(Event::Keyboard(keyboard_event))
|
);
|
||||||
}
|
|
||||||
core::Event::Window(_) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(canvas_event) = canvas_event {
|
if let Some(action) = self.program.update(state, event, bounds, cursor)
|
||||||
let state = tree.state.downcast_mut::<P::State>();
|
{
|
||||||
|
let (message, redraw_request, event_status) = action.into_inner();
|
||||||
let (event_status, message) =
|
|
||||||
self.program.update(state, canvas_event, bounds, cursor);
|
|
||||||
|
|
||||||
if let Some(message) = message {
|
if let Some(message) = message {
|
||||||
shell.publish(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(
|
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::mouse;
|
||||||
use crate::canvas::Geometry;
|
use crate::canvas::{Event, Geometry};
|
||||||
use crate::core::Rectangle;
|
use crate::core::Rectangle;
|
||||||
use crate::graphics::geometry;
|
use crate::graphics::geometry;
|
||||||
|
use crate::Action;
|
||||||
|
|
||||||
/// The state and logic of a [`Canvas`].
|
/// 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
|
/// When a [`Program`] is used in a [`Canvas`], the runtime will call this
|
||||||
/// method for each [`Event`].
|
/// method for each [`Event`].
|
||||||
///
|
///
|
||||||
/// This method can optionally return a `Message` to notify an application
|
/// This method can optionally return an [`Action`] to either notify an
|
||||||
/// of any meaningful interactions.
|
/// application of any meaningful interactions, capture the event, or
|
||||||
|
/// request a redraw.
|
||||||
///
|
///
|
||||||
/// By default, this method does and returns nothing.
|
/// By default, this method does and returns nothing.
|
||||||
///
|
///
|
||||||
|
|
@ -34,8 +35,8 @@ where
|
||||||
_event: Event,
|
_event: Event,
|
||||||
_bounds: Rectangle,
|
_bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> (event::Status, Option<Message>) {
|
) -> Option<Action<Message>> {
|
||||||
(event::Status::Ignored, None)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
|
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
|
||||||
|
|
@ -84,7 +85,7 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
) -> (event::Status, Option<Message>) {
|
) -> Option<Action<Message>> {
|
||||||
T::update(self, state, event, bounds, cursor)
|
T::update(self, state, event, bounds, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@
|
||||||
//! ```
|
//! ```
|
||||||
//! 
|
//! 
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
|
|
@ -40,9 +39,10 @@ use crate::core::theme::palette;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Border, Clipboard, Color, Element, Layout, Length, Pixels,
|
Background, Border, Clipboard, Color, Element, Event, Layout, Length,
|
||||||
Rectangle, Shell, Size, Theme, Widget,
|
Pixels, Rectangle, Shell, Size, Theme, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A box that can be checked.
|
/// A box that can be checked.
|
||||||
|
|
@ -100,6 +100,7 @@ pub struct Checkbox<
|
||||||
font: Option<Renderer::Font>,
|
font: Option<Renderer::Font>,
|
||||||
icon: Icon<Renderer::Font>,
|
icon: Icon<Renderer::Font>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -139,6 +140,7 @@ where
|
||||||
shaping: text::Shaping::Basic,
|
shaping: text::Shaping::Basic,
|
||||||
},
|
},
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,7 +302,7 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_tree: &mut Tree,
|
_tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -310,7 +312,7 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
|
|
@ -319,14 +321,35 @@ where
|
||||||
if mouse_over {
|
if mouse_over {
|
||||||
if let Some(on_toggle) = &self.on_toggle {
|
if let Some(on_toggle) = &self.on_toggle {
|
||||||
shell.publish((on_toggle)(!self.is_checked));
|
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(
|
fn mouse_interaction(
|
||||||
|
|
@ -351,24 +374,17 @@ where
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
defaults: &renderer::Style,
|
defaults: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
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 mut children = layout.children();
|
||||||
|
|
||||||
let status = if is_disabled {
|
let style = theme.style(
|
||||||
Status::Disabled { is_checked }
|
&self.class,
|
||||||
} else if is_mouse_over {
|
self.last_status.unwrap_or(Status::Disabled {
|
||||||
Status::Hovered { is_checked }
|
is_checked: self.is_checked,
|
||||||
} else {
|
}),
|
||||||
Status::Active { is_checked }
|
);
|
||||||
};
|
|
||||||
|
|
||||||
let style = theme.style(&self.class, status);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let layout = children.next().unwrap();
|
let layout = children.next().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
//! Distribute content vertically.
|
//! Distribute content vertically.
|
||||||
use crate::core::alignment::{self, Alignment};
|
use crate::core::alignment::{self, Alignment};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::{Operation, Tree};
|
use crate::core::widget::{Operation, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell,
|
Clipboard, Element, Event, Layout, Length, Padding, Pixels, Rectangle,
|
||||||
Size, Vector, Widget,
|
Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A container that distributes its contents vertically.
|
/// A container that distributes its contents vertically.
|
||||||
|
|
@ -258,7 +257,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -268,24 +267,24 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.children
|
for ((child, state), layout) in self
|
||||||
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(&mut tree.children)
|
.zip(&mut tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.map(|((child, state), layout)| {
|
{
|
||||||
child.as_widget_mut().on_event(
|
child.as_widget_mut().update(
|
||||||
state,
|
state,
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::keyboard::key;
|
use crate::core::keyboard::key;
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
|
|
@ -64,8 +63,10 @@ use crate::core::renderer;
|
||||||
use crate::core::text;
|
use crate::core::text;
|
||||||
use crate::core::time::Instant;
|
use crate::core::time::Instant;
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
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::overlay::menu;
|
||||||
use crate::text::LineHeight;
|
use crate::text::LineHeight;
|
||||||
|
|
@ -509,7 +510,7 @@ where
|
||||||
vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _, _>)]
|
vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _, _>)]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut widget::Tree,
|
tree: &mut widget::Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -519,7 +520,7 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let menu = tree.state.downcast_mut::<Menu<T>>();
|
let menu = tree.state.downcast_mut::<Menu<T>>();
|
||||||
|
|
||||||
let started_focused = {
|
let started_focused = {
|
||||||
|
|
@ -538,7 +539,7 @@ where
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
// Provide it to the widget
|
// Provide it to the widget
|
||||||
let mut event_status = self.text_input.on_event(
|
self.text_input.update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -549,13 +550,27 @@ where
|
||||||
viewport,
|
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
|
// Then finally react to them here
|
||||||
for message in local_messages {
|
for message in local_messages {
|
||||||
let TextInputEvent::TextChanged(new_value) = message;
|
let TextInputEvent::TextChanged(new_value) = message;
|
||||||
|
|
||||||
if let Some(on_input) = &self.on_input {
|
if let Some(on_input) = &self.on_input {
|
||||||
shell.publish((on_input)(new_value.clone()));
|
shell.publish((on_input)(new_value.clone()));
|
||||||
published_message_to_shell = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Couple the filtered options with the `ComboBox`
|
// Couple the filtered options with the `ComboBox`
|
||||||
|
|
@ -576,6 +591,7 @@ where
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
shell.invalidate_layout();
|
shell.invalidate_layout();
|
||||||
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_focused = {
|
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) => {
|
(key::Named::ArrowUp, _) | (key::Named::Tab, true) => {
|
||||||
if let Some(index) = &mut menu.hovered_option {
|
if let Some(index) = &mut menu.hovered_option {
|
||||||
if *index == 0 {
|
if *index == 0 {
|
||||||
|
|
@ -656,7 +672,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event_status = event::Status::Captured;
|
shell.capture_event();
|
||||||
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
(key::Named::ArrowDown, _)
|
(key::Named::ArrowDown, _)
|
||||||
| (key::Named::Tab, false)
|
| (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;
|
published_message_to_shell = true;
|
||||||
|
|
||||||
// Unfocus the input
|
// Unfocus the input
|
||||||
let _ = self.text_input.on_event(
|
self.text_input.update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(
|
Event::Mouse(mouse::Event::ButtonPressed(
|
||||||
mouse::Button::Left,
|
mouse::Button::Left,
|
||||||
|
|
@ -761,8 +779,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event_status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::alignment::{self, Alignment};
|
use crate::core::alignment::{self, Alignment};
|
||||||
use crate::core::border::{self, Border};
|
use crate::core::border::{self, Border};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::gradient::{self, Gradient};
|
use crate::core::gradient::{self, Gradient};
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
|
|
@ -30,7 +29,7 @@ use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::{self, Operation};
|
use crate::core::widget::{self, Operation};
|
||||||
use crate::core::{
|
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,
|
Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,
|
||||||
Widget,
|
Widget,
|
||||||
};
|
};
|
||||||
|
|
@ -298,7 +297,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -308,8 +307,8 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.content.as_widget_mut().on_event(
|
self.content.as_widget_mut().update(
|
||||||
tree,
|
tree,
|
||||||
event,
|
event,
|
||||||
layout.children().next().unwrap(),
|
layout.children().next().unwrap(),
|
||||||
|
|
@ -318,7 +317,7 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -363,12 +363,11 @@ where
|
||||||
Theme: 'a,
|
Theme: 'a,
|
||||||
Renderer: core::Renderer + 'a,
|
Renderer: core::Renderer + 'a,
|
||||||
{
|
{
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
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> {
|
struct Opaque<'a, Message, Theme, Renderer> {
|
||||||
content: Element<'a, Message, Theme, Renderer>,
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
|
|
@ -439,7 +438,7 @@ where
|
||||||
.operate(state, layout, renderer, operation);
|
.operate(state, layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut Tree,
|
state: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -449,25 +448,19 @@ where
|
||||||
clipboard: &mut dyn core::Clipboard,
|
clipboard: &mut dyn core::Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let is_mouse_press = matches!(
|
let is_mouse_press = matches!(
|
||||||
event,
|
event,
|
||||||
core::Event::Mouse(mouse::Event::ButtonPressed(_))
|
core::Event::Mouse(mouse::Event::ButtonPressed(_))
|
||||||
);
|
);
|
||||||
|
|
||||||
if let core::event::Status::Captured =
|
self.content.as_widget_mut().update(
|
||||||
self.content.as_widget_mut().on_event(
|
state, event, layout, cursor, renderer, clipboard, shell,
|
||||||
state, event, layout, cursor, renderer, clipboard, shell,
|
viewport,
|
||||||
viewport,
|
);
|
||||||
)
|
|
||||||
{
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_mouse_press && cursor.is_over(layout.bounds()) {
|
if is_mouse_press && cursor.is_over(layout.bounds()) {
|
||||||
event::Status::Captured
|
shell.capture_event();
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -530,18 +523,18 @@ where
|
||||||
Theme: 'a,
|
Theme: 'a,
|
||||||
Renderer: core::Renderer + 'a,
|
Renderer: core::Renderer + 'a,
|
||||||
{
|
{
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
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> {
|
struct Hover<'a, Message, Theme, Renderer> {
|
||||||
base: Element<'a, Message, Theme, Renderer>,
|
base: Element<'a, Message, Theme, Renderer>,
|
||||||
top: Element<'a, Message, Theme, Renderer>,
|
top: Element<'a, Message, Theme, Renderer>,
|
||||||
is_top_focused: bool,
|
is_top_focused: bool,
|
||||||
is_top_overlay_active: bool,
|
is_top_overlay_active: bool,
|
||||||
|
is_hovered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
|
@ -648,7 +641,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -658,11 +651,13 @@ where
|
||||||
clipboard: &mut dyn core::Clipboard,
|
clipboard: &mut dyn core::Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut children = layout.children().zip(&mut tree.children);
|
let mut children = layout.children().zip(&mut tree.children);
|
||||||
let (base_layout, base_tree) = children.next().unwrap();
|
let (base_layout, base_tree) = children.next().unwrap();
|
||||||
let (top_layout, top_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(_)))
|
if matches!(event, Event::Window(window::Event::RedrawRequested(_)))
|
||||||
{
|
{
|
||||||
let mut count_focused = operation::focusable::count();
|
let mut count_focused = operation::focusable::count();
|
||||||
|
|
@ -678,19 +673,23 @@ where
|
||||||
operation::Outcome::Some(count) => count.focused.is_some(),
|
operation::Outcome::Some(count) => count.focused.is_some(),
|
||||||
_ => false,
|
_ => 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,
|
||||||
Event::Mouse(
|
Event::Mouse(
|
||||||
mouse::Event::CursorMoved { .. }
|
mouse::Event::CursorMoved { .. }
|
||||||
| mouse::Event::ButtonReleased(_)
|
| mouse::Event::ButtonReleased(_)
|
||||||
)
|
)
|
||||||
) || cursor.is_over(layout.bounds())
|
) || is_hovered
|
||||||
|| self.is_top_focused
|
|| self.is_top_focused
|
||||||
|| self.is_top_overlay_active
|
|| self.is_top_overlay_active
|
||||||
{
|
{
|
||||||
self.top.as_widget_mut().on_event(
|
self.top.as_widget_mut().update(
|
||||||
top_tree,
|
top_tree,
|
||||||
event.clone(),
|
event.clone(),
|
||||||
top_layout,
|
top_layout,
|
||||||
|
|
@ -699,16 +698,14 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if top_status == event::Status::Captured {
|
if shell.is_event_captured() {
|
||||||
return top_status;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.base.as_widget_mut().on_event(
|
self.base.as_widget_mut().update(
|
||||||
base_tree,
|
base_tree,
|
||||||
event.clone(),
|
event.clone(),
|
||||||
base_layout,
|
base_layout,
|
||||||
|
|
@ -717,7 +714,7 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -777,6 +774,7 @@ where
|
||||||
top: top.into(),
|
top: top.into(),
|
||||||
is_top_focused: false,
|
is_top_focused: false,
|
||||||
is_top_overlay_active: false,
|
is_top_overlay_active: false,
|
||||||
|
is_hovered: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
//! Zoom and pan on an image.
|
//! Zoom and pan on an image.
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::image::{self, FilterMethod};
|
use crate::core::image::{self, FilterMethod};
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, ContentFit, Element, Image, Layout, Length, Pixels, Point,
|
Clipboard, ContentFit, Element, Event, Image, Layout, Length, Pixels,
|
||||||
Radians, Rectangle, Shell, Size, Vector, Widget,
|
Point, Radians, Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A frame that displays an image with the ability to zoom in/out and pan.
|
/// 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)
|
layout::Node::new(final_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -157,15 +156,15 @@ where
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
_shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
|
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
|
||||||
let Some(cursor_position) = cursor.position_over(bounds) else {
|
let Some(cursor_position) = cursor.position_over(bounds) else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match delta {
|
match delta {
|
||||||
|
|
@ -216,29 +215,25 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Captured
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
let Some(cursor_position) = cursor.position_over(bounds) else {
|
let Some(cursor_position) = cursor.position_over(bounds) else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
state.cursor_grabbed_at = Some(cursor_position);
|
state.cursor_grabbed_at = Some(cursor_position);
|
||||||
state.starting_offset = state.current_offset;
|
state.starting_offset = state.current_offset;
|
||||||
|
shell.capture_event();
|
||||||
event::Status::Captured
|
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
if state.cursor_grabbed_at.is_some() {
|
if state.cursor_grabbed_at.is_some() {
|
||||||
state.cursor_grabbed_at = None;
|
state.cursor_grabbed_at = None;
|
||||||
|
shell.capture_event();
|
||||||
event::Status::Captured
|
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
||||||
|
|
@ -278,13 +273,10 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
state.current_offset = Vector::new(x, y);
|
state.current_offset = Vector::new(x, y);
|
||||||
|
shell.capture_event();
|
||||||
event::Status::Captured
|
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => event::Status::Ignored,
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
//! Keyed columns distribute content vertically while keeping continuity.
|
//! Keyed columns distribute content vertically while keeping continuity.
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -7,8 +6,8 @@ use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::Operation;
|
use crate::core::widget::Operation;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle,
|
Alignment, Clipboard, Element, Event, Layout, Length, Padding, Pixels,
|
||||||
Shell, Size, Vector, Widget,
|
Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A container that distributes its contents vertically while keeping continuity.
|
/// A container that distributes its contents vertically while keeping continuity.
|
||||||
|
|
@ -298,7 +297,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -308,24 +307,24 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.children
|
for ((child, state), layout) in self
|
||||||
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(&mut tree.children)
|
.zip(&mut tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.map(|((child, state), layout)| {
|
{
|
||||||
child.as_widget_mut().on_event(
|
child.as_widget_mut().update(
|
||||||
state,
|
state,
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ pub use responsive::Responsive;
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
|
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -19,7 +18,7 @@ use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::Element;
|
use crate::core::Element;
|
||||||
use crate::core::{
|
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;
|
use crate::runtime::overlay::Nested;
|
||||||
|
|
||||||
|
|
@ -196,7 +195,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -206,9 +205,9 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.with_element_mut(|element| {
|
self.with_element_mut(|element| {
|
||||||
element.as_widget_mut().on_event(
|
element.as_widget_mut().update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -217,8 +216,8 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -387,7 +386,7 @@ where
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -395,11 +394,10 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.with_overlay_mut_maybe(|overlay| {
|
let _ = self.with_overlay_mut_maybe(|overlay| {
|
||||||
overlay.on_event(event, layout, cursor, renderer, clipboard, shell)
|
overlay.update(event, layout, cursor, renderer, clipboard, shell);
|
||||||
})
|
});
|
||||||
.unwrap_or(event::Status::Ignored)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_over(
|
fn is_over(
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
//! Build and reuse custom widgets using The Elm Architecture.
|
//! Build and reuse custom widgets using The Elm Architecture.
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
|
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
|
||||||
Widget,
|
Widget,
|
||||||
|
|
@ -311,7 +311,7 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: core::Event,
|
event: core::Event,
|
||||||
|
|
@ -321,13 +321,13 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut local_messages = Vec::new();
|
let mut local_messages = Vec::new();
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
|
let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
|
||||||
let event_status = self.with_element_mut(|element| {
|
self.with_element_mut(|element| {
|
||||||
element.as_widget_mut().on_event(
|
element.as_widget_mut().update(
|
||||||
&mut t.borrow_mut().as_mut().unwrap().children[0],
|
&mut t.borrow_mut().as_mut().unwrap().children[0],
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -336,13 +336,24 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
&mut local_shell,
|
&mut local_shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if local_shell.is_event_captured() {
|
||||||
|
shell.capture_event();
|
||||||
|
}
|
||||||
|
|
||||||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||||
|
|
||||||
if let Some(redraw_request) = local_shell.redraw_request() {
|
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() {
|
if !local_messages.is_empty() {
|
||||||
|
|
@ -369,8 +380,6 @@ where
|
||||||
|
|
||||||
shell.invalidate_layout();
|
shell.invalidate_layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
event_status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -592,7 +601,7 @@ where
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: core::Event,
|
event: core::Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -600,27 +609,36 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut local_messages = Vec::new();
|
let mut local_messages = Vec::new();
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
let event_status = self
|
let _ = self.with_overlay_mut_maybe(|overlay| {
|
||||||
.with_overlay_mut_maybe(|overlay| {
|
overlay.update(
|
||||||
overlay.on_event(
|
event,
|
||||||
event,
|
layout,
|
||||||
layout,
|
cursor,
|
||||||
cursor,
|
renderer,
|
||||||
renderer,
|
clipboard,
|
||||||
clipboard,
|
&mut local_shell,
|
||||||
&mut local_shell,
|
);
|
||||||
)
|
});
|
||||||
})
|
|
||||||
.unwrap_or(event::Status::Ignored);
|
if local_shell.is_event_captured() {
|
||||||
|
shell.capture_event();
|
||||||
|
}
|
||||||
|
|
||||||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||||
|
|
||||||
if let Some(redraw_request) = local_shell.redraw_request() {
|
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() {
|
if !local_messages.is_empty() {
|
||||||
|
|
@ -658,8 +676,6 @@ where
|
||||||
|
|
||||||
shell.invalidate_layout();
|
shell.invalidate_layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
event_status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_over(
|
fn is_over(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -6,8 +5,8 @@ use crate::core::renderer;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
|
self, Clipboard, Element, Event, Length, Point, Rectangle, Shell, Size,
|
||||||
Widget,
|
Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::horizontal_space;
|
use crate::horizontal_space;
|
||||||
use crate::runtime::overlay::Nested;
|
use crate::runtime::overlay::Nested;
|
||||||
|
|
@ -83,18 +82,21 @@ where
|
||||||
new_size: Size,
|
new_size: Size,
|
||||||
view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
|
view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
|
||||||
) {
|
) {
|
||||||
let is_tree_empty =
|
if self.size != new_size {
|
||||||
tree.tag == tree::Tag::stateless() && tree.children.is_empty();
|
self.element = view(new_size);
|
||||||
|
self.size = new_size;
|
||||||
|
self.layout = None;
|
||||||
|
|
||||||
if !is_tree_empty && self.size == new_size {
|
tree.diff(&self.element);
|
||||||
return;
|
} 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>(
|
fn resolve<R, T>(
|
||||||
|
|
@ -183,7 +185,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -193,20 +195,20 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
let mut content = self.content.borrow_mut();
|
let mut content = self.content.borrow_mut();
|
||||||
|
|
||||||
let mut local_messages = vec![];
|
let mut local_messages = vec![];
|
||||||
let mut local_shell = Shell::new(&mut local_messages);
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
|
|
||||||
let status = content.resolve(
|
content.resolve(
|
||||||
&mut state.tree.borrow_mut(),
|
&mut state.tree.borrow_mut(),
|
||||||
renderer,
|
renderer,
|
||||||
layout,
|
layout,
|
||||||
&self.view,
|
&self.view,
|
||||||
|tree, renderer, layout, element| {
|
|tree, renderer, layout, element| {
|
||||||
element.as_widget_mut().on_event(
|
element.as_widget_mut().update(
|
||||||
tree,
|
tree,
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -215,7 +217,7 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
&mut local_shell,
|
&mut local_shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -224,8 +226,6 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.merge(local_shell, std::convert::identity);
|
shell.merge(local_shell, std::convert::identity);
|
||||||
|
|
||||||
status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -417,7 +417,7 @@ where
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -425,28 +425,20 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut is_layout_invalid = false;
|
let mut is_layout_invalid = false;
|
||||||
|
|
||||||
let event_status = self
|
let _ = self.with_overlay_mut_maybe(|overlay| {
|
||||||
.with_overlay_mut_maybe(|overlay| {
|
overlay.update(event, layout, cursor, renderer, clipboard, shell);
|
||||||
let event_status = overlay.on_event(
|
|
||||||
event, layout, cursor, renderer, clipboard, shell,
|
|
||||||
);
|
|
||||||
|
|
||||||
is_layout_invalid = shell.is_layout_invalid();
|
is_layout_invalid = shell.is_layout_invalid();
|
||||||
|
});
|
||||||
event_status
|
|
||||||
})
|
|
||||||
.unwrap_or(event::Status::Ignored);
|
|
||||||
|
|
||||||
if is_layout_invalid {
|
if is_layout_invalid {
|
||||||
self.with_overlay_mut(|(_overlay, layout)| {
|
self.with_overlay_mut(|(_overlay, layout)| {
|
||||||
**layout = None;
|
**layout = None;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
event_status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_over(
|
fn is_over(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub use iced_renderer::graphics;
|
||||||
pub use iced_runtime as runtime;
|
pub use iced_runtime as runtime;
|
||||||
pub use iced_runtime::core;
|
pub use iced_runtime::core;
|
||||||
|
|
||||||
|
mod action;
|
||||||
mod column;
|
mod column;
|
||||||
mod mouse_area;
|
mod mouse_area;
|
||||||
mod row;
|
mod row;
|
||||||
|
|
@ -131,4 +132,5 @@ pub use qr_code::QRCode;
|
||||||
pub mod markdown;
|
pub mod markdown;
|
||||||
|
|
||||||
pub use crate::core::theme::{self, Theme};
|
pub use crate::core::theme::{self, Theme};
|
||||||
|
pub use action::Action;
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
//! A container for capturing mouse events.
|
//! A container for capturing mouse events.
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -7,8 +6,8 @@ use crate::core::renderer;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::{tree, Operation, Tree};
|
use crate::core::widget::{tree, Operation, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
|
Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size,
|
||||||
Widget,
|
Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Emit messages on mouse events.
|
/// Emit messages on mouse events.
|
||||||
|
|
@ -216,7 +215,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -226,8 +225,8 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
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],
|
&mut tree.children[0],
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -236,11 +235,13 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
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(
|
fn mouse_interaction(
|
||||||
|
|
@ -329,7 +330,7 @@ fn update<Message: Clone, Theme, Renderer>(
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state: &mut State = tree.state.downcast_mut();
|
let state: &mut State = tree.state.downcast_mut();
|
||||||
|
|
||||||
let cursor_position = cursor.position();
|
let cursor_position = cursor.position();
|
||||||
|
|
@ -363,104 +364,71 @@ fn update<Message: Clone, Theme, Renderer>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cursor.is_over(layout.bounds()) {
|
if !cursor.is_over(layout.bounds()) {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
match event {
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) = event
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
{
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let mut captured = false;
|
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() {
|
if let Some(position) = cursor_position {
|
||||||
captured = true;
|
if let Some(message) = widget.on_double_click.as_ref() {
|
||||||
shell.publish(message.clone());
|
let new_click = mouse::Click::new(
|
||||||
}
|
position,
|
||||||
|
mouse::Button::Left,
|
||||||
|
state.previous_click,
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(position) = cursor_position {
|
if matches!(new_click.kind(), mouse::click::Kind::Double) {
|
||||||
if let Some(message) = widget.on_double_click.as_ref() {
|
shell.publish(message.clone());
|
||||||
let new_click = mouse::Click::new(
|
}
|
||||||
position,
|
|
||||||
mouse::Button::Left,
|
|
||||||
state.previous_click,
|
|
||||||
);
|
|
||||||
|
|
||||||
if matches!(new_click.kind(), mouse::click::Kind::Double) {
|
state.previous_click = Some(new_click);
|
||||||
shell.publish(message.clone());
|
|
||||||
|
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
if captured {
|
| Event::Touch(touch::Event::FingerLifted { .. }) => {
|
||||||
return event::Status::Captured;
|
if let Some(message) = widget.on_release.as_ref() {
|
||||||
|
shell.publish(message.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
|
||||||
|
if let Some(message) = widget.on_right_press.as_ref() {
|
||||||
if let Some(message) = widget.on_release.as_ref() {
|
shell.publish(message.clone());
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
shell.capture_event();
|
||||||
| Event::Touch(touch::Event::FingerLifted { .. }) = 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() {
|
||||||
if let Some(message) = widget.on_right_press.as_ref() {
|
shell.publish(message.clone());
|
||||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
|
}
|
||||||
event
|
|
||||||
{
|
|
||||||
shell.publish(message.clone());
|
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => {
|
||||||
|
if let Some(message) = widget.on_middle_press.as_ref() {
|
||||||
if let Some(message) = widget.on_right_release.as_ref() {
|
shell.publish(message.clone());
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(
|
shell.capture_event();
|
||||||
mouse::Button::Right,
|
}
|
||||||
)) = 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() {
|
||||||
if let Some(message) = widget.on_middle_press.as_ref() {
|
shell.publish(message.clone());
|
||||||
if let Event::Mouse(mouse::Event::ButtonPressed(
|
}
|
||||||
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() {
|
||||||
if let Some(message) = widget.on_middle_release.as_ref() {
|
shell.publish(on_scroll(delta));
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(
|
shell.capture_event();
|
||||||
mouse::Button::Middle,
|
}
|
||||||
)) = event
|
|
||||||
{
|
|
||||||
shell.publish(message.clone());
|
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
//! Build and show dropdown menus.
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::border::{self, Border};
|
use crate::core::border::{self, Border};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::text::{self, Text};
|
use crate::core::text::{self, Text};
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::Tree;
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Clipboard, Color, Length, Padding, Pixels, Point, Rectangle,
|
Background, Clipboard, Color, Event, Length, Padding, Pixels, Point,
|
||||||
Size, Theme, Vector,
|
Rectangle, Size, Theme, Vector,
|
||||||
};
|
};
|
||||||
use crate::core::{Element, Shell, Widget};
|
use crate::core::{Element, Shell, Widget};
|
||||||
use crate::scrollable::{self, Scrollable};
|
use crate::scrollable::{self, Scrollable};
|
||||||
|
|
@ -262,7 +262,7 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -270,13 +270,13 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
self.list.on_event(
|
self.list.update(
|
||||||
self.state, event, layout, cursor, renderer, clipboard, shell,
|
self.state, event, layout, cursor, renderer, clipboard, shell,
|
||||||
&bounds,
|
&bounds,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -334,6 +334,10 @@ where
|
||||||
class: &'a <Theme as Catalog>::Class<'b>,
|
class: &'a <Theme as Catalog>::Class<'b>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ListState {
|
||||||
|
is_hovered: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||||
for List<'a, 'b, T, Message, Theme, Renderer>
|
for List<'a, 'b, T, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
|
|
@ -341,6 +345,14 @@ where
|
||||||
Theme: Catalog,
|
Theme: Catalog,
|
||||||
Renderer: text::Renderer,
|
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> {
|
fn size(&self) -> Size<Length> {
|
||||||
Size {
|
Size {
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
|
|
@ -374,9 +386,9 @@ where
|
||||||
layout::Node::new(size)
|
layout::Node::new(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
|
|
@ -384,14 +396,14 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
if let Some(index) = *self.hovered_option {
|
if let Some(index) = *self.hovered_option {
|
||||||
if let Some(option) = self.options.get(index) {
|
if let Some(option) = self.options.get(index) {
|
||||||
shell.publish((self.on_selected)(option.clone()));
|
shell.publish((self.on_selected)(option.clone()));
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -411,14 +423,18 @@ where
|
||||||
let new_hovered_option =
|
let new_hovered_option =
|
||||||
(cursor_position.y / option_height) as usize;
|
(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 *self.hovered_option != Some(new_hovered_option) {
|
if let Some(option) =
|
||||||
if let Some(option) =
|
self.options.get(new_hovered_option)
|
||||||
self.options.get(new_hovered_option)
|
{
|
||||||
|
if let Some(on_option_hovered) =
|
||||||
|
self.on_option_hovered
|
||||||
{
|
{
|
||||||
shell
|
shell
|
||||||
.publish(on_option_hovered(option.clone()));
|
.publish(on_option_hovered(option.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -443,7 +459,7 @@ where
|
||||||
if let Some(index) = *self.hovered_option {
|
if let Some(index) = *self.hovered_option {
|
||||||
if let Some(option) = self.options.get(index) {
|
if let Some(option) = self.options.get(index) {
|
||||||
shell.publish((self.on_selected)(option.clone()));
|
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(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,6 @@ pub use state::State;
|
||||||
pub use title_bar::TitleBar;
|
pub use title_bar::TitleBar;
|
||||||
|
|
||||||
use crate::container;
|
use crate::container;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay::{self, Group};
|
use crate::core::overlay::{self, Group};
|
||||||
|
|
@ -87,8 +86,9 @@ use crate::core::renderer;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
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,
|
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_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
||||||
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
||||||
class: <Theme as Catalog>::Class<'a>,
|
class: <Theme as Catalog>::Class<'a>,
|
||||||
|
last_mouse_interaction: Option<mouse::Interaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -203,6 +204,7 @@ where
|
||||||
on_drag: None,
|
on_drag: None,
|
||||||
on_resize: None,
|
on_resize: None,
|
||||||
class: <Theme as Catalog>::default(),
|
class: <Theme as Catalog>::default(),
|
||||||
|
last_mouse_interaction: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,6 +295,52 @@ where
|
||||||
.then(|| self.on_drag.is_some())
|
.then(|| self.on_drag.is_some())
|
||||||
.unwrap_or_default()
|
.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)]
|
#[derive(Default)]
|
||||||
|
|
@ -423,7 +471,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -433,9 +481,7 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut event_status = event::Status::Ignored;
|
|
||||||
|
|
||||||
let Memory { action, .. } = tree.state.downcast_mut();
|
let Memory { action, .. } = tree.state.downcast_mut();
|
||||||
let node = self.internal.layout();
|
let node = self.internal.layout();
|
||||||
|
|
||||||
|
|
@ -445,13 +491,43 @@ where
|
||||||
&None
|
&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 {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
if let Some(cursor_position) = cursor.position_over(bounds) {
|
if let Some(cursor_position) = cursor.position_over(bounds) {
|
||||||
event_status = event::Status::Captured;
|
shell.capture_event();
|
||||||
|
|
||||||
match &self.on_resize {
|
match &self.on_resize {
|
||||||
Some((leeway, _)) => {
|
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;
|
*action = state::Action::Idle;
|
||||||
|
|
@ -600,44 +672,48 @@ where
|
||||||
ratio,
|
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
|
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
|
||||||
.iter()
|
self.last_mouse_interaction = Some(interaction);
|
||||||
.copied()
|
} else if self.last_mouse_interaction.is_some_and(
|
||||||
.zip(&mut self.contents)
|
|last_mouse_interaction| last_mouse_interaction != interaction,
|
||||||
.zip(&mut tree.children)
|
) {
|
||||||
.zip(layout.children())
|
shell.request_redraw();
|
||||||
.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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -650,41 +726,10 @@ where
|
||||||
) -> mouse::Interaction {
|
) -> mouse::Interaction {
|
||||||
let Memory { action, .. } = tree.state.downcast_ref();
|
let Memory { action, .. } = tree.state.downcast_ref();
|
||||||
|
|
||||||
if action.picked_pane().is_some() {
|
if let Some(grid_interaction) =
|
||||||
return mouse::Interaction::Grabbing;
|
self.grid_interaction(action, layout, cursor)
|
||||||
}
|
{
|
||||||
|
return grid_interaction;
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.panes
|
self.panes
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::container;
|
use crate::container;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::{self, Tree};
|
use crate::core::widget::{self, Tree};
|
||||||
use crate::core::{
|
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};
|
use crate::pane_grid::{Draggable, TitleBar};
|
||||||
|
|
||||||
|
|
@ -239,7 +239,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn on_event(
|
pub(crate) fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -250,13 +250,11 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
is_picked: bool,
|
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 body_layout = if let Some(title_bar) = &mut self.title_bar {
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
|
|
||||||
event_status = title_bar.on_event(
|
title_bar.update(
|
||||||
&mut tree.children[1],
|
&mut tree.children[1],
|
||||||
event.clone(),
|
event.clone(),
|
||||||
children.next().unwrap(),
|
children.next().unwrap(),
|
||||||
|
|
@ -272,10 +270,8 @@ where
|
||||||
layout
|
layout
|
||||||
};
|
};
|
||||||
|
|
||||||
let body_status = if is_picked {
|
if !is_picked {
|
||||||
event::Status::Ignored
|
self.body.as_widget_mut().update(
|
||||||
} else {
|
|
||||||
self.body.as_widget_mut().on_event(
|
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event,
|
event,
|
||||||
body_layout,
|
body_layout,
|
||||||
|
|
@ -284,10 +280,33 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
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(
|
pub(crate) fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use crate::container;
|
use crate::container;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::{self, Tree};
|
use crate::core::widget::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
|
self, Clipboard, Element, Event, Layout, Padding, Point, Rectangle, Shell,
|
||||||
Vector,
|
Size, Vector,
|
||||||
};
|
};
|
||||||
use crate::pane_grid::controls::Controls;
|
use crate::pane_grid::controls::Controls;
|
||||||
|
|
||||||
|
|
@ -428,7 +427,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn on_event(
|
pub(crate) fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -438,7 +437,7 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
let padded = children.next().unwrap();
|
let padded = children.next().unwrap();
|
||||||
|
|
||||||
|
|
@ -446,15 +445,16 @@ where
|
||||||
let title_layout = children.next().unwrap();
|
let title_layout = children.next().unwrap();
|
||||||
let mut show_title = true;
|
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();
|
let controls_layout = children.next().unwrap();
|
||||||
|
|
||||||
if title_layout.bounds().width + controls_layout.bounds().width
|
if title_layout.bounds().width + controls_layout.bounds().width
|
||||||
> padded.bounds().width
|
> padded.bounds().width
|
||||||
{
|
{
|
||||||
if let Some(compact) = controls.compact.as_mut() {
|
if let Some(compact) = controls.compact.as_mut() {
|
||||||
let compact_layout = children.next().unwrap();
|
let compact_layout = children.next().unwrap();
|
||||||
|
|
||||||
compact.as_widget_mut().on_event(
|
compact.as_widget_mut().update(
|
||||||
&mut tree.children[2],
|
&mut tree.children[2],
|
||||||
event.clone(),
|
event.clone(),
|
||||||
compact_layout,
|
compact_layout,
|
||||||
|
|
@ -463,11 +463,11 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
} else {
|
} else {
|
||||||
show_title = false;
|
show_title = false;
|
||||||
|
|
||||||
controls.full.as_widget_mut().on_event(
|
controls.full.as_widget_mut().update(
|
||||||
&mut tree.children[1],
|
&mut tree.children[1],
|
||||||
event.clone(),
|
event.clone(),
|
||||||
controls_layout,
|
controls_layout,
|
||||||
|
|
@ -476,10 +476,10 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controls.full.as_widget_mut().on_event(
|
controls.full.as_widget_mut().update(
|
||||||
&mut tree.children[1],
|
&mut tree.children[1],
|
||||||
event.clone(),
|
event.clone(),
|
||||||
controls_layout,
|
controls_layout,
|
||||||
|
|
@ -488,14 +488,12 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
event::Status::Ignored
|
|
||||||
};
|
|
||||||
|
|
||||||
let title_status = if show_title {
|
if show_title {
|
||||||
self.content.as_widget_mut().on_event(
|
self.content.as_widget_mut().update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event,
|
event,
|
||||||
title_layout,
|
title_layout,
|
||||||
|
|
@ -504,12 +502,8 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
} else {
|
}
|
||||||
event::Status::Ignored
|
|
||||||
};
|
|
||||||
|
|
||||||
control_status.merge(title_status)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn mouse_interaction(
|
pub(crate) fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
|
|
@ -71,9 +70,10 @@ use crate::core::text::paragraph;
|
||||||
use crate::core::text::{self, Text};
|
use crate::core::text::{self, Text};
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Border, Clipboard, Color, Element, Layout, Length, Padding,
|
Background, Border, Clipboard, Color, Element, Event, Layout, Length,
|
||||||
Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::overlay::menu::{self, Menu};
|
use crate::overlay::menu::{self, Menu};
|
||||||
|
|
||||||
|
|
@ -173,6 +173,7 @@ pub struct PickList<
|
||||||
handle: Handle<Renderer::Font>,
|
handle: Handle<Renderer::Font>,
|
||||||
class: <Theme as Catalog>::Class<'a>,
|
class: <Theme as Catalog>::Class<'a>,
|
||||||
menu_class: <Theme as menu::Catalog>::Class<'a>,
|
menu_class: <Theme as menu::Catalog>::Class<'a>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, L, V, Message, Theme, Renderer>
|
impl<'a, T, L, V, Message, Theme, Renderer>
|
||||||
|
|
@ -208,6 +209,7 @@ where
|
||||||
handle: Handle::default(),
|
handle: Handle::default(),
|
||||||
class: <Theme as Catalog>::default(),
|
class: <Theme as Catalog>::default(),
|
||||||
menu_class: <Theme as Catalog>::default_menu(),
|
menu_class: <Theme as Catalog>::default_menu(),
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -425,7 +427,7 @@ where
|
||||||
layout::Node::new(size)
|
layout::Node::new(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -435,13 +437,12 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
|
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let state =
|
|
||||||
tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
|
||||||
|
|
||||||
if state.is_open {
|
if state.is_open {
|
||||||
// Event wasn't processed by overlay, so cursor was clicked either outside its
|
// 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.
|
// bounds or on the drop-down, either way we close the overlay.
|
||||||
|
|
@ -451,7 +452,7 @@ where
|
||||||
shell.publish(on_close.clone());
|
shell.publish(on_close.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Captured
|
shell.capture_event();
|
||||||
} else if cursor.is_over(layout.bounds()) {
|
} else if cursor.is_over(layout.bounds()) {
|
||||||
let selected = self.selected.as_ref().map(Borrow::borrow);
|
let selected = self.selected.as_ref().map(Borrow::borrow);
|
||||||
|
|
||||||
|
|
@ -466,17 +467,12 @@ where
|
||||||
shell.publish(on_open.clone());
|
shell.publish(on_open.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Captured
|
shell.capture_event();
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::WheelScrolled {
|
Event::Mouse(mouse::Event::WheelScrolled {
|
||||||
delta: mouse::ScrollDelta::Lines { y, .. },
|
delta: mouse::ScrollDelta::Lines { y, .. },
|
||||||
}) => {
|
}) => {
|
||||||
let state =
|
|
||||||
tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
|
||||||
|
|
||||||
if state.keyboard_modifiers.command()
|
if state.keyboard_modifiers.command()
|
||||||
&& cursor.is_over(layout.bounds())
|
&& cursor.is_over(layout.bounds())
|
||||||
&& !state.is_open
|
&& !state.is_open
|
||||||
|
|
@ -513,20 +509,34 @@ where
|
||||||
shell.publish((self.on_select)(next_option.clone()));
|
shell.publish((self.on_select)(next_option.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Captured
|
shell.capture_event();
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||||
let state =
|
|
||||||
tree.state.downcast_mut::<State<Renderer::Paragraph>>();
|
|
||||||
|
|
||||||
state.keyboard_modifiers = modifiers;
|
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,
|
theme: &Theme,
|
||||||
_style: &renderer::Style,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let font = self.font.unwrap_or_else(|| renderer.default_font());
|
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 state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let is_mouse_over = cursor.is_over(bounds);
|
|
||||||
let is_selected = selected.is_some();
|
|
||||||
|
|
||||||
let status = if state.is_open {
|
let style = Catalog::style(
|
||||||
Status::Opened
|
theme,
|
||||||
} else if is_mouse_over {
|
&self.class,
|
||||||
Status::Hovered
|
self.last_status.unwrap_or(Status::Active),
|
||||||
} else {
|
);
|
||||||
Status::Active
|
|
||||||
};
|
|
||||||
|
|
||||||
let style = Catalog::style(theme, &self.class, status);
|
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
|
|
@ -671,7 +675,7 @@ where
|
||||||
wrapping: text::Wrapping::default(),
|
wrapping: text::Wrapping::default(),
|
||||||
},
|
},
|
||||||
Point::new(bounds.x + self.padding.left, bounds.center_y()),
|
Point::new(bounds.x + self.padding.left, bounds.center_y()),
|
||||||
if is_selected {
|
if selected.is_some() {
|
||||||
style.text_color
|
style.text_color
|
||||||
} else {
|
} else {
|
||||||
style.placeholder_color
|
style.placeholder_color
|
||||||
|
|
@ -824,7 +828,10 @@ pub enum Status {
|
||||||
/// The [`PickList`] is being hovered.
|
/// The [`PickList`] is being hovered.
|
||||||
Hovered,
|
Hovered,
|
||||||
/// The [`PickList`] is open.
|
/// The [`PickList`] is open.
|
||||||
Opened,
|
Opened {
|
||||||
|
/// Whether the [`PickList`] is hovered, while open.
|
||||||
|
is_hovered: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The appearance of a pick list.
|
/// The appearance of a pick list.
|
||||||
|
|
@ -898,7 +905,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
Status::Active => active,
|
Status::Active => active,
|
||||||
Status::Hovered | Status::Opened => Style {
|
Status::Hovered | Status::Opened { .. } => Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
color: palette.primary.strong.color,
|
color: palette.primary.strong.color,
|
||||||
..active.border
|
..active.border
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::border::{self, Border};
|
use crate::core::border::{self, Border};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
|
|
@ -66,9 +65,10 @@ use crate::core::text;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle,
|
Background, Clipboard, Color, Element, Event, Layout, Length, Pixels,
|
||||||
Shell, Size, Theme, Widget,
|
Rectangle, Shell, Size, Theme, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A circular button representing a choice.
|
/// A circular button representing a choice.
|
||||||
|
|
@ -147,6 +147,7 @@ where
|
||||||
text_wrapping: text::Wrapping,
|
text_wrapping: text::Wrapping,
|
||||||
font: Option<Renderer::Font>,
|
font: Option<Renderer::Font>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -192,6 +193,7 @@ where
|
||||||
text_wrapping: text::Wrapping::default(),
|
text_wrapping: text::Wrapping::default(),
|
||||||
font: None,
|
font: None,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -321,7 +323,7 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: &mut Tree,
|
_state: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -331,20 +333,37 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
if cursor.is_over(layout.bounds()) {
|
if cursor.is_over(layout.bounds()) {
|
||||||
shell.publish(self.on_click.clone());
|
shell.publish(self.on_click.clone());
|
||||||
|
shell.capture_event();
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
fn mouse_interaction(
|
||||||
|
|
@ -369,21 +388,17 @@ where
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
defaults: &renderer::Style,
|
defaults: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let is_mouse_over = cursor.is_over(layout.bounds());
|
|
||||||
let is_selected = self.is_selected;
|
|
||||||
|
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
|
|
||||||
let status = if is_mouse_over {
|
let style = theme.style(
|
||||||
Status::Hovered { is_selected }
|
&self.class,
|
||||||
} else {
|
self.last_status.unwrap_or(Status::Active {
|
||||||
Status::Active { is_selected }
|
is_selected: self.is_selected,
|
||||||
};
|
}),
|
||||||
|
);
|
||||||
let style = theme.style(&self.class, status);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let layout = children.next().unwrap();
|
let layout = children.next().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
//! Distribute content horizontally.
|
//! Distribute content horizontally.
|
||||||
use crate::core::alignment::{self, Alignment};
|
use crate::core::alignment::{self, Alignment};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::{Operation, Tree};
|
use crate::core::widget::{Operation, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size,
|
Clipboard, Element, Event, Length, Padding, Pixels, Rectangle, Shell, Size,
|
||||||
Vector, Widget,
|
Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -254,7 +253,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -264,24 +263,24 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.children
|
for ((child, state), layout) in self
|
||||||
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(&mut tree.children)
|
.zip(&mut tree.children)
|
||||||
.zip(layout.children())
|
.zip(layout.children())
|
||||||
.map(|((child, state), layout)| {
|
{
|
||||||
child.as_widget_mut().on_event(
|
child.as_widget_mut().update(
|
||||||
state,
|
state,
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -493,7 +492,7 @@ where
|
||||||
self.row.operate(tree, layout, renderer, operation);
|
self.row.operate(tree, layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -503,10 +502,10 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.row.on_event(
|
self.row.update(
|
||||||
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
|
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
//! ```
|
//! ```
|
||||||
use crate::container;
|
use crate::container;
|
||||||
use crate::core::border::{self, Border};
|
use crate::core::border::{self, Border};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
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::widget::tree::{self, Tree};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Background, Clipboard, Color, Element, Layout, Length, Padding,
|
self, Background, Clipboard, Color, Element, Event, Layout, Length,
|
||||||
Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::runtime::task::{self, Task};
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::runtime::Action;
|
use crate::runtime::Action;
|
||||||
|
|
@ -81,6 +80,7 @@ pub struct Scrollable<
|
||||||
content: Element<'a, Message, Theme, Renderer>,
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
|
on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -108,6 +108,7 @@ where
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
on_scroll: None,
|
on_scroll: None,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
.validate()
|
.validate()
|
||||||
}
|
}
|
||||||
|
|
@ -507,7 +508,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -517,7 +518,7 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let cursor_over_scrollable = cursor.position_over(bounds);
|
let cursor_over_scrollable = cursor.position_over(bounds);
|
||||||
|
|
@ -531,6 +532,8 @@ where
|
||||||
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
|
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
|
||||||
scrollbars.is_mouse_over(cursor);
|
scrollbars.is_mouse_over(cursor);
|
||||||
|
|
||||||
|
let last_offsets = (state.offset_x, state.offset_y);
|
||||||
|
|
||||||
if let Some(last_scrolled) = state.last_scrolled {
|
if let Some(last_scrolled) = state.last_scrolled {
|
||||||
let clear_transaction = match event {
|
let clear_transaction = match event {
|
||||||
Event::Mouse(
|
Event::Mouse(
|
||||||
|
|
@ -549,309 +552,65 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
|
let mut update = || {
|
||||||
match event {
|
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_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;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 =>
|
|
||||||
{
|
|
||||||
match event {
|
match event {
|
||||||
touch::Event::FingerPressed { .. } => {
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
let Some(cursor_position) = cursor.position() else {
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
return event::Status::Ignored;
|
if let Some(scrollbar) = scrollbars.y {
|
||||||
};
|
|
||||||
|
|
||||||
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()
|
let Some(cursor_position) = cursor.position()
|
||||||
else {
|
else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let delta = Vector::new(
|
state.scroll_y_to(
|
||||||
scroll_box_touched_at.x - cursor_position.x,
|
scrollbar.scroll_percentage_y(
|
||||||
scroll_box_touched_at.y - cursor_position.y,
|
scroller_grabbed_at,
|
||||||
);
|
cursor_position,
|
||||||
|
),
|
||||||
state.scroll(
|
|
||||||
self.direction.align(delta),
|
|
||||||
bounds,
|
bounds,
|
||||||
content_bounds,
|
content_bounds,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.scroll_area_touched_at =
|
let _ = notify_scroll(
|
||||||
Some(cursor_position);
|
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(
|
let _ = notify_scroll(
|
||||||
state,
|
state,
|
||||||
&self.on_scroll,
|
&self.on_scroll,
|
||||||
|
|
@ -860,24 +619,313 @@ where
|
||||||
shell,
|
shell,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Captured
|
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::RedrawRequested(_)) => {
|
|
||||||
let _ = notify_viewport(
|
if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at {
|
||||||
state,
|
match event {
|
||||||
&self.on_scroll,
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
bounds,
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
content_bounds,
|
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,
|
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,
|
_ => mouse::Cursor::Unavailable,
|
||||||
};
|
};
|
||||||
|
|
||||||
let status = if state.y_scroller_grabbed_at.is_some()
|
let style = theme
|
||||||
|| state.x_scroller_grabbed_at.is_some()
|
.style(&self.class, self.last_status.unwrap_or(Status::Active));
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
container::draw_background(renderer, &style.container, layout.bounds());
|
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 {
|
enum Offset {
|
||||||
Absolute(f32),
|
Absolute(f32),
|
||||||
Relative(f32),
|
Relative(f32),
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
//! A custom shader widget for wgpu applications.
|
//! A custom shader widget for wgpu applications.
|
||||||
mod event;
|
|
||||||
mod program;
|
mod program;
|
||||||
|
|
||||||
pub use event::Event;
|
|
||||||
pub use program::Program;
|
pub use program::Program;
|
||||||
|
|
||||||
use crate::core;
|
use crate::core::event;
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::window;
|
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 crate::renderer::wgpu::primitive;
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub use crate::graphics::Viewport;
|
pub use crate::graphics::Viewport;
|
||||||
|
pub use crate::Action;
|
||||||
pub use primitive::{Primitive, Storage};
|
pub use primitive::{Primitive, Storage};
|
||||||
|
|
||||||
/// A widget which can render custom shaders with Iced's `wgpu` backend.
|
/// A widget which can render custom shaders with Iced's `wgpu` backend.
|
||||||
|
|
@ -87,7 +86,7 @@ where
|
||||||
layout::atomic(limits, self.width, self.height)
|
layout::atomic(limits, self.width, self.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: crate::core::Event,
|
event: crate::core::Event,
|
||||||
|
|
@ -97,40 +96,34 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let custom_shader_event = match event {
|
let state = tree.state.downcast_mut::<P::State>();
|
||||||
core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
|
|
||||||
core::Event::Keyboard(keyboard_event) => {
|
|
||||||
Some(Event::Keyboard(keyboard_event))
|
|
||||||
}
|
|
||||||
core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
|
|
||||||
core::Event::Window(window::Event::RedrawRequested(instant)) => {
|
|
||||||
Some(Event::RedrawRequested(instant))
|
|
||||||
}
|
|
||||||
core::Event::Window(_) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(custom_shader_event) = custom_shader_event {
|
if let Some(action) = self.program.update(state, event, bounds, cursor)
|
||||||
let state = tree.state.downcast_mut::<P::State>();
|
{
|
||||||
|
let (message, redraw_request, event_status) = action.into_inner();
|
||||||
let (event_status, message) = self.program.update(
|
|
||||||
state,
|
|
||||||
custom_shader_event,
|
|
||||||
bounds,
|
|
||||||
cursor,
|
|
||||||
shell,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(message) = message {
|
if let Some(message) = message {
|
||||||
shell.publish(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(
|
fn mouse_interaction(
|
||||||
|
|
@ -194,9 +187,8 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
shell: &mut Shell<'_, Message>,
|
) -> Option<Action<Message>> {
|
||||||
) -> (event::Status, Option<Message>) {
|
T::update(self, state, event, bounds, cursor)
|
||||||
T::update(self, state, event, bounds, cursor, shell)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
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::mouse;
|
||||||
use crate::core::{Rectangle, Shell};
|
use crate::core::Rectangle;
|
||||||
use crate::renderer::wgpu::Primitive;
|
use crate::renderer::wgpu::Primitive;
|
||||||
use crate::shader;
|
use crate::shader::{self, Action};
|
||||||
|
|
||||||
/// The state and logic of a [`Shader`] widget.
|
/// The state and logic of a [`Shader`] widget.
|
||||||
///
|
///
|
||||||
|
|
@ -18,10 +17,10 @@ pub trait Program<Message> {
|
||||||
type Primitive: Primitive + 'static;
|
type Primitive: Primitive + 'static;
|
||||||
|
|
||||||
/// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes
|
/// 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
|
/// based on mouse & other events. You can return an [`Action`] to publish a message, request a
|
||||||
/// redraw for the window, etc.
|
/// redraw, or capture the event.
|
||||||
///
|
///
|
||||||
/// By default, this method does and returns nothing.
|
/// By default, this method returns `None`.
|
||||||
///
|
///
|
||||||
/// [`State`]: Self::State
|
/// [`State`]: Self::State
|
||||||
fn update(
|
fn update(
|
||||||
|
|
@ -30,9 +29,8 @@ pub trait Program<Message> {
|
||||||
_event: shader::Event,
|
_event: shader::Event,
|
||||||
_bounds: Rectangle,
|
_bounds: Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
_shell: &mut Shell<'_, Message>,
|
) -> Option<Action<Message>> {
|
||||||
) -> (event::Status, Option<Message>) {
|
None
|
||||||
(event::Status::Ignored, None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the [`Primitive`].
|
/// Draws the [`Primitive`].
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::border::{self, Border};
|
use crate::core::border::{self, Border};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::keyboard::key::{self, Key};
|
use crate::core::keyboard::key::{self, Key};
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
|
|
@ -37,9 +36,10 @@ use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
|
self, Background, Clipboard, Color, Element, Event, Layout, Length, Pixels,
|
||||||
Rectangle, Shell, Size, Theme, Widget,
|
Point, Rectangle, Shell, Size, Theme, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
@ -95,6 +95,7 @@ where
|
||||||
width: Length,
|
width: Length,
|
||||||
height: f32,
|
height: f32,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
|
impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme>
|
||||||
|
|
@ -141,6 +142,7 @@ where
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
height: Self::DEFAULT_HEIGHT,
|
height: Self::DEFAULT_HEIGHT,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,7 +242,7 @@ where
|
||||||
layout::atomic(limits, self.width, self.height)
|
layout::atomic(limits, self.width, self.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -250,19 +252,43 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
let is_dragging = state.is_dragging;
|
let mut update = || {
|
||||||
let current_value = self.value;
|
let current_value = self.value;
|
||||||
|
|
||||||
let locate = |cursor_position: Point| -> Option<T> {
|
let locate = |cursor_position: Point| -> Option<T> {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let new_value = if cursor_position.x <= bounds.x {
|
|
||||||
Some(*self.range.start())
|
let new_value = if cursor_position.x <= bounds.x {
|
||||||
} else if cursor_position.x >= bounds.x + bounds.width {
|
Some(*self.range.start())
|
||||||
Some(*self.range.end())
|
} else if cursor_position.x >= bounds.x + bounds.width {
|
||||||
} else {
|
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() {
|
let step = if state.keyboard_modifiers.shift() {
|
||||||
self.shift_step.unwrap_or(self.step)
|
self.shift_step.unwrap_or(self.step)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -270,168 +296,158 @@ where
|
||||||
}
|
}
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let start = (*self.range.start()).into();
|
let steps = (value.into() / step).round();
|
||||||
let end = (*self.range.end()).into();
|
let new_value = step * (steps + 1.0);
|
||||||
|
|
||||||
let percent = f64::from(cursor_position.x - bounds.x)
|
if new_value > (*self.range.end()).into() {
|
||||||
/ f64::from(bounds.width);
|
return Some(*self.range.end());
|
||||||
|
}
|
||||||
|
|
||||||
let steps = (percent * (end - start) / step).round();
|
T::from_f64(new_value)
|
||||||
let value = steps * step + start;
|
|
||||||
|
|
||||||
T::from_f64(value.min(end))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 steps = (value.into() / step).round();
|
||||||
let step = if state.keyboard_modifiers.shift() {
|
let new_value = step * (steps - 1.0);
|
||||||
self.shift_step.unwrap_or(self.step)
|
|
||||||
} else {
|
|
||||||
self.step
|
|
||||||
}
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let steps = (value.into() / step).round();
|
if new_value < (*self.range.start()).into() {
|
||||||
let new_value = step * (steps + 1.0);
|
return Some(*self.range.start());
|
||||||
|
}
|
||||||
|
|
||||||
if new_value > (*self.range.end()).into() {
|
T::from_f64(new_value)
|
||||||
return Some(*self.range.end());
|
};
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
self.value = new_value;
|
||||||
let step = if state.keyboard_modifiers.shift() {
|
}
|
||||||
self.shift_step.unwrap_or(self.step)
|
};
|
||||||
} else {
|
|
||||||
self.step
|
|
||||||
}
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let steps = (value.into() / step).round();
|
match &event {
|
||||||
let new_value = step * (steps - 1.0);
|
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() {
|
shell.capture_event();
|
||||||
return Some(*self.range.start());
|
}
|
||||||
}
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonReleased(
|
||||||
T::from_f64(new_value)
|
mouse::Button::Left,
|
||||||
};
|
))
|
||||||
|
| Event::Touch(touch::Event::FingerLifted { .. })
|
||||||
let change = |new_value: T| {
|
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
||||||
if (self.value.into() - new_value.into()).abs() > f64::EPSILON {
|
if state.is_dragging {
|
||||||
shell.publish((self.on_change)(new_value));
|
if let Some(on_release) = self.on_release.clone() {
|
||||||
|
shell.publish(on_release);
|
||||||
self.value = new_value;
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
|
||||||
if let Some(cursor_position) =
|
|
||||||
cursor.position_over(layout.bounds())
|
|
||||||
{
|
|
||||||
if state.keyboard_modifiers.command() {
|
|
||||||
let _ = self.default.map(change);
|
|
||||||
state.is_dragging = false;
|
state.is_dragging = false;
|
||||||
} else {
|
|
||||||
let _ = locate(cursor_position).map(change);
|
shell.capture_event();
|
||||||
state.is_dragging = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||||
| Event::Touch(touch::Event::FingerLifted { .. })
|
if state.is_dragging {
|
||||||
| Event::Touch(touch::Event::FingerLost { .. }) => {
|
let _ = cursor.position().and_then(locate).map(change);
|
||||||
if is_dragging {
|
|
||||||
if let Some(on_release) = self.on_release.clone() {
|
shell.capture_event();
|
||||||
shell.publish(on_release);
|
|
||||||
}
|
}
|
||||||
state.is_dragging = false;
|
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
if state.keyboard_modifiers.control() =>
|
||||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
{
|
||||||
if is_dragging {
|
if cursor.is_over(layout.bounds()) {
|
||||||
let _ = cursor.position().and_then(locate).map(change);
|
let delta = match delta {
|
||||||
|
mouse::ScrollDelta::Lines { x: _, y } => y,
|
||||||
|
mouse::ScrollDelta::Pixels { x: _, y } => y,
|
||||||
|
};
|
||||||
|
|
||||||
return event::Status::Captured;
|
if *delta < 0.0 {
|
||||||
}
|
let _ = decrement(current_value).map(change);
|
||||||
}
|
} else {
|
||||||
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) => {
|
|
||||||
let _ = increment(current_value).map(change);
|
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::KeyPressed {
|
||||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
key, ..
|
||||||
state.keyboard_modifiers = modifiers;
|
}) => {
|
||||||
}
|
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(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
tree: &Tree,
|
_tree: &Tree,
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
_style: &renderer::Style,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_ref::<State>();
|
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let is_mouse_over = cursor.is_over(bounds);
|
|
||||||
|
|
||||||
let style = theme.style(
|
let style =
|
||||||
&self.class,
|
theme.style(&self.class, self.status.unwrap_or(Status::Active));
|
||||||
if state.is_dragging {
|
|
||||||
Status::Dragged
|
|
||||||
} else if is_mouse_over {
|
|
||||||
Status::Hovered
|
|
||||||
} else {
|
|
||||||
Status::Active
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let (handle_width, handle_height, handle_border_radius) =
|
let (handle_width, handle_height, handle_border_radius) =
|
||||||
match style.handle.shape {
|
match style.handle.shape {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
//! Display content on top of other content.
|
//! Display content on top of other content.
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::{Operation, Tree};
|
use crate::core::widget::{Operation, Tree};
|
||||||
use crate::core::{
|
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.
|
/// A container that displays children on top of each other.
|
||||||
|
|
@ -204,7 +204,7 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -214,40 +214,41 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let is_over = cursor.is_over(layout.bounds());
|
let is_over = cursor.is_over(layout.bounds());
|
||||||
|
|
||||||
self.children
|
for ((child, state), layout) in self
|
||||||
|
.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.rev()
|
.rev()
|
||||||
.zip(tree.children.iter_mut().rev())
|
.zip(tree.children.iter_mut().rev())
|
||||||
.zip(layout.children().rev())
|
.zip(layout.children().rev())
|
||||||
.map(|((child, state), layout)| {
|
{
|
||||||
let status = child.as_widget_mut().on_event(
|
child.as_widget_mut().update(
|
||||||
state,
|
state,
|
||||||
event.clone(),
|
event.clone(),
|
||||||
layout,
|
layout,
|
||||||
cursor,
|
cursor,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
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 {
|
if interaction != mouse::Interaction::None {
|
||||||
let interaction = child.as_widget().mouse_interaction(
|
cursor = mouse::Cursor::Unavailable;
|
||||||
state, layout, cursor, viewport, renderer,
|
|
||||||
);
|
|
||||||
|
|
||||||
if interaction != mouse::Interaction::None {
|
|
||||||
cursor = mouse::Cursor::Unavailable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
status
|
if shell.is_event_captured() {
|
||||||
})
|
return;
|
||||||
.find(|&status| status == event::Status::Captured)
|
}
|
||||||
.unwrap_or(event::Status::Ignored)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
|
|
@ -355,7 +354,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -365,7 +364,7 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Link>,
|
shell: &mut Shell<'_, Link>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
match event {
|
match event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
if let Some(position) = cursor.position_in(layout.bounds()) {
|
if let Some(position) = cursor.position_in(layout.bounds()) {
|
||||||
|
|
@ -374,9 +373,16 @@ where
|
||||||
.downcast_mut::<State<Link, Renderer::Paragraph>>();
|
.downcast_mut::<State<Link, Renderer::Paragraph>>();
|
||||||
|
|
||||||
if let Some(span) = state.paragraph.hit_span(position) {
|
if let Some(span) = state.paragraph.hit_span(position) {
|
||||||
state.span_pressed = Some(span);
|
if self
|
||||||
|
.spans
|
||||||
return event::Status::Captured;
|
.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(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::clipboard::{self, Clipboard};
|
use crate::core::clipboard::{self, Clipboard};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::keyboard::key;
|
use crate::core::keyboard::key;
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
|
|
@ -47,7 +46,7 @@ use crate::core::widget::operation;
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{
|
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,
|
Rectangle, Shell, Size, SmolStr, Theme, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -120,6 +119,7 @@ pub struct TextEditor<
|
||||||
&Highlighter::Highlight,
|
&Highlighter::Highlight,
|
||||||
&Theme,
|
&Theme,
|
||||||
) -> highlighter::Format<Renderer::Font>,
|
) -> highlighter::Format<Renderer::Font>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -147,6 +147,7 @@ where
|
||||||
highlighter_format: |_highlight, _theme| {
|
highlighter_format: |_highlight, _theme| {
|
||||||
highlighter::Format::default()
|
highlighter::Format::default()
|
||||||
},
|
},
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -270,6 +271,7 @@ where
|
||||||
on_edit: self.on_edit,
|
on_edit: self.on_edit,
|
||||||
highlighter_settings: settings,
|
highlighter_settings: settings,
|
||||||
highlighter_format: to_format,
|
highlighter_format: to_format,
|
||||||
|
last_status: self.last_status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,7 +598,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut widget::Tree,
|
tree: &mut widget::Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -606,12 +608,16 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let Some(on_edit) = self.on_edit.as_ref() else {
|
let Some(on_edit) = self.on_edit.as_ref() else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = tree.state.downcast_mut::<State<Highlighter>>();
|
let state = tree.state.downcast_mut::<State<Highlighter>>();
|
||||||
|
let is_redraw = matches!(
|
||||||
|
event,
|
||||||
|
Event::Window(window::Event::RedrawRequested(_now)),
|
||||||
|
);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Window(window::Event::Unfocused) => {
|
Event::Window(window::Event::Unfocused) => {
|
||||||
|
|
@ -624,7 +630,7 @@ where
|
||||||
focus.is_window_focused = true;
|
focus.is_window_focused = true;
|
||||||
focus.updated_at = Instant::now();
|
focus.updated_at = Instant::now();
|
||||||
|
|
||||||
shell.request_redraw(window::RedrawRequest::NextFrame);
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||||
|
|
@ -637,168 +643,193 @@ where
|
||||||
- (now - focus.updated_at).as_millis()
|
- (now - focus.updated_at).as_millis()
|
||||||
% Focus::CURSOR_BLINK_INTERVAL_MILLIS;
|
% Focus::CURSOR_BLINK_INTERVAL_MILLIS;
|
||||||
|
|
||||||
shell.request_redraw(window::RedrawRequest::At(
|
shell.request_redraw_at(
|
||||||
now + Duration::from_millis(
|
now + Duration::from_millis(
|
||||||
millis_until_redraw as u64,
|
millis_until_redraw as u64,
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(update) = Update::from_event(
|
if let Some(update) = Update::from_event(
|
||||||
event,
|
event,
|
||||||
state,
|
state,
|
||||||
layout.bounds(),
|
layout.bounds(),
|
||||||
self.padding,
|
self.padding,
|
||||||
cursor,
|
cursor,
|
||||||
self.key_binding.as_deref(),
|
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 {
|
state.focus = Some(Focus::now());
|
||||||
Update::Click(click) => {
|
state.last_click = Some(click);
|
||||||
let action = match click.kind() {
|
state.drag_click = Some(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());
|
shell.publish(on_edit(action));
|
||||||
state.last_click = Some(click);
|
shell.capture_event();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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;
|
if bounds.height >= i32::MAX as f32 {
|
||||||
state.partial_scroll = lines.fract();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
shell.publish(on_edit(Action::Scroll {
|
let lines = lines + state.partial_scroll;
|
||||||
lines: lines as i32,
|
state.partial_scroll = lines.fract();
|
||||||
}));
|
|
||||||
}
|
|
||||||
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 {
|
shell.publish(on_edit(Action::Scroll {
|
||||||
Binding::Unfocus => {
|
lines: lines as i32,
|
||||||
state.focus = None;
|
}));
|
||||||
state.drag_click = None;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
Binding::Copy => {
|
Update::Binding(binding) => {
|
||||||
if let Some(selection) = content.selection() {
|
fn apply_binding<
|
||||||
clipboard.write(
|
H: text::Highlighter,
|
||||||
clipboard::Kind::Standard,
|
R: text::Renderer,
|
||||||
selection,
|
Message,
|
||||||
);
|
>(
|
||||||
|
binding: Binding<Message>,
|
||||||
|
content: &Content<R>,
|
||||||
|
state: &mut State<H>,
|
||||||
|
on_edit: &dyn Fn(Action) -> Message,
|
||||||
|
clipboard: &mut dyn Clipboard,
|
||||||
|
shell: &mut Shell<'_, Message>,
|
||||||
|
) {
|
||||||
|
let mut publish =
|
||||||
|
|action| shell.publish(on_edit(action));
|
||||||
|
|
||||||
|
match binding {
|
||||||
|
Binding::Unfocus => {
|
||||||
|
state.focus = None;
|
||||||
|
state.drag_click = None;
|
||||||
}
|
}
|
||||||
}
|
Binding::Copy => {
|
||||||
Binding::Cut => {
|
if let Some(selection) = content.selection() {
|
||||||
if let Some(selection) = content.selection() {
|
clipboard.write(
|
||||||
clipboard.write(
|
clipboard::Kind::Standard,
|
||||||
clipboard::Kind::Standard,
|
selection,
|
||||||
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));
|
publish(Action::Edit(Edit::Delete));
|
||||||
}
|
}
|
||||||
}
|
Binding::Sequence(sequence) => {
|
||||||
Binding::Paste => {
|
for binding in sequence {
|
||||||
if let Some(contents) =
|
apply_binding(
|
||||||
clipboard.read(clipboard::Kind::Standard)
|
binding, content, state, on_edit,
|
||||||
{
|
clipboard, shell,
|
||||||
publish(Action::Edit(Edit::Paste(Arc::new(
|
);
|
||||||
contents,
|
}
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
}
|
Binding::Custom(message) => {
|
||||||
Binding::Move(motion) => {
|
shell.publish(message);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
apply_binding(
|
apply_binding(
|
||||||
binding,
|
binding,
|
||||||
self.content,
|
self.content,
|
||||||
state,
|
state,
|
||||||
on_edit,
|
on_edit,
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(focus) = &mut state.focus {
|
if let Some(focus) = &mut state.focus {
|
||||||
focus.updated_at = Instant::now();
|
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(
|
fn draw(
|
||||||
|
|
@ -808,7 +839,7 @@ where
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
_defaults: &renderer::Style,
|
_defaults: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
@ -824,20 +855,8 @@ where
|
||||||
|highlight| (self.highlighter_format)(highlight, theme),
|
|highlight| (self.highlighter_format)(highlight, theme),
|
||||||
);
|
);
|
||||||
|
|
||||||
let is_disabled = self.on_edit.is_none();
|
let style = theme
|
||||||
let is_mouse_over = cursor.is_over(bounds);
|
.style(&self.class, self.last_status.unwrap_or(Status::Active));
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
|
|
@ -1036,7 +1055,7 @@ impl<Message> Binding<Message> {
|
||||||
status,
|
status,
|
||||||
} = event;
|
} = event;
|
||||||
|
|
||||||
if status != Status::Focused {
|
if !matches!(status, Status::Focused { .. }) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1176,7 +1195,9 @@ impl<Message> Update<Message> {
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
let status = if state.focus.is_some() {
|
let status = if state.focus.is_some() {
|
||||||
Status::Focused
|
Status::Focused {
|
||||||
|
is_hovered: cursor.is_over(bounds),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Status::Active
|
Status::Active
|
||||||
};
|
};
|
||||||
|
|
@ -1222,7 +1243,10 @@ pub enum Status {
|
||||||
/// The [`TextEditor`] is being hovered.
|
/// The [`TextEditor`] is being hovered.
|
||||||
Hovered,
|
Hovered,
|
||||||
/// The [`TextEditor`] is focused.
|
/// The [`TextEditor`] is focused.
|
||||||
Focused,
|
Focused {
|
||||||
|
/// Whether the [`TextEditor`] is hovered, while focused.
|
||||||
|
is_hovered: bool,
|
||||||
|
},
|
||||||
/// The [`TextEditor`] cannot be interacted with.
|
/// The [`TextEditor`] cannot be interacted with.
|
||||||
Disabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
@ -1297,7 +1321,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
},
|
},
|
||||||
..active
|
..active
|
||||||
},
|
},
|
||||||
Status::Focused => Style {
|
Status::Focused { .. } => Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
color: palette.primary.strong.color,
|
color: palette.primary.strong.color,
|
||||||
..active.border
|
..active.border
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ use editor::Editor;
|
||||||
|
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::clipboard::{self, Clipboard};
|
use crate::core::clipboard::{self, Clipboard};
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::keyboard::key;
|
use crate::core::keyboard::key;
|
||||||
use crate::core::layout;
|
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::widget::tree::{self, Tree};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point,
|
Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels,
|
||||||
Rectangle, Shell, Size, Theme, Vector, Widget,
|
Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::runtime::task::{self, Task};
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::runtime::Action;
|
use crate::runtime::Action;
|
||||||
|
|
@ -120,6 +119,7 @@ pub struct TextInput<
|
||||||
on_submit: Option<Message>,
|
on_submit: Option<Message>,
|
||||||
icon: Option<Icon<Renderer::Font>>,
|
icon: Option<Icon<Renderer::Font>>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default [`Padding`] of a [`TextInput`].
|
/// The default [`Padding`] of a [`TextInput`].
|
||||||
|
|
@ -150,6 +150,7 @@ where
|
||||||
on_submit: None,
|
on_submit: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -400,7 +401,7 @@ where
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
value: Option<&Value>,
|
value: Option<&Value>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
|
|
@ -416,19 +417,8 @@ where
|
||||||
let mut children_layout = layout.children();
|
let mut children_layout = layout.children();
|
||||||
let text_bounds = children_layout.next().unwrap().bounds();
|
let text_bounds = children_layout.next().unwrap().bounds();
|
||||||
|
|
||||||
let is_mouse_over = cursor.is_over(bounds);
|
let style = theme
|
||||||
|
.style(&self.class, self.last_status.unwrap_or(Status::Disabled));
|
||||||
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);
|
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
|
|
@ -637,7 +627,7 @@ where
|
||||||
operation.text_input(state, self.id.as_ref().map(|id| &id.0));
|
operation.text_input(state, self.id.as_ref().map(|id| &id.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -647,7 +637,7 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let update_cache = |state, value| {
|
let update_cache = |state, value| {
|
||||||
replace_paragraph(
|
replace_paragraph(
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -660,22 +650,21 @@ where
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
match event {
|
match &event {
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
let click_position = cursor.position_over(layout.bounds());
|
let click_position = cursor.position_over(layout.bounds());
|
||||||
|
|
||||||
state.is_focused = if click_position.is_some() {
|
state.is_focused = if click_position.is_some() {
|
||||||
state.is_focused.or_else(|| {
|
let now = Instant::now();
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
Some(Focus {
|
Some(Focus {
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
now,
|
now,
|
||||||
is_window_focused: true,
|
is_window_focused: true,
|
||||||
})
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -760,7 +749,11 @@ where
|
||||||
|
|
||||||
state.last_click = Some(click);
|
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))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
|
|
@ -801,11 +794,21 @@ where
|
||||||
)
|
)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
let selection_before = state.cursor.selection(&value);
|
||||||
|
|
||||||
state
|
state
|
||||||
.cursor
|
.cursor
|
||||||
.select_range(state.cursor.start(&value), position);
|
.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 {
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
|
|
@ -815,7 +818,6 @@ where
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
let modifiers = state.keyboard_modifiers;
|
let modifiers = state.keyboard_modifiers;
|
||||||
focus.updated_at = Instant::now();
|
|
||||||
|
|
||||||
match key.as_ref() {
|
match key.as_ref() {
|
||||||
keyboard::Key::Character("c")
|
keyboard::Key::Character("c")
|
||||||
|
|
@ -831,14 +833,15 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
keyboard::Key::Character("x")
|
keyboard::Key::Character("x")
|
||||||
if state.keyboard_modifiers.command()
|
if state.keyboard_modifiers.command()
|
||||||
&& !self.is_secure =>
|
&& !self.is_secure =>
|
||||||
{
|
{
|
||||||
let Some(on_input) = &self.on_input else {
|
let Some(on_input) = &self.on_input else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((start, end)) =
|
if let Some((start, end)) =
|
||||||
|
|
@ -856,17 +859,18 @@ where
|
||||||
|
|
||||||
let message = (on_input)(editor.contents());
|
let message = (on_input)(editor.contents());
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
shell.capture_event();
|
||||||
|
|
||||||
|
focus.updated_at = Instant::now();
|
||||||
update_cache(state, &self.value);
|
update_cache(state, &self.value);
|
||||||
|
return;
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
keyboard::Key::Character("v")
|
keyboard::Key::Character("v")
|
||||||
if state.keyboard_modifiers.command()
|
if state.keyboard_modifiers.command()
|
||||||
&& !state.keyboard_modifiers.alt() =>
|
&& !state.keyboard_modifiers.alt() =>
|
||||||
{
|
{
|
||||||
let Some(on_input) = &self.on_input else {
|
let Some(on_input) = &self.on_input else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = match state.is_pasting.take() {
|
let content = match state.is_pasting.take() {
|
||||||
|
|
@ -885,7 +889,6 @@ where
|
||||||
|
|
||||||
let mut editor =
|
let mut editor =
|
||||||
Editor::new(&mut self.value, &mut state.cursor);
|
Editor::new(&mut self.value, &mut state.cursor);
|
||||||
|
|
||||||
editor.paste(content.clone());
|
editor.paste(content.clone());
|
||||||
|
|
||||||
let message = if let Some(paste) = &self.on_paste {
|
let message = if let Some(paste) = &self.on_paste {
|
||||||
|
|
@ -894,26 +897,35 @@ where
|
||||||
(on_input)(editor.contents())
|
(on_input)(editor.contents())
|
||||||
};
|
};
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
shell.capture_event();
|
||||||
|
|
||||||
state.is_pasting = Some(content);
|
state.is_pasting = Some(content);
|
||||||
|
focus.updated_at = Instant::now();
|
||||||
update_cache(state, &self.value);
|
update_cache(state, &self.value);
|
||||||
|
return;
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
keyboard::Key::Character("a")
|
keyboard::Key::Character("a")
|
||||||
if state.keyboard_modifiers.command() =>
|
if state.keyboard_modifiers.command() =>
|
||||||
{
|
{
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
state.cursor.select_all(&self.value);
|
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 {
|
if let Some(text) = text {
|
||||||
let Some(on_input) = &self.on_input else {
|
let Some(on_input) = &self.on_input else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
|
|
@ -928,12 +940,11 @@ where
|
||||||
|
|
||||||
let message = (on_input)(editor.contents());
|
let message = (on_input)(editor.contents());
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
shell.capture_event();
|
||||||
|
|
||||||
focus.updated_at = Instant::now();
|
focus.updated_at = Instant::now();
|
||||||
|
|
||||||
update_cache(state, &self.value);
|
update_cache(state, &self.value);
|
||||||
|
return;
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -941,11 +952,12 @@ where
|
||||||
keyboard::Key::Named(key::Named::Enter) => {
|
keyboard::Key::Named(key::Named::Enter) => {
|
||||||
if let Some(on_submit) = self.on_submit.clone() {
|
if let Some(on_submit) = self.on_submit.clone() {
|
||||||
shell.publish(on_submit);
|
shell.publish(on_submit);
|
||||||
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyboard::Key::Named(key::Named::Backspace) => {
|
keyboard::Key::Named(key::Named::Backspace) => {
|
||||||
let Some(on_input) = &self.on_input else {
|
let Some(on_input) = &self.on_input else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if modifiers.jump()
|
if modifiers.jump()
|
||||||
|
|
@ -968,12 +980,14 @@ where
|
||||||
|
|
||||||
let message = (on_input)(editor.contents());
|
let message = (on_input)(editor.contents());
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
shell.capture_event();
|
||||||
|
|
||||||
|
focus.updated_at = Instant::now();
|
||||||
update_cache(state, &self.value);
|
update_cache(state, &self.value);
|
||||||
}
|
}
|
||||||
keyboard::Key::Named(key::Named::Delete) => {
|
keyboard::Key::Named(key::Named::Delete) => {
|
||||||
let Some(on_input) = &self.on_input else {
|
let Some(on_input) = &self.on_input else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if modifiers.jump()
|
if modifiers.jump()
|
||||||
|
|
@ -999,10 +1013,14 @@ where
|
||||||
|
|
||||||
let message = (on_input)(editor.contents());
|
let message = (on_input)(editor.contents());
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
shell.capture_event();
|
||||||
|
|
||||||
|
focus.updated_at = Instant::now();
|
||||||
update_cache(state, &self.value);
|
update_cache(state, &self.value);
|
||||||
}
|
}
|
||||||
keyboard::Key::Named(key::Named::Home) => {
|
keyboard::Key::Named(key::Named::Home) => {
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
state.cursor.select_range(
|
state.cursor.select_range(
|
||||||
state.cursor.start(&self.value),
|
state.cursor.start(&self.value),
|
||||||
|
|
@ -1011,8 +1029,18 @@ where
|
||||||
} else {
|
} else {
|
||||||
state.cursor.move_to(0);
|
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) => {
|
keyboard::Key::Named(key::Named::End) => {
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
state.cursor.select_range(
|
state.cursor.select_range(
|
||||||
state.cursor.start(&self.value),
|
state.cursor.start(&self.value),
|
||||||
|
|
@ -1021,10 +1049,20 @@ where
|
||||||
} else {
|
} else {
|
||||||
state.cursor.move_to(self.value.len());
|
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)
|
keyboard::Key::Named(key::Named::ArrowLeft)
|
||||||
if modifiers.macos_command() =>
|
if modifiers.macos_command() =>
|
||||||
{
|
{
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
state.cursor.select_range(
|
state.cursor.select_range(
|
||||||
state.cursor.start(&self.value),
|
state.cursor.start(&self.value),
|
||||||
|
|
@ -1033,10 +1071,20 @@ where
|
||||||
} else {
|
} else {
|
||||||
state.cursor.move_to(0);
|
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)
|
keyboard::Key::Named(key::Named::ArrowRight)
|
||||||
if modifiers.macos_command() =>
|
if modifiers.macos_command() =>
|
||||||
{
|
{
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
state.cursor.select_range(
|
state.cursor.select_range(
|
||||||
state.cursor.start(&self.value),
|
state.cursor.start(&self.value),
|
||||||
|
|
@ -1045,8 +1093,18 @@ where
|
||||||
} else {
|
} else {
|
||||||
state.cursor.move_to(self.value.len());
|
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) => {
|
keyboard::Key::Named(key::Named::ArrowLeft) => {
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
if modifiers.jump() && !self.is_secure {
|
if modifiers.jump() && !self.is_secure {
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
state
|
state
|
||||||
|
|
@ -1062,8 +1120,18 @@ where
|
||||||
} else {
|
} else {
|
||||||
state.cursor.move_left(&self.value);
|
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) => {
|
keyboard::Key::Named(key::Named::ArrowRight) => {
|
||||||
|
let cursor_before = state.cursor;
|
||||||
|
|
||||||
if modifiers.jump() && !self.is_secure {
|
if modifiers.jump() && !self.is_secure {
|
||||||
if modifiers.shift() {
|
if modifiers.shift() {
|
||||||
state
|
state
|
||||||
|
|
@ -1079,6 +1147,14 @@ where
|
||||||
} else {
|
} else {
|
||||||
state.cursor.move_right(&self.value);
|
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) => {
|
keyboard::Key::Named(key::Named::Escape) => {
|
||||||
state.is_focused = None;
|
state.is_focused = None;
|
||||||
|
|
@ -1087,39 +1163,22 @@ where
|
||||||
|
|
||||||
state.keyboard_modifiers =
|
state.keyboard_modifiers =
|
||||||
keyboard::Modifiers::default();
|
keyboard::Modifiers::default();
|
||||||
}
|
|
||||||
keyboard::Key::Named(
|
shell.capture_event();
|
||||||
key::Named::Tab
|
|
||||||
| key::Named::ArrowUp
|
|
||||||
| key::Named::ArrowDown,
|
|
||||||
) => {
|
|
||||||
return event::Status::Ignored;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
|
Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
if state.is_focused.is_some() {
|
if state.is_focused.is_some() {
|
||||||
match key.as_ref() {
|
if let keyboard::Key::Character("v") = key.as_ref() {
|
||||||
keyboard::Key::Character("v") => {
|
state.is_pasting = None;
|
||||||
state.is_pasting = None;
|
|
||||||
}
|
|
||||||
keyboard::Key::Named(
|
|
||||||
key::Named::Tab
|
|
||||||
| key::Named::ArrowUp
|
|
||||||
| key::Named::ArrowDown,
|
|
||||||
) => {
|
|
||||||
return event::Status::Ignored;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
|
|
@ -1127,7 +1186,7 @@ where
|
||||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
state.keyboard_modifiers = modifiers;
|
state.keyboard_modifiers = *modifiers;
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::Unfocused) => {
|
Event::Window(window::Event::Unfocused) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
|
@ -1143,32 +1202,59 @@ where
|
||||||
focus.is_window_focused = true;
|
focus.is_window_focused = true;
|
||||||
focus.updated_at = Instant::now();
|
focus.updated_at = Instant::now();
|
||||||
|
|
||||||
shell.request_redraw(window::RedrawRequest::NextFrame);
|
shell.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
if focus.is_window_focused {
|
if focus.is_window_focused
|
||||||
focus.now = now;
|
&& matches!(
|
||||||
|
state.cursor.state(&self.value),
|
||||||
|
cursor::State::Index(_)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
focus.now = *now;
|
||||||
|
|
||||||
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
||||||
- (now - focus.updated_at).as_millis()
|
- (*now - focus.updated_at).as_millis()
|
||||||
% CURSOR_BLINK_INTERVAL_MILLIS;
|
% CURSOR_BLINK_INTERVAL_MILLIS;
|
||||||
|
|
||||||
shell.request_redraw(window::RedrawRequest::At(
|
shell.request_redraw_at(
|
||||||
now + Duration::from_millis(
|
*now + Duration::from_millis(
|
||||||
millis_until_redraw as u64,
|
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(
|
fn draw(
|
||||||
|
|
@ -1535,7 +1621,10 @@ pub enum Status {
|
||||||
/// The [`TextInput`] is being hovered.
|
/// The [`TextInput`] is being hovered.
|
||||||
Hovered,
|
Hovered,
|
||||||
/// The [`TextInput`] is focused.
|
/// The [`TextInput`] is focused.
|
||||||
Focused,
|
Focused {
|
||||||
|
/// Whether the [`TextInput`] is hovered, while focused.
|
||||||
|
is_hovered: bool,
|
||||||
|
},
|
||||||
/// The [`TextInput`] cannot be interacted with.
|
/// The [`TextInput`] cannot be interacted with.
|
||||||
Disabled,
|
Disabled,
|
||||||
}
|
}
|
||||||
|
|
@ -1612,7 +1701,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
},
|
},
|
||||||
..active
|
..active
|
||||||
},
|
},
|
||||||
Status::Focused => Style {
|
Status::Focused { .. } => Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
color: palette.primary.strong.color,
|
color: palette.primary.strong.color,
|
||||||
..active.border
|
..active.border
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
use crate::text_input::Value;
|
use crate::text_input::Value;
|
||||||
|
|
||||||
/// The cursor of a text input.
|
/// The cursor of a text input.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Cursor {
|
pub struct Cursor {
|
||||||
state: State,
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The state of a [`Cursor`].
|
/// The state of a [`Cursor`].
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
/// Cursor without a selection
|
/// Cursor without a selection
|
||||||
Index(usize),
|
Index(usize),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::container;
|
use crate::container;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -7,8 +6,8 @@ use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::Operation;
|
use crate::core::widget::Operation;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
|
Background, Clipboard, Color, Element, Event, Layout, Length, Point,
|
||||||
Shell, Size, Vector, Widget,
|
Rectangle, Shell, Size, Vector, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
@ -111,7 +110,7 @@ where
|
||||||
.operate(tree, layout, renderer, operation);
|
.operate(tree, layout, renderer, operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -121,10 +120,10 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
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,
|
tree, event, layout, cursor, renderer, clipboard, shell, viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -219,7 +218,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -227,9 +226,9 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) {
|
||||||
self.content
|
self.content
|
||||||
.on_event(event, layout, cursor, renderer, clipboard, shell)
|
.update(event, layout, cursor, renderer, clipboard, shell);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::event;
|
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
|
|
@ -39,6 +38,7 @@ use crate::core::text;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Border, Clipboard, Color, Element, Event, Layout, Length, Pixels,
|
Border, Clipboard, Color, Element, Event, Layout, Length, Pixels,
|
||||||
Rectangle, Shell, Size, Theme, Widget,
|
Rectangle, Shell, Size, Theme, Widget,
|
||||||
|
|
@ -99,6 +99,7 @@ pub struct Toggler<
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
font: Option<Renderer::Font>,
|
font: Option<Renderer::Font>,
|
||||||
class: Theme::Class<'a>,
|
class: Theme::Class<'a>,
|
||||||
|
last_status: Option<Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer>
|
impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -132,6 +133,7 @@ where
|
||||||
spacing: Self::DEFAULT_SIZE / 2.0,
|
spacing: Self::DEFAULT_SIZE / 2.0,
|
||||||
font: None,
|
font: None,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
|
last_status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,7 +306,7 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state: &mut Tree,
|
_state: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -314,9 +316,9 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let Some(on_toggle) = &self.on_toggle else {
|
let Some(on_toggle) = &self.on_toggle else {
|
||||||
return event::Status::Ignored;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -326,13 +328,31 @@ where
|
||||||
|
|
||||||
if mouse_over {
|
if mouse_over {
|
||||||
shell.publish(on_toggle(!self.is_toggled));
|
shell.publish(on_toggle(!self.is_toggled));
|
||||||
|
shell.capture_event();
|
||||||
event::Status::Captured
|
|
||||||
} else {
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => 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,
|
theme: &Theme,
|
||||||
style: &renderer::Style,
|
style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
/// Makes sure that the border radius of the toggler looks good at every size.
|
/// 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 bounds = toggler_layout.bounds();
|
||||||
let is_mouse_over = cursor.is_over(layout.bounds());
|
let style = theme
|
||||||
|
.style(&self.class, self.last_status.unwrap_or(Status::Disabled));
|
||||||
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 border_radius = bounds.height / BORDER_RADIUS_RATIO;
|
let border_radius = bounds.height / BORDER_RADIUS_RATIO;
|
||||||
let space = SPACE_RATIO * bounds.height;
|
let space = SPACE_RATIO * bounds.height;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
use crate::container;
|
use crate::container;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
|
|
@ -30,8 +29,8 @@ use crate::core::renderer;
|
||||||
use crate::core::text;
|
use crate::core::text;
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
|
Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle,
|
||||||
Vector,
|
Shell, Size, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An element to display a widget over another.
|
/// An element to display a widget over another.
|
||||||
|
|
@ -190,7 +189,7 @@ where
|
||||||
.layout(&mut tree.children[0], renderer, limits)
|
.layout(&mut tree.children[0], renderer, limits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut widget::Tree,
|
tree: &mut widget::Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -200,7 +199,7 @@ where
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
let was_idle = *state == State::Idle;
|
let was_idle = *state == State::Idle;
|
||||||
|
|
@ -216,7 +215,7 @@ where
|
||||||
shell.invalidate_layout();
|
shell.invalidate_layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.content.as_widget_mut().on_event(
|
self.content.as_widget_mut().update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
event,
|
event,
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -225,7 +224,7 @@ where
|
||||||
clipboard,
|
clipboard,
|
||||||
shell,
|
shell,
|
||||||
viewport,
|
viewport,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ pub use crate::slider::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::core::border::Border;
|
use crate::core::border::Border;
|
||||||
use crate::core::event::{self, Event};
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::keyboard::key::{self, Key};
|
use crate::core::keyboard::key::{self, Key};
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
|
|
@ -44,8 +43,8 @@ use crate::core::renderer;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, Size,
|
self, Clipboard, Element, Event, Length, Pixels, Point, Rectangle, Shell,
|
||||||
Widget,
|
Size, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An vertical bar and a handle that selects a single value from a range of
|
/// 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)
|
layout::atomic(limits, self.width, self.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree: &mut Tree,
|
tree: &mut Tree,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -254,7 +253,7 @@ where
|
||||||
_clipboard: &mut dyn Clipboard,
|
_clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) -> event::Status {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
let is_dragging = state.is_dragging;
|
let is_dragging = state.is_dragging;
|
||||||
let current_value = self.value;
|
let current_value = self.value;
|
||||||
|
|
@ -350,7 +349,7 @@ where
|
||||||
state.is_dragging = true;
|
state.is_dragging = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
|
|
@ -362,7 +361,7 @@ where
|
||||||
}
|
}
|
||||||
state.is_dragging = false;
|
state.is_dragging = false;
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||||
|
|
@ -370,7 +369,7 @@ where
|
||||||
if is_dragging {
|
if is_dragging {
|
||||||
let _ = cursor.position().and_then(locate).map(change);
|
let _ = cursor.position().and_then(locate).map(change);
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
Event::Mouse(mouse::Event::WheelScrolled { delta })
|
||||||
|
|
@ -388,7 +387,7 @@ where
|
||||||
let _ = increment(current_value).map(change);
|
let _ = increment(current_value).map(change);
|
||||||
}
|
}
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
|
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
|
||||||
|
|
@ -403,7 +402,7 @@ where
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
return event::Status::Captured;
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||||
|
|
@ -411,8 +410,6 @@ where
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
event::Status::Ignored
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ x11 = ["winit/x11"]
|
||||||
wayland = ["winit/wayland"]
|
wayland = ["winit/wayland"]
|
||||||
wayland-dlopen = ["winit/wayland-dlopen"]
|
wayland-dlopen = ["winit/wayland-dlopen"]
|
||||||
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
|
||||||
multi-window = ["iced_runtime/multi-window"]
|
unconditional-rendering = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_futures.workspace = true
|
iced_futures.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -758,14 +758,25 @@ async fn run_instance<P, C>(
|
||||||
}
|
}
|
||||||
Event::EventLoopAwakened(event) => {
|
Event::EventLoopAwakened(event) => {
|
||||||
match event {
|
match event {
|
||||||
event::Event::NewEvents(
|
event::Event::NewEvents(event::StartCause::Init) => {
|
||||||
event::StartCause::Init
|
|
||||||
| event::StartCause::ResumeTimeReached { .. },
|
|
||||||
) => {
|
|
||||||
for (_id, window) in window_manager.iter_mut() {
|
for (_id, window) in window_manager.iter_mut() {
|
||||||
window.raw.request_redraw();
|
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::Event::PlatformSpecific(
|
||||||
event::PlatformSpecific::MacOS(
|
event::PlatformSpecific::MacOS(
|
||||||
event::MacOS::ReceivedUrl(url),
|
event::MacOS::ReceivedUrl(url),
|
||||||
|
|
@ -807,11 +818,39 @@ async fn run_instance<P, C>(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Avoid redrawing all the time by forcing widgets to
|
let physical_size = window.state.physical_size();
|
||||||
// request redraws on state changes
|
|
||||||
//
|
if physical_size.width == 0 || physical_size.height == 0
|
||||||
// Then, we can use the `interface_state` here to decide if a redraw
|
{
|
||||||
// is needed right away, or simply wait until a specific time.
|
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(
|
let redraw_event = core::Event::Window(
|
||||||
window::Event::RedrawRequested(Instant::now()),
|
window::Event::RedrawRequested(Instant::now()),
|
||||||
);
|
);
|
||||||
|
|
@ -857,81 +896,18 @@ async fn run_instance<P, C>(
|
||||||
status: core::event::Status::Ignored,
|
status: core::event::Status::Ignored,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = control_sender.start_send(Control::ChangeFlow(
|
if let user_interface::State::Updated {
|
||||||
match ui_state {
|
redraw_request: Some(redraw_request),
|
||||||
user_interface::State::Updated {
|
} = ui_state
|
||||||
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
|
|
||||||
{
|
{
|
||||||
continue;
|
match redraw_request {
|
||||||
}
|
window::RedrawRequest::NextFrame => {
|
||||||
|
window.raw.request_redraw();
|
||||||
if window.viewport_version
|
}
|
||||||
!= window.state.viewport_version()
|
window::RedrawRequest::At(at) => {
|
||||||
{
|
window.redraw_at = Some(at);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compositor.configure_surface(
|
|
||||||
&mut window.surface,
|
|
||||||
physical_size.width,
|
|
||||||
physical_size.height,
|
|
||||||
);
|
|
||||||
|
|
||||||
window.viewport_version =
|
|
||||||
window.state.viewport_version();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.render_started();
|
debug.render_started();
|
||||||
|
|
@ -993,6 +969,13 @@ async fn run_instance<P, C>(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
window_event,
|
||||||
|
winit::event::WindowEvent::Resized(_)
|
||||||
|
) {
|
||||||
|
window.raw.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
window_event,
|
window_event,
|
||||||
winit::event::WindowEvent::CloseRequested
|
winit::event::WindowEvent::CloseRequested
|
||||||
|
|
@ -1031,7 +1014,10 @@ async fn run_instance<P, C>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event::Event::AboutToWait => {
|
event::Event::AboutToWait => {
|
||||||
if events.is_empty() && messages.is_empty() {
|
if events.is_empty()
|
||||||
|
&& messages.is_empty()
|
||||||
|
&& window_manager.is_idle()
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1065,13 +1051,27 @@ async fn run_instance<P, C>(
|
||||||
&mut messages,
|
&mut messages,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "unconditional-rendering")]
|
||||||
window.raw.request_redraw();
|
window.raw.request_redraw();
|
||||||
|
|
||||||
if !uis_stale {
|
match ui_state {
|
||||||
uis_stale = matches!(
|
#[cfg(not(
|
||||||
ui_state,
|
feature = "unconditional-rendering"
|
||||||
user_interface::State::Outdated
|
))]
|
||||||
);
|
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
|
for (event, status) in window_events
|
||||||
|
|
@ -1139,6 +1139,17 @@ async fn run_instance<P, C>(
|
||||||
actions = 0;
|
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::mouse;
|
||||||
|
use crate::core::time::Instant;
|
||||||
use crate::core::window::Id;
|
use crate::core::window::Id;
|
||||||
use crate::core::{Point, Size};
|
use crate::core::{Point, Size};
|
||||||
use crate::graphics::Compositor;
|
use crate::graphics::Compositor;
|
||||||
|
|
@ -62,6 +63,7 @@ where
|
||||||
surface,
|
surface,
|
||||||
renderer,
|
renderer,
|
||||||
mouse_interaction: mouse::Interaction::None,
|
mouse_interaction: mouse::Interaction::None,
|
||||||
|
redraw_at: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -74,6 +76,19 @@ where
|
||||||
self.entries.is_empty()
|
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>> {
|
pub fn first(&self) -> Option<&Window<P, C>> {
|
||||||
self.entries.first_key_value().map(|(_id, window)| window)
|
self.entries.first_key_value().map(|(_id, window)| window)
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +153,7 @@ where
|
||||||
pub mouse_interaction: mouse::Interaction,
|
pub mouse_interaction: mouse::Interaction,
|
||||||
pub surface: C::Surface,
|
pub surface: C::Surface,
|
||||||
pub renderer: P::Renderer,
|
pub renderer: P::Renderer,
|
||||||
|
pub redraw_at: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, C> Window<P, C>
|
impl<P, C> Window<P, C>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue