refactor events; watch prep work

This commit is contained in:
galister
2025-06-27 04:16:24 +09:00
parent 1215a50324
commit afb4919970
21 changed files with 613 additions and 420 deletions

View File

@@ -6,7 +6,7 @@ use taffy::{AlignItems, JustifyContent, prelude::length};
use crate::{
animation::{Animation, AnimationEasing},
drawing::{self, Color},
event::{EventListener, WidgetCallback},
event::WidgetCallback,
layout::{Layout, WidgetID},
renderer_vk::text::{FontWeight, TextStyle},
widget::{
@@ -135,8 +135,6 @@ pub fn construct(
},
)?;
let mut widget = layout.widget_map.get(rect_id).unwrap().lock().unwrap();
let button = Arc::new(Button {
body: rect_id,
color: params.color,
@@ -144,25 +142,9 @@ pub fn construct(
text_node,
});
// Highlight background on mouse enter
{
let button = button.clone();
widget.add_event_listener(EventListener::MouseEnter(Box::new(move |data, _| {
data
.animations
.push(anim_hover_in(button.clone(), data.widget_id));
})));
}
//TODO: Highlight background on mouse enter
// Bring back old color on mouse leave
{
let button = button.clone();
widget.add_event_listener(EventListener::MouseLeave(Box::new(move |data, _| {
data
.animations
.push(anim_hover_out(button.clone(), data.widget_id));
})));
}
//TODO: Bring back old color on mouse leave
Ok(button)
}
}

View File

@@ -1,4 +1,5 @@
use glam::Vec2;
use slotmap::SecondaryMap;
use crate::{
animation,
@@ -105,6 +106,7 @@ pub struct CallbackData<'a> {
pub dirty_nodes: &'a mut Vec<taffy::NodeId>,
pub needs_redraw: bool,
pub trigger_haptics: bool,
pub metadata: CallbackMetadata,
}
impl<'a> WidgetCallback<'a> for CallbackData<'a> {
@@ -121,16 +123,70 @@ impl<'a> WidgetCallback<'a> for CallbackData<'a> {
}
}
pub type MouseEnterCallback = Box<dyn Fn(&mut CallbackData, ())>;
pub type MouseLeaveCallback = Box<dyn Fn(&mut CallbackData, ())>;
pub type MousePressCallback = Box<dyn Fn(&mut CallbackData, MouseButton)>;
pub type MouseReleaseCallback = Box<dyn Fn(&mut CallbackData, MouseButton)>;
pub type InternalStateChangeCallback = Box<dyn Fn(&mut CallbackData, usize)>;
pub enum EventListener {
MouseEnter(MouseEnterCallback),
MouseLeave(MouseLeaveCallback),
MousePress(MousePressCallback),
MouseRelease(MouseReleaseCallback),
InternalStateChange(InternalStateChangeCallback),
pub enum CallbackMetadata {
None,
MouseButton(MouseButton),
Custom(usize),
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum EventListenerKind {
MousePress,
MouseRelease,
MouseEnter,
MouseLeave,
InternalStateChange,
}
pub type EventCallback<U1, U2> = Box<dyn Fn(&mut CallbackData, &mut U1, &mut U2)>;
pub struct EventListener<U1, U2> {
pub kind: EventListenerKind,
pub callback: EventCallback<U1, U2>,
}
impl<U1, U2> EventListener<U1, U2> {
pub fn callback_for_kind(
&self,
kind: EventListenerKind,
) -> Option<&impl Fn(&mut CallbackData, &mut U1, &mut U2)> {
if self.kind == kind {
Some(&self.callback)
} else {
None
}
}
}
pub struct EventListenerCollection<U1, U2> {
map: SecondaryMap<WidgetID, Vec<EventListener<U1, U2>>>,
}
// derive only works if generics also implement Default
impl<U1, U2> Default for EventListenerCollection<U1, U2> {
fn default() -> Self {
Self {
map: SecondaryMap::default(),
}
}
}
impl<U1, U2> EventListenerCollection<U1, U2> {
pub fn add(
&mut self,
widget_id: WidgetID,
kind: EventListenerKind,
callback: EventCallback<U1, U2>,
) {
let new_item = EventListener { kind, callback };
if let Some(vec) = self.map.get_mut(widget_id) {
vec.push(new_item);
} else {
self.map.insert(widget_id, vec![new_item]);
}
}
pub fn get(&self, widget_id: WidgetID) -> Option<&[EventListener<U1, U2>]> {
self.map.get(widget_id).map(|v| v.as_slice())
}
}

View File

@@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex};
use crate::{
animation::{self, Animations},
assets::AssetProvider,
event::{self, EventListener},
event::{self, EventListenerCollection},
transform_stack::{Transform, TransformStack},
widget::{self, EventParams, WidgetState, div::Div},
};
@@ -89,26 +89,30 @@ impl Layout {
)
}
fn push_event_children(
fn push_event_children<U1, U2>(
&self,
listeners: &EventListenerCollection<U1, U2>,
parent_node_id: taffy::NodeId,
state: &mut PushEventState,
event: &event::Event,
dirty_nodes: &mut Vec<taffy::NodeId>,
user_data: &mut (&mut U1, &mut U2),
) -> anyhow::Result<()> {
for child_id in self.tree.child_ids(parent_node_id) {
self.push_event_widget(state, child_id, event, dirty_nodes)?;
self.push_event_widget(listeners, state, child_id, event, dirty_nodes, user_data)?;
}
Ok(())
}
fn push_event_widget(
fn push_event_widget<U1, U2>(
&self,
listeners: &EventListenerCollection<U1, U2>,
state: &mut PushEventState,
node_id: taffy::NodeId,
event: &event::Event,
dirty_nodes: &mut Vec<taffy::NodeId>,
user_data: &mut (&mut U1, &mut U2),
) -> anyhow::Result<()> {
let l = self.tree.layout(node_id)?;
let Some(widget_id) = self.tree.get_node_context(node_id).cloned() else {
@@ -134,38 +138,42 @@ impl Layout {
let mut iter_children = true;
match widget.process_event(
widget_id,
node_id,
event,
&mut EventParams {
transform_stack: state.transform_stack,
widgets: &self.widget_map,
tree: &self.tree,
animations: state.animations,
needs_redraw: &mut state.needs_redraw,
trigger_haptics: &mut state.trigger_haptics,
if let Some(listeners) = listeners.get(widget_id) {
match widget.process_event(
widget_id,
listeners,
node_id,
style,
taffy_layout: l,
dirty_nodes,
},
) {
widget::EventResult::Pass => {
// go on
}
widget::EventResult::Consumed => {
iter_children = false;
}
widget::EventResult::Outside => {
iter_children = false;
event,
user_data,
&mut EventParams {
transform_stack: state.transform_stack,
widgets: &self.widget_map,
tree: &self.tree,
animations: state.animations,
needs_redraw: &mut state.needs_redraw,
trigger_haptics: &mut state.trigger_haptics,
node_id,
style,
taffy_layout: l,
dirty_nodes,
},
) {
widget::EventResult::Pass => {
// go on
}
widget::EventResult::Consumed => {
iter_children = false;
}
widget::EventResult::Outside => {
iter_children = false;
}
}
}
drop(widget); // free mutex
if iter_children {
self.push_event_children(node_id, state, event, dirty_nodes)?;
self.push_event_children(listeners, node_id, state, event, dirty_nodes, user_data)?;
}
state.transform_stack.pop();
@@ -191,7 +199,12 @@ impl Layout {
}
}
pub fn push_event(&mut self, event: &event::Event) -> anyhow::Result<()> {
pub fn push_event<U1, U2>(
&mut self,
listeners: &EventListenerCollection<U1, U2>,
event: &event::Event,
mut user_data: (&mut U1, &mut U2),
) -> anyhow::Result<()> {
let mut transform_stack = TransformStack::new();
let mut animations_to_add = Vec::<animation::Animation>::new();
let mut dirty_nodes = Vec::new();
@@ -203,7 +216,14 @@ impl Layout {
trigger_haptics: false,
};
self.push_event_widget(&mut state, self.root_node, event, &mut dirty_nodes)?;
self.push_event_widget(
listeners,
&mut state,
self.root_node,
event,
&mut dirty_nodes,
&mut user_data,
)?;
for node in dirty_nodes {
self.tree.mark_dirty(node)?;
@@ -339,13 +359,4 @@ impl Layout {
Ok(())
}
// helper function
pub fn add_event_listener(&self, widget_id: WidgetID, listener: EventListener) {
let Some(widget) = self.widget_map.get(widget_id) else {
debug_assert!(false);
return;
};
widget.lock().unwrap().add_event_listener(listener);
}
}
}

View File

@@ -5,7 +5,9 @@ use crate::{
animation,
any::AnyTrait,
drawing,
event::{CallbackData, Event, EventListener, MouseWheelEvent},
event::{
CallbackData, CallbackMetadata, Event, EventListener, EventListenerKind, MouseWheelEvent,
},
layout::{Layout, WidgetID, WidgetMap},
transform_stack::TransformStack,
};
@@ -70,7 +72,6 @@ impl WidgetData {
pub struct WidgetState {
pub data: WidgetData,
pub obj: Box<dyn WidgetObj>,
pub event_listeners: Vec<EventListener>,
}
impl WidgetState {
@@ -88,7 +89,6 @@ impl WidgetState {
scrolling: Vec2::default(),
transform: glam::Mat4::IDENTITY,
},
event_listeners: Vec::new(),
obj,
})
}
@@ -182,9 +182,9 @@ impl dyn WidgetObj {
}
macro_rules! call_event {
($self:ident, $widget_id:ident, $node_id:ident, $params:ident, $ev:ident, $cb_arg:expr) => {
for listener in &$self.event_listeners {
if let EventListener::$ev(callback) = listener {
($self:ident, $listeners:ident, $widget_id:ident, $node_id:ident, $params:ident, $kind:ident, $user_data:expr, $metadata:expr) => {
for listener in $listeners {
if let Some(callback) = listener.callback_for_kind(EventListenerKind::$kind) {
let mut data = CallbackData {
obj: $self.obj.as_mut(),
widget_data: &mut $self.data,
@@ -195,8 +195,9 @@ macro_rules! call_event {
$node_id,
needs_redraw: false,
trigger_haptics: false,
metadata: $metadata,
};
callback(&mut data, $cb_arg);
callback(&mut data, $user_data.0, $user_data.1);
if data.trigger_haptics {
*$params.trigger_haptics = true;
}
@@ -209,10 +210,6 @@ macro_rules! call_event {
}
impl WidgetState {
pub fn add_event_listener(&mut self, listener: EventListener) {
self.event_listeners.push(listener);
}
pub fn get_scroll_shift(&self, info: &ScrollbarInfo, l: &taffy::Layout) -> Vec2 {
Vec2::new(
(info.content_size.x - l.content_box_width()) * self.data.scrolling.x,
@@ -322,11 +319,13 @@ impl WidgetState {
true
}
pub fn process_event(
pub fn process_event<U1, U2>(
&mut self,
widget_id: WidgetID,
listeners: &[EventListener<U1, U2>],
node_id: taffy::NodeId,
event: &Event,
user_data: &mut (&mut U1, &mut U2),
params: &mut EventParams,
) -> EventResult {
let hovered = event.test_mouse_within_transform(params.transform_stack.get());
@@ -334,12 +333,30 @@ impl WidgetState {
match &event {
Event::MouseDown(e) => {
if hovered && self.data.set_device_pressed(e.device, true) {
call_event!(self, widget_id, node_id, params, MousePress, e.button);
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MousePress,
user_data,
CallbackMetadata::MouseButton(e.button)
);
}
}
Event::MouseUp(e) => {
if self.data.set_device_pressed(e.device, false) {
call_event!(self, widget_id, node_id, params, MouseRelease, e.button);
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseRelease,
user_data,
CallbackMetadata::MouseButton(e.button)
);
}
}
Event::MouseWheel(e) => {
@@ -350,28 +367,57 @@ impl WidgetState {
Event::MouseMotion(e) => {
if self.data.set_device_hovered(e.device, hovered) {
if self.data.is_hovered() {
call_event!(self, widget_id, node_id, params, MouseEnter, ());
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseEnter,
user_data,
CallbackMetadata::None
);
} else {
call_event!(self, widget_id, node_id, params, MouseLeave, ());
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseLeave,
user_data,
CallbackMetadata::None
);
}
}
}
Event::MouseLeave(e) => {
if self.data.set_device_hovered(e.device, false) {
call_event!(self, widget_id, node_id, params, MouseLeave, ());
call_event!(
self,
listeners,
widget_id,
node_id,
params,
MouseLeave,
user_data,
CallbackMetadata::None
);
}
}
Event::InternalStateChange(e) => {
call_event!(
self,
listeners,
widget_id,
node_id,
params,
InternalStateChange,
e.metadata
user_data,
CallbackMetadata::Custom(e.metadata)
);
}
}
EventResult::Pass
}
}
}

View File

@@ -50,6 +50,10 @@ impl TextLabel {
}
pub fn set_text(&mut self, text: &str) {
if self.params.content.as_str() == text {
return;
}
self.params.content = String::from(text);
let attrs = Attrs::from(&self.params.style);
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
@@ -63,6 +67,10 @@ impl TextLabel {
self.params.style.align.map(|a| a.into()),
);
}
pub fn get_text(&self) -> &str {
&self.params.content
}
}
impl WidgetObj for TextLabel {