use crate::theme::get_global_color;
use egui::{
ecolor::Color32, pos2, FontId, Rect, Response,
Sense, Ui, Vec2, Widget,
};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BadgeColor {
Primary,
Error,
Success,
Warning,
Neutral,
Custom(Color32, Color32), }
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BadgeSize {
Small,
Regular,
Large,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BadgePosition {
TopRight,
TopLeft,
BottomRight,
BottomLeft,
Custom(Vec2),
}
#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
pub struct MaterialBadge {
content: String,
color: BadgeColor,
size: BadgeSize,
dot: bool,
position_offset: Vec2,
}
impl MaterialBadge {
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
color: BadgeColor::Error,
size: BadgeSize::Regular,
dot: false,
position_offset: Vec2::new(0.0, 0.0),
}
}
pub fn dot() -> Self {
Self {
content: String::new(),
color: BadgeColor::Error,
size: BadgeSize::Small,
dot: true,
position_offset: Vec2::new(0.0, 0.0),
}
}
pub fn color(mut self, color: BadgeColor) -> Self {
self.color = color;
self
}
pub fn size(mut self, size: BadgeSize) -> Self {
self.size = size;
self
}
pub fn as_dot(mut self, dot: bool) -> Self {
self.dot = dot;
self
}
pub fn position_offset(mut self, offset: Vec2) -> Self {
self.position_offset = offset;
self
}
pub fn draw_on(
&self,
ui: &mut Ui,
target_rect: Rect,
position: BadgePosition,
) -> Response {
let (bg_color, text_color) = self.get_colors();
let (min_width, min_height, font_size) = self.get_dimensions();
let painter = ui.painter();
let text_galley = if !self.dot && !self.content.is_empty() {
Some(painter.layout_no_wrap(
self.content.clone(),
FontId::proportional(font_size),
text_color,
))
} else {
None
};
let badge_width = if let Some(ref galley) = text_galley {
(galley.size().x + min_width).max(min_height)
} else {
min_height
};
let badge_height = min_height;
let overlap_factor = 0.95;
let badge_pos = match position {
BadgePosition::TopRight => {
pos2(
target_rect.max.x - badge_width * (1.0 - overlap_factor),
target_rect.min.y - badge_height * (1.0 - overlap_factor),
)
}
BadgePosition::TopLeft => {
pos2(
target_rect.min.x - badge_width * overlap_factor,
target_rect.min.y - badge_height * (1.0 - overlap_factor),
)
}
BadgePosition::BottomRight => {
pos2(
target_rect.max.x - badge_width * (1.0 - overlap_factor),
target_rect.max.y - badge_height * (1.0 - overlap_factor),
)
}
BadgePosition::BottomLeft => {
pos2(
target_rect.min.x - badge_width * overlap_factor,
target_rect.max.y - badge_height * (1.0 - overlap_factor),
)
}
BadgePosition::Custom(offset) => {
pos2(target_rect.center().x + offset.x, target_rect.center().y + offset.y)
}
};
let badge_pos = pos2(
badge_pos.x + self.position_offset.x,
badge_pos.y + self.position_offset.y,
);
let badge_rect = Rect::from_center_size(badge_pos, Vec2::new(badge_width, badge_height));
painter.rect_filled(badge_rect, badge_height / 2.0, bg_color);
if let Some(galley) = text_galley {
painter.galley(
pos2(
badge_rect.center().x - galley.size().x / 2.0,
badge_rect.center().y - galley.size().y / 2.0,
),
galley,
text_color,
);
}
ui.interact(badge_rect, ui.id().with("badge"), Sense::hover())
}
fn get_colors(&self) -> (Color32, Color32) {
match self.color {
BadgeColor::Primary => {
let bg = get_global_color("primary");
let text = get_global_color("onPrimary");
(bg, text)
}
BadgeColor::Error => (
Color32::from_rgb(239, 68, 68), Color32::WHITE,
),
BadgeColor::Success => (
Color32::from_rgb(34, 197, 94), Color32::WHITE,
),
BadgeColor::Warning => (
Color32::from_rgb(234, 179, 8), Color32::WHITE,
),
BadgeColor::Neutral => (
Color32::from_rgb(107, 114, 128), Color32::WHITE,
),
BadgeColor::Custom(bg, text) => (bg, text),
}
}
fn get_dimensions(&self) -> (f32, f32, f32) {
match self.size {
BadgeSize::Small => {
if self.dot {
(0.0, 8.0, 0.0) } else {
(4.0, 16.0, 10.0) }
}
BadgeSize::Regular => {
if self.dot {
(0.0, 10.0, 0.0) } else {
(6.0, 20.0, 12.0) }
}
BadgeSize::Large => {
if self.dot {
(0.0, 12.0, 0.0) } else {
(8.0, 24.0, 14.0) }
}
}
}
}
impl Widget for MaterialBadge {
fn ui(self, ui: &mut Ui) -> Response {
let (bg_color, text_color) = self.get_colors();
let (min_width, min_height, font_size) = self.get_dimensions();
let text_galley = if !self.dot && !self.content.is_empty() {
Some(ui.painter().layout_no_wrap(
self.content.clone(),
FontId::proportional(font_size),
text_color,
))
} else {
None
};
let badge_width = if let Some(ref galley) = text_galley {
(galley.size().x + min_width).max(min_height)
} else {
min_height
};
let badge_height = min_height;
let desired_size = Vec2::new(badge_width, badge_height);
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::hover());
ui.painter()
.rect_filled(rect, badge_height / 2.0, bg_color);
if let Some(galley) = text_galley {
ui.painter().galley(
pos2(
rect.center().x - galley.size().x / 2.0,
rect.center().y - galley.size().y / 2.0,
),
galley,
text_color,
);
}
response
}
}
pub fn badge(content: impl Into<String>) -> MaterialBadge {
MaterialBadge::new(content)
}
pub fn badge_dot() -> MaterialBadge {
MaterialBadge::dot()
}