Skip to content

[WIP] Add Action associated type to Widget trait #939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

PoignardAzur
Copy link
Contributor

No description provided.

@PoignardAzur PoignardAzur changed the title TMP: Add Action associated type to Widget trait [WIP] Add Action associated type to Widget trait Apr 28, 2025
Copy link
Member

@DJMcNab DJMcNab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super excited for this!

/// Trait alias for traits [`Debug`] and [`Any`].
///
/// Used to be able to declare `Box<dyn DebugAndAny>`.
pub trait DebugAndAny: Debug + Any {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd consider calling this AnyAction. That opens the door to future trait bounds without needing to rename all uses of this.

(That name suggestion is based on this being exactly the same idea as

/// A simple dynamically typed message for the [`View`] trait.
///
/// This is a thin wrapper around `Box<dyn Any>`, with added support for debug printing.
/// It is used as the default message type in Xilem Core.
/// The contained messages must also be [`Send`], which makes using this message type in a multithreaded context easier.
/// [`View`] is generic over the message type, in case this requirement is too restrictive.
/// Indeed, this functionality is used in Xilem Web.
///
/// To convert a `DynMessage` into its concrete message type, you should use
/// [`downcast`](Self::downcast).
///
/// This type is a struct rather than (say) a type alias, because type aliases are sometimes resolved by
/// rust-analyzer when autofilling a trait, which can also lead to buggy behaviour (we've previously seen
/// `Box<dyn Box<dyn Message>>` be generated).
///
/// If the message contains sensitive data, make sure this isn't output in its `Debug` implementation,
/// as that may be called by the Xilem runtime (e.g. due to a bug meaning messages are redirected) or
/// any parent views. (That is, views do not need to design themselves as if the Debug implementation is )
///
/// [`View`]: crate::View
#[derive(Debug)]
pub struct DynMessage(pub Box<dyn AnyMessage>);
impl DynMessage {
/// Utility to make a `DynMessage` from a message value.
pub fn new(x: impl AnyMessage) -> Self {
Self(Box::new(x))
}
/// Access the actual type of this [`DynMessage`].
///
/// ## Errors
///
/// If the message contained within `self` is not of type `T`, returns `self`
/// (so that e.g. a different type can be used).
///
/// In most cases, to handle this error, you will want to make an `error` log,
/// and return this as [`MessageResult::Stale`]; this case indicates that a parent
/// view has routed things incorrectly, but it's reasonable to be robust.
pub fn downcast<T: AnyMessage>(self) -> Result<Box<T>, Self> {
self.0.downcast().map_err(Self)
}
/// Returns `true` if the inner type is the same as `T`.
pub fn is<T: AnyMessage>(&self) -> bool {
self.0.is::<T>()
}
}
/// Types which can be used in [`DynMessage`] (and so can be the messages for Xilem views).
///
/// The `Debug` requirement allows inspecting messages which were sent to the wrong place.
// TODO: Should/Could we remove the `Send` requirement here?
// It's not like implementing message handling in parallel is a meaningful operation.
// (If you need to send one, you can always use `dyn AnyMessage + Send`)
// Making that change would mean we could make View no longer generic over the message type again.
pub trait AnyMessage: Any + Debug + Send {}
impl<T> AnyMessage for T where T: Any + Debug + Send {}
impl dyn AnyMessage {
/// Access the actual type of this [`DynMessage`].
///
/// ## Errors
///
/// If the message contained within `self` is not of type `T`, returns `self`
/// (so that e.g. a different type can be used)
pub fn downcast<T: AnyMessage>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
if self.is::<T>() {
Ok((self as Box<dyn Any>).downcast::<T>().unwrap())
} else {
Err(self)
}
}
/// Returns `true` if the inner type is the same as `T`.
pub fn is<T: AnyMessage>(&self) -> bool {
let this: &dyn Any = self;
this.is::<T>()
}
}
; you may as well import similar tests and utility methods as well)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the point where I saw AnyMessage was when I decided to just commit what I had and stop.

My plan is to create a clean slate PR that moves AnyMessage to Masonry, and when it's merged, rebase this PR on top of it.

// TODO - TextCursor changed, ImeChanged, EnterKey, MouseEnter
#[non_exhaustive]
/// Events from UI elements.
///
/// Note: Actions are still a WIP feature.
pub enum Action {
pub enum DefaultAction {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we document that this is an intermediate state. I think for reviewing, it wouldn't be unreasonable to leave this as-is, and then split it out as a follow-up

@PoignardAzur
Copy link
Contributor Author

PoignardAzur commented Apr 29, 2025

For the record, this is extremely incomplete code (I just committed what I had when I stopped working) and it's not super high-priority, so I might leave it as it is for a while.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants