* 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>
417 lines
14 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|