use std::any::Any;
use std::collections::{HashMap, HashSet};
use blinc_core::events::Event;
use blinc_core::fsm::{EventId, StateMachine};
use crate::tree::LayoutNodeId;
pub trait NodeState: Send + 'static {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Send + 'static> NodeState for T {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[derive(Default)]
struct NodeData {
fsm: Option<StateMachine>,
state: Option<Box<dyn NodeState>>,
}
#[derive(Default)]
pub struct DirtyTracker {
dirty: HashSet<LayoutNodeId>,
needs_layout: bool,
}
impl DirtyTracker {
pub fn new() -> Self {
Self::default()
}
pub fn mark(&mut self, id: LayoutNodeId) {
self.dirty.insert(id);
}
pub fn mark_layout(&mut self) {
self.needs_layout = true;
}
pub fn is_dirty(&self, id: LayoutNodeId) -> bool {
self.dirty.contains(&id)
}
pub fn has_dirty(&self) -> bool {
!self.dirty.is_empty()
}
pub fn needs_layout(&self) -> bool {
self.needs_layout
}
pub fn take_dirty(&mut self) -> Vec<LayoutNodeId> {
self.dirty.drain().collect()
}
pub fn clear_layout(&mut self) {
self.needs_layout = false;
}
pub fn clear_all(&mut self) {
self.dirty.clear();
self.needs_layout = false;
}
}
pub struct InteractiveContext {
nodes: HashMap<u64, NodeData>,
dirty: DirtyTracker,
}
impl Default for InteractiveContext {
fn default() -> Self {
Self::new()
}
}
impl InteractiveContext {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
dirty: DirtyTracker::new(),
}
}
fn key(id: LayoutNodeId) -> u64 {
use slotmap::Key;
id.data().as_ffi()
}
pub fn register(&mut self, id: LayoutNodeId, fsm: Option<StateMachine>) {
self.nodes
.insert(Self::key(id), NodeData { fsm, state: None });
self.dirty.mark(id);
}
pub fn register_with_fsm(&mut self, id: LayoutNodeId, fsm: StateMachine) {
self.register(id, Some(fsm));
}
pub fn unregister(&mut self, id: LayoutNodeId) {
self.nodes.remove(&Self::key(id));
}
pub fn is_registered(&self, id: LayoutNodeId) -> bool {
self.nodes.contains_key(&Self::key(id))
}
pub fn get_fsm_state(&self, id: LayoutNodeId) -> Option<u32> {
self.nodes
.get(&Self::key(id))
.and_then(|d| d.fsm.as_ref())
.map(|fsm| fsm.current_state())
}
pub fn send_event(&mut self, id: LayoutNodeId, event_type: EventId) -> bool {
let key = Self::key(id);
if let Some(data) = self.nodes.get_mut(&key) {
if let Some(ref mut fsm) = data.fsm {
let old_state = fsm.current_state();
fsm.send(event_type);
let new_state = fsm.current_state();
if old_state != new_state {
self.dirty.mark(id);
return true;
}
}
}
false
}
pub fn dispatch_event(&mut self, id: LayoutNodeId, event: &Event) -> bool {
self.send_event(id, event.event_type)
}
pub fn set_state<S: NodeState>(&mut self, id: LayoutNodeId, state: S) {
let key = Self::key(id);
if let Some(data) = self.nodes.get_mut(&key) {
data.state = Some(Box::new(state));
self.dirty.mark(id);
} else {
self.nodes.insert(
key,
NodeData {
fsm: None,
state: Some(Box::new(state)),
},
);
self.dirty.mark(id);
}
}
pub fn get_state<S: 'static>(&self, id: LayoutNodeId) -> Option<&S> {
self.nodes
.get(&Self::key(id))
.and_then(|d| d.state.as_ref())
.and_then(|s| {
(**s).as_any().downcast_ref()
})
}
pub fn get_state_mut<S: 'static>(&mut self, id: LayoutNodeId) -> Option<&mut S> {
self.nodes
.get_mut(&Self::key(id))
.and_then(|d| d.state.as_mut())
.and_then(|s| {
(**s).as_any_mut().downcast_mut()
})
}
pub fn mark_dirty(&mut self, id: LayoutNodeId) {
self.dirty.mark(id);
}
pub fn mark_layout(&mut self) {
self.dirty.mark_layout();
}
pub fn is_dirty(&self, id: LayoutNodeId) -> bool {
self.dirty.is_dirty(id)
}
pub fn has_dirty(&self) -> bool {
self.dirty.has_dirty()
}
pub fn needs_layout(&self) -> bool {
self.dirty.needs_layout()
}
pub fn take_dirty(&mut self) -> Vec<LayoutNodeId> {
self.dirty.take_dirty()
}
pub fn clear_layout(&mut self) {
self.dirty.clear_layout();
}
pub fn clear_all(&mut self) {
self.dirty.clear_all();
}
pub fn dirty_tracker(&self) -> &DirtyTracker {
&self.dirty
}
pub fn dirty_tracker_mut(&mut self) -> &mut DirtyTracker {
&mut self.dirty
}
}
#[cfg(test)]
mod tests {
use super::*;
use blinc_core::events::{event_types, EventData};
use blinc_core::fsm::StateMachine;
use slotmap::SlotMap;
fn create_node_id() -> LayoutNodeId {
let mut sm: SlotMap<LayoutNodeId, ()> = SlotMap::with_key();
sm.insert(())
}
#[test]
fn test_dirty_tracker() {
let mut tracker = DirtyTracker::new();
let id = create_node_id();
assert!(!tracker.is_dirty(id));
assert!(!tracker.has_dirty());
tracker.mark(id);
assert!(tracker.is_dirty(id));
assert!(tracker.has_dirty());
let dirty = tracker.take_dirty();
assert_eq!(dirty.len(), 1);
assert!(!tracker.has_dirty());
}
#[test]
fn test_interactive_context_state() {
let mut ctx = InteractiveContext::new();
let id = create_node_id();
ctx.set_state(id, 42u32);
let state = ctx.get_state::<u32>(id);
assert_eq!(state, Some(&42));
if let Some(s) = ctx.get_state_mut::<u32>(id) {
*s = 100;
}
assert_eq!(ctx.get_state::<u32>(id), Some(&100));
}
#[test]
fn test_interactive_context_fsm() {
let mut ctx = InteractiveContext::new();
let id = create_node_id();
let fsm = StateMachine::builder(0)
.on(0, event_types::POINTER_ENTER, 1)
.on(1, event_types::POINTER_LEAVE, 0)
.build();
ctx.register_with_fsm(id, fsm);
assert_eq!(ctx.get_fsm_state(id), Some(0));
ctx.take_dirty();
let transitioned = ctx.send_event(id, event_types::POINTER_ENTER);
assert!(transitioned);
assert_eq!(ctx.get_fsm_state(id), Some(1));
assert!(ctx.is_dirty(id));
ctx.take_dirty();
let event = Event {
event_type: event_types::POINTER_LEAVE,
target: 0,
data: EventData::Pointer {
x: 0.0,
y: 0.0,
button: 0,
pressure: 1.0,
},
timestamp: 0,
propagation_stopped: false,
};
let transitioned = ctx.dispatch_event(id, &event);
assert!(transitioned);
assert_eq!(ctx.get_fsm_state(id), Some(0));
}
#[test]
fn test_complex_state_type() {
#[derive(Debug, PartialEq)]
struct ButtonState {
scale: f32,
clicked: bool,
}
let mut ctx = InteractiveContext::new();
let id = create_node_id();
ctx.set_state(
id,
ButtonState {
scale: 1.0,
clicked: false,
},
);
if let Some(state) = ctx.get_state_mut::<ButtonState>(id) {
state.scale = 0.95;
state.clicked = true;
}
let state = ctx.get_state::<ButtonState>(id).unwrap();
assert_eq!(state.scale, 0.95);
assert!(state.clicked);
}
}