use crate::theme::Theme;
use ratatui::prelude::*;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum StatusBarItemContent {
Static(String),
ElapsedTime {
elapsed_ms: u64,
running: bool,
long_format: bool,
},
Counter {
value: u64,
label: Option<String>,
},
Heartbeat {
active: bool,
frame: usize,
},
}
impl StatusBarItemContent {
pub fn static_text(text: impl Into<String>) -> Self {
Self::Static(text.into())
}
pub fn elapsed_time() -> Self {
Self::ElapsedTime {
elapsed_ms: 0,
running: false,
long_format: false,
}
}
pub fn counter() -> Self {
Self::Counter {
value: 0,
label: None,
}
}
pub fn heartbeat() -> Self {
Self::Heartbeat {
active: false,
frame: 0,
}
}
pub(super) fn display_text(&self) -> String {
match self {
Self::Static(text) => text.clone(),
Self::ElapsedTime {
elapsed_ms,
long_format,
..
} => {
let total_seconds = elapsed_ms / 1000;
let hours = total_seconds / 3600;
let minutes = (total_seconds % 3600) / 60;
let seconds = total_seconds % 60;
if *long_format || hours > 0 {
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
} else {
format!("{:02}:{:02}", minutes, seconds)
}
}
Self::Counter { value, label } => {
if let Some(label) = label {
format!("{}: {}", label, value)
} else {
value.to_string()
}
}
Self::Heartbeat { active, frame } => {
const FRAMES: [&str; 4] = ["♡", "♥", "♥", "♡"];
if *active {
FRAMES[*frame % FRAMES.len()].to_string()
} else {
"♡".to_string()
}
}
}
}
pub(super) fn is_dynamic(&self) -> bool {
!matches!(self, Self::Static(_))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum StatusBarStyle {
#[default]
Default,
Info,
Success,
Warning,
Error,
Muted,
}
impl StatusBarStyle {
pub(super) fn style(self, theme: &Theme) -> Style {
match self {
Self::Default => theme.normal_style(),
Self::Info => theme.info_style(),
Self::Success => theme.success_style(),
Self::Warning => theme.warning_style(),
Self::Error => theme.error_style(),
Self::Muted => theme.disabled_style(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct StatusBarItem {
pub(super) content: StatusBarItemContent,
pub(super) style: StatusBarStyle,
separator: bool,
}
impl StatusBarItem {
pub fn new(text: impl Into<String>) -> Self {
Self {
content: StatusBarItemContent::Static(text.into()),
style: StatusBarStyle::Default,
separator: true,
}
}
pub fn elapsed_time() -> Self {
Self {
content: StatusBarItemContent::elapsed_time(),
style: StatusBarStyle::Default,
separator: true,
}
}
pub fn elapsed_time_long() -> Self {
Self {
content: StatusBarItemContent::ElapsedTime {
elapsed_ms: 0,
running: false,
long_format: true,
},
style: StatusBarStyle::Default,
separator: true,
}
}
pub fn counter() -> Self {
Self {
content: StatusBarItemContent::counter(),
style: StatusBarStyle::Default,
separator: true,
}
}
pub fn heartbeat() -> Self {
Self {
content: StatusBarItemContent::heartbeat(),
style: StatusBarStyle::Default,
separator: true,
}
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
if let StatusBarItemContent::Counter {
value,
label: ref mut lbl,
} = self.content
{
*lbl = Some(label.into());
self.content = StatusBarItemContent::Counter {
value,
label: lbl.clone(),
};
}
self
}
pub fn with_long_format(mut self, long: bool) -> Self {
if let StatusBarItemContent::ElapsedTime {
elapsed_ms,
running,
..
} = self.content
{
self.content = StatusBarItemContent::ElapsedTime {
elapsed_ms,
running,
long_format: long,
};
}
self
}
pub fn with_style(mut self, style: StatusBarStyle) -> Self {
self.style = style;
self
}
pub fn with_separator(mut self, separator: bool) -> Self {
self.separator = separator;
self
}
pub fn text(&self) -> String {
self.content.display_text()
}
pub fn content(&self) -> &StatusBarItemContent {
&self.content
}
pub fn content_mut(&mut self) -> &mut StatusBarItemContent {
&mut self.content
}
pub fn set_text(&mut self, text: impl Into<String>) {
self.content = StatusBarItemContent::Static(text.into());
}
pub fn style(&self) -> StatusBarStyle {
self.style
}
pub fn set_style(&mut self, style: StatusBarStyle) {
self.style = style;
}
pub fn has_separator(&self) -> bool {
self.separator
}
pub fn set_separator(&mut self, separator: bool) {
self.separator = separator;
}
pub fn is_dynamic(&self) -> bool {
self.content.is_dynamic()
}
pub(super) fn tick(&mut self, delta_ms: u64) -> bool {
match &mut self.content {
StatusBarItemContent::ElapsedTime {
elapsed_ms,
running: true,
..
} => {
*elapsed_ms += delta_ms;
true
}
StatusBarItemContent::Heartbeat {
active: true,
frame,
} => {
*frame = (*frame + 1) % 4;
true
}
_ => false,
}
}
}