Update to use shadcn ui theme

This commit is contained in:
Jason Lee 2024-06-24 15:39:26 +08:00
parent 11af9900eb
commit 94cb714ebf
11 changed files with 276 additions and 221 deletions

2
Cargo.lock generated
View file

@ -4250,6 +4250,8 @@ dependencies = [
"catppuccin",
"gpui",
"log",
"serde",
"serde_json",
]
[[package]]

View file

@ -16,3 +16,5 @@ workspace = { path = "crates/workspace" }
util = { path = "crates/util" }
anyhow = "1"
log = "0.4"
serde = "1.0.203"
serde_json = "1"

View file

@ -7,3 +7,5 @@ edition = "2021"
catppuccin = "2.4.0"
gpui.workspace = true
log.wokrspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -5,11 +5,9 @@ use gpui::{
};
use crate::{
colors::Color,
disableable::{Clickable, Disableable, Selectable},
label::Label,
theme::Theme,
HlsaExt as _,
theme::{Colorize as _, Theme},
};
pub enum ButtonRounded {
@ -120,6 +118,7 @@ impl RenderOnce for Button {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let theme = cx.global::<Theme>();
let style: ButtonStyle = self.style;
let normal_style = style.normal(cx);
self.base
.id(self.id)
@ -127,7 +126,7 @@ impl RenderOnce for Button {
.items_center()
.justify_center()
.map(|this| match self.size {
ButtonSize::Small => this.px_3().py_2().h_5(),
ButtonSize::Small => this.px_3().py_2().h_6(),
ButtonSize::Medium => this.px_4().py_2().h_8(),
})
.map(|this| match self.rounded {
@ -137,8 +136,14 @@ impl RenderOnce for Button {
ButtonRounded::None => this.rounded_none(),
})
.when(!self.disabled, |this| {
this.hover(|this| this.border_color(theme.blue))
.active(|this| this.bg(theme.blue.lighten(10.0)))
this.hover(|this| {
let hover_style = style.hovered(cx);
this.bg(hover_style.bg).border_color(hover_style.border)
})
.active(|this| {
let active_style = style.active(cx);
this.bg(active_style.bg).border_color(active_style.border)
})
})
.when_some(
self.on_click.filter(|_| !self.disabled),
@ -157,13 +162,13 @@ impl RenderOnce for Button {
.border_color(disabled_style.border)
})
.border_1()
.border_color(theme.crust)
.bg(theme.base)
.border_color(normal_style.border)
.bg(normal_style.bg)
.child({
let text_color = if self.disabled {
theme.text_disabled
normal_style.fg.opacity(0.6)
} else {
theme.text
normal_style.fg
};
Label::new(self.label)
@ -183,58 +188,64 @@ struct ButtonStyles {
}
impl ButtonStyle {
fn bg_color(&self) -> Color {
fn bg_color(&self, cx: &WindowContext) -> Hsla {
let theme = cx.global::<Theme>();
match self {
ButtonStyle::Primary => Color::Primary,
ButtonStyle::Secondary => Color::Secondary,
ButtonStyle::Danger => Color::Destructive,
ButtonStyle::Primary => theme.primary,
ButtonStyle::Secondary => theme.secondary,
ButtonStyle::Danger => theme.destructive,
}
}
fn text_color(&self) -> Color {
fn text_color(&self, cx: &WindowContext) -> Hsla {
let theme = cx.global::<Theme>();
match self {
ButtonStyle::Primary => Color::PrimaryForeground,
ButtonStyle::Secondary => Color::SecondaryForeground,
ButtonStyle::Danger => Color::DestructiveForeground,
ButtonStyle::Primary => theme.primary_foreground,
ButtonStyle::Secondary => theme.secondary_foreground,
ButtonStyle::Danger => theme.destructive_foreground,
}
}
fn border_color(&self) -> Color {
fn border_color(&self, cx: &WindowContext) -> Hsla {
let theme = cx.global::<Theme>();
match self {
ButtonStyle::Primary => Color::Primary,
ButtonStyle::Secondary => Color::Secondary,
ButtonStyle::Danger => Color::Destructive,
ButtonStyle::Primary => theme.primary,
ButtonStyle::Secondary => theme.secondary,
ButtonStyle::Danger => theme.destructive,
}
}
fn normal(&self, cx: &WindowContext) -> ButtonStyles {
let bg = self.bg_color().color(cx);
let border = self.border_color().color(cx);
let fg = self.text_color().color(cx);
let bg = self.bg_color(cx);
let border = self.border_color(cx);
let fg = self.text_color(cx);
ButtonStyles { bg, border, fg }
}
fn hovered(&self, cx: &WindowContext) -> ButtonStyles {
let bg = self.bg_color().color(cx).lighten(0.05);
let border = self.border_color().color(cx).lighten(0.05);
let fg = self.text_color().color(cx);
// Hover color = color/90
let bg = self.bg_color(cx).divide(0.9);
let border = self.border_color(cx).divide(0.9);
let fg = self.text_color(cx).divide(0.9);
ButtonStyles { bg, border, fg }
}
fn active(&self, cx: &WindowContext) -> ButtonStyles {
let bg = self.bg_color().color(cx).darken(0.05);
let border = self.border_color().color(cx).darken(0.05);
let fg = self.text_color().color(cx);
let bg = self.bg_color(cx);
let border = self.border_color(cx);
let fg = self.text_color(cx);
ButtonStyles { bg, border, fg }
}
fn disabled(&self, cx: &WindowContext) -> ButtonStyles {
let bg = self.bg_color().color(cx).grayscale();
let border = self.border_color().color(cx).grayscale();
let fg = self.text_color().color(cx).grayscale();
let bg = self.bg_color(cx).grayscale();
let border = self.border_color(cx).grayscale();
let fg = self.text_color(cx).grayscale();
ButtonStyles { bg, border, fg }
}

View file

@ -1,102 +0,0 @@
use gpui::{hsla, Hsla, WindowContext};
pub fn hls(h: f32, l: f32, s: f32) -> Hsla {
hsla(h, l / 100., s / 100., 1.)
}
/// Extension trait for `Hsla` to provide more color manipulation methods.
pub trait HlsaExt {
fn darken(&self, amount: f32) -> Hsla;
fn lighten(&self, amount: f32) -> Hsla;
}
impl HlsaExt for Hsla {
/// Darken the color by a percentage.
///
/// `amount` value is 0.0 - 1.0
fn darken(&self, amount: f32) -> Hsla {
let l = self.l - (self.l * amount);
hsla(self.h, l, self.s, self.a)
}
/// Lighten the color by a percentage.
///
/// `amount` value is 0.0 - 1.0
fn lighten(&self, amount: f32) -> Hsla {
let l = self.l + (self.l * amount);
hsla(self.h, l, self.s, self.a)
}
}
// .dark {
// --background: 240 10% 3.9%;
// --foreground: 0 0% 98%;
// --card: 240 10% 3.9%;
// --card-foreground: 0 0% 98%;
// --popover: 240 10% 3.9%;
// --popover-foreground: 0 0% 98%;
// --primary: 0 0% 98%;
// --primary-foreground: 240 5.9% 10%;
// --secondary: 240 3.7% 15.9%;
// --secondary-foreground: 0 0% 98%;
// --muted: 240 3.7% 15.9%;
// --muted-foreground: 240 5% 64.9%;
// --accent: 240 3.7% 15.9%;
// --accent-foreground: 0 0% 98%;
// --destructive: 0 62.8% 30.6%;
// --destructive-foreground: 0 85.7% 97.3%;
// --border: 240 3.7% 15.9%;
// --input: 240 3.7% 15.9%;
// --ring: 240 4.9% 83.9%;
// --selection: 240 21.6% 50.6%;
// }
pub enum Color {
Background,
Foreground,
Card,
CardForeground,
Popover,
PopoverForeground,
Primary,
PrimaryForeground,
Secondary,
SecondaryForeground,
Muted,
MutedForeground,
Accent,
AccentForeground,
Destructive,
DestructiveForeground,
Border,
Input,
Ring,
Selection,
}
impl Color {
pub fn color(&self, _cx: &WindowContext) -> Hsla {
match self {
Color::Background => hls(240., 10., 3.9),
Color::Foreground => hls(0., 0., 98.),
Color::Card => hls(240., 10., 3.9),
Color::CardForeground => hls(0., 0., 98.),
Color::Popover => hls(240., 10., 3.9),
Color::PopoverForeground => hls(0., 0., 98.),
Color::Primary => hls(0., 0., 98.),
Color::PrimaryForeground => hls(240., 5.9, 10.),
Color::Secondary => hls(240., 3.7, 15.9),
Color::SecondaryForeground => hls(0., 0., 98.),
Color::Muted => hls(240., 3.7, 15.9),
Color::MutedForeground => hls(240., 5., 64.9),
Color::Accent => hls(240., 3.7, 15.9),
Color::AccentForeground => hls(0., 0., 98.),
Color::Destructive => hls(0., 62.8, 30.6),
Color::DestructiveForeground => hls(0., 85.7, 97.3),
Color::Border => hls(240., 3.7, 15.9),
Color::Input => hls(240., 3.7, 15.9),
Color::Ring => hls(240., 4.9, 83.9),
Color::Selection => hls(213., 21.6, 50.6),
}
}
}

View file

@ -3,7 +3,7 @@ use gpui::{
ParentElement, RenderOnce, SharedString, Styled, WindowContext,
};
use crate::{hls, theme::Theme};
use crate::theme::Theme;
#[derive(IntoElement)]
pub struct Label {
@ -21,7 +21,7 @@ impl Label {
base: div(),
label: label.into(),
multiple_lines: false,
color: hls(0., 0., 0.),
color: Hsla::white(),
line_height: None,
text_size: None,
}
@ -46,8 +46,6 @@ impl Styled for Label {
impl RenderOnce for Label {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let theme = cx.global::<Theme>();
let label_text = if !self.multiple_lines {
SharedString::from(self.label.replace('\n', ""))
} else {
@ -56,7 +54,7 @@ impl RenderOnce for Label {
self.base
.child(label_text)
.text_color(theme.text)
.text_color(self.color)
.map(|this| {
if let Some(text_size) = self.text_size {
this.text_size(text_size)

View file

@ -1,10 +1,7 @@
pub mod button;
mod colors;
pub mod cursor;
pub mod disableable;
pub mod label;
pub mod story;
pub mod text_field;
pub mod theme;
pub use colors::*;

View file

@ -2,7 +2,10 @@ mod blink_manager;
mod cursor_layout;
mod text_view;
use crate::{disableable::Disableable, theme::Theme};
use crate::{
disableable::Disableable,
theme::{Colorize as _, Theme},
};
use blink_manager::BlinkManager;
use gpui::{
div, ClipboardItem, Context, EventEmitter, FocusHandle, InteractiveElement, IntoElement,
@ -198,9 +201,9 @@ impl RenderOnce for TextField {
});
})
.border_color(if self.focus_handle.is_focused(cx) {
theme.blue
theme.ring
} else {
theme.crust
theme.input
})
.border_1()
.rounded_sm()
@ -208,9 +211,9 @@ impl RenderOnce for TextField {
.px_3()
.min_w_20()
.bg(if self.disable {
theme.crust
theme.background.opacity(0.5)
} else {
theme.base
theme.background
})
.child(view)
}

View file

@ -3,7 +3,7 @@ use std::ops::Range;
use super::{
blink_manager::BlinkManager, cursor_layout::CursorLayout, TextEvent, CURSOR_BLINK_INTERVAL,
};
use crate::{hls, theme::Theme};
use crate::theme::{Colorize as _, Theme};
use gpui::{
px, relative, Context, Element, EventEmitter, FocusHandle, HighlightStyle, InteractiveText,
IntoElement, Model, Render, Style, StyledText, TextStyle, View, ViewContext, VisualContext,
@ -32,11 +32,13 @@ impl TextView {
) -> View<Self> {
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
let theme = cx.global::<Theme>();
let cursor = CursorLayout::new(
gpui::Point::new(gpui::px(0.0), gpui::px(0.0)),
px(2.0),
px(20.0),
hls(212., 92., 45.),
theme.ring,
None,
);
@ -219,8 +221,7 @@ impl Render for TextView {
dbg!("textView render", &text);
let mut style = TextStyle {
color: theme.text,
font_family: theme.font_sans.clone(),
color: theme.foreground,
..Default::default()
};
@ -229,15 +230,14 @@ impl Render for TextView {
}
let mut selection_style = HighlightStyle::default();
let mut color = theme.lavender;
color.fade_out(0.8);
let color = theme.foreground.opacity(0.8);
selection_style.background_color = Some(color);
let mut highlights = vec![(self.char_range_to_text_range(&text), selection_style)];
if text.is_empty() {
text = self.placeholder.to_string();
style.color = theme.subtext0;
style.color = theme.muted_foreground;
highlights = vec![];
}

View file

@ -1,65 +1,214 @@
use std::sync::Arc;
use gpui::{AppContext, Global, Hsla, Rgba};
use serde_json::json;
use catppuccin::{Flavor, FlavorColors};
use gpui::{AppContext, Global, Hsla, Rgba, SharedString};
fn hex(color: &str) -> Hsla {
let color: Rgba = serde_json::from_value(json!(color)).unwrap();
color.into()
}
fn color_to_hsla(color: catppuccin::Color) -> Hsla {
Rgba {
r: color.rgb.r as f32 / 255.0,
g: color.rgb.g as f32 / 255.0,
b: color.rgb.b as f32 / 255.0,
a: 1.0,
fn hsl(h: f32, s: f32, l: f32) -> Hsla {
Hsla { h, s, l, a: 1.0 }
}
pub trait Colorize {
fn opacity(&self, opacity: f32) -> Hsla;
fn divide(&self, divisor: f32) -> Hsla;
}
impl Colorize for Hsla {
/// Returns a new color with the given opacity.
///
/// The opacity is a value between 0.0 and 1.0, where 0.0 is fully transparent and 1.0 is fully opaque.
fn opacity(&self, opacity: f32) -> Hsla {
Hsla {
a: self.a * opacity,
..*self
}
}
/// Returns a new color with each channel divided by the given divisor.
///
/// The divisor in range of 0.0 .. 1.0
fn divide(&self, divisor: f32) -> Hsla {
Hsla {
a: divisor,
..*self
}
}
}
// @layer base {
// :root {
// --background: 0 0% 100%;
// --foreground: 240 10% 3.9%;
// --card: 0 0% 100%;
// --card-foreground: 240 10% 3.9%;
// --popover: 0 0% 100%;
// --popover-foreground: 240 10% 3.9%;
// --primary: 240 5.9% 10%;
// --primary-foreground: 0 0% 98%;
// --secondary: 240 4.8% 95.9%;
// --secondary-foreground: 240 5.9% 10%;
// --muted: 240 4.8% 95.9%;
// --muted-foreground: 240 3.8% 46.1%;
// --accent: 240 4.8% 95.9%;
// --accent-foreground: 240 5.9% 10%;
// --destructive: 0 84.2% 60.2%;
// --destructive-foreground: 0 0% 98%;
// --border: 240 5.9% 90%;
// --input: 240 5.9% 90%;
// --ring: 240 5.9% 10%;
// --radius: 0rem;
// }
// .dark {
// --background: 240 10% 3.9%;
// --foreground: 0 0% 98%;
// --card: 240 10% 3.9%;
// --card-foreground: 0 0% 98%;
// --popover: 240 10% 3.9%;
// --popover-foreground: 0 0% 98%;
// --primary: 0 0% 98%;
// --primary-foreground: 240 5.9% 10%;
// --secondary: 240 3.7% 15.9%;
// --secondary-foreground: 0 0% 98%;
// --muted: 240 3.7% 15.9%;
// --muted-foreground: 240 5% 64.9%;
// --accent: 240 3.7% 15.9%;
// --accent-foreground: 0 0% 98%;
// --destructive: 0 62.8% 30.6%;
// --destructive-foreground: 0 0% 98%;
// --border: 240 3.7% 15.9%;
// --input: 240 3.7% 15.9%;
// --ring: 240 4.9% 83.9%;
// }
// }
#[derive(Debug, Clone, Copy)]
struct Colors {
pub background: Hsla,
pub foreground: Hsla,
pub card: Hsla,
pub card_foreground: Hsla,
pub popover: Hsla,
pub popover_foreground: Hsla,
pub primary: Hsla,
pub primary_foreground: Hsla,
pub secondary: Hsla,
pub secondary_foreground: Hsla,
pub muted: Hsla,
pub muted_foreground: Hsla,
pub accent: Hsla,
pub accent_foreground: Hsla,
pub destructive: Hsla,
pub destructive_foreground: Hsla,
pub border: Hsla,
pub input: Hsla,
pub ring: Hsla,
pub radius: f32,
}
impl Colors {
fn light() -> Colors {
Colors {
background: hsl(0.0, 0.0, 1.0),
foreground: hsl(240.0, 0.1, 0.039),
card: hsl(0.0, 0.0, 1.0),
card_foreground: hsl(240.0, 0.1, 0.039),
popover: hsl(0.0, 0.0, 1.0),
popover_foreground: hsl(240.0, 0.1, 0.039),
primary: hsl(240.0, 0.059, 0.1),
primary_foreground: hsl(0.0, 0.0, 0.98),
secondary: hsl(240.0, 0.048, 0.959),
secondary_foreground: hsl(240.0, 0.059, 0.1),
muted: hsl(240.0, 0.048, 0.959),
muted_foreground: hsl(240.0, 0.038, 0.461),
accent: hsl(240.0, 0.048, 0.959),
accent_foreground: hsl(240.0, 0.059, 0.1),
destructive: hsl(0.0, 0.842, 0.602),
destructive_foreground: hsl(0.0, 0.0, 0.98),
border: hsl(240.0, 0.059, 0.9),
input: hsl(240.0, 0.059, 0.9),
ring: hsl(240.0, 0.059, 0.1),
radius: 0.0,
}
}
fn dark() -> Colors {
Colors {
background: hsl(240.0, 0.1, 0.039),
foreground: hsl(0.0, 0.0, 0.98),
card: hsl(240.0, 0.1, 0.039),
card_foreground: hsl(0.0, 0.0, 0.98),
popover: hsl(240.0, 0.1, 0.039),
popover_foreground: hsl(0.0, 0.0, 0.98),
primary: hsl(0.0, 0.0, 0.98),
primary_foreground: hsl(240.0, 0.059, 0.1),
secondary: hsl(240.0, 0.037, 0.159),
secondary_foreground: hsl(0.0, 0.0, 0.98),
muted: hsl(240.0, 0.037, 0.159),
muted_foreground: hsl(240.0, 0.05, 0.649),
accent: hsl(240.0, 0.037, 0.159),
accent_foreground: hsl(0.0, 0.0, 0.98),
destructive: hsl(0.0, 0.628, 0.306),
destructive_foreground: hsl(0.0, 0.0, 0.98),
border: hsl(240.0, 0.037, 0.159),
input: hsl(240.0, 0.037, 0.159),
ring: hsl(240.0, 0.049, 0.839),
radius: 0.0,
}
}
.into()
}
#[derive(Debug)]
pub struct Theme {
pub font_sans: SharedString,
pub font_mono: SharedString,
pub crust: Hsla,
pub text: Hsla,
pub base: Hsla,
pub mantle: Hsla,
pub green: Hsla,
pub red: Hsla,
pub blue: Hsla,
pub text_disabled: Hsla,
pub subtext1: Hsla,
pub subtext0: Hsla,
pub overlay2: Hsla,
pub overlay1: Hsla,
pub overlay0: Hsla,
pub surface2: Hsla,
pub surface1: Hsla,
pub surface0: Hsla,
pub lavender: Hsla,
pub background: Hsla,
pub foreground: Hsla,
pub card: Hsla,
pub card_foreground: Hsla,
pub popover: Hsla,
pub popover_foreground: Hsla,
pub primary: Hsla,
pub primary_foreground: Hsla,
pub secondary: Hsla,
pub secondary_foreground: Hsla,
pub muted: Hsla,
pub muted_foreground: Hsla,
pub accent: Hsla,
pub accent_foreground: Hsla,
pub destructive: Hsla,
pub destructive_foreground: Hsla,
pub border: Hsla,
pub input: Hsla,
pub ring: Hsla,
pub radius: f32,
}
impl Global for Theme {}
impl From<FlavorColors> for Theme {
fn from(colors: FlavorColors) -> Self {
impl From<Colors> for Theme {
fn from(colors: Colors) -> Self {
Theme {
font_sans: "Inter".into(),
font_mono: "JetBrains Mono".into(),
crust: color_to_hsla(colors.crust),
text: color_to_hsla(colors.text),
base: color_to_hsla(colors.base),
mantle: color_to_hsla(colors.mantle),
green: color_to_hsla(colors.green),
red: color_to_hsla(colors.red),
blue: color_to_hsla(colors.blue),
subtext0: color_to_hsla(colors.subtext0),
subtext1: color_to_hsla(colors.subtext1),
text_disabled: color_to_hsla(colors.subtext0),
overlay0: color_to_hsla(colors.overlay0),
overlay1: color_to_hsla(colors.overlay1),
overlay2: color_to_hsla(colors.overlay2),
surface0: color_to_hsla(colors.surface0),
surface1: color_to_hsla(colors.surface1),
surface2: color_to_hsla(colors.surface2),
lavender: color_to_hsla(colors.lavender),
background: colors.background,
foreground: colors.foreground,
card: colors.card,
card_foreground: colors.card_foreground,
popover: colors.popover,
popover_foreground: colors.popover_foreground,
primary: colors.primary,
primary_foreground: colors.primary_foreground,
secondary: colors.secondary,
secondary_foreground: colors.secondary_foreground,
muted: colors.muted,
muted_foreground: colors.muted_foreground,
accent: colors.accent,
accent_foreground: colors.accent_foreground,
destructive: colors.destructive,
destructive_foreground: colors.destructive_foreground,
border: colors.border,
input: colors.input,
ring: colors.ring,
radius: colors.radius,
}
}
}
@ -71,7 +220,7 @@ pub enum ThemeMode {
impl Theme {
fn new() -> Self {
Self::from(catppuccin::PALETTE.latte.colors)
Self::from(Colors::light())
}
pub fn init(cx: &mut AppContext) {
@ -79,12 +228,12 @@ impl Theme {
}
pub fn change(mode: ThemeMode, cx: &mut AppContext) {
let flavour = match mode {
ThemeMode::Light => catppuccin::PALETTE.latte,
ThemeMode::Dark => catppuccin::PALETTE.mocha,
let colors = match mode {
ThemeMode::Light => Colors::light(),
ThemeMode::Dark => Colors::dark(),
};
cx.set_global(Self::from(flavour.colors));
cx.set_global(Self::from(colors));
cx.refresh();
}
}

View file

@ -1,14 +1,7 @@
use gpui::{prelude::FluentBuilder, *};
use gpui::*;
use std::sync::Arc;
use ui::{
button::Button,
disableable::Clickable as _,
story::Stories,
text_field::{self, TextField},
theme::Theme,
Color,
};
use ui::{button::Button, disableable::Clickable as _, story::Stories, theme::Theme};
use util::ResultExt as _;
mod app_state;
@ -101,7 +94,7 @@ impl Render for Workspace {
.flex_col()
.size_full()
.p_4()
.bg(theme.base)
.bg(theme.background)
.gap_4()
.child(
div()