kui_core/message.rs
1//! Typed messages over the plain-data payload.
2//!
3//! Payloads are [`Value`]s so that Lua, C and JSX share them. A Rust app
4//! gets an exhaustive `match` back with `#[derive(Message)]` (from
5//! `kui-derive`, re-exported by `kui-native` and, behind the `derive`
6//! feature, by this crate): it turns an enum into a `{kind, ...fields}`
7//! map and back, so `on_click(Msg::Save)` builds the payload and
8//! [`UiEvent::message::<Msg>()`](crate::input::UiEvent::message) reads it.
9//!
10//! ```rust,ignore
11//! // Needs the `derive` feature (on by default in kui-native).
12//! #[derive(kui::Message)]
13//! enum Msg {
14//! Save,
15//! Rename { to: String },
16//! }
17//!
18//! fn update(ev: &kui::UiEvent) {
19//! match ev.message::<Msg>() {
20//! Some(Msg::Save) => { /* ... */ }
21//! Some(Msg::Rename { to }) => { /* ... */ }
22//! None => {}
23//! }
24//! }
25//! ```
26//!
27//! The derive is built from [`MessageField`], the conversion each field's
28//! type has, and [`MessageError`], what a payload that is not one of the
29//! enum's says. Both are plain enough to implement by hand for a type the
30//! derive does not cover.
31
32use crate::value::Value;
33
34/// A type a message field can hold: to a [`Value`] and back. Implemented
35/// for the numbers, `bool`, `String`, `Value` itself, `Option` (absent or
36/// null is `None`) and `Vec`; `#[derive(Message)]` implements it for the
37/// type it derives, so one message can carry another.
38pub trait MessageField: Sized {
39 fn to_value(self) -> Value;
40 /// `None` for a value of the wrong shape.
41 fn from_value(v: &Value) -> Option<Self>;
42}
43
44/// Why a payload is not the message it was read as.
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum MessageError {
47 /// It has no `kind` string (and, for a string-shaped message, is not a
48 /// string).
49 NoKind,
50 /// Its `kind` names no variant of the type.
51 UnknownKind(String),
52 /// A field is missing, or holds a value of the wrong shape.
53 Field { kind: String, field: &'static str },
54}
55
56impl std::fmt::Display for MessageError {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 MessageError::NoKind => f.write_str("the payload has no `kind`"),
60 MessageError::UnknownKind(k) => write!(f, "no message is of kind {k:?}"),
61 MessageError::Field { kind, field } => write!(
62 f,
63 "a {kind:?} message's `{field}` is missing or of the wrong type"
64 ),
65 }
66 }
67}
68
69impl std::error::Error for MessageError {}
70
71/// Field `key` of the map `v`, as `T`: what the derive reads each field
72/// with. An absent key reads as null, so an `Option` field may be left out.
73pub fn field<T: MessageField>(v: &Value, kind: &str, key: &'static str) -> Result<T, MessageError> {
74 T::from_value(v.get(key).unwrap_or(&Value::Null)).ok_or_else(|| MessageError::Field {
75 kind: kind.to_string(),
76 field: key,
77 })
78}
79
80/// The `kind` of a map payload, for the derive's match.
81pub fn kind_of(v: &Value) -> Result<&str, MessageError> {
82 v.get_str("kind").ok_or(MessageError::NoKind)
83}
84
85impl MessageField for Value {
86 fn to_value(self) -> Value {
87 self
88 }
89 fn from_value(v: &Value) -> Option<Self> {
90 Some(v.clone())
91 }
92}
93
94impl MessageField for bool {
95 fn to_value(self) -> Value {
96 Value::Bool(self)
97 }
98 fn from_value(v: &Value) -> Option<Self> {
99 v.as_bool()
100 }
101}
102
103impl MessageField for String {
104 fn to_value(self) -> Value {
105 Value::Str(self)
106 }
107 fn from_value(v: &Value) -> Option<Self> {
108 v.as_str().map(str::to_string)
109 }
110}
111
112impl MessageField for f64 {
113 fn to_value(self) -> Value {
114 Value::Float(self)
115 }
116 fn from_value(v: &Value) -> Option<Self> {
117 v.as_float()
118 }
119}
120
121impl MessageField for f32 {
122 fn to_value(self) -> Value {
123 Value::float(self)
124 }
125 fn from_value(v: &Value) -> Option<Self> {
126 v.as_float().map(|f| f as f32)
127 }
128}
129
130/// The integers, through `Value::Int`, refusing one out of the type's range
131/// rather than wrapping it.
132macro_rules! int_fields {
133 ($($t:ty),*) => {$(
134 impl MessageField for $t {
135 fn to_value(self) -> Value {
136 Value::Int(i64::try_from(self).unwrap_or(i64::MAX))
137 }
138 fn from_value(v: &Value) -> Option<Self> {
139 v.as_int().and_then(|n| <$t>::try_from(n).ok())
140 }
141 }
142 )*};
143}
144int_fields!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
145
146impl<T: MessageField> MessageField for Option<T> {
147 fn to_value(self) -> Value {
148 self.map_or(Value::Null, T::to_value)
149 }
150 fn from_value(v: &Value) -> Option<Self> {
151 match v {
152 Value::Null => Some(None),
153 v => T::from_value(v).map(Some),
154 }
155 }
156}
157
158impl<T: MessageField> MessageField for Vec<T> {
159 fn to_value(self) -> Value {
160 Value::List(self.into_iter().map(T::to_value).collect())
161 }
162 fn from_value(v: &Value) -> Option<Self> {
163 v.as_list()?.iter().map(T::from_value).collect()
164 }
165}
166
167impl<T: MessageField> MessageField for Box<T> {
168 fn to_value(self) -> Value {
169 (*self).to_value()
170 }
171 fn from_value(v: &Value) -> Option<Self> {
172 T::from_value(v).map(Box::new)
173 }
174}
175
176impl crate::input::UiEvent {
177 /// The app's message this event carries, as `M`: the payload itself for
178 /// a click (what `on_click` was handed), and otherwise the `tag` inside
179 /// a core event — a drag, a change, a scroll, a drop, a layout — which
180 /// is what `on_drag` and the rest were handed. `None` when neither is
181 /// an `M`: a key press, a core event with no tag, another type's.
182 ///
183 /// The event's own fields stay on `payload` — a drag's `phase` and
184 /// `x`/`y`, a change's `value` — so a handler matches on the message and
185 /// reads those beside it.
186 ///
187 /// A core event's tag is read before its payload: an app's `Msg::Key`
188 /// is `{kind: "key"}`, and so is every key event a sink hears, so
189 /// reading the payload first would hand the app its own variant for a
190 /// key meant for another sink's tag (backlog F148). A click's payload
191 /// is the app's own message, whatever its `kind`, and reads as itself.
192 pub fn message<M>(&self) -> Option<M>
193 where
194 M: for<'a> TryFrom<&'a Value>,
195 {
196 let tag = || self.payload.get("tag").and_then(|t| M::try_from(t).ok());
197 if self.kind().is_some_and(is_core_event)
198 && let Some(m) = tag()
199 {
200 return Some(m);
201 }
202 M::try_from(&self.payload).ok().or_else(tag)
203 }
204}
205
206/// A `kind` the core's own events carry, the app's message under their
207/// `tag`. `click` is not one: a click's payload is the app's message as-is.
208fn is_core_event(kind: &str) -> bool {
209 kind != "click" && crate::schema::EVENTS.iter().any(|e| e.kind == kind)
210}