refactor events; watch prep work
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user