Files
coop/crates/theme/src/lib.rs
reya c188f12993 chore: Refine the UI (#102)
* update deps

* update window options

* linux title bar

* fix build

* .

* fix build

* rounded corners on linux

* .

* .

* fix i18n key

* fix change subject modal

* .

* update new account

* .

* update relay modal

* .

* fix i18n keys

---------

Co-authored-by: reya <reya@macbook.local>
2025-08-02 11:37:15 +07:00

417 lines
14 KiB
Rust

use std::ops::{Deref, DerefMut};
use colors::{brand, hsl, neutral};
use gpui::{px, App, Global, Hsla, Pixels, SharedString, Window, WindowAppearance};
use crate::colors::{danger, warning};
use crate::platform_kind::PlatformKind;
use crate::scrollbar_mode::ScrollBarMode;
mod colors;
mod scale;
pub mod platform_kind;
pub mod scrollbar_mode;
/// Defines window border radius for platforms that use client side decorations.
pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0);
/// Defines window shadow size for platforms that use client side decorations.
pub const CLIENT_SIDE_DECORATION_SHADOW: Pixels = px(10.0);
pub fn init(cx: &mut App) {
Theme::sync_system_appearance(None, cx);
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ThemeColor {
// Surface colors
pub background: Hsla,
pub surface_background: Hsla,
pub elevated_surface_background: Hsla,
pub panel_background: Hsla,
pub overlay: Hsla,
pub title_bar: Hsla,
pub title_bar_inactive: Hsla,
pub window_border: Hsla,
// Border colors
pub border: Hsla,
pub border_variant: Hsla,
pub border_focused: Hsla,
pub border_selected: Hsla,
pub border_transparent: Hsla,
pub border_disabled: Hsla,
pub ring: Hsla,
// Text colors
pub text: Hsla,
pub text_muted: Hsla,
pub text_placeholder: Hsla,
pub text_accent: Hsla,
// Icon colors
pub icon: Hsla,
pub icon_muted: Hsla,
pub icon_accent: Hsla,
// Element colors
pub element_foreground: Hsla,
pub element_background: Hsla,
pub element_hover: Hsla,
pub element_active: Hsla,
pub element_selected: Hsla,
pub element_disabled: Hsla,
// Secondary element colors
pub secondary_foreground: Hsla,
pub secondary_background: Hsla,
pub secondary_hover: Hsla,
pub secondary_active: Hsla,
pub secondary_selected: Hsla,
pub secondary_disabled: Hsla,
// Danger element colors
pub danger_foreground: Hsla,
pub danger_background: Hsla,
pub danger_hover: Hsla,
pub danger_active: Hsla,
pub danger_selected: Hsla,
pub danger_disabled: Hsla,
// Warning element colors
pub warning_foreground: Hsla,
pub warning_background: Hsla,
pub warning_hover: Hsla,
pub warning_active: Hsla,
pub warning_selected: Hsla,
pub warning_disabled: Hsla,
// Ghost element colors
pub ghost_element_background: Hsla,
pub ghost_element_background_alt: Hsla,
pub ghost_element_hover: Hsla,
pub ghost_element_active: Hsla,
pub ghost_element_selected: Hsla,
pub ghost_element_disabled: Hsla,
// Tab colors
pub tab_inactive_background: Hsla,
pub tab_hover_background: Hsla,
pub tab_active_background: Hsla,
// Scrollbar colors
pub scrollbar_thumb_background: Hsla,
pub scrollbar_thumb_hover_background: Hsla,
pub scrollbar_thumb_border: Hsla,
pub scrollbar_track_background: Hsla,
pub scrollbar_track_border: Hsla,
// Interactive colors
pub drop_target_background: Hsla,
pub cursor: Hsla,
pub selection: Hsla,
}
/// The default colors for the theme.
///
/// Themes that do not specify all colors are refined off of these defaults.
impl ThemeColor {
/// Returns the default colors for light themes.
///
/// Themes that do not specify all colors are refined off of these defaults.
pub fn light() -> Self {
Self {
background: neutral().light().step_1(),
surface_background: neutral().light().step_2(),
elevated_surface_background: neutral().light().step_3(),
panel_background: gpui::white(),
overlay: neutral().light_alpha().step_3(),
title_bar: gpui::transparent_black(),
title_bar_inactive: neutral().light().step_1(),
window_border: hsl(240.0, 5.9, 78.0),
border: neutral().light().step_6(),
border_variant: neutral().light().step_5(),
border_focused: brand().light().step_7(),
border_selected: brand().light().step_7(),
border_transparent: gpui::transparent_black(),
border_disabled: neutral().light().step_3(),
ring: brand().light().step_8(),
text: neutral().light().step_12(),
text_muted: neutral().light().step_11(),
text_placeholder: neutral().light().step_10(),
text_accent: brand().light().step_11(),
icon: neutral().light().step_11(),
icon_muted: neutral().light().step_10(),
icon_accent: brand().light().step_11(),
element_foreground: brand().light().step_12(),
element_background: brand().light().step_9(),
element_hover: brand().light_alpha().step_10(),
element_active: brand().light().step_10(),
element_selected: brand().light().step_11(),
element_disabled: brand().light_alpha().step_3(),
secondary_foreground: brand().light().step_11(),
secondary_background: brand().light().step_3(),
secondary_hover: brand().light_alpha().step_4(),
secondary_active: brand().light().step_5(),
secondary_selected: brand().light().step_5(),
secondary_disabled: brand().light_alpha().step_3(),
danger_foreground: danger().light().step_12(),
danger_background: danger().light().step_3(),
danger_hover: danger().light_alpha().step_4(),
danger_active: danger().light().step_5(),
danger_selected: danger().light().step_5(),
danger_disabled: danger().light_alpha().step_3(),
warning_foreground: warning().light().step_12(),
warning_background: warning().light().step_3(),
warning_hover: warning().light_alpha().step_4(),
warning_active: warning().light().step_5(),
warning_selected: warning().light().step_5(),
warning_disabled: warning().light_alpha().step_3(),
ghost_element_background: gpui::transparent_black(),
ghost_element_background_alt: neutral().light().step_3(),
ghost_element_hover: neutral().light_alpha().step_4(),
ghost_element_active: neutral().light().step_5(),
ghost_element_selected: neutral().light().step_5(),
ghost_element_disabled: neutral().light_alpha().step_2(),
tab_inactive_background: neutral().light().step_3(),
tab_hover_background: neutral().light().step_4(),
tab_active_background: neutral().light().step_5(),
scrollbar_thumb_background: neutral().light_alpha().step_3(),
scrollbar_thumb_hover_background: neutral().light_alpha().step_4(),
scrollbar_thumb_border: gpui::transparent_black(),
scrollbar_track_background: gpui::transparent_black(),
scrollbar_track_border: neutral().light().step_5(),
drop_target_background: brand().light_alpha().step_2(),
cursor: hsl(200., 100., 50.),
selection: hsl(200., 100., 50.).alpha(0.25),
}
}
/// Returns the default colors for dark themes.
///
/// Themes that do not specify all colors are refined off of these defaults.
pub fn dark() -> Self {
Self {
background: neutral().dark().step_1(),
surface_background: neutral().dark().step_2(),
elevated_surface_background: neutral().dark().step_3(),
panel_background: gpui::black(),
overlay: neutral().dark_alpha().step_3(),
title_bar: gpui::transparent_black(),
title_bar_inactive: neutral().dark().step_1(),
window_border: hsl(240.0, 3.7, 28.0),
border: neutral().dark().step_6(),
border_variant: neutral().dark().step_5(),
border_focused: brand().dark().step_7(),
border_selected: brand().dark().step_7(),
border_transparent: gpui::transparent_black(),
border_disabled: neutral().dark().step_3(),
ring: brand().dark().step_8(),
text: neutral().dark().step_12(),
text_muted: neutral().dark().step_11(),
text_placeholder: neutral().dark().step_10(),
text_accent: brand().dark().step_11(),
icon: neutral().dark().step_11(),
icon_muted: neutral().dark().step_10(),
icon_accent: brand().dark().step_11(),
element_foreground: brand().dark().step_1(),
element_background: brand().dark().step_9(),
element_hover: brand().dark_alpha().step_10(),
element_active: brand().dark().step_10(),
element_selected: brand().dark().step_11(),
element_disabled: brand().dark_alpha().step_3(),
secondary_foreground: brand().dark().step_12(),
secondary_background: brand().dark().step_3(),
secondary_hover: brand().dark_alpha().step_4(),
secondary_active: brand().dark().step_5(),
secondary_selected: brand().dark().step_5(),
secondary_disabled: brand().dark_alpha().step_3(),
danger_foreground: danger().dark().step_12(),
danger_background: danger().dark().step_3(),
danger_hover: danger().dark_alpha().step_4(),
danger_active: danger().dark().step_5(),
danger_selected: danger().dark().step_5(),
danger_disabled: danger().dark_alpha().step_3(),
warning_foreground: warning().dark().step_12(),
warning_background: warning().dark().step_3(),
warning_hover: warning().dark_alpha().step_4(),
warning_active: warning().dark().step_5(),
warning_selected: warning().dark().step_5(),
warning_disabled: warning().dark_alpha().step_3(),
ghost_element_background: gpui::transparent_black(),
ghost_element_background_alt: neutral().dark().step_3(),
ghost_element_hover: neutral().dark_alpha().step_4(),
ghost_element_active: neutral().dark().step_5(),
ghost_element_selected: neutral().dark().step_5(),
ghost_element_disabled: neutral().dark_alpha().step_2(),
tab_inactive_background: neutral().dark().step_3(),
tab_hover_background: neutral().dark().step_4(),
tab_active_background: neutral().dark().step_5(),
scrollbar_thumb_background: neutral().dark_alpha().step_3(),
scrollbar_thumb_hover_background: neutral().dark_alpha().step_4(),
scrollbar_thumb_border: gpui::transparent_black(),
scrollbar_track_background: gpui::transparent_black(),
scrollbar_track_border: neutral().dark().step_5(),
drop_target_background: brand().dark_alpha().step_2(),
cursor: hsl(200., 100., 50.),
selection: hsl(200., 100., 50.).alpha(0.25),
}
}
}
pub trait ActiveTheme {
fn theme(&self) -> &Theme;
}
impl ActiveTheme for App {
#[inline(always)]
fn theme(&self) -> &Theme {
Theme::global(self)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Eq, Hash)]
pub enum ThemeMode {
Light,
#[default]
Dark,
}
impl ThemeMode {
pub fn is_dark(&self) -> bool {
matches!(self, Self::Dark)
}
/// Return lower_case theme name: `light`, `dark`.
pub fn name(&self) -> &'static str {
match self {
ThemeMode::Light => "light",
ThemeMode::Dark => "dark",
}
}
}
impl From<WindowAppearance> for ThemeMode {
fn from(appearance: WindowAppearance) -> Self {
match appearance {
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
}
}
}
#[derive(Debug, Clone)]
pub struct Theme {
pub colors: ThemeColor,
pub mode: ThemeMode,
pub font_family: SharedString,
pub font_size: Pixels,
pub radius: Pixels,
pub scrollbar_mode: ScrollBarMode,
pub platform_kind: PlatformKind,
}
impl Deref for Theme {
type Target = ThemeColor;
fn deref(&self) -> &Self::Target {
&self.colors
}
}
impl DerefMut for Theme {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.colors
}
}
impl Global for Theme {}
impl Theme {
/// Returns the global theme reference
pub fn global(cx: &App) -> &Theme {
cx.global::<Theme>()
}
/// Returns the global theme mutable reference
pub fn global_mut(cx: &mut App) -> &mut Theme {
cx.global_mut::<Theme>()
}
/// Returns true if the theme is dark.
pub fn is_dark(&self) -> bool {
self.mode.is_dark()
}
/// Sync the theme with the system appearance
pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
let appearance = window
.as_ref()
.map(|window| window.appearance())
.unwrap_or_else(|| cx.window_appearance());
Self::change(appearance, window, cx);
}
/// Change the app's appearance
pub fn change(mode: impl Into<ThemeMode>, window: Option<&mut Window>, cx: &mut App) {
let mode = mode.into();
let colors = match mode {
ThemeMode::Light => ThemeColor::light(),
ThemeMode::Dark => ThemeColor::dark(),
};
if !cx.has_global::<Theme>() {
let theme = Theme::from(colors);
cx.set_global(theme);
}
let theme = cx.global_mut::<Theme>();
theme.mode = mode;
theme.colors = colors;
if let Some(window) = window {
window.refresh();
}
}
}
impl From<ThemeColor> for Theme {
fn from(colors: ThemeColor) -> Self {
let mode = ThemeMode::default();
Theme {
font_size: px(15.),
font_family: ".SystemUIFont".into(),
radius: px(5.),
scrollbar_mode: ScrollBarMode::default(),
platform_kind: PlatformKind::platform(),
mode,
colors,
}
}
}