use std::sync::Arc;
use crate::*;
#[derive(Clone)]
pub struct StatusBarItem {
pub id: SharedString,
pub label: SharedString,
pub icon: Option<IconName>,
pub tooltip: Option<SharedString>,
pub on_click: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + Send + Sync>>,
pub muted: bool,
pub active: bool,
pub disabled: bool,
}
impl StatusBarItem {
pub fn new(label: impl Into<SharedString>) -> Self {
let label = label.into();
Self {
id: label.clone(),
label,
icon: None,
tooltip: None,
on_click: None,
muted: false,
active: false,
disabled: false,
}
}
pub fn id(mut self, id: impl Into<SharedString>) -> Self {
self.id = id.into();
self
}
pub fn icon(mut self, icon: IconName) -> Self {
self.icon = Some(icon);
self
}
pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
pub fn on_click(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + Send + Sync + 'static,
) -> Self {
self.on_click = Some(Arc::new(handler));
self
}
pub fn muted(mut self, muted: bool) -> Self {
self.muted = muted;
self
}
pub fn active(mut self, active: bool) -> Self {
self.active = active;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
#[derive(IntoElement)]
pub struct StatusBar {
left_items: Vec<StatusBarItem>,
right_items: Vec<StatusBarItem>,
style: StyleRefinement,
}
impl StatusBar {
pub fn new() -> Self {
Self {
left_items: Vec::new(),
right_items: Vec::new(),
style: StyleRefinement::default(),
}
}
pub fn left(mut self, items: Vec<StatusBarItem>) -> Self {
self.left_items = items;
self
}
pub fn right(mut self, items: Vec<StatusBarItem>) -> Self {
self.right_items = items;
self
}
}
impl Styled for StatusBar {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for StatusBar {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let user_style = self.style;
h_flex()
.w_full()
.h(px(28.))
.items_center()
.justify_between()
.px_2()
.bg(theme.status_bar)
.border_t_1()
.border_color(theme.status_bar_border)
.child(
h_flex().items_center().gap_3().children(
self.left_items
.iter()
.map(|item| render_status_bar_item("left", item, cx)),
),
)
.child(
h_flex().items_center().gap_3().children(
self.right_items
.iter()
.map(|item| render_status_bar_item("right", item, cx)),
),
)
.refine_style(&user_style)
}
}
fn render_status_bar_item(side: &'static str, item: &StatusBarItem, cx: &mut App) -> AnyElement {
let theme = cx.theme();
let clickable = item.on_click.is_some() && !item.disabled;
let mut element = div()
.id(format!("status-bar-{side}-{}", item.id))
.h_full()
.flex()
.items_center()
.gap_1()
.px_2()
.rounded(theme.radius * 0.5)
.text_xs()
.text_color(if item.muted || item.disabled {
theme.muted_foreground
} else {
theme.foreground
});
if item.disabled {
element = element.opacity(0.5);
} else if item.active {
element = element.bg(theme.accent.opacity(0.15));
}
if let Some(icon) = item.icon.clone() {
element = element.child(Icon::new(icon).small());
}
element = element.child(item.label.clone());
if clickable {
let on_click = item.on_click.clone().expect("clickable implies handler");
element = element
.cursor_pointer()
.hover(|this| this.bg(theme.secondary_hover))
.on_click(move |event, window, cx| on_click(event, window, cx));
}
if let Some(tooltip) = item.tooltip.clone() {
element =
element.tooltip(move |window, cx| Tooltip::new(tooltip.clone()).build(window, cx));
}
element.into_any_element()
}
#[cfg(test)]
mod tests {
use super::{StatusBar, StatusBarItem};
use crate::{Context, IconName, Render, SharedString, Window};
struct Probe {
left: Vec<StatusBarItem>,
right: Vec<StatusBarItem>,
}
impl Render for Probe {
fn render(
&mut self,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> impl crate::IntoElement {
StatusBar::new()
.left(self.left.clone())
.right(self.right.clone())
}
}
#[test]
fn status_bar_defaults_to_empty() {
let bar = StatusBar::new();
assert!(bar.left_items.is_empty());
assert!(bar.right_items.is_empty());
}
#[test]
fn item_id_defaults_to_label() {
assert_eq!(StatusBarItem::new("42").id, SharedString::from("42"));
assert_eq!(
StatusBarItem::new("42").id("cursor").id,
SharedString::from("cursor")
);
}
#[rgpui::test]
fn renders_items_without_panic(cx: &mut crate::TestAppContext) {
let left = vec![
StatusBarItem::new("main")
.id("branch")
.icon(IconName::Check)
.on_click(|_, _, _| {}),
StatusBarItem::new("rendered")
.id("mode")
.active(true)
.on_click(|_, _, _| {}),
];
let right = vec![
StatusBarItem::new("Ln 1, Col 1").id("cursor"),
StatusBarItem::new("3 words")
.id("words")
.muted(true)
.tooltip("word count"),
StatusBarItem::new("off").id("off").disabled(true),
];
let (_view, cx) = cx.add_window_view(|_, _| Probe { left, right });
cx.update(|window, cx| {
_ = window.draw(cx);
});
}
}