Detailed changes
@@ -15,12 +15,45 @@ pub struct DispatchContext {
map: HashMap<SharedString, SharedString>,
}
+impl<'a> TryFrom<&'a str> for DispatchContext {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'a str) -> Result<Self> {
+ Self::parse(value)
+ }
+}
+
impl DispatchContext {
- pub fn new() -> Self {
- DispatchContext {
- set: HashSet::default(),
- map: HashMap::default(),
+ pub fn parse(source: &str) -> Result<Self> {
+ let mut context = Self::default();
+ let source = skip_whitespace(source);
+ Self::parse_expr(&source, &mut context)?;
+ Ok(context)
+ }
+
+ fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
+ if source.is_empty() {
+ return Ok(());
+ }
+
+ let key = source
+ .chars()
+ .take_while(|ch| ch.is_alphanumeric())
+ .collect::<String>();
+ source = skip_whitespace(&source[key.len()..]);
+ if let Some(suffix) = source.strip_prefix('=') {
+ source = skip_whitespace(suffix);
+ let value = source
+ .chars()
+ .take_while(|ch| ch.is_alphanumeric())
+ .collect::<String>();
+ source = skip_whitespace(&source[value.len()..]);
+ context.set(key, value);
+ } else {
+ context.insert(key);
}
+
+ Self::parse_expr(source, context)
}
pub fn is_empty(&self) -> bool {
@@ -41,11 +74,11 @@ impl DispatchContext {
}
}
- pub fn add_identifier<I: Into<SharedString>>(&mut self, identifier: I) {
+ pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
self.set.insert(identifier.into());
}
- pub fn add_key<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
+ pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
self.map.insert(key.into(), value.into());
}
}
@@ -63,7 +96,7 @@ pub enum DispatchContextPredicate {
impl DispatchContextPredicate {
pub fn parse(source: &str) -> Result<Self> {
- let source = Self::skip_whitespace(source);
+ let source = skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if let Some(next) = rest.chars().next() {
Err(anyhow!("unexpected character {next:?}"))
@@ -113,7 +146,7 @@ impl DispatchContextPredicate {
("!=", PRECEDENCE_EQ, Self::new_neq as Op),
] {
if source.starts_with(operator) && precedence >= min_precedence {
- source = Self::skip_whitespace(&source[operator.len()..]);
+ source = skip_whitespace(&source[operator.len()..]);
let (right, rest) = Self::parse_expr(source, precedence + 1)?;
predicate = constructor(predicate, right)?;
source = rest;
@@ -133,17 +166,17 @@ impl DispatchContextPredicate {
.ok_or_else(|| anyhow!("unexpected eof"))?;
match next {
'(' => {
- source = Self::skip_whitespace(&source[1..]);
+ source = skip_whitespace(&source[1..]);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') {
- source = Self::skip_whitespace(&rest[1..]);
+ source = skip_whitespace(&rest[1..]);
Ok((predicate, source))
} else {
Err(anyhow!("expected a ')'"))
}
}
'!' => {
- let source = Self::skip_whitespace(&source[1..]);
+ let source = skip_whitespace(&source[1..]);
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
}
@@ -152,7 +185,7 @@ impl DispatchContextPredicate {
.find(|c: char| !(c.is_alphanumeric() || c == '_'))
.unwrap_or(source.len());
let (identifier, rest) = source.split_at(len);
- source = Self::skip_whitespace(rest);
+ source = skip_whitespace(rest);
Ok((
DispatchContextPredicate::Identifier(identifier.to_string().into()),
source,
@@ -162,13 +195,6 @@ impl DispatchContextPredicate {
}
}
- fn skip_whitespace(source: &str) -> &str {
- let len = source
- .find(|c: char| !c.is_whitespace())
- .unwrap_or(source.len());
- &source[len..]
- }
-
fn new_or(self, other: Self) -> Result<Self> {
Ok(Self::Or(Box::new(self), Box::new(other)))
}
@@ -204,9 +230,31 @@ const PRECEDENCE_AND: u32 = 3;
const PRECEDENCE_EQ: u32 = 4;
const PRECEDENCE_NOT: u32 = 5;
+fn skip_whitespace(source: &str) -> &str {
+ let len = source
+ .find(|c: char| !c.is_whitespace())
+ .unwrap_or(source.len());
+ &source[len..]
+}
+
#[cfg(test)]
mod tests {
- use super::DispatchContextPredicate::{self, *};
+ use super::*;
+ use DispatchContextPredicate::*;
+
+ #[test]
+ fn test_parse_context() {
+ let mut expected = DispatchContext::default();
+ expected.set("foo", "bar");
+ expected.insert("baz");
+ assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
+ assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
+ assert_eq!(
+ DispatchContext::parse(" baz foo = bar baz").unwrap(),
+ expected
+ );
+ assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
+ }
#[test]
fn test_parse_identifiers() {
@@ -595,7 +595,7 @@ pub struct InteractiveElementState {
impl<V> Default for StatelessInteraction<V> {
fn default() -> Self {
Self {
- dispatch_context: DispatchContext::new(),
+ dispatch_context: DispatchContext::default(),
mouse_down_listeners: SmallVec::new(),
mouse_up_listeners: SmallVec::new(),
mouse_move_listeners: SmallVec::new(),
@@ -39,6 +39,23 @@ impl Action for ActionB {
}
}
+#[derive(Clone)]
+struct ActionC;
+
+impl Action for ActionC {
+ fn eq(&self, action: &dyn Action) -> bool {
+ action.as_any().downcast_ref::<Self>().is_some()
+ }
+
+ fn boxed_clone(&self) -> Box<dyn Action> {
+ Box::new(self.clone())
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
pub struct FocusStory {
text: View<()>,
}
@@ -46,8 +63,9 @@ pub struct FocusStory {
impl FocusStory {
pub fn view(cx: &mut WindowContext) -> View<()> {
cx.bind_keys([
- KeyBinding::new("cmd-a", ActionA, None),
- KeyBinding::new("cmd-b", ActionB, None),
+ KeyBinding::new("cmd-a", ActionA, Some("parent")),
+ KeyBinding::new("cmd-a", ActionB, Some("child-1")),
+ KeyBinding::new("cmd-c", ActionC, None),
]);
let theme = rose_pine();
@@ -63,6 +81,7 @@ impl FocusStory {
let child_2 = cx.focus_handle();
view(cx.entity(|cx| ()), move |_, cx| {
div()
+ .context("parent")
.on_action(|_, action: &ActionA, phase, cx| {
println!("Action A dispatched on parent during {:?}", phase);
})
@@ -86,7 +105,8 @@ impl FocusStory {
.focus_in(|style| style.bg(color_3))
.child(
div()
- .id("child 1")
+ .id("child-1")
+ .context("child-1")
.on_action(|_, action: &ActionA, phase, cx| {
println!("Action A dispatched on child 1 during {:?}", phase);
})
@@ -110,7 +130,8 @@ impl FocusStory {
)
.child(
div()
- .id("child 2")
+ .id("child-2")
+ .context("child-2")
.on_action(|_, action: &ActionB, phase, cx| {
println!("Action B dispatched on child 2 during {:?}", phase);
})
@@ -1,16 +1,24 @@
use std::{
borrow::Cow,
fmt::{self, Debug},
+ hash::{Hash, Hasher},
sync::Arc,
};
-#[derive(PartialEq, Eq)]
pub enum ArcCow<'a, T: ?Sized> {
Borrowed(&'a T),
Owned(Arc<T>),
}
-use std::hash::{Hash, Hasher};
+impl<'a, T: ?Sized + PartialEq> PartialEq for ArcCow<'a, T> {
+ fn eq(&self, other: &Self) -> bool {
+ let a = self.as_ref();
+ let b = other.as_ref();
+ a == b
+ }
+}
+
+impl<'a, T: ?Sized + Eq> Eq for ArcCow<'a, T> {}
impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> {
fn hash<H: Hasher>(&self, state: &mut H) {