use crate::clock::EventInstant;
use crate::reactive::focus::{focus_within, request_focus};
use super::*;
pub(super) struct HitContext {
pub bounds: Rect,
pub corners: crate::widgets::Corners,
pub transform: Transform,
pub pivot: Pivot,
}
impl HitContext {
#[inline]
pub(super) fn contains(&self, at: Option<Point>) -> bool {
self.bounds.contains_shape_at(at, self.corners)
}
pub(super) fn local(&self, at: Point) -> Option<Point> {
Some(
untransform_point(&self.transform, self.pivot, self.bounds, at)?
.offset(-self.bounds.x, -self.bounds.y),
)
}
pub(super) fn rebase(&self, at: Point) -> Point {
at.offset(-self.bounds.x, -self.bounds.y)
}
}
pub(super) fn untransform_point(
transform: &Transform,
origin: Pivot,
bounds: Rect,
at: Point,
) -> Option<Point> {
if transform.is_identity() {
return Some(at);
}
let (origin_x, origin_y) = origin.resolve(bounds);
let (ux, uy) = transform
.center_at(origin_x, origin_y)
.inverse()?
.transform_point(at.x, at.y);
Some(Point::new(ux, uy))
}
impl Container {
pub(super) fn request_state_change_repaint(&self, id: WidgetId) {
if self.has_animated_state_properties() {
request_job(id, JobRequest::Animation(RequiredJob::Paint));
}
}
pub(super) fn pointer_left(&mut self, tree: &mut Tree, id: WidgetId) {
let now = tree.event_instant();
let Some(ref mut ix) = self.interaction else {
return;
};
let was_hovered = ix.is_hovered();
let was_pressed = ix.is_pressed();
if was_hovered {
ix.set_flag(InteractionFlags::HOVERED, false);
if let Some(ref callback) = ix.on_hover {
tree.hover_changed(id, false, callback);
}
}
ix.set_flag(InteractionFlags::PRESSED, false);
if ix.ripple.is_active()
&& let Some(config) = ix.ripple_config()
{
ix.ripple.cancel(&config, now.as_frame_start());
request_job(id, JobRequest::Animation(RequiredJob::Paint));
}
if (was_hovered && ix.declares(|w| matches!(w, StateWhen::Hovered)))
|| (was_pressed && ix.declares(|w| matches!(w, StateWhen::Pressed)))
{
self.request_state_change_repaint(id);
}
}
pub(super) fn switched_off(&mut self, tree: &mut Tree, id: WidgetId) {
let children = self.children_source.get();
let owed = tree.event_targets(
id,
children,
&Event::MouseLeave,
None,
self.children_sorted_along,
);
for (index, _) in owed {
let child_id = children[index];
if !is_detached(child_id) {
tree.with_widget_mut(child_id, |child, child_id, tree| {
child.event(tree, child_id, &Event::MouseLeave)
});
}
}
self.pointer_left(tree, id);
}
pub(super) fn claim_the_cursor(&self, tree: &mut Tree, hit: &HitContext, event: &Event) {
if let Some(ix) = self.interaction.as_deref()
&& ix.cursor.is_set()
&& self.takes_input.get_or_untracked(true)
&& hit.contains(event.coords())
{
tree.point_shows_cursor(ix.cursor);
}
}
pub(super) fn is_hit(&self, id: WidgetId, hit: &HitContext, event: &Event) -> bool {
hit.contains(event.coords())
&& (self.is_control()
|| self
.interaction
.as_deref()
.is_some_and(|ix| ix.cursor.is_set())
|| self.draws_its_own_surface(id))
}
pub(super) fn track_pointer(
&mut self,
tree: &mut Tree,
id: WidgetId,
hit: &HitContext,
event: &Event,
now: EventInstant,
) {
let has_animated = self.has_animated_state_properties();
let ripple_config = self.interaction.as_ref().and_then(|ix| ix.ripple_config());
let Some(ref mut ix) = self.interaction else {
return;
};
let request_repaint = |id: WidgetId| {
if has_animated {
request_job(id, JobRequest::Animation(RequiredJob::Paint));
}
};
match event {
Event::MouseEnter { at: None } if ix.is_hovered() => {
ix.set_flag(InteractionFlags::HOVERED, false);
if ix.declares(|w| matches!(w, StateWhen::Hovered)) {
request_repaint(id);
}
if let Some(ref callback) = ix.on_hover {
tree.hover_changed(id, false, callback);
}
}
Event::MouseEnter { at } if hit.contains(*at) && !ix.is_hovered() => {
ix.set_flag(InteractionFlags::HOVERED, true);
if ix.declares(|w| matches!(w, StateWhen::Hovered)) {
request_repaint(id);
}
if let Some(ref callback) = ix.on_hover {
tree.hover_changed(id, true, callback);
}
}
Event::MouseMove { at, .. } => {
let inside = hit.contains(*at);
if let Some(ref callback) = ix.on_pointer_move
&& (inside || ix.is_pressed())
&& let Some(at) = at
{
let local = hit.rebase(*at);
callback(local.x, local.y);
}
let was_hovered = ix.is_hovered();
ix.set_flag(InteractionFlags::HOVERED, inside);
if was_hovered
&& !ix.is_hovered()
&& ix.ripple.is_active()
&& let Some(ref config) = ripple_config
{
ix.ripple.cancel(config, now.as_frame_start());
request_job(id, JobRequest::Animation(RequiredJob::Paint));
}
if was_hovered != ix.is_hovered() {
if ix.declares(|w| matches!(w, StateWhen::Hovered)) {
request_repaint(id);
}
if let Some(ref callback) = ix.on_hover {
tree.hover_changed(id, ix.is_hovered(), callback);
}
}
}
_ => {}
}
}
pub(super) fn handle_own_event(
&mut self,
tree: &mut Tree,
id: WidgetId,
hit: &HitContext,
event: &Event,
local: &Event,
now: EventInstant,
) -> EventResponse {
let pressed_inside =
matches!(local, Event::MouseDown { .. }) && hit.contains(local.coords());
if pressed_inside && self.is_focusable() && !tree.focus_claimed_the_press() {
request_focus(tree, id);
tree.keep_the_focus_this_press_landed_on();
}
match local {
Event::MouseEnter { .. } | Event::MouseMove { .. } => {}
Event::MouseDown { button, .. } if *button != MouseButton::Left => {
if pressed_inside && let Some(ref ix) = self.interaction {
let callback = match button {
MouseButton::Right => ix.on_right_click.as_ref(),
MouseButton::Middle => ix.on_middle_click.as_ref(),
MouseButton::Left => None,
};
if let Some(callback) = callback {
callback();
return EventResponse::Handled;
}
}
}
Event::MouseDown { at, button, .. } => {
if pressed_inside
&& let Some(at) = at
&& *button == MouseButton::Left
&& let Some(ref mut ix) = self.interaction
{
let was_pressed = ix.is_pressed();
ix.set_flag(InteractionFlags::PRESSED, true);
let has_ripple = ix
.states
.iter()
.any(|(w, s)| matches!(w, StateWhen::Pressed) && s.ripple.is_some());
if has_ripple {
let on_screen = event.coords().unwrap_or(*at);
if let Some(local) = hit.local(on_screen) {
ix.ripple.start(local.x, local.y, now.as_frame_start());
}
request_job(id, JobRequest::Animation(RequiredJob::Paint));
}
if !was_pressed && ix.declares(|w| matches!(w, StateWhen::Pressed)) {
self.request_state_change_repaint(id);
}
if let Some(ref ix) = self.interaction
&& let Some(ref callback) = ix.on_mouse_down
{
let local = hit.rebase(*at);
callback(local.x, local.y);
return EventResponse::Handled;
}
if let Some(ref ix) = self.interaction
&& (ix.on_click.is_some() || ix.on_mouse_up.is_some())
{
return EventResponse::Handled;
}
}
}
Event::MouseUp { at, button } => {
if let Some(ref mut ix) = self.interaction
&& ix.is_pressed()
&& *button == MouseButton::Left
{
let was_pressed = ix.is_pressed();
ix.set_flag(InteractionFlags::PRESSED, false);
if ix.ripple.is_active()
&& let Some(config) = ix.ripple_config()
{
if hit.contains(*at) {
ix.ripple.release(&config, now.as_frame_start());
} else {
ix.ripple.cancel(&config, now.as_frame_start());
}
request_job(id, JobRequest::Animation(RequiredJob::Paint));
}
if was_pressed && ix.declares(|w| matches!(w, StateWhen::Pressed)) {
self.request_state_change_repaint(id);
}
let mut handled = false;
if let Some(at) = at
&& let Some(ref ix) = self.interaction
&& let Some(ref callback) = ix.on_mouse_up
{
let local = hit.rebase(*at);
callback(local.x, local.y);
handled = true;
}
if let Some(ref ix) = self.interaction
&& hit.contains(*at)
&& let Some(ref callback) = ix.on_click
{
callback();
return EventResponse::Handled;
}
if handled {
return EventResponse::Handled;
}
}
}
Event::MouseLeave => self.pointer_left(tree, id),
Event::Scroll {
at,
delta_x,
delta_y,
source,
} => {
if hit.contains(*at) {
if self.scroll_axis != ScrollAxis::None
&& self.apply_scroll(*delta_x, *delta_y, *source, now)
{
request_job(id, JobRequest::Paint);
return EventResponse::Handled;
}
if let Some(ref ix) = self.interaction
&& let Some(ref callback) = ix.on_scroll
{
callback(*delta_x, *delta_y, *source);
return EventResponse::Handled;
}
}
}
Event::KeyDown {
key,
modifiers,
repeat,
} => {
if let Some(ref ix) = self.interaction
&& let Some(ref callback) = ix.on_key_down
&& !tree.heard_this_key(id)
&& callback(*key, *modifiers, *repeat) == EventResponse::Handled
{
return EventResponse::Handled;
}
}
Event::KeyUp { key, modifiers } => {
if let Some(ref ix) = self.interaction
&& let Some(ref callback) = ix.on_key_up
&& !tree.heard_this_key(id)
&& callback(*key, *modifiers) == EventResponse::Handled
{
return EventResponse::Handled;
}
}
Event::ScrollEnd { at } => {
if self.scroll_axis != ScrollAxis::None && hit.contains(*at) {
return self.hand_off_to_momentum(id, now);
}
}
Event::FocusIn | Event::FocusOut | Event::Pasted(_) => {}
}
if pressed_inside && self.holds_the_focus_it_draws(id) {
tree.keep_the_focus_this_press_landed_on();
}
EventResponse::Ignored
}
fn holds_the_focus_it_draws(&self, id: WidgetId) -> bool {
self.interaction
.as_ref()
.is_some_and(|ix| ix.declares(|w| matches!(w, StateWhen::Focused)))
&& focus_within(id)
}
}