From 1792cba1f5586242bb2ff735b518ca82b8417a0a Mon Sep 17 00:00:00 2001 From: bbb651 Date: Thu, 22 Aug 2024 15:43:49 +0300 Subject: [PATCH] improv(toaster): switch to a slotmap for storing toasts This fixes the toast clear-after-duration commands not clearing the right toasts, leading to toasts being clearing too early or not at all. This switches the public interface from `usize` index based ids to opaque `ToastId` slotmap keys. --- src/widget/mod.rs | 2 +- src/widget/toaster/mod.rs | 41 +++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index e54b82cd..9eb9c16b 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -319,7 +319,7 @@ pub use text_input::{ pub mod toaster; #[doc(inline)] -pub use toaster::{toaster, Toast, Toasts}; +pub use toaster::{toaster, Toast, ToastId, Toasts}; mod toggler; #[doc(inline)] diff --git a/src/widget/toaster/mod.rs b/src/widget/toaster/mod.rs index 8f1e436c..82ebde82 100644 --- a/src/widget/toaster/mod.rs +++ b/src/widget/toaster/mod.rs @@ -10,6 +10,8 @@ use crate::widget::container; use crate::widget::Column; use crate::Command; use iced_core::Element; +use slotmap::new_key_type; +use slotmap::SlotMap; use widget::Toaster; use crate::ext::CollectionWidget; @@ -33,7 +35,7 @@ pub fn toaster<'a, Message: Clone + 'static>( .. } = theme.cosmic().spacing; - let make_toast = move |(id, toast): (usize, &'a Toast)| { + let make_toast = move |(id, toast): (ToastId, &'a Toast)| { let row = row() .push(text(&toast.message)) .push( @@ -57,9 +59,9 @@ pub fn toaster<'a, Message: Clone + 'static>( }; let col = toasts - .toasts + .queue .iter() - .enumerate() + .filter_map(|id| Some((*id, toasts.toasts.get(*id)?))) .rev() .map(make_toast) .fold(column::with_capacity(toasts.toasts.len()), Column::push) @@ -99,7 +101,7 @@ impl From for Duration { #[derive(Clone)] pub struct Action { pub description: String, - pub message: Rc Message>, + pub message: Rc Message>, } impl std::fmt::Debug for Action { @@ -133,7 +135,7 @@ impl Toast { pub fn action( mut self, description: String, - message: impl Fn(usize) -> Message + 'static, + message: impl Fn(ToastId) -> Message + 'static, ) -> Self { self.action.replace(Action { description, @@ -150,32 +152,41 @@ impl Toast { } } +new_key_type! { pub struct ToastId; } + #[derive(Debug, Clone)] pub struct Toasts { - toasts: VecDeque>, - on_close: fn(usize) -> Message, + toasts: SlotMap>, + // Note: can ghost keys left from calling `self.remove` + queue: VecDeque, + on_close: fn(ToastId) -> Message, limit: usize, } impl Toasts { - pub fn new(on_close: fn(usize) -> Message) -> Self { + pub fn new(on_close: fn(ToastId) -> Message) -> Self { + let limit = 5; Self { - toasts: VecDeque::new(), + toasts: SlotMap::with_capacity_and_key(limit), + queue: VecDeque::new(), on_close, - limit: 5, + limit, } } /// Add a new [`Toast`] pub fn push(&mut self, toast: Toast) -> Command { while self.toasts.len() >= self.limit { - self.toasts.pop_front(); + self.toasts.remove( + self.queue + .pop_front() + .expect("Queue must contain all toast ids"), + ); } let duration = toast.duration.duration(); - - let id = self.toasts.len(); - self.toasts.push_back(toast); + let id = self.toasts.insert(toast); + self.queue.push_back(id); #[cfg(feature = "tokio")] { @@ -192,7 +203,7 @@ impl Toasts { } /// Remove a [`Toast`] - pub fn remove(&mut self, id: usize) { + pub fn remove(&mut self, id: ToastId) { self.toasts.remove(id); } }