update theme

This commit is contained in:
2026-10-08 13:55:23 +07:00
parent f54f5f35a8
commit 16cd0ae903
14 changed files with 49 additions and 64 deletions
+3 -3
View File
@@ -1244,7 +1244,7 @@ impl ChatPanel {
.w_full() .w_full()
.px_2() .px_2()
.border_l_2() .border_l_2()
.border_color(cx.theme().accent.active) .border_color(cx.theme().accent.strong)
.text_sm() .text_sm()
.child(div().font_semibold().child(author.name())) .child(div().font_semibold().child(author.name()))
.child( .child(
@@ -1647,7 +1647,7 @@ impl ChatPanel {
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.rounded(cx.theme().radius) .rounded(cx.theme().radius)
.child(Icon::new(IconName::Lock).text_color(cx.theme().accent.primary)) .child(Icon::new(IconName::Lock).text_color(cx.theme().accent.strong))
.child( .child(
v_flex() v_flex()
.min_w_0() .min_w_0()
@@ -1753,7 +1753,7 @@ impl ChatPanel {
.w_full() .w_full()
.pl_2() .pl_2()
.border_l_2() .border_l_2()
.border_color(cx.theme().accent.active) .border_color(cx.theme().accent.strong)
.child( .child(
div() div()
.flex() .flex()
+2 -2
View File
@@ -180,8 +180,8 @@ pub(crate) fn render_activity(ix: usize, activity: &Activity, cx: &App) -> AnyEl
fn badge(activity: &Activity, cx: &App) -> AnyElement { fn badge(activity: &Activity, cx: &App) -> AnyElement {
let (icon, color) = match activity.kind { let (icon, color) = match activity.kind {
Kind::GroupJoinRequest => (IconName::Login, cx.theme().accent.primary), Kind::GroupJoinRequest => (IconName::Login, cx.theme().accent.strong),
Kind::GroupPutUser => (IconName::Login, cx.theme().accent.primary), Kind::GroupPutUser => (IconName::Login, cx.theme().accent.strong),
Kind::GroupLeaveRequest => (IconName::Logout, cx.theme().danger), Kind::GroupLeaveRequest => (IconName::Logout, cx.theme().danger),
Kind::GroupRemoveUser => (IconName::Ban, cx.theme().danger), Kind::GroupRemoveUser => (IconName::Ban, cx.theme().danger),
_ => (IconName::Info, cx.theme().text_faint), _ => (IconName::Info, cx.theme().text_faint),
+9 -9
View File
@@ -241,7 +241,7 @@ fn coop_dark() -> ThemeVariant {
appearance: Appearance::Dark, appearance: Appearance::Dark,
treatment: SurfaceTreatment::Frosted, treatment: SurfaceTreatment::Frosted,
background: "#060606", background: "#060606",
shell: "#0d0d0d", shell: "#16161b",
raised: "#343438", raised: "#343438",
card: "#0e0e0e", card: "#0e0e0e",
text: "#e8e8ea", text: "#e8e8ea",
@@ -274,18 +274,18 @@ fn coop_light() -> ThemeVariant {
name: "Coop Light", name: "Coop Light",
appearance: Appearance::Light, appearance: Appearance::Light,
treatment: SurfaceTreatment::Frosted, treatment: SurfaceTreatment::Frosted,
background: "#ffffff", background: "#fffdf4",
shell: "#f3f3f5", shell: "#f7f3e8",
raised: "#ededf0", raised: "#ede8d8",
card: "#ffffff", card: "#ffffff",
text: "#303035", text: "#33302a",
muted: "#62626a", muted: "#6d675a",
faint: "#797981", faint: "#87816f",
accent: "#a19200", accent: "#FFE629",
danger: "#dc2626", danger: "#dc2626",
warning: "#a16207", warning: "#a16207",
success: "#15803d", success: "#15803d",
terminal_background: "#fafafa", terminal_background: "#fbf8ee",
ansi: ANSI_LIGHT, ansi: ANSI_LIGHT,
syntax: [ syntax: [
"#6b7280", "#5b43e8", "#15803d", "#a16207", "#7e22ce", "#2563eb", "#be185d", "#303035", "#6b7280", "#5b43e8", "#15803d", "#a16207", "#7e22ce", "#2563eb", "#be185d", "#303035",
+2 -2
View File
@@ -29,7 +29,7 @@ pub fn init(cx: &mut App) {
log::warn!("theme {}: {}", issue.variant_id, issue.message); log::warn!("theme {}: {}", issue.variant_id, issue.message);
} }
Theme::sync_system_appearance(None, cx); Theme::change(Appearance::default(), None, cx);
Theme::sync_scrollbar_appearance(cx); Theme::sync_scrollbar_appearance(cx);
} }
@@ -75,7 +75,7 @@ pub fn sync_base(cx: &mut App) {
tokens.destructive_foreground = accent.on_danger; tokens.destructive_foreground = accent.on_danger;
tokens.border = colors.border; tokens.border = colors.border;
tokens.input = colors.input; tokens.input = colors.input;
tokens.ring = accent.primary; tokens.ring = accent.strong;
tokens.selection = accent.selection; tokens.selection = accent.selection;
} }
+20 -13
View File
@@ -109,10 +109,11 @@ impl Color {
} }
pub fn best_on_color(self) -> Self { pub fn best_on_color(self) -> Self {
if Self::WHITE.contrast(self) >= Self::BLACK.contrast(self) { // Dark surfaces carry white text even where black would contrast more.
Self::WHITE if self.luminance() > 0.3 {
} else {
Self::BLACK Self::BLACK
} else {
Self::WHITE
} }
} }
@@ -275,7 +276,7 @@ impl AccentPreset {
pub fn color(self, appearance: Appearance) -> Color { pub fn color(self, appearance: Appearance) -> Color {
let (dark, light) = match self { let (dark, light) = match self {
Self::Coop => ("#FFE629", "#a19200"), Self::Coop => ("#FFE629", "#FFE629"),
Self::Orange => ("#fb923c", "#c2410c"), Self::Orange => ("#fb923c", "#c2410c"),
Self::Amber => ("#fbbf24", "#a16207"), Self::Amber => ("#fbbf24", "#a16207"),
Self::Green => ("#4ade80", "#15803d"), Self::Green => ("#4ade80", "#15803d"),
@@ -320,11 +321,17 @@ pub struct AccentRoles {
impl AccentRoles { impl AccentRoles {
pub fn derive(primary: Color, appearance: Appearance, background: Color) -> Self { pub fn derive(primary: Color, appearance: Appearance, background: Color) -> Self {
let primary = primary.ensure_contrast(background, 3.0); // Light mode keeps the raw brand accent for fills; `strong` backs strokes at 3:1.
let (primary, mut strong) = if appearance.is_dark() {
let primary = primary.ensure_contrast(background, 3.0);
(primary, primary)
} else {
(primary, primary.ensure_contrast(background, 3.0))
};
let on = primary.best_on_color(); let on = primary.best_on_color();
let mut strong = primary; // 3:1 pairing so white-on-dark accents don't force the fill darker.
if on.contrast(strong) < 4.5 { if on.contrast(strong) < 3.0 {
strong = strong.ensure_contrast(on, 4.5); strong = strong.ensure_contrast(on, 3.0);
} }
let light = primary.mix( let light = primary.mix(
if appearance.is_dark() { if appearance.is_dark() {
@@ -528,8 +535,8 @@ impl ThemeRegistry {
validate_contrast( validate_contrast(
&mut issues, &mut issues,
variant, variant,
"accent", "accent-strong",
variant.accent.primary, variant.accent.strong,
variant.colors.background, variant.colors.background,
3.0, 3.0,
); );
@@ -539,7 +546,7 @@ impl ThemeRegistry {
"on-accent", "on-accent",
variant.accent.on, variant.accent.on,
variant.accent.strong, variant.accent.strong,
4.5, 3.0,
); );
validate_contrast( validate_contrast(
&mut issues, &mut issues,
@@ -667,11 +674,11 @@ mod tests {
for preset in AccentPreset::ALL { for preset in AccentPreset::ALL {
let roles = AccentRoles::derive(preset.color(appearance), appearance, background); let roles = AccentRoles::derive(preset.color(appearance), appearance, background);
assert!( assert!(
roles.primary.contrast(background) >= 3.0, roles.strong.contrast(background) >= 3.0,
"{appearance:?} {preset:?}" "{appearance:?} {preset:?}"
); );
assert!( assert!(
roles.on.contrast(roles.strong) >= 4.5, roles.on.contrast(roles.strong) >= 3.0,
"{appearance:?} {preset:?}" "{appearance:?} {preset:?}"
); );
} }
+3
View File
@@ -14,6 +14,8 @@ fn hsla(color: Color) -> Hsla {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct ResolvedAccentRoles { pub struct ResolvedAccentRoles {
pub primary: Hsla, pub primary: Hsla,
/// Accent for strokes, icons, and text on the background; meets 3:1 contrast.
pub strong: Hsla,
pub wash: Hsla, pub wash: Hsla,
pub on: Hsla, pub on: Hsla,
pub selection: Hsla, pub selection: Hsla,
@@ -32,6 +34,7 @@ impl ResolvedAccentRoles {
let wash_background = accent.wash.blend_over(background); let wash_background = accent.wash.blend_over(background);
Self { Self {
primary: hsla(accent.primary), primary: hsla(accent.primary),
strong: hsla(accent.strong),
wash: hsla(accent.wash), wash: hsla(accent.wash),
on: hsla(accent.on), on: hsla(accent.on),
selection: hsla(accent.selection), selection: hsla(accent.selection),
-18
View File
@@ -10,15 +10,6 @@ use crate::model::{
use crate::resolved::{ResolvedAccentRoles, ResolvedColors}; use crate::resolved::{ResolvedAccentRoles, ResolvedColors};
use crate::{NotificationSettings, PlatformKind, ScrollbarMode, sync_base}; use crate::{NotificationSettings, PlatformKind, ScrollbarMode, sync_base};
impl From<gpui::WindowAppearance> for Appearance {
fn from(appearance: gpui::WindowAppearance) -> Self {
match appearance {
gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => Self::Dark,
gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => Self::Light,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Theme { pub struct Theme {
pub variant: Arc<ThemeVariant>, pub variant: Arc<ThemeVariant>,
@@ -99,15 +90,6 @@ impl Theme {
} }
} }
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);
}
pub fn sync_scrollbar_appearance(cx: &mut App) { pub fn sync_scrollbar_appearance(cx: &mut App) {
Theme::global_mut(cx).scrollbar_mode = if cx.should_auto_hide_scrollbars() { Theme::global_mut(cx).scrollbar_mode = if cx.should_auto_hide_scrollbars() {
ScrollbarMode::Scrolling ScrollbarMode::Scrolling
+4 -4
View File
@@ -465,7 +465,7 @@ impl TabGroupSkin {
let bar = TabBar::new("tab-bar") let bar = TabBar::new("tab-bar")
.track_scroll(&self.scroll_handle) .track_scroll(&self.scroll_handle)
.h(TABBAR_HEIGHT) .h(TABBAR_HEIGHT)
.bg(cx.theme().shell) .bg(cx.theme().background)
.when(needs_traffic_light_padding, |this| { .when(needs_traffic_light_padding, |this| {
this.pl(px(TRAFFIC_LIGHT_PADDING)) this.pl(px(TRAFFIC_LIGHT_PADDING))
}) })
@@ -567,7 +567,7 @@ impl TabGroupSkin {
h_flex() h_flex()
.h(TABBAR_HEIGHT) .h(TABBAR_HEIGHT)
.w_full() .w_full()
.bg(cx.theme().shell) .bg(cx.theme().background)
.child(bar.flex_1()) .child(bar.flex_1())
.child(window_controls()) .child(window_controls())
.into_any_element() .into_any_element()
@@ -717,7 +717,7 @@ impl TabGroupRenderer for TabGroupSkin {
.child( .child(
div() div()
.size_full() .size_full()
.bg(cx.theme().shell) .bg(cx.theme().background)
.overflow_hidden() .overflow_hidden()
.child(panel.cached(StyleRefinement::default().v_flex().size_full())), .child(panel.cached(StyleRefinement::default().v_flex().size_full())),
) )
@@ -760,7 +760,7 @@ impl TabGroupRenderer for TabGroupSkin {
.h(height) .h(height)
.rounded(cx.theme().radius_lg) .rounded(cx.theme().radius_lg)
.border_1() .border_1()
.border_color(cx.theme().accent.primary.opacity(0.3)) .border_color(cx.theme().accent.strong.opacity(0.3))
.bg(cx.theme().accent.selection) .bg(cx.theme().accent.selection)
.into_any_element(), .into_any_element(),
) )
+1 -1
View File
@@ -69,7 +69,7 @@ impl RenderedText {
TextViewStyle::default() TextViewStyle::default()
.with_foreground(theme.text) .with_foreground(theme.text)
.with_muted_foreground(theme.text_muted) .with_muted_foreground(theme.text_muted)
.with_link(theme.accent.primary) .with_link(theme.accent.strong)
.with_code_background(theme.raised) .with_code_background(theme.raised)
.with_border(theme.border) .with_border(theme.border)
.with_code_block(code_block) .with_code_block(code_block)
+1 -1
View File
@@ -46,7 +46,7 @@ impl NotificationKind {
.text_color(cx.theme().text_muted), .text_color(cx.theme().text_muted),
Self::Success => Icon::new(IconName::CheckCircle) Self::Success => Icon::new(IconName::CheckCircle)
.with_size(Size::Medium) .with_size(Size::Medium)
.text_color(cx.theme().accent.primary), .text_color(cx.theme().accent.strong),
Self::Warning => Icon::new(IconName::Warning) Self::Warning => Icon::new(IconName::Warning)
.with_size(Size::Medium) .with_size(Size::Medium)
.text_color(cx.theme().warning), .text_color(cx.theme().warning),
+1 -1
View File
@@ -12,7 +12,7 @@ pub(crate) fn resize_handle_appearance() -> ResizeHandleRenderer {
Rc::new( Rc::new(
|context: &ResizeHandleContext, _: &mut Window, cx: &mut App| { |context: &ResizeHandleContext, _: &mut Window, cx: &mut App| {
let color = if context.is_active() { let color = if context.is_active() {
cx.theme().accent.primary cx.theme().accent.strong
} else { } else {
cx.theme().border cx.theme().border
}; };
+1 -1
View File
@@ -533,7 +533,7 @@ fn status_badge(status: Option<bool>, cx: &App) -> Div {
if let Some(status) = status { if let Some(status) = status {
this.child(Icon::new(IconName::CheckCircle).small().text_color({ this.child(Icon::new(IconName::CheckCircle).small().text_color({
if status { if status {
cx.theme().accent.primary cx.theme().accent.strong
} else { } else {
cx.theme().text_faint cx.theme().text_faint
} }
+1 -8
View File
@@ -15,7 +15,7 @@ use nostr_sdk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::{NostrRegistry, StateEvent}; use state::{NostrRegistry, StateEvent};
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry, ThemeVariant}; use theme::{ActiveTheme, SIDEBAR_WIDTH, ThemeRegistry, ThemeVariant};
use ui::button::{Button, ButtonVariants}; use ui::button::{Button, ButtonVariants};
use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle}; use ui::dock::{self, DockArea, DockLayout, DockPlacement, Panel, PanelHandle};
use ui::notification::{Notification, NotificationKind}; use ui::notification::{Notification, NotificationKind};
@@ -82,13 +82,6 @@ impl Workspace {
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
subscriptions.push(
// Observe system appearance and update theme
cx.observe_window_appearance(window, |_this, window, cx| {
Theme::sync_system_appearance(Some(window), cx);
}),
);
subscriptions.push( subscriptions.push(
// Subscribe to the nostr events // Subscribe to the nostr events
cx.subscribe_in(&nostr, window, move |_this, _state, event, window, cx| { cx.subscribe_in(&nostr, window, move |_this, _state, event, window, cx| {
+1 -1
View File
@@ -336,7 +336,7 @@ impl Render for Sidebar {
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.text_color(cx.theme().text_muted) .text_color(cx.theme().text_muted)
.child(Indicator::new().small().color(cx.theme().accent.primary)) .child(Indicator::new().small().color(cx.theme().accent.strong))
.child("Getting messages..."), .child("Getting messages..."),
), ),
) )