use egui::{Color32, Rect, Response, Sense, Stroke, StrokeKind, Ui, Vec2, Widget, vec2};
use super::{alpha, corner};
use crate::{Icon, RADIUS, SPACING, palette_of};
pub struct ActivityItem<'a, T> {
pub value: T,
pub icon: Icon,
pub tooltip: Option<&'a str>,
pub badge: bool,
pub disabled: bool,
}
impl<'a, T> ActivityItem<'a, T> {
pub fn new(value: T, icon: Icon) -> Self {
Self {
value,
icon,
tooltip: None,
badge: false,
disabled: false,
}
}
pub fn tooltip(mut self, text: &'a str) -> Self {
self.tooltip = Some(text);
self
}
pub fn badge(mut self, on: bool) -> Self {
self.badge = on;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
pub struct ActivityBar<'a, T: PartialEq + Clone> {
selected: &'a mut Option<T>,
items: Vec<ActivityItem<'a, T>>,
bottom: Vec<ActivityItem<'a, T>>,
width: f32,
}
impl<'a, T: PartialEq + Clone> ActivityBar<'a, T> {
pub fn new(selected: &'a mut Option<T>) -> Self {
Self {
selected,
items: Vec::new(),
bottom: Vec::new(),
width: 44.0,
}
}
pub fn item(mut self, item: ActivityItem<'a, T>) -> Self {
self.items.push(item);
self
}
pub fn bottom_item(mut self, item: ActivityItem<'a, T>) -> Self {
self.bottom.push(item);
self
}
pub fn width(mut self, w: f32) -> Self {
self.width = w;
self
}
pub fn show(self, ui: &mut Ui) -> Response {
let palette = palette_of(ui.ctx());
let Self {
selected,
items,
bottom,
width,
} = self;
let mut last: Option<Response> = None;
ui.vertical(|ui| {
ui.set_width(width);
ui.spacing_mut().item_spacing.y = 2.0;
ui.add_space(SPACING.s2);
for item in &items {
let r = paint_item(ui, item, selected, width);
last = Some(r);
}
if !bottom.is_empty() {
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
ui.add_space(SPACING.s2);
for item in bottom.iter().rev() {
let r = paint_item(ui, item, selected, width);
last = Some(r);
}
let y = ui.cursor().top() - 4.0;
ui.painter().line_segment(
[
egui::pos2(ui.min_rect().left() + 8.0, y),
egui::pos2(ui.min_rect().right() - 8.0, y),
],
Stroke::new(1.0, palette.border_subtle),
);
});
}
});
last.unwrap_or_else(|| ui.allocate_response(vec2(0.0, 0.0), Sense::hover()))
}
}
fn paint_item<T: PartialEq + Clone>(
ui: &mut Ui,
item: &ActivityItem<'_, T>,
selected: &mut Option<T>,
width: f32,
) -> Response {
let palette = palette_of(ui.ctx());
let h = 36.0;
let sense = if item.disabled {
Sense::hover()
} else {
Sense::click()
};
let (rect, mut response) = ui.allocate_exact_size(vec2(width, h), sense);
let is_selected = selected.as_ref() == Some(&item.value);
let bg = if item.disabled {
Color32::TRANSPARENT
} else if is_selected {
alpha(palette.brand_default, 0.14)
} else if response.hovered() {
palette.bg_hover
} else {
Color32::TRANSPARENT
};
ui.painter().rect_filled(rect, corner(RADIUS.sm), bg);
if is_selected {
let bar = Rect::from_min_max(
egui::pos2(rect.left(), rect.top() + 6.0),
egui::pos2(rect.left() + 2.5, rect.bottom() - 6.0),
);
ui.painter()
.rect_filled(bar, corner(RADIUS.sm), palette.brand_default);
}
let icon_size: f32 = 18.0;
let icon_rect = Rect::from_center_size(rect.center(), Vec2::splat(icon_size));
let color = if item.disabled {
alpha(palette.text_secondary, 0.45)
} else if is_selected {
palette.brand_default
} else {
palette.text_secondary
};
item.icon.paint(ui.painter(), icon_rect, color);
if item.badge {
let dot_r = 3.0;
let center = egui::pos2(rect.right() - 9.0, rect.top() + 9.0);
ui.painter()
.circle_filled(center, dot_r + 1.5, palette.bg_app);
ui.painter().circle_filled(center, dot_r, palette.error);
}
if response.clicked() && !item.disabled {
if is_selected {
*selected = None;
} else {
*selected = Some(item.value.clone());
}
response.mark_changed();
}
if response.has_focus() && !item.disabled {
ui.painter().rect_stroke(
rect.expand(1.0),
corner(RADIUS.sm),
Stroke::new(2.0, palette.focus_ring),
StrokeKind::Outside,
);
}
if let Some(tip) = item.tooltip {
return crate::components::tooltip(&response, tip);
}
response
}
impl<'a, T: PartialEq + Clone> Widget for ActivityBar<'a, T> {
fn ui(self, ui: &mut Ui) -> Response {
self.show(ui)
}
}