tag: Add color to Tag to support more colors. (#698)
- Add `ColorName` enum. <img width="588" alt="image" src="https://github.com/user-attachments/assets/20670946-1a4f-45ff-b5ac-81e7eb760242" /> <img width="586" alt="image" src="https://github.com/user-attachments/assets/7ce4a87f-fa6b-4794-83c5-3f18e6d09dcb" />
This commit is contained in:
parent
4c9c7980ee
commit
3e4a3ed046
5 changed files with 210 additions and 10 deletions
|
|
@ -19,7 +19,12 @@ impl Example {
|
||||||
|
|
||||||
impl Render for Example {
|
impl Render for Example {
|
||||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div().p_4().size_full().child(self.root.clone())
|
div()
|
||||||
|
.p_4()
|
||||||
|
.id("example")
|
||||||
|
.overflow_y_scroll()
|
||||||
|
.size_full()
|
||||||
|
.child(self.root.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use gpui_component::{
|
||||||
link::Link,
|
link::Link,
|
||||||
red_500,
|
red_500,
|
||||||
tag::Tag,
|
tag::Tag,
|
||||||
v_flex, yellow_500, yellow_800, IconName, Kbd, Sizable, StyledExt,
|
v_flex, yellow_500, yellow_800, ColorName, IconName, Kbd, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::section;
|
use crate::section;
|
||||||
|
|
@ -218,6 +218,18 @@ impl Render for TextStory {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
section("Tag::color", cx).child(
|
||||||
|
v_flex().gap_4().child(
|
||||||
|
h_flex().gap_2().flex_wrap().children(
|
||||||
|
ColorName::all()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|color| *color != ColorName::Gray)
|
||||||
|
.map(|color| Tag::color(color).child(color.to_string())),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
section("Kbd", cx).child(
|
section("Kbd", cx).child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
use gpui::Hsla;
|
use gpui::{Hsla, SharedString};
|
||||||
use serde::{de::Error, Deserialize, Deserializer};
|
use serde::{de::Error, Deserialize, Deserializer};
|
||||||
|
|
||||||
use crate::hsl;
|
use crate::hsl;
|
||||||
|
|
@ -181,6 +181,126 @@ mod color_scales {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enum representing the available color names.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ColorName {
|
||||||
|
Gray,
|
||||||
|
Red,
|
||||||
|
Orange,
|
||||||
|
Amber,
|
||||||
|
Yellow,
|
||||||
|
Lime,
|
||||||
|
Green,
|
||||||
|
Emerald,
|
||||||
|
Teal,
|
||||||
|
Cyan,
|
||||||
|
Sky,
|
||||||
|
Blue,
|
||||||
|
Indigo,
|
||||||
|
Violet,
|
||||||
|
Purple,
|
||||||
|
Fuchsia,
|
||||||
|
Pink,
|
||||||
|
Rose,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ColorName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for ColorName {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
match value.to_lowercase().as_str() {
|
||||||
|
"gray" => ColorName::Gray,
|
||||||
|
"red" => ColorName::Red,
|
||||||
|
"orange" => ColorName::Orange,
|
||||||
|
"amber" => ColorName::Amber,
|
||||||
|
"yellow" => ColorName::Yellow,
|
||||||
|
"lime" => ColorName::Lime,
|
||||||
|
"green" => ColorName::Green,
|
||||||
|
"emerald" => ColorName::Emerald,
|
||||||
|
"teal" => ColorName::Teal,
|
||||||
|
"cyan" => ColorName::Cyan,
|
||||||
|
"sky" => ColorName::Sky,
|
||||||
|
"blue" => ColorName::Blue,
|
||||||
|
"indigo" => ColorName::Indigo,
|
||||||
|
"violet" => ColorName::Violet,
|
||||||
|
"purple" => ColorName::Purple,
|
||||||
|
"fuchsia" => ColorName::Fuchsia,
|
||||||
|
"pink" => ColorName::Pink,
|
||||||
|
"rose" => ColorName::Rose,
|
||||||
|
_ => ColorName::Gray,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SharedString> for ColorName {
|
||||||
|
fn from(value: SharedString) -> Self {
|
||||||
|
value.as_ref().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorName {
|
||||||
|
/// Returns all available color names.
|
||||||
|
pub fn all() -> [Self; 18] {
|
||||||
|
[
|
||||||
|
ColorName::Gray,
|
||||||
|
ColorName::Red,
|
||||||
|
ColorName::Orange,
|
||||||
|
ColorName::Amber,
|
||||||
|
ColorName::Yellow,
|
||||||
|
ColorName::Lime,
|
||||||
|
ColorName::Green,
|
||||||
|
ColorName::Emerald,
|
||||||
|
ColorName::Teal,
|
||||||
|
ColorName::Cyan,
|
||||||
|
ColorName::Sky,
|
||||||
|
ColorName::Blue,
|
||||||
|
ColorName::Indigo,
|
||||||
|
ColorName::Violet,
|
||||||
|
ColorName::Purple,
|
||||||
|
ColorName::Fuchsia,
|
||||||
|
ColorName::Pink,
|
||||||
|
ColorName::Rose,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the color for the given scale.
|
||||||
|
///
|
||||||
|
/// The `scale` is any of `[50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]`
|
||||||
|
/// falls back to 500 if out of range.
|
||||||
|
pub fn scale(&self, scale: usize) -> Hsla {
|
||||||
|
let colors = match self {
|
||||||
|
ColorName::Gray => &DEFAULT_COLOR.gray,
|
||||||
|
ColorName::Red => &DEFAULT_COLOR.red,
|
||||||
|
ColorName::Orange => &DEFAULT_COLOR.orange,
|
||||||
|
ColorName::Amber => &DEFAULT_COLOR.amber,
|
||||||
|
ColorName::Yellow => &DEFAULT_COLOR.yellow,
|
||||||
|
ColorName::Lime => &DEFAULT_COLOR.lime,
|
||||||
|
ColorName::Green => &DEFAULT_COLOR.green,
|
||||||
|
ColorName::Emerald => &DEFAULT_COLOR.emerald,
|
||||||
|
ColorName::Teal => &DEFAULT_COLOR.teal,
|
||||||
|
ColorName::Cyan => &DEFAULT_COLOR.cyan,
|
||||||
|
ColorName::Sky => &DEFAULT_COLOR.sky,
|
||||||
|
ColorName::Blue => &DEFAULT_COLOR.blue,
|
||||||
|
ColorName::Indigo => &DEFAULT_COLOR.indigo,
|
||||||
|
ColorName::Violet => &DEFAULT_COLOR.violet,
|
||||||
|
ColorName::Purple => &DEFAULT_COLOR.purple,
|
||||||
|
ColorName::Fuchsia => &DEFAULT_COLOR.fuchsia,
|
||||||
|
ColorName::Pink => &DEFAULT_COLOR.pink,
|
||||||
|
ColorName::Rose => &DEFAULT_COLOR.rose,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(color) = colors.get(&scale) {
|
||||||
|
color.hsla
|
||||||
|
} else {
|
||||||
|
colors.get(&500).unwrap().hsla
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
|
||||||
pub(crate) struct ShadcnColors {
|
pub(crate) struct ShadcnColors {
|
||||||
pub(crate) black: ShadcnColor,
|
pub(crate) black: ShadcnColor,
|
||||||
|
|
@ -426,4 +546,20 @@ mod tests {
|
||||||
assert_eq!(green.mix(red, 0.5).to_hex(), "#FFFF00");
|
assert_eq!(green.mix(red, 0.5).to_hex(), "#FFFF00");
|
||||||
assert_eq!(blue.mix(yellow, 0.2).to_hex(), "#0098FF");
|
assert_eq!(blue.mix(yellow, 0.2).to_hex(), "#0098FF");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_color_name() {
|
||||||
|
assert_eq!(ColorName::Purple.to_string(), "Purple");
|
||||||
|
assert_eq!(format!("{}", ColorName::Green), "Green");
|
||||||
|
assert_eq!(format!("{:?}", ColorName::Yellow), "Yellow");
|
||||||
|
|
||||||
|
let color = ColorName::Green;
|
||||||
|
assert_eq!(color.scale(500).to_hex(), "#21C55E");
|
||||||
|
assert_eq!(color.scale(1500).to_hex(), "#21C55E");
|
||||||
|
|
||||||
|
for name in ColorName::all().iter() {
|
||||||
|
let name1: ColorName = name.to_string().as_str().into();
|
||||||
|
assert_eq!(name1, *name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{theme::ActiveTheme as _, Sizable, Size};
|
use crate::{theme::ActiveTheme as _, ColorName, Sizable, Size};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, relative, transparent_black, AnyElement, App, Div, Hsla,
|
div, prelude::FluentBuilder as _, relative, transparent_black, AnyElement, App, Div, Hsla,
|
||||||
InteractiveElement as _, IntoElement, ParentElement, RenderOnce, Styled, Window,
|
InteractiveElement as _, IntoElement, ParentElement, RenderOnce, Styled, Window,
|
||||||
|
|
@ -7,16 +7,18 @@ use gpui::{
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub enum TagVariant {
|
pub enum TagVariant {
|
||||||
#[default]
|
#[default]
|
||||||
|
Outline,
|
||||||
Primary,
|
Primary,
|
||||||
Secondary,
|
Secondary,
|
||||||
Outline,
|
|
||||||
Danger,
|
Danger,
|
||||||
|
Color(ColorName),
|
||||||
Custom {
|
Custom {
|
||||||
color: Hsla,
|
color: Hsla,
|
||||||
foreground: Hsla,
|
foreground: Hsla,
|
||||||
border: Hsla,
|
border: Hsla,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TagVariant {
|
impl TagVariant {
|
||||||
fn bg(&self, cx: &App) -> Hsla {
|
fn bg(&self, cx: &App) -> Hsla {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -24,6 +26,13 @@ impl TagVariant {
|
||||||
Self::Secondary => cx.theme().secondary,
|
Self::Secondary => cx.theme().secondary,
|
||||||
Self::Outline => transparent_black(),
|
Self::Outline => transparent_black(),
|
||||||
Self::Danger => cx.theme().danger,
|
Self::Danger => cx.theme().danger,
|
||||||
|
Self::Color(color) => {
|
||||||
|
if cx.theme().is_dark() {
|
||||||
|
color.scale(950).opacity(0.5)
|
||||||
|
} else {
|
||||||
|
color.scale(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Custom { color, .. } => *color,
|
Self::Custom { color, .. } => *color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +43,13 @@ impl TagVariant {
|
||||||
Self::Secondary => cx.theme().secondary,
|
Self::Secondary => cx.theme().secondary,
|
||||||
Self::Outline => cx.theme().border,
|
Self::Outline => cx.theme().border,
|
||||||
Self::Danger => cx.theme().danger,
|
Self::Danger => cx.theme().danger,
|
||||||
|
Self::Color(color) => {
|
||||||
|
if cx.theme().is_dark() {
|
||||||
|
color.scale(800).opacity(0.5)
|
||||||
|
} else {
|
||||||
|
color.scale(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Custom { border, .. } => *border,
|
Self::Custom { border, .. } => *border,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +60,13 @@ impl TagVariant {
|
||||||
Self::Secondary => cx.theme().secondary_foreground,
|
Self::Secondary => cx.theme().secondary_foreground,
|
||||||
Self::Outline => cx.theme().foreground,
|
Self::Outline => cx.theme().foreground,
|
||||||
Self::Danger => cx.theme().danger_foreground,
|
Self::Danger => cx.theme().danger_foreground,
|
||||||
|
Self::Color(color) => {
|
||||||
|
if cx.theme().is_dark() {
|
||||||
|
color.scale(300)
|
||||||
|
} else {
|
||||||
|
color.scale(600)
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Custom { foreground, .. } => *foreground,
|
Self::Custom { foreground, .. } => *foreground,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,22 +95,29 @@ impl Tag {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new tag with default variant ([`TagVariant::Primary`]).
|
||||||
pub fn primary() -> Self {
|
pub fn primary() -> Self {
|
||||||
Self::new().with_variant(TagVariant::Primary)
|
Self::new().with_variant(TagVariant::Primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new tag with default variant ([`TagVariant::Secondary`]).
|
||||||
pub fn secondary() -> Self {
|
pub fn secondary() -> Self {
|
||||||
Self::new().with_variant(TagVariant::Secondary)
|
Self::new().with_variant(TagVariant::Secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new tag with default variant ([`TagVariant::Outline`]).
|
||||||
|
///
|
||||||
|
/// See also [`Tag::new`].
|
||||||
pub fn outline() -> Self {
|
pub fn outline() -> Self {
|
||||||
Self::new().with_variant(TagVariant::Outline)
|
Self::new().with_variant(TagVariant::Outline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new tag with default variant ([`TagVariant::Danger`]).
|
||||||
pub fn danger() -> Self {
|
pub fn danger() -> Self {
|
||||||
Self::new().with_variant(TagVariant::Danger)
|
Self::new().with_variant(TagVariant::Danger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new tag with default variant ([`TagVariant::Custom`]).
|
||||||
pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self {
|
pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self {
|
||||||
Self::new().with_variant(TagVariant::Custom {
|
Self::new().with_variant(TagVariant::Custom {
|
||||||
color,
|
color,
|
||||||
|
|
@ -95,6 +125,11 @@ impl Tag {
|
||||||
border,
|
border,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new tag with default variant ([`TagVariant::Color`]).
|
||||||
|
pub fn color(color: impl Into<ColorName>) -> Self {
|
||||||
|
Self::new().with_variant(TagVariant::Color(color.into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Sizable for Tag {
|
impl Sizable for Tag {
|
||||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||||
|
|
@ -109,6 +144,10 @@ impl ParentElement for Tag {
|
||||||
}
|
}
|
||||||
impl RenderOnce for Tag {
|
impl RenderOnce for Tag {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let bg = self.variant.bg(cx);
|
||||||
|
let fg = self.variant.fg(cx);
|
||||||
|
let border = self.variant.border(cx);
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
.line_height(relative(1.3))
|
.line_height(relative(1.3))
|
||||||
.text_xs()
|
.text_xs()
|
||||||
|
|
@ -116,9 +155,9 @@ impl RenderOnce for Tag {
|
||||||
Size::XSmall | Size::Small => this.px_1p5().py_0().rounded(cx.theme().radius / 2.),
|
Size::XSmall | Size::Small => this.px_1p5().py_0().rounded(cx.theme().radius / 2.),
|
||||||
_ => this.px_2p5().py_0p5().rounded(cx.theme().radius),
|
_ => this.px_2p5().py_0p5().rounded(cx.theme().radius),
|
||||||
})
|
})
|
||||||
.bg(self.variant.bg(cx))
|
.bg(bg)
|
||||||
.text_color(self.variant.fg(cx))
|
.text_color(fg)
|
||||||
.border_color(self.variant.border(cx))
|
.border_color(border)
|
||||||
.hover(|this| this.opacity(0.9))
|
.hover(|this| this.opacity(0.9))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -411,15 +411,23 @@ impl Global for Theme {}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
/// Returns the global theme reference
|
/// Returns the global theme reference
|
||||||
|
#[inline(always)]
|
||||||
pub fn global(cx: &App) -> &Theme {
|
pub fn global(cx: &App) -> &Theme {
|
||||||
cx.global::<Theme>()
|
cx.global::<Theme>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the global theme mutable reference
|
/// Returns the global theme mutable reference
|
||||||
|
#[inline(always)]
|
||||||
pub fn global_mut(cx: &mut App) -> &mut Theme {
|
pub fn global_mut(cx: &mut App) -> &mut Theme {
|
||||||
cx.global_mut::<Theme>()
|
cx.global_mut::<Theme>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the theme is dark.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_dark(&self) -> bool {
|
||||||
|
self.mode.is_dark()
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply a mask color to the theme.
|
/// Apply a mask color to the theme.
|
||||||
pub fn apply_color(&mut self, mask_color: Hsla) {
|
pub fn apply_color(&mut self, mask_color: Hsla) {
|
||||||
self.title_bar = self.title_bar.apply(mask_color);
|
self.title_bar = self.title_bar.apply(mask_color);
|
||||||
|
|
@ -576,7 +584,7 @@ pub enum ThemeMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemeMode {
|
impl ThemeMode {
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn is_dark(&self) -> bool {
|
pub fn is_dark(&self) -> bool {
|
||||||
matches!(self, Self::Dark)
|
matches!(self, Self::Dark)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue