refactor theme
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m46s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m35s

This commit is contained in:
2026-01-25 09:00:00 +07:00
parent 2f81753fff
commit cb4b7ff36d
41 changed files with 199 additions and 1446 deletions

View File

@@ -254,8 +254,6 @@ impl ChatRegistry {
self.notifications = Some(cx.background_spawn(async move {
let loop_duration = Duration::from_secs(12);
let mut is_start_processing = false;
let mut total_loops = 0;
loop {
@@ -263,7 +261,6 @@ impl ChatRegistry {
total_loops += 1;
if status.load(Ordering::Acquire) {
is_start_processing = true;
// Reset gift wrap processing flag
_ = status.compare_exchange(
true,
@@ -271,16 +268,12 @@ impl ChatRegistry {
Ordering::Release,
Ordering::Relaxed,
);
tx.send_async(NostrEvent::Unwrapping(true)).await.ok();
} else {
// Only run further if we are already processing
// Wait until after 2 loops to prevent exiting early while events are still being processed
if is_start_processing && total_loops >= 2 {
// Wait at least 2 loops to prevent exiting early while events are still being processed
if total_loops >= 2 {
tx.send_async(NostrEvent::Unwrapping(false)).await.ok();
// Reset the counter
is_start_processing = false;
total_loops = 0;
}
}

View File

@@ -12,6 +12,7 @@ use ui::Root;
use crate::actions::Quit;
mod actions;
mod panels;
mod sidebar;
mod user;
mod views;

View File

@@ -0,0 +1,124 @@
use dock::panel::{Panel, PanelEvent};
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Window,
};
use state::NostrRegistry;
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::{h_flex, v_flex, Icon, IconName, StyledExt};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Greeter> {
cx.new(|cx| Greeter::new(window, cx))
}
pub struct Greeter {
name: SharedString,
focus_handle: FocusHandle,
}
impl Greeter {
fn new(_window: &mut Window, cx: &mut App) -> Self {
Self {
name: "Greeter".into(),
focus_handle: cx.focus_handle(),
}
}
}
impl Panel for Greeter {
fn panel_id(&self) -> SharedString {
self.name.clone()
}
fn title(&self, cx: &App) -> AnyElement {
h_flex()
.gap_1p5()
.child(
svg()
.path("brand/coop.svg")
.size_4()
.text_color(cx.theme().text_muted),
)
.child(
div()
.text_sm()
.text_color(cx.theme().text_muted)
.child(self.name.clone()),
)
.into_any_element()
}
}
impl EventEmitter<PanelEvent> for Greeter {}
impl Focusable for Greeter {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Greeter {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
const TITLE: &str = "Welcome to Coop!";
const DESCRIPTION: &str = "You can bring your own keys to use your identity";
let nostr = NostrRegistry::global(cx);
let identity = nostr.read(cx).identity();
h_flex()
.relative()
.size_full()
.items_center()
.justify_center()
.child(
v_flex()
.gap_4()
.items_center()
.justify_center()
.child(
svg()
.path("brand/coop.svg")
.size_16()
.text_color(cx.theme().elevated_surface_background),
)
.when(!identity.read(cx).owned, |this| {
this.child(
v_flex()
.text_center()
.child(
div()
.font_semibold()
.line_height(relative(1.25))
.child(SharedString::from(TITLE)),
)
.child(
div()
.text_sm()
.text_color(cx.theme().text_muted)
.child(SharedString::from(DESCRIPTION)),
),
)
.child(
v_flex()
.gap_2()
.child(
Button::new("connect")
.icon(Icon::new(IconName::ArrowRight))
.label(SharedString::from(
"Connect account via Nostr Connect",
))
.primary()
.reverse(),
)
.child(
Button::new("key")
.label("Import a secret key or bunker")
.ghost_alt(),
),
)
}),
)
}
}

View File

@@ -0,0 +1 @@
pub mod greeter;

View File

@@ -629,29 +629,30 @@ impl Render for Sidebar {
.size_full()
.relative()
.gap_3()
.bg(cx.theme().surface_background)
// Titlebar
.child(
h_flex().h(TITLEBAR_HEIGHT).w_full().items_center().child(
h_flex()
.h_6()
.w_full()
.gap_2()
.justify_between()
.when_some(identity.read(cx).public_key, |this, public_key| {
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(&public_key, cx);
h_flex()
.child(
h_flex()
.w_full()
.gap_2()
.justify_between()
.when_some(identity.read(cx).public_key, |this, public_key| {
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(&public_key, cx);
this.child(
Button::new("user")
.small()
.reverse()
.transparent()
.icon(IconName::CaretDown)
.child(Avatar::new(profile.avatar()).size(rems(1.5))),
)
})
.child(div().pr_2p5().child(compose_button())),
),
this.child(
Button::new("user")
.label(profile.name())
.reverse()
.transparent()
.child(Avatar::new(profile.avatar()).size(rems(1.6))),
)
})
.child(div().pr_2p5().child(compose_button())),
)
.h(TITLEBAR_HEIGHT),
)
// Search Input
.child(

View File

@@ -2,4 +2,3 @@ pub mod compose;
pub mod preferences;
pub mod screening;
pub mod setup_relay;
pub mod welcome;

View File

@@ -20,8 +20,9 @@ use ui::{h_flex, v_flex, Root, Sizable, WindowExtension};
use crate::actions::{
reset, DarkMode, KeyringPopup, Logout, Settings, Themes, ViewProfile, ViewRelays,
};
use crate::panels::greeter;
use crate::user::viewer;
use crate::views::{preferences, setup_relay, welcome};
use crate::views::{preferences, setup_relay};
use crate::{sidebar, user};
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
@@ -117,7 +118,7 @@ impl Workspace {
let center = DockItem::split_with_sizes(
Axis::Vertical,
vec![DockItem::tabs(
vec![Arc::new(welcome::init(window, cx))],
vec![Arc::new(greeter::init(window, cx))],
None,
&weak_dock,
window,

View File

@@ -105,28 +105,33 @@ impl Sizable for Tab {
impl RenderOnce for Tab {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let (text_color, bg_color, hover_bg_color) = match (self.selected, self.disabled) {
(true, false) => (
cx.theme().text,
cx.theme().tab_active_background,
cx.theme().tab_hover_background,
),
(false, false) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
(true, true) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
(false, true) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
),
};
let (text_color, bg_color, hover_bg_color, border_color) =
match (self.selected, self.disabled) {
(true, false) => (
cx.theme().text,
cx.theme().tab_active_background,
cx.theme().tab_hover_background,
cx.theme().border,
),
(false, false) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
cx.theme().border_transparent,
),
(true, true) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
cx.theme().border_disabled,
),
(false, true) => (
cx.theme().text_muted,
cx.theme().ghost_element_background,
cx.theme().tab_hover_background,
cx.theme().border_disabled,
),
};
self.base
.id(self.ix)
@@ -144,14 +149,9 @@ impl RenderOnce for Tab {
.bg(bg_color)
.border_l(px(1.))
.border_r(px(1.))
.border_color(cx.theme().border)
.when(!self.selected, |this| {
this.hover(|this| {
this.text_color(text_color)
.bg(hover_bg_color)
.border_l(px(0.))
.border_r(px(0.))
})
.border_color(border_color)
.when(!self.selected && !self.disabled, |this| {
this.hover(|this| this.text_color(text_color).bg(hover_bg_color))
})
.when_some(self.prefix, |this, prefix| {
this.child(prefix).text_color(text_color)

View File

@@ -122,7 +122,6 @@ impl RenderOnce for TabBar {
h_flex()
.id("tabs")
.flex_grow()
.gap_1()
.overflow_x_scroll()
.when_some(self.scroll_handle, |this, scroll_handle| {
this.track_scroll(&scroll_handle)

View File

@@ -40,7 +40,7 @@ impl Identity {
pub fn new() -> Self {
Self {
public_key: None,
owned: false,
owned: true,
relay_list: RelayState::default(),
messaging_relays: RelayState::default(),
}

View File

@@ -98,92 +98,10 @@ pub struct ThemeColors {
///
/// Themes that do not specify all colors are refined off of these defaults.
impl ThemeColors {
/// 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: neutral().light().step_1(),
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_2(),
tab_hover_background: neutral().light().step_3(),
tab_active_background: neutral().light().step_1(),
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 {
pub fn colors() -> Self {
Self {
background: neutral().dark().step_1(),
surface_background: neutral().dark().step_2(),

View File

@@ -160,11 +160,7 @@ impl Theme {
theme.mode = mode;
// Set the theme colors
if mode.is_dark() {
theme.colors = *theme.theme.dark();
} else {
theme.colors = *theme.theme.light();
}
theme.colors = *theme.theme.colors();
// Refresh the window if available
if let Some(window) = window {
@@ -177,16 +173,18 @@ impl From<ThemeFamily> for Theme {
fn from(family: ThemeFamily) -> Self {
let platform = PlatformKind::platform();
let mode = ThemeMode::default();
// Define the theme colors based on the appearance
let colors = match mode {
ThemeMode::Light => family.light(),
ThemeMode::Dark => family.dark(),
let colors = family.colors();
// Define the font family based on the platform.
// TODO: Use native fonts on Linux too.
let font_family = match platform {
PlatformKind::Linux => "Inter",
_ => ".SystemUIFont",
};
Theme {
font_size: px(15.),
font_family: ".SystemUIFont".into(),
font_family: font_family.into(),
radius: px(5.),
radius_lg: px(10.),
shadow: true,

View File

@@ -51,37 +51,27 @@ pub struct ThemeFamily {
/// The URL of the theme.
pub url: String,
/// The light colors for the theme.
pub light: ThemeColors,
/// The dark colors for the theme.
pub dark: ThemeColors,
/// The colors for the theme.
pub colors: ThemeColors,
}
impl Default for ThemeFamily {
fn default() -> Self {
ThemeFamily {
id: "coop".into(),
name: "Coop Default Theme".into(),
name: "Coop Dark".into(),
author: "Coop".into(),
url: "https://github.com/lumehq/coop".into(),
light: ThemeColors::light(),
dark: ThemeColors::dark(),
colors: ThemeColors::colors(),
}
}
}
impl ThemeFamily {
/// Returns the light colors for the theme.
/// Returns the colors for the theme.
#[inline(always)]
pub fn light(&self) -> &ThemeColors {
&self.light
}
/// Returns the dark colors for the theme.
#[inline(always)]
pub fn dark(&self) -> &ThemeColors {
&self.dark
pub fn colors(&self) -> &ThemeColors {
&self.colors
}
/// Load a theme family from a JSON file.