use std::any::Any;
use std::fmt;
use std::sync::Arc;
pub use crate::effect::PopoverRequest;
use crate::ui::{Action, Item, Notice, Panel};
use crate::{Effect, NudgeRequest, Subscription, Task, Widget};
#[derive(Clone)]
pub struct AnyMessage {
value: Arc<dyn Any + Send + Sync>,
type_name: &'static str,
}
impl AnyMessage {
pub fn new<M: Any + Send + Sync>(message: M) -> Self {
Self {
value: Arc::new(message),
type_name: std::any::type_name::<M>(),
}
}
pub fn downcast<M: Any + Clone + Send + Sync>(self) -> Option<M> {
let value = self.value.downcast::<M>().ok()?;
Some(Arc::try_unwrap(value).unwrap_or_else(|shared| (*shared).clone()))
}
}
impl fmt::Debug for AnyMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AnyMessage<{}>", self.type_name)
}
}
pub trait ErasedWidget {
fn update(&mut self, message: AnyMessage) -> Effect<AnyMessage>;
fn subscription(&self) -> Subscription<AnyMessage>;
fn item(&self) -> Item<AnyMessage>;
fn popover(&self) -> Option<Panel<AnyMessage>>;
fn notices(&self) -> Vec<Notice<AnyMessage>>;
fn actions(&self) -> Vec<Action<AnyMessage>>;
}
impl<W: Widget> ErasedWidget for W {
fn update(&mut self, message: AnyMessage) -> Effect<AnyMessage> {
let Some(message) = message.downcast::<W::Message>() else {
debug_assert!(false, "message routed to the wrong widget");
return Effect::none();
};
let (task, popover, nudge) = Widget::update(self, message).into_parts();
let effect = Effect::task(task.map(AnyMessage::new));
let effect = match popover {
Some(PopoverRequest::Open) => effect.open_popover(),
Some(PopoverRequest::Close) => effect.close_popover(),
None => effect,
};
match nudge {
Some(NudgeRequest {
nudge,
lasting: Some(lasting),
}) => effect.nudge_lasting(nudge, lasting),
Some(NudgeRequest { nudge, .. }) => effect.nudge(nudge),
None => effect,
}
}
fn subscription(&self) -> Subscription<AnyMessage> {
Widget::subscription(self).map(AnyMessage::new::<W::Message>)
}
fn item(&self) -> Item<AnyMessage> {
Widget::item(self).map(&AnyMessage::new)
}
fn popover(&self) -> Option<Panel<AnyMessage>> {
Widget::popover(self).map(|panel| panel.map(&AnyMessage::new))
}
fn notices(&self) -> Vec<Notice<AnyMessage>> {
Widget::notices(self)
.into_iter()
.map(|notice| notice.map(&AnyMessage::new))
.collect()
}
fn actions(&self) -> Vec<Action<AnyMessage>> {
Widget::actions(self)
.into_iter()
.map(|action| action.map(&AnyMessage::new))
.collect()
}
}
pub type Constructor = fn(toml::Table) -> Result<(Box<dyn ErasedWidget>, Task<AnyMessage>), String>;
pub(crate) fn construct<W: Widget>(
config: toml::Table,
) -> Result<(Box<dyn ErasedWidget>, Task<AnyMessage>), String> {
let config: W::Config = config.try_into().map_err(|error: toml::de::Error| {
error
.to_string()
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
})?;
let (widget, task) = W::new(config);
Ok((Box::new(widget), task.map(AnyMessage::new)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_config_accepts_only_nothing() {
let empty: Result<crate::NoConfig, _> = toml::Table::new().try_into();
assert!(empty.is_ok());
let mut extra = toml::Table::new();
extra.insert("format".into(), "x".into());
assert!(extra.try_into::<crate::NoConfig>().is_err());
}
#[test]
fn actions_carry_the_widgets_messages() {
struct Acting;
impl Widget for Acting {
type Config = crate::NoConfig;
type Message = u8;
fn new(_: Self::Config) -> (Self, Task<u8>) {
(Acting, Task::none())
}
fn update(&mut self, _: u8) -> Effect<u8> {
Effect::none()
}
fn item(&self) -> Item<u8> {
Item::new(crate::ui::empty())
}
fn actions(&self) -> Vec<Action<u8>> {
vec![crate::ui::action("Mute", 7).keywords(["sound"])]
}
}
let actions = ErasedWidget::actions(&Acting);
let parts = actions.into_iter().next().unwrap().into_parts();
assert_eq!(parts.title, "Mute");
assert_eq!(parts.keywords, ["sound"]);
assert_eq!(parts.message.downcast::<u8>(), Some(7));
}
#[test]
fn downcast_round_trips() {
let message = AnyMessage::new(42u8);
assert_eq!(message.clone().downcast::<u8>(), Some(42));
assert_eq!(message.downcast::<u16>(), None);
}
}