Implement Feathers toasts#24071
Conversation
- Added four new (definitely not final) palette colors for severity levels error, warning, success and info - Implemented a toast widget that shows a progress bar and despawns automatically when duration reaches zero - Toast::on_remove component hook repositions other toasts higher in order than the removed toast
|
Welcome, new contributor! Please make sure you've read our contributing guide, as well as our policy regarding AI usage, and we look forward to reviewing your pull request shortly ✨ |
|
I'd like to point your attention to the visual style seen in react-toastify: https://www.npmjs.com/package/react-toastify - if you follow that link, you will see a video that shows examples of three different styles (color schemes) of toasts. Some of the styles are bright and colorful, others are more subdued. I think that for the Bevy editor, we probably want one of the more subdued styles - I think that bright, fluorescent colors will clash with the overall editor aesthetic. Ideally we'd get some of our artist volunteers to work on this, however the vibe I am getting from them is that they are not in favor of toasts as a feedback mechanism and prefer to use other means for status notifications (like messages in the status bar). That's OK, because I think toasts could be valuable even if the official bevy editor doesn't use them, as other editors using feathers might want to use toasts. |
While i can understand the rationale, I'm a bit surprised as this would break the mental model of "feathers is only opinionated theming on top of core widgets" (or maybe my understanding of feather's purpose is wrong). Regardless where it sits, games can always opt to not use/create their own widgets for something; why (try to) make that decision for them in advance? |
|
Ah, this same issue came up in the discussion on #24060 and I'll quote my response here:
Whether a widget has a headless impl or is feathers-only depends, I think, on the following criteria:
|
amtep
left a comment
There was a problem hiding this comment.
Hi I am new to bevy but I did the best I could :) I may be mistaken about how some things work though. I do have several years of experience with Rust.
| let mut entity_mut = world.entity_mut(entity); | ||
| let mut node = entity_mut | ||
| .get_mut::<Node>() | ||
| .expect("Node should be present in ToastPosition on_add"); |
There was a problem hiding this comment.
Is this guaranteed? I've had issues with on_add firing on a component before other components have been added to an entity.
Also, ToolPosition is pub and doesn't have require(Node) so you're relying on the user to make sure there is a Node.
Panicking may be extreme in this situation. Can you insert the Node if it's missing, instead?
There was a problem hiding this comment.
To be honest I have no idea if it's guaranteed but at least I've never personally experienced it not being there.
The only intended way to spawn a Toast is via the FeathersToast SceneComponent and it's fn scene adds the Node.. E.g. as described in the description
bsn! {
:FeathersToast {
@message: "Default success toast in bottom right",
}
};| match position { | ||
| ToastPosition::BottomRight => { | ||
| node.bottom = px(offset); | ||
| node.right = px(10.0); |
There was a problem hiding this comment.
Style note
This 10.0 here, is it TOAST_MARGIN as well? Should probably get a named constant.
More generally, I feel it would be better to get these layout sizes from a ToastSettings resource that the user can insert.
There was a problem hiding this comment.
Good suggestion to replace the literal value with TOAST_MARGIN_PX, that's done now.
As for user configurable settings I think the Feathers implementation shouldn't have user configurable settings as they're supposed to (AFAIK) be very opinionated. If it's decided that there should be bevy_ui_widgets headless implementation of toasts, then definitely all of the styling and behavioural settings should be user configurable (see #24071 (comment)).
| #[reflect(Component, Clone, Default)] | ||
| #[component(on_add, on_despawn)] | ||
| pub enum ToastPosition { | ||
| /// Bottom right corner of the screen. Siblings stack upwards. |
There was a problem hiding this comment.
This says "of the screen", but if a ToastPosition has a parent entity, does it get laid out inside the parent? (Which would be a useful feature, if it isn't already)
There was a problem hiding this comment.
The intended usage of these is only via the FeathersToast SceneComponent and therefore this implementation hasn't even considered such usage.
| #[derive(Component, Clone, Default, Reflect, Debug, PartialEq, Eq, Hash)] | ||
| #[reflect(Component, Clone, Default)] | ||
| #[component(on_add, on_despawn)] | ||
| pub enum ToastPosition { |
There was a problem hiding this comment.
I feel TopCenter and BottomCenter would also be useful. My main experience with toasts (Paradox's Crusader Kings game) has them top center.
There was a problem hiding this comment.
Imo and afaik the Feathers implementation should focus on editor/tooling UI. Should there be a need for Bevy provided toasts in games, that should reside in bevy_ui_widgets as a headless widget. I'll leave this open and not make any changes should we get more opinions on this. I'm more than okay with moving these to bevy_ui_widgets and provide a Feathers reference implementation tho!
| Self { | ||
| message: "".to_string(), // TODO: Could multiline messages be supported by passing a [`SceneList`]? | ||
| variant: ToastVariant::default(), | ||
| duration: Some(Duration::from_secs(3)), |
There was a problem hiding this comment.
Opinion
I feel that None would be a better default than picking a number of seconds, which is by nature arbitrary.
There was a problem hiding this comment.
I don't have a strong opinion in either direction. I'll keep this open an unchanged should more people want to weigh their opinions.
There was a problem hiding this comment.
However the duration of 3 sec came from Androids Snackbar.LENGTH_SHORT
- Renamed TOAST_HEIGHT to TOAST_HEIGHT_PX and TOAST_MARGIN to TOAST_MARGIN_PX - Replaced 10.0 literals with TOAST_MARGIN_PX - Corrected negative offset in ToastPosition::on_despawn - Replaced unwrapping props.duration with `if let` - Unconditionally always spawn Toast close button
|
It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. |
Objective
This PR adds one type of implementation of toasts/notifications in Feathers widgets.
Toasts can have different severity levels with
FeathersToastProps::variant, currently there areInfo,Success,WarningandError. Defaults toToastVariant::Info.Toasts can be positioned with
FeathersToastProps::position. Possible values areTopLeft,TopRight,BottomRightandBottomLeft. Defaults toToastPosition::BottomLeft.Toasts can be set to despawn automatically with
FeathersToastProps::duration. Defaults to 3 seconds.None. That way they're only closed by pressing the close icon.Toasts are positioned with
position_type: PositionType::Absoluteand their positioning is handled inToastPosition::on_addandToastPosition::on_despawnComponent hooks.ToastPositionsresource keeps track of each currently spawned toast asHashMap<ToastPosition, Vec<Entity>>. When a toast is despawned, the despawned toastson_despawnhook re-positions the remaining (higher indexed) toasts with sameToastPositionToastContainercomponent was spawned lazily for each corner and the toasts themselves were children of thatNode. That didn't require absolute positioning asTransformpropagation handled (re)positioning of the toasts. However discussions in Discord ui channel led me to try this type of approach instead as I thought that goal would be more easily achieved with this kind of architecture. I don't have a strong opinion in either approach and I'm open to suggestions!Part of Bevy Standard Widgets: Roadmap #19236 even tho notifications are under Core widgets in the tracking issue these are implemented as only Feathers widgets per discussions with @viridia.
Please consider the visual aspects of the toasts as a mere example, I'm terrible at designing user interfaces and I'm expecting some more professional designers to help me iterate this towards the final form, should this feature be considered wanted!
TODO:
Entityof the toast and despawn it once complete or b) observe anEntityEventon the toast that is triggered by the consumer once the asynchronous operation is completeTesting
feathers_galleryexample withcargo run --example feathers_gallery --features experimental_bevy_feathersShowcase
Screen.Recording.2026-05-02.at.9.20.09.mov
Spawning a toast is as simple as