Allow actions to be deserialized from JSON

Introduce separate macro for implementing 'internal' actions which
are not intended to be loaded from keymaps.
This commit is contained in:
Max Brunsfeld 2022-04-08 15:32:56 -07:00
parent 1778622960
commit fd4b81c8fc
26 changed files with 559 additions and 335 deletions

View file

@ -715,12 +715,14 @@ type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
type GlobalObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
pub struct MutableAppContext {
weak_self: Option<rc::Weak<RefCell<Self>>>,
foreground_platform: Rc<dyn platform::ForegroundPlatform>,
assets: Arc<AssetCache>,
cx: AppContext,
action_deserializers: HashMap<&'static str, DeserializeActionCallback>,
capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
@ -773,6 +775,7 @@ impl MutableAppContext {
font_cache,
platform,
},
action_deserializers: HashMap::new(),
capture_actions: HashMap::new(),
actions: HashMap::new(),
global_actions: HashMap::new(),
@ -857,6 +860,18 @@ impl MutableAppContext {
.and_then(|(presenter, _)| presenter.borrow().debug_elements(self))
}
pub fn deserialize_action(
&self,
name: &str,
argument: Option<&str>,
) -> Result<Box<dyn Action>> {
let callback = self
.action_deserializers
.get(name)
.ok_or_else(|| anyhow!("unknown action {}", name))?;
callback(argument.unwrap_or("{}"))
}
pub fn add_action<A, V, F>(&mut self, handler: F)
where
A: Action,
@ -899,6 +914,10 @@ impl MutableAppContext {
},
);
self.action_deserializers
.entry(A::qualified_name())
.or_insert(A::from_json_str);
let actions = if capture {
&mut self.capture_actions
} else {
@ -934,6 +953,10 @@ impl MutableAppContext {
handler(action, cx);
});
self.action_deserializers
.entry(A::qualified_name())
.or_insert(A::from_json_str);
if self
.global_actions
.insert(TypeId::of::<A>(), handler)
@ -4575,7 +4598,8 @@ impl RefCounts {
#[cfg(test)]
mod tests {
use super::*;
use crate::{elements::*, impl_actions};
use crate::{actions, elements::*, impl_actions};
use serde::Deserialize;
use smol::future::poll_once;
use std::{
cell::Cell,
@ -5683,6 +5707,42 @@ mod tests {
);
}
#[crate::test(self)]
fn test_deserialize_actions(cx: &mut MutableAppContext) {
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct ComplexAction {
arg: String,
count: usize,
}
actions!(test::something, [SimpleAction]);
impl_actions!(test::something, [ComplexAction]);
cx.add_global_action(move |_: &SimpleAction, _: &mut MutableAppContext| {});
cx.add_global_action(move |_: &ComplexAction, _: &mut MutableAppContext| {});
let action1 = cx
.deserialize_action(
"test::something::ComplexAction",
Some(r#"{"arg": "a", "count": 5}"#),
)
.unwrap();
let action2 = cx
.deserialize_action("test::something::SimpleAction", None)
.unwrap();
assert_eq!(
action1.as_any().downcast_ref::<ComplexAction>().unwrap(),
&ComplexAction {
arg: "a".to_string(),
count: 5,
}
);
assert_eq!(
action2.as_any().downcast_ref::<SimpleAction>().unwrap(),
&SimpleAction
);
}
#[crate::test(self)]
fn test_dispatch_action(cx: &mut MutableAppContext) {
struct ViewA {
@ -5721,32 +5781,32 @@ mod tests {
}
}
#[derive(Clone)]
pub struct Action(pub &'static str);
#[derive(Clone, Deserialize)]
pub struct Action(pub String);
impl_actions!(test, [Action]);
let actions = Rc::new(RefCell::new(Vec::new()));
{
cx.add_global_action({
let actions = actions.clone();
cx.add_global_action(move |_: &Action, _: &mut MutableAppContext| {
move |_: &Action, _: &mut MutableAppContext| {
actions.borrow_mut().push("global".to_string());
});
}
}
});
{
cx.add_action({
let actions = actions.clone();
cx.add_action(move |view: &mut ViewA, action: &Action, cx| {
move |view: &mut ViewA, action: &Action, cx| {
assert_eq!(action.0, "bar");
cx.propagate_action();
actions.borrow_mut().push(format!("{} a", view.id));
});
}
}
});
{
cx.add_action({
let actions = actions.clone();
cx.add_action(move |view: &mut ViewA, _: &Action, cx| {
move |view: &mut ViewA, _: &Action, cx| {
if view.id != 1 {
cx.add_view(|cx| {
cx.propagate_action(); // Still works on a nested ViewContext
@ -5754,32 +5814,32 @@ mod tests {
});
}
actions.borrow_mut().push(format!("{} b", view.id));
});
}
}
});
{
cx.add_action({
let actions = actions.clone();
cx.add_action(move |view: &mut ViewB, _: &Action, cx| {
move |view: &mut ViewB, _: &Action, cx| {
cx.propagate_action();
actions.borrow_mut().push(format!("{} c", view.id));
});
}
}
});
{
cx.add_action({
let actions = actions.clone();
cx.add_action(move |view: &mut ViewB, _: &Action, cx| {
move |view: &mut ViewB, _: &Action, cx| {
cx.propagate_action();
actions.borrow_mut().push(format!("{} d", view.id));
});
}
}
});
{
cx.capture_action({
let actions = actions.clone();
cx.capture_action(move |view: &mut ViewA, _: &Action, cx| {
move |view: &mut ViewA, _: &Action, cx| {
cx.propagate_action();
actions.borrow_mut().push(format!("{} capture", view.id));
});
}
}
});
let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 });
let view_2 = cx.add_view(window_id, |_| ViewB { id: 2 });
@ -5789,7 +5849,7 @@ mod tests {
cx.dispatch_action(
window_id,
vec![view_1.id(), view_2.id(), view_3.id(), view_4.id()],
&Action("bar"),
&Action("bar".to_string()),
);
assert_eq!(
@ -5812,7 +5872,7 @@ mod tests {
cx.dispatch_action(
window_id,
vec![view_2.id(), view_3.id(), view_4.id()],
&Action("bar"),
&Action("bar".to_string()),
);
assert_eq!(
@ -5832,8 +5892,8 @@ mod tests {
#[crate::test(self)]
fn test_dispatch_keystroke(cx: &mut MutableAppContext) {
#[derive(Clone)]
pub struct Action(pub &'static str);
#[derive(Clone, Deserialize)]
pub struct Action(String);
impl_actions!(test, [Action]);
@ -5887,16 +5947,20 @@ mod tests {
// "a" and "b" in its context, but not "c".
cx.add_bindings(vec![keymap::Binding::new(
"a",
Action("a"),
Action("a".to_string()),
Some("a && b && !c"),
)]);
cx.add_bindings(vec![keymap::Binding::new("b", Action("b"), None)]);
cx.add_bindings(vec![keymap::Binding::new(
"b",
Action("b".to_string()),
None,
)]);
let actions = Rc::new(RefCell::new(Vec::new()));
{
cx.add_action({
let actions = actions.clone();
cx.add_action(move |view: &mut View, action: &Action, cx| {
move |view: &mut View, action: &Action, cx| {
if action.0 == "a" {
actions.borrow_mut().push(format!("{} a", view.id));
} else {
@ -5905,14 +5969,15 @@ mod tests {
.push(format!("{} {}", view.id, action.0));
cx.propagate_action();
}
});
}
{
}
});
cx.add_global_action({
let actions = actions.clone();
cx.add_global_action(move |action: &Action, _| {
move |action: &Action, _| {
actions.borrow_mut().push(format!("global {}", action.0));
});
}
}
});
cx.dispatch_keystroke(
window_id,