use std::fmt;
use serde::de::{self, DeserializeSeed, Deserializer, IgnoredAny, MapAccess, Visitor};
use crate::element::{AnyAttribute, AttrDirty, Attrs, Common, DecodeElement, ElementInfo};
use crate::style::{Style, StyleDirty};
#[derive(Debug, Clone, Default)]
pub struct Props {
pub style: Option<Style>,
pub hover_style: Option<Style>,
pub press_style: Option<Style>,
pub focus_style: Option<Style>,
pub on_click: bool,
pub on_pointer_down: bool,
pub on_pointer_move: bool,
pub on_pointer_up: bool,
pub on_pointer_enter: bool,
pub on_pointer_leave: bool,
pub scroll_top: Option<f32>,
pub scroll_left: Option<f32>,
pub scroll_step: Option<f32>,
pub on_scroll: bool,
pub on_wheel: bool,
pub name: Option<String>,
pub shared_tag: Option<String>,
pub attrs: Attrs,
pub handlers: u64,
}
#[derive(Debug, Default)]
pub struct PropsDirty {
pub style: StyleDirty,
pub style_old: crate::style::OldValues,
pub hover_style: bool,
pub press_style: bool,
pub focus_style: bool,
pub pointer: bool,
pub scroll_listener: bool,
pub wheel: bool,
pub scroll_step: bool,
pub name: bool,
pub shared_tag: bool,
pub attrs: AttrDirty,
pub handlers: bool,
}
impl PropsDirty {
pub fn any_style_variant(&self) -> bool {
self.style.any() || self.hover_style || self.press_style || self.focus_style
}
}
#[derive(Debug, Default)]
pub struct UpdateEvents {
pub scroll_top: Option<f32>,
pub scroll_left: Option<f32>,
pub attrs: Attrs,
}
enum Key {
Style,
HoverStyle,
PressStyle,
FocusStyle,
OnClick,
OnPointerDown,
OnPointerMove,
OnPointerUp,
OnPointerEnter,
OnPointerLeave,
OnScroll,
OnWheel,
ScrollTop,
ScrollLeft,
ScrollStep,
Name,
SharedTag,
Attr(u8, &'static dyn AnyAttribute),
Handler(u8),
Skip,
}
fn common_key(name: &str) -> Option<(Key, Option<Common>)> {
Some(match name {
"style" => (Key::Style, None),
"hoverStyle" => (Key::HoverStyle, Some(Common::VARIANTS)),
"pressStyle" => (Key::PressStyle, Some(Common::VARIANTS)),
"focusStyle" => (Key::FocusStyle, Some(Common::VARIANTS)),
"onClick" => (Key::OnClick, Some(Common::POINTER)),
"onPointerDown" => (Key::OnPointerDown, Some(Common::POINTER)),
"onPointerMove" => (Key::OnPointerMove, Some(Common::POINTER)),
"onPointerUp" => (Key::OnPointerUp, Some(Common::POINTER)),
"onPointerEnter" => (Key::OnPointerEnter, Some(Common::POINTER)),
"onPointerLeave" => (Key::OnPointerLeave, Some(Common::POINTER)),
"onScroll" => (Key::OnScroll, Some(Common::SCROLL)),
"scrollTop" => (Key::ScrollTop, Some(Common::SCROLL)),
"scrollLeft" => (Key::ScrollLeft, Some(Common::SCROLL)),
"scrollStep" => (Key::ScrollStep, Some(Common::SCROLL)),
"onWheel" => (Key::OnWheel, Some(Common::WHEEL)),
"name" => (Key::Name, Some(Common::IDENTITY)),
"sharedTag" => (Key::SharedTag, Some(Common::IDENTITY)),
_ => return None,
})
}
pub(crate) fn is_common_prop(name: &str) -> bool {
common_key(name).is_some()
}
struct KeySeed<'a>(Option<&'a DecodeElement>);
impl<'de> DeserializeSeed<'de> for KeySeed<'_> {
type Value = Key;
fn deserialize<D: Deserializer<'de>>(self, d: D) -> Result<Key, D::Error> {
d.deserialize_str(self)
}
}
impl<'de> Visitor<'de> for KeySeed<'_> {
type Value = Key;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a prop name")
}
fn visit_str<E: de::Error>(self, name: &str) -> Result<Key, E> {
let Some((fixed, group)) = common_key(name) else {
return Ok(element_key(self.0, name));
};
if let (Some(DecodeElement::Known(info)), Some(group)) = (self.0, group)
&& !info.decl.common.contains(group)
{
super::decode_warn(
"propIgnored",
name,
&format!("`{name}` has no effect on <{}>", info.name()),
);
return Ok(Key::Skip);
}
Ok(fixed)
}
}
fn element_key(element: Option<&DecodeElement>, name: &str) -> Key {
match element {
Some(DecodeElement::Known(info)) => {
if let Some((index, attr)) = info.attr(name) {
return Key::Attr(index, attr);
}
if let Some(index) = info.event_for_prop(name) {
return Key::Handler(index);
}
super::decode_warn(
"unknownProp",
name,
&format!("<{}> has no attribute `{name}`", info.name()),
);
Key::Skip
}
Some(DecodeElement::Unknown) => Key::Skip,
None => {
super::decode_warn(
"unknownProp",
name,
&format!("unknown prop `{name}` (no element in scope)"),
);
Key::Skip
}
}
}
struct AttrSeed(&'static dyn AnyAttribute);
impl<'de> DeserializeSeed<'de> for AttrSeed {
type Value = Option<crate::style::StoredValue>;
fn deserialize<D: Deserializer<'de>>(self, d: D) -> Result<Self::Value, D::Error> {
let mut erased = <dyn erased_serde::Deserializer>::erase(d);
self.0.decode_value(&mut erased).map_err(de::Error::custom)
}
}
impl<'de> serde::Deserialize<'de> for Props {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl<'de> Visitor<'de> for V {
type Value = Props;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a props object")
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Props, A::Error> {
let element = crate::element::decode_element();
let mut props = Props::default();
while let Some(key) = map.next_key_seed(KeySeed(element.as_ref()))? {
match key {
Key::Style => props.style = map.next_value()?,
Key::HoverStyle => props.hover_style = map.next_value()?,
Key::PressStyle => props.press_style = map.next_value()?,
Key::FocusStyle => props.focus_style = map.next_value()?,
Key::OnClick => props.on_click = flag(&mut map)?,
Key::OnPointerDown => props.on_pointer_down = flag(&mut map)?,
Key::OnPointerMove => props.on_pointer_move = flag(&mut map)?,
Key::OnPointerUp => props.on_pointer_up = flag(&mut map)?,
Key::OnPointerEnter => props.on_pointer_enter = flag(&mut map)?,
Key::OnPointerLeave => props.on_pointer_leave = flag(&mut map)?,
Key::OnScroll => props.on_scroll = flag(&mut map)?,
Key::OnWheel => props.on_wheel = flag(&mut map)?,
Key::ScrollTop => props.scroll_top = map.next_value()?,
Key::ScrollLeft => props.scroll_left = map.next_value()?,
Key::ScrollStep => props.scroll_step = map.next_value()?,
Key::Name => props.name = map.next_value()?,
Key::SharedTag => props.shared_tag = map.next_value()?,
Key::Attr(index, attr) => match map.next_value_seed(AttrSeed(attr))? {
Some(value) => {
props
.attrs
.insert(crate::element::Entry { index, attr, value })
}
None => {
props.attrs.remove_index(index);
}
},
Key::Handler(index) => {
if flag(&mut map)? {
props.handlers |= 1 << index;
}
}
Key::Skip => {
map.next_value::<IgnoredAny>()?;
}
}
}
Ok(props)
}
}
d.deserialize_map(V)
}
}
fn flag<'de, A: MapAccess<'de>>(map: &mut A) -> Result<bool, A::Error> {
Ok(map.next_value::<Option<bool>>()?.unwrap_or(false))
}
impl Props {
pub fn decode_for(kind: &str, json: serde_json::Value) -> Box<Props> {
let _scope = crate::element::DecodeScope::new(kind);
serde_json::from_value(json).expect("valid props")
}
pub(crate) fn handler_events(
&self,
info: &ElementInfo,
) -> Vec<&'static dyn crate::element::AnyElementEvent> {
info.decl
.events
.iter()
.enumerate()
.filter(|(i, _)| self.handlers & (1 << i) != 0)
.map(|(_, e)| *e)
.collect()
}
}
#[cfg(test)]
pub(crate) fn props_from_json(json: serde_json::Value) -> Props {
props_for("node", json)
}
#[cfg(test)]
impl Props {
pub(crate) fn merge_delta_node(
&mut self,
delta: impl Into<Box<Props>>,
unset: &[String],
style_unset: &[String],
) -> (PropsDirty, UpdateEvents) {
let info = crate::ext::core_registry()
.element_info("node")
.cloned()
.expect("core <node>");
self.merge_delta(delta, unset, style_unset, &info)
}
}
#[cfg(test)]
pub(crate) fn props_for(kind: &str, json: serde_json::Value) -> Props {
crate::ext::install_builtin_registry();
*Props::decode_for(kind, json)
}
#[cfg(test)]
#[test]
fn props_stays_small() {
let size = size_of::<Props>();
assert!(
size <= 1024,
"Props grew to {size} B (>1024 B): every Create/Update op pays for this. \
Box the new field instead of inlining it — see the `hover_style` docs."
);
}