1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! Event-dispatch logic for [`super::Button`], split out of `button.rs`
//! so the parent file stays under the project's 800-line cap.
//!
//! Pulled in via `#[path]` as a *child* module of `button` so this
//! file has direct access to `Button`'s private fields and helper
//! methods — the trait impl in `button.rs` keeps only the
//! `fn on_event` shell delegating to [`Button::handle_event`].
use crate::event::{Event, EventResult, MouseButton};
use crate::widget::Widget;
use super::Button;
impl Button {
/// Process one event. Called from `<Button as Widget>::on_event`.
pub(super) fn handle_event(&mut self, event: &Event) -> EventResult {
if !self.is_enabled() {
// Clear any lingering hover / pressed state so the button
// looks idle the instant it's disabled mid-interaction.
self.hovered = false;
self.pressed = false;
return EventResult::Ignored;
}
match event {
Event::MouseMove { pos } => {
let was_hovered = self.hovered;
let was_pressed = self.pressed;
self.hovered = self.hit_test(*pos);
if !self.hovered {
self.pressed = false;
}
if was_hovered != self.hovered || was_pressed != self.pressed {
crate::animation::request_draw();
return EventResult::Consumed;
}
EventResult::Ignored
}
Event::MouseDown {
button: MouseButton::Left,
..
} => {
if !self.pressed {
crate::animation::request_draw();
}
self.pressed = true;
// Note: we deliberately do NOT set `hovered` here. Touch has
// no hover phase, and nothing clears `hovered` on a
// touchscreen (no MouseMove/MouseLeave after a tap), so
// setting it would leave the button stuck in its hovered
// style. The press visual is covered by `pressed`, and the
// click test in MouseUp checks `hit_test(*pos)` directly so
// hover-less taps still fire.
EventResult::Consumed
}
Event::MouseUp {
pos,
button: MouseButton::Left,
..
} => {
let was_pressed = self.pressed;
self.pressed = false;
if was_pressed {
crate::animation::request_draw();
}
// Fire when the release lands within the button. Checking
// the release position (rather than only the cached hover
// flag) makes taps work on touch, where no MouseMove ever
// sets `hovered`, while still cancelling a press that drags
// off the button before release.
if was_pressed && (self.hovered || self.hit_test(*pos)) {
self.fire_click();
// Clear the focus ring after a mouse click — the ring is a
// keyboard-navigation aid and should not persist after a
// pointer interaction.
self.focused = false;
// Click handler almost always mutates app state that
// affects the next paint; request one so the handler's
// side-effects are visible.
crate::animation::request_draw();
}
EventResult::Consumed
}
Event::KeyDown { key, .. } => {
use crate::event::Key;
match key {
Key::Enter | Key::Char(' ') => {
self.fire_click();
crate::animation::request_draw();
EventResult::Consumed
}
_ => EventResult::Ignored,
}
}
Event::FocusGained => {
let was = self.focused;
self.focused = true;
if !was {
crate::animation::request_draw();
EventResult::Consumed
} else {
EventResult::Ignored
}
}
Event::FocusLost => {
let was_focused = self.focused;
let was_pressed = self.pressed;
self.focused = false;
self.pressed = false;
if was_focused || was_pressed {
crate::animation::request_draw();
EventResult::Consumed
} else {
EventResult::Ignored
}
}
_ => EventResult::Ignored,
}
}
}