use std::any::Any;
use std::rc::Rc;
pub struct Intent {
pub name: &'static str,
pub source: crate::telemetry::IntentSource,
payload: Option<Rc<dyn Any>>,
origin_window: Option<crate::window::TeksiloWindowId>,
activation_token: Option<String>,
crossings: usize,
kept_in_modal: bool,
}
impl Intent {
pub fn new(name: &'static str) -> Self {
Self {
name,
source: crate::telemetry::IntentSource::Programmatic,
payload: None,
origin_window: None,
activation_token: None,
crossings: 0,
kept_in_modal: false,
}
}
pub fn with_payload<T: 'static>(name: &'static str, payload: T) -> Self {
Self {
name,
source: crate::telemetry::IntentSource::Programmatic,
payload: Some(Rc::new(payload)),
origin_window: None,
activation_token: None,
crossings: 0,
kept_in_modal: false,
}
}
pub fn with_source(mut self, source: crate::telemetry::IntentSource) -> Self {
self.source = source;
self
}
#[doc(hidden)]
pub fn kept_in_modal(mut self) -> Self {
self.kept_in_modal = true;
self
}
#[doc(hidden)]
pub fn stays_in_modal(&self) -> bool {
self.kept_in_modal || self.source == crate::telemetry::IntentSource::Shortcut
}
pub fn payload<T: 'static>(&self) -> Option<&T> {
let any: &dyn Any = &**self.payload.as_ref()?;
any.downcast_ref::<T>()
}
pub fn has_payload(&self) -> bool {
self.payload.is_some()
}
pub fn origin_window(&self) -> Option<crate::window::TeksiloWindowId> {
self.origin_window
}
pub(crate) fn note_origin_window(&mut self, window: Option<crate::window::TeksiloWindowId>) {
if self.origin_window.is_none() {
self.origin_window = window;
}
}
#[doc(hidden)]
pub fn activation_token(&self) -> Option<&str> {
self.activation_token.as_deref()
}
pub(crate) fn take_activation_token(&mut self) -> Option<String> {
self.activation_token.take()
}
pub(crate) fn crossings(&self) -> usize {
self.crossings
}
pub(crate) fn note_crossings(&mut self, crossings: usize) {
self.crossings = self.crossings.max(crossings);
}
pub(crate) fn note_activation_token(&mut self, token: Option<String>) {
if self.activation_token.is_none() {
self.activation_token = token;
}
}
}
impl Clone for Intent {
fn clone(&self) -> Self {
Self {
name: self.name,
source: self.source,
payload: self.payload.clone(),
origin_window: self.origin_window,
activation_token: self.activation_token.clone(),
crossings: self.crossings,
kept_in_modal: self.kept_in_modal,
}
}
}
impl std::fmt::Debug for Intent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Intent")
.field("name", &self.name)
.field("source", &self.source)
.field("has_payload", &self.payload.is_some())
.finish()
}
}
pub trait IntentKind: Sized + 'static {
fn into_intent(self) -> Intent;
fn from_intent(intent: &Intent) -> Option<&Self>;
}
impl<K: IntentKind> From<K> for Intent {
fn from(kind: K) -> Self {
kind.into_intent()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IntentResponse {
#[default]
Handled,
Propagated,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intent_without_payload() {
let i = Intent::new("app.save");
assert_eq!(i.name, "app.save");
assert!(!i.has_payload());
assert_eq!(i.payload::<i32>(), None);
}
#[test]
fn typed_payload_round_trip() {
let i = Intent::with_payload("app.scroll_by", 42_i64);
assert!(i.has_payload());
assert_eq!(i.payload::<i64>(), Some(&42_i64));
assert_eq!(i.payload::<String>(), None);
}
#[test]
fn payload_carries_complex_types() {
#[derive(Debug, PartialEq)]
struct Dto {
id: i64,
name: String,
}
let i = Intent::with_payload(
"app.add_item",
Dto {
id: 7,
name: "Foo".into(),
},
);
assert_eq!(
i.payload::<Dto>(),
Some(&Dto {
id: 7,
name: "Foo".into(),
})
);
}
#[test]
fn default_response_is_handled() {
assert_eq!(IntentResponse::default(), IntentResponse::Handled);
}
}