From 1fdcff3b5152701b202634b015b38e7fd1bd2cb2 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 16 Oct 2024 18:38:21 +0800 Subject: [PATCH] theme: Add `apply_color` to theme to support merge a new color into theme color. (#353) - Fix #334 to keep selected theme color. ## Example image image image image --- crates/app/src/story_workspace.rs | 44 +++++++++++-------- crates/story/src/dropdown_story.rs | 27 ++++-------- crates/ui/src/theme.rs | 69 ++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 37 deletions(-) diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index 2a25d9db..056aefb1 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -14,7 +14,7 @@ use ui::{ dock::{DockArea, DockAreaState, DockEvent, DockItem, PanelView}, h_flex, popup_menu::PopupMenuExt, - theme::{ActiveTheme, Colorize as _, Theme}, + theme::{ActiveTheme, Theme}, ContextModal, IconName, Root, Sizable, TitleBar, }; @@ -40,6 +40,7 @@ pub fn init(_app_state: Arc, cx: &mut AppContext) { } pub struct StoryWorkspace { + theme_color: Option, dock_area: View, locale_selector: View, theme_color_picker: View, @@ -96,27 +97,22 @@ impl StoryWorkspace { let mut picker = ColorPicker::new("theme-color-picker", cx) .xsmall() .anchor(AnchorCorner::TopRight) - .label("Primary Color"); + .label("Theme Color"); picker.set_value(cx.theme().primary, cx); picker }); cx.subscribe( &theme_color_picker, - |_, _, ev: &ColorPickerEvent, cx| match ev { + |this, _, ev: &ColorPickerEvent, cx| match ev { ColorPickerEvent::Change(color) => { - if let Some(color) = color { - let theme = cx.global_mut::(); - theme.primary = *color; - theme.primary_hover = color.lighten(0.1); - theme.primary_active = color.darken(0.1); - cx.refresh(); - } + this.set_theme_color(*color, cx); } }, ) .detach(); Self { + theme_color: None, dock_area, locale_selector, theme_color_picker, @@ -125,6 +121,25 @@ impl StoryWorkspace { } } + fn set_theme_color(&mut self, color: Option, cx: &mut ViewContext) { + self.theme_color = color; + if let Some(color) = self.theme_color { + let theme = cx.global_mut::(); + theme.apply_color(color); + cx.refresh(); + } + } + + fn change_color_mode(&mut self, _: &ClickEvent, cx: &mut ViewContext) { + let mode = match cx.theme().mode.is_dark() { + true => ui::theme::ThemeMode::Light, + false => ui::theme::ThemeMode::Dark, + }; + + Theme::change(mode, cx); + self.set_theme_color(self.theme_color, cx); + } + fn save_layout(&mut self, dock_area: View, cx: &mut ViewContext) { self._save_layout_task = Some(cx.spawn(|this, mut cx| async move { Timer::after(Duration::from_secs(10)).await; @@ -336,14 +351,7 @@ impl Render for StoryWorkspace { }) .small() .ghost() - .on_click(move |_, cx| { - let mode = match cx.theme().mode.is_dark() { - true => ui::theme::ThemeMode::Light, - false => ui::theme::ThemeMode::Dark, - }; - - Theme::change(mode, cx); - }), + .on_click(cx.listener(Self::change_color_mode)), ) .child(self.locale_selector.clone()) .child( diff --git a/crates/story/src/dropdown_story.rs b/crates/story/src/dropdown_story.rs index 1e451829..be0dbb26 100644 --- a/crates/story/src/dropdown_story.rs +++ b/crates/story/src/dropdown_story.rs @@ -4,12 +4,11 @@ use gpui::{ }; use ui::{ - button::{Button, ButtonStyled}, - button_group::ButtonGroup, + checkbox::Checkbox, dropdown::{Dropdown, DropdownEvent, DropdownItem, SearchableVec}, h_flex, theme::ActiveTheme, - v_flex, FocusableCycle, IconName, Selectable, Sizable, + v_flex, FocusableCycle, IconName, Sizable, }; actions!(dropdown_story, [Tab, TabPrev]); @@ -232,22 +231,12 @@ impl Render for DropdownStory { .size_full() .gap_4() .child( - ButtonGroup::new("button-group") - .primary() - .small() - .on_click(cx.listener(|this, index: &Vec, cx| { - this.toggle_disabled(index.contains(&1), cx); - })) - .child( - Button::new("Enable") - .label("Enable") - .selected(!self.disabled), - ) - .child( - Button::new("Disable") - .label("Disable") - .selected(self.disabled), - ), + Checkbox::new("disable-dropdowns") + .label("Disabled") + .checked(self.disabled) + .on_click(cx.listener(|this, checked, cx| { + this.toggle_disabled(*checked, cx); + })), ) .child( h_flex() diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index 323f313e..0f1830fb 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -74,6 +74,7 @@ pub trait Colorize { fn invert_l(&self) -> Hsla; fn lighten(&self, amount: f32) -> Hsla; fn darken(&self, amount: f32) -> Hsla; + fn apply(&self, base_color: Hsla) -> Hsla; } impl Colorize for Hsla { @@ -128,6 +129,16 @@ impl Colorize for Hsla { Hsla { l, ..*self } } + + /// Return a new color with the same lightness and alpha but different hue and saturation. + fn apply(&self, new_color: Hsla) -> Hsla { + Hsla { + h: new_color.h, + s: new_color.s, + l: self.l, + a: self.a, + } + } } #[derive(Debug, Clone, Copy)] @@ -331,6 +342,64 @@ impl Theme { pub fn get_global(cx: &AppContext) -> &Self { cx.global::() } + + pub fn apply_color(&mut self, mask_color: Hsla) { + self.title_bar_background = self.title_bar_background.apply(mask_color); + self.background = self.background.apply(mask_color); + self.foreground = self.foreground.apply(mask_color); + self.card = self.card.apply(mask_color); + self.card_foreground = self.card_foreground.apply(mask_color); + self.popover = self.popover.apply(mask_color); + self.popover_foreground = self.popover_foreground.apply(mask_color); + self.primary = self.primary.apply(mask_color); + self.primary_hover = self.primary_hover.apply(mask_color); + self.primary_active = self.primary_active.apply(mask_color); + self.primary_foreground = self.primary_foreground.apply(mask_color); + self.secondary = self.secondary.apply(mask_color); + self.secondary_hover = self.secondary_hover.apply(mask_color); + self.secondary_active = self.secondary_active.apply(mask_color); + self.secondary_foreground = self.secondary_foreground.apply(mask_color); + self.destructive = self.destructive.apply(mask_color); + self.destructive_hover = self.destructive_hover.apply(mask_color); + self.destructive_active = self.destructive_active.apply(mask_color); + self.destructive_foreground = self.destructive_foreground.apply(mask_color); + self.muted = self.muted.apply(mask_color); + self.muted_foreground = self.muted_foreground.apply(mask_color); + self.accent = self.accent.apply(mask_color); + self.accent_foreground = self.accent_foreground.apply(mask_color); + self.border = self.border.apply(mask_color); + self.input = self.input.apply(mask_color); + self.ring = self.ring.apply(mask_color); + self.selection = self.selection.apply(mask_color); + self.scrollbar = self.scrollbar.apply(mask_color); + self.scrollbar_thumb = self.scrollbar_thumb.apply(mask_color); + self.panel = self.panel.apply(mask_color); + self.drag_border = self.drag_border.apply(mask_color); + self.drop_target = self.drop_target.apply(mask_color); + self.tab_bar = self.tab_bar.apply(mask_color); + self.tab = self.tab.apply(mask_color); + self.tab_active = self.tab_active.apply(mask_color); + self.tab_foreground = self.tab_foreground.apply(mask_color); + self.tab_active_foreground = self.tab_active_foreground.apply(mask_color); + self.progress_bar = self.progress_bar.apply(mask_color); + self.slider_bar = self.slider_bar.apply(mask_color); + self.slider_thumb = self.slider_thumb.apply(mask_color); + self.list = self.list.apply(mask_color); + self.list_even = self.list_even.apply(mask_color); + self.list_head = self.list_head.apply(mask_color); + self.list_active = self.list_active.apply(mask_color); + self.list_hover = self.list_hover.apply(mask_color); + self.table = self.table.apply(mask_color); + self.table_even = self.table_even.apply(mask_color); + self.table_active = self.table_active.apply(mask_color); + self.table_hover = self.table_hover.apply(mask_color); + self.table_row_border = self.table_row_border.apply(mask_color); + self.table_head_foreground = self.table_head_foreground.apply(mask_color); + self.link = self.link.apply(mask_color); + self.link_hover = self.link_hover.apply(mask_color); + self.link_active = self.link_active.apply(mask_color); + self.skeleton = self.skeleton.apply(mask_color); + } } impl From for Theme {