diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-02-28 18:01:33 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-02-28 18:49:31 +0800 |
| commit | 05b7b483056902a07f83bc14f443ab59ce1471d8 (patch) | |
| tree | e46c9e5e672a6d0e9baf4bca09ea159ad669f881 /src/progress.rs | |
First version0.1.1
Diffstat (limited to 'src/progress.rs')
| -rw-r--r-- | src/progress.rs | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/src/progress.rs b/src/progress.rs new file mode 100644 index 0000000..a5509ef --- /dev/null +++ b/src/progress.rs @@ -0,0 +1,200 @@ +use std::{collections::HashMap, fmt::Display, sync::OnceLock}; +use tokio::sync::watch; + +pub static PROGRESS_CENTER: OnceLock<ProgressCenter> = OnceLock::new(); + +const SPECIAL_FLAG_CLEAR: &str = "_clear"; +const SPECIAL_FLAG_CLOSE: &str = "_close"; + +#[derive(Debug)] +pub struct ProgressCenter { + tx: watch::Sender<HashMap<String, ProgressState>>, + rx: watch::Receiver<HashMap<String, ProgressState>>, +} + +/// Initialize `ProgressCenter` early in the program +/// +/// ``` rust +/// use just_progress::progress; +/// +/// fn main() { +/// // Initialize +/// let progress_center = progress::init(); +/// } +/// ``` +pub fn init() -> &'static ProgressCenter { + let (tx, rx) = watch::channel::<HashMap<String, ProgressState>>(HashMap::new()); + PROGRESS_CENTER.get_or_init(|| ProgressCenter { tx, rx }) +} + +/// Bind a callback to ProgressCenter and create a Future for receiving and rendering messages +/// +/// +/// ```rust +/// use just_progress::progress; +/// +/// #[tokio::main] +/// async fn main() { +/// // Initialize +/// let progress_center = progress::init(); +/// let bind_future = progress::bind(progress_center, |name, state| { +/// println!("Update progress `{}` state to `{}`", name, state); +/// }); +/// } +/// ``` +pub fn bind<F>( + center: &'static ProgressCenter, + mut callback: F, +) -> impl std::future::Future<Output = ()> + Send +where + F: FnMut(String, ProgressState) + Send + 'static, +{ + let mut rx = center.rx.clone(); + async move { + while rx.changed().await.is_ok() { + let state = rx.borrow().clone(); + + // If it's a close flag, break + if let Some(_) = state.get(SPECIAL_FLAG_CLOSE) { + break; + } + // Clear flag + if let Some(_) = state.get(SPECIAL_FLAG_CLEAR) { + // Send all known names + progress 0 + EmptyState + for (name, _) in state.iter().filter(|(k, _)| *k != SPECIAL_FLAG_CLEAR) { + callback( + name.clone(), + ProgressState { + progress: 0.0, + info: ProgressInfo::Empty, + }, + ); + } + // Clear the entire HashMap + let _ = center.tx.send(HashMap::new()); + } else { + for (name, progress_state) in state { + callback(name, progress_state); + } + } + } + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct ProgressState { + pub(crate) progress: f32, + pub(crate) info: ProgressInfo, +} + +impl ProgressState { + /// Get the progress value (0.0 to 1.0) + pub fn progress(&self) -> f32 { + self.progress + } + + /// Get the progress info + pub fn info(&self) -> ProgressInfo { + self.info + } +} + +impl Display for ProgressState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let space = if !matches!(self.info, ProgressInfo::Empty) { + " " + } else { + "" + }; + write!(f, "{}{}({:.1}%)", self.info, space, self.progress * 100.0) + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub enum ProgressInfo { + #[default] + Empty, + Info(&'static str), + Warning(&'static str), + Error(&'static str), +} + +impl Display for ProgressInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProgressInfo::Empty => write!(f, ""), + ProgressInfo::Info(msg) => write!(f, "Info: `{}`", msg), + ProgressInfo::Warning(msg) => write!(f, "Warning: `{}`", msg), + ProgressInfo::Error(msg) => write!(f, "Error: `{}`", msg), + } + } +} + +/// Complete an item +/// +/// Set an item's progress to 100% so it disappears from progress rendering +pub fn complete(name: &str) { + update_progress(name, 1.); +} + +/// Clear all +/// +/// Send a message to clear all items, ensuring no progress remains +pub fn clear_all() { + update(SPECIAL_FLAG_CLEAR, 0., ProgressInfo::Empty); +} + +/// Close the channel +/// +/// Send a close message to stop bind() from running +pub fn close() { + update(SPECIAL_FLAG_CLOSE, 0., ProgressInfo::Empty); +} + +/// Update +/// +/// Update a progress item's information +pub fn update(name: &str, progress: f32, info: ProgressInfo) { + let Some(center) = PROGRESS_CENTER.get() else { + return; + }; + let mut state = center.tx.borrow().clone(); + state.insert(name.to_string(), ProgressState { progress, info }); + let _ = center.tx.send(state); +} + +/// Update progress +/// +/// Modify a progress item's value +pub fn update_progress(name: &str, progress: f32) { + let Some(center) = PROGRESS_CENTER.get() else { + return; + }; + let mut state = center.tx.borrow().clone(); + let entry = state + .entry(name.to_string()) + .or_insert_with(|| ProgressState { + progress: 0.0, + info: ProgressInfo::default(), + }); + entry.progress = progress; + let _ = center.tx.send(state); +} + +/// Update info +/// +/// Modify a progress item's status information +pub fn update_info(name: &str, info: ProgressInfo) { + let Some(center) = PROGRESS_CENTER.get() else { + return; + }; + let mut state = center.tx.borrow().clone(); + let entry = state + .entry(name.to_string()) + .or_insert_with(|| ProgressState { + progress: 0.0, + info: ProgressInfo::default(), + }); + entry.info = info; + let _ = center.tx.send(state); +} |
