use crate::render::{Cell, Modifier};
use crate::style::Color;
use crate::widget::theme::{DARK_GRAY, SEPARATOR_COLOR, SUBTLE_GRAY};
use crate::widget::traits::{RenderContext, View, WidgetProps};
use crate::{impl_props_builders, impl_styled_view};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TagStyle {
#[default]
Filled,
Outlined,
Subtle,
}
pub struct Tag {
text: String,
color: Color,
text_color: Option<Color>,
style: TagStyle,
closable: bool,
icon: Option<char>,
selected: bool,
disabled: bool,
props: WidgetProps,
}
impl Tag {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
color: DARK_GRAY,
text_color: None,
style: TagStyle::Filled,
closable: false,
icon: None,
selected: false,
disabled: false,
props: WidgetProps::new(),
}
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn text_color(mut self, color: Color) -> Self {
self.text_color = Some(color);
self
}
pub fn style(mut self, style: TagStyle) -> Self {
self.style = style;
self
}
pub fn outlined(mut self) -> Self {
self.style = TagStyle::Outlined;
self
}
pub fn subtle(mut self) -> Self {
self.style = TagStyle::Subtle;
self
}
pub fn closable(mut self) -> Self {
self.closable = true;
self
}
pub fn icon(mut self, icon: char) -> Self {
self.icon = Some(icon);
self
}
pub fn selected(mut self) -> Self {
self.selected = true;
self
}
pub fn disabled(mut self) -> Self {
self.disabled = true;
self
}
pub fn blue(mut self) -> Self {
self.color = Color::rgb(60, 120, 200);
self
}
pub fn green(mut self) -> Self {
self.color = Color::rgb(40, 160, 80);
self
}
pub fn red(mut self) -> Self {
self.color = Color::rgb(200, 60, 60);
self
}
pub fn yellow(mut self) -> Self {
self.color = Color::rgb(200, 180, 40);
self
}
pub fn purple(mut self) -> Self {
self.color = Color::rgb(140, 80, 180);
self
}
fn effective_colors(&self) -> (Option<Color>, Color) {
let text_color = self.text_color.unwrap_or(Color::WHITE);
if self.disabled {
return (Some(SEPARATOR_COLOR), SUBTLE_GRAY);
}
match self.style {
TagStyle::Filled => (Some(self.color), text_color),
TagStyle::Outlined => (None, self.color),
TagStyle::Subtle => {
let light_bg = Color::rgb(
self.color.r.saturating_add(180),
self.color.g.saturating_add(180),
self.color.b.saturating_add(180),
);
(Some(light_bg), self.color)
}
}
}
}
impl Default for Tag {
fn default() -> Self {
Self::new("")
}
}
impl View for Tag {
crate::impl_view_meta!("Tag");
fn render(&self, ctx: &mut RenderContext) {
let area = ctx.area;
let (bg, fg) = self.effective_colors();
let mut content = String::new();
if let Some(icon) = self.icon {
content.push(icon);
content.push(' ');
}
content.push_str(&self.text);
if self.closable {
content.push_str(" ×");
}
let _text_len = content.chars().count() as u16;
let (left_char, right_char) = match self.style {
TagStyle::Outlined => ('⟨', '⟩'),
_ => (' ', ' '),
};
let mut x: u16 = 0;
let mut left = Cell::new(left_char);
if let Some(bg_color) = bg {
left.bg = Some(bg_color);
}
left.fg = Some(fg);
ctx.set(x, 0, left);
x += 1;
for ch in content.chars() {
if x >= area.width - 1 {
break;
}
let mut cell = Cell::new(ch);
cell.fg = Some(fg);
if let Some(bg_color) = bg {
cell.bg = Some(bg_color);
}
if self.selected {
cell.modifier |= Modifier::BOLD;
}
if self.disabled {
cell.modifier |= Modifier::DIM;
}
ctx.set(x, 0, cell);
x += 1;
}
if x < area.width {
let mut right = Cell::new(right_char);
if let Some(bg_color) = bg {
right.bg = Some(bg_color);
}
right.fg = Some(fg);
ctx.set(x, 0, right);
}
}
}
impl_styled_view!(Tag);
impl_props_builders!(Tag);
pub fn tag(text: impl Into<String>) -> Tag {
Tag::new(text)
}
pub fn chip(text: impl Into<String>) -> Tag {
Tag::new(text)
}