use crate::{Actor, ActorBehavior, Message, Port};
use anyhow::{Error, Result};
use reflow_actor::ActorContext;
use reflow_actor_macro::actor;
use serde_json::{json, Value};
use std::collections::HashMap;
#[actor(
HitTestActor,
inports::<100>(values),
outports::<50>(enter, leave),
state(MemoryState)
)]
pub async fn hit_test_actor(ctx: ActorContext) -> Result<HashMap<String, Message>, Error> {
let payload = ctx.get_payload();
let config = ctx.get_config_hashmap();
let source = config
.get("source")
.and_then(|v| v.as_str())
.unwrap_or("s2");
let target = config
.get("target")
.and_then(|v| v.as_str())
.unwrap_or("s0");
let target_w = config
.get("target_width")
.and_then(|v| v.as_f64())
.unwrap_or(100.0);
let target_h = config
.get("target_height")
.and_then(|v| v.as_f64())
.unwrap_or(100.0);
if let Some(Message::Object(obj)) = payload.get("values") {
let v: Value = obj.as_ref().clone().into();
if let Some(map) = v.as_object() {
for (k, val) in map {
if let Some(f) = val.as_f64() {
ctx.pool_upsert("_ht_vals", k, json!(f));
}
}
}
}
let get_val = |prefix: &str, prop: &str| -> f64 {
ctx.get_pool("_ht_vals")
.into_iter()
.find(|(k, _)| k == &format!("{}_{}", prefix, prop))
.and_then(|(_, v)| v.as_f64())
.unwrap_or(0.0)
};
let pool: Vec<(String, Value)> = ctx.get_pool("_ht").into_iter().collect();
let initialized = pool.iter().any(|(k, _)| k == "inside");
let was_inside = pool
.into_iter()
.find(|(k, _)| k == "inside")
.and_then(|(_, v)| v.as_bool())
.unwrap_or(false);
if !initialized {
ctx.pool_upsert("_ht", "inside", json!(false));
return Ok(HashMap::new());
}
let src_x = get_val(source, "x");
let src_y = get_val(source, "y");
let tgt_x = get_val(target, "x");
let tgt_y = get_val(target, "y");
let padding = config
.get("padding")
.and_then(|v| v.as_f64())
.unwrap_or(8.0);
let extra = if was_inside { padding } else { 0.0 };
let hw = target_w / 2.0 + extra;
let hh = target_h / 2.0 + extra;
let inside =
src_x >= tgt_x - hw && src_x <= tgt_x + hw && src_y >= tgt_y - hh && src_y <= tgt_y + hh;
if inside == was_inside {
return Ok(HashMap::new());
}
ctx.pool_upsert("_ht", "inside", json!(inside));
let mut out = HashMap::new();
if inside {
out.insert(
"enter".to_string(),
Message::String("HOVER".to_string().into()),
);
} else {
out.insert(
"leave".to_string(),
Message::String("LEAVE".to_string().into()),
);
}
Ok(out)
}