Skip to content

Commit

Permalink
Add theme preview (#20039)
Browse files Browse the repository at this point in the history
This PR adds a theme preview tab to help get an at a glance overview of
the styles in a theme.

![CleanShot 2024-10-31 at 11 27
18@2x](https://github.com/user-attachments/assets/798e97cf-9f80-4994-b2fd-ac1dcd58e4d9)

You can open it using `debug: open theme preview`.

The next major theme preview PR will move this into it's own crate, as
it will grow substantially as we add content.

Next for theme preview:

- Update layout to two columns, with controls on the right for selecting
theme, layer/elevation-index, etc.
- Cover more UI elements in preview
- Display theme colors in a more helpful way
- Add syntax & markdown previews


Release Notes:

- Added a way to preview the current theme's styles with the `debug:
open theme preview` command.
  • Loading branch information
iamnbutler authored Oct 31, 2024
1 parent 9c77bcc commit a347c4d
Show file tree
Hide file tree
Showing 9 changed files with 813 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion crates/gpui/src/color.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{bail, Context};
use serde::de::{self, Deserialize, Deserializer, Visitor};
use std::{
fmt,
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
};

Expand Down Expand Up @@ -279,6 +279,19 @@ impl Hash for Hsla {
}
}

impl Display for Hsla {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"hsla({:.2}, {:.2}%, {:.2}%, {:.2})",
self.h * 360.,
self.s * 100.,
self.l * 100.,
self.a
)
}
}

/// Construct an [`Hsla`] object from plain values
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla {
Expand Down
1 change: 1 addition & 0 deletions crates/theme/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ serde_json.workspace = true
serde_json_lenient.workspace = true
serde_repr.workspace = true
settings.workspace = true
strum.workspace = true
util.workspace = true
uuid.workspace = true

Expand Down
237 changes: 235 additions & 2 deletions crates/theme/src/styles/colors.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#![allow(missing_docs)]

use gpui::{Hsla, WindowBackgroundAppearance};
use gpui::{Hsla, SharedString, WindowBackgroundAppearance, WindowContext};
use refineable::Refineable;
use std::sync::Arc;
use strum::{AsRefStr, EnumIter, IntoEnumIterator};

use crate::{
AccentColors, PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors,
AccentColors, ActiveTheme, PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme,
SystemColors,
};

#[derive(Refineable, Clone, Debug, PartialEq)]
Expand Down Expand Up @@ -249,6 +251,237 @@ pub struct ThemeColors {
pub link_text_hover: Hsla,
}

#[derive(EnumIter, Debug, Clone, Copy, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum ThemeColorField {
Border,
BorderVariant,
BorderFocused,
BorderSelected,
BorderTransparent,
BorderDisabled,
ElevatedSurfaceBackground,
SurfaceBackground,
Background,
ElementBackground,
ElementHover,
ElementActive,
ElementSelected,
ElementDisabled,
DropTargetBackground,
GhostElementBackground,
GhostElementHover,
GhostElementActive,
GhostElementSelected,
GhostElementDisabled,
Text,
TextMuted,
TextPlaceholder,
TextDisabled,
TextAccent,
Icon,
IconMuted,
IconDisabled,
IconPlaceholder,
IconAccent,
StatusBarBackground,
TitleBarBackground,
TitleBarInactiveBackground,
ToolbarBackground,
TabBarBackground,
TabInactiveBackground,
TabActiveBackground,
SearchMatchBackground,
PanelBackground,
PanelFocusedBorder,
PanelIndentGuide,
PanelIndentGuideHover,
PanelIndentGuideActive,
PaneFocusedBorder,
PaneGroupBorder,
ScrollbarThumbBackground,
ScrollbarThumbHoverBackground,
ScrollbarThumbBorder,
ScrollbarTrackBackground,
ScrollbarTrackBorder,
EditorForeground,
EditorBackground,
EditorGutterBackground,
EditorSubheaderBackground,
EditorActiveLineBackground,
EditorHighlightedLineBackground,
EditorLineNumber,
EditorActiveLineNumber,
EditorInvisible,
EditorWrapGuide,
EditorActiveWrapGuide,
EditorIndentGuide,
EditorIndentGuideActive,
EditorDocumentHighlightReadBackground,
EditorDocumentHighlightWriteBackground,
EditorDocumentHighlightBracketBackground,
TerminalBackground,
TerminalForeground,
TerminalBrightForeground,
TerminalDimForeground,
TerminalAnsiBackground,
TerminalAnsiBlack,
TerminalAnsiBrightBlack,
TerminalAnsiDimBlack,
TerminalAnsiRed,
TerminalAnsiBrightRed,
TerminalAnsiDimRed,
TerminalAnsiGreen,
TerminalAnsiBrightGreen,
TerminalAnsiDimGreen,
TerminalAnsiYellow,
TerminalAnsiBrightYellow,
TerminalAnsiDimYellow,
TerminalAnsiBlue,
TerminalAnsiBrightBlue,
TerminalAnsiDimBlue,
TerminalAnsiMagenta,
TerminalAnsiBrightMagenta,
TerminalAnsiDimMagenta,
TerminalAnsiCyan,
TerminalAnsiBrightCyan,
TerminalAnsiDimCyan,
TerminalAnsiWhite,
TerminalAnsiBrightWhite,
TerminalAnsiDimWhite,
LinkTextHover,
}

impl ThemeColors {
pub fn color(&self, field: ThemeColorField) -> Hsla {
match field {
ThemeColorField::Border => self.border,
ThemeColorField::BorderVariant => self.border_variant,
ThemeColorField::BorderFocused => self.border_focused,
ThemeColorField::BorderSelected => self.border_selected,
ThemeColorField::BorderTransparent => self.border_transparent,
ThemeColorField::BorderDisabled => self.border_disabled,
ThemeColorField::ElevatedSurfaceBackground => self.elevated_surface_background,
ThemeColorField::SurfaceBackground => self.surface_background,
ThemeColorField::Background => self.background,
ThemeColorField::ElementBackground => self.element_background,
ThemeColorField::ElementHover => self.element_hover,
ThemeColorField::ElementActive => self.element_active,
ThemeColorField::ElementSelected => self.element_selected,
ThemeColorField::ElementDisabled => self.element_disabled,
ThemeColorField::DropTargetBackground => self.drop_target_background,
ThemeColorField::GhostElementBackground => self.ghost_element_background,
ThemeColorField::GhostElementHover => self.ghost_element_hover,
ThemeColorField::GhostElementActive => self.ghost_element_active,
ThemeColorField::GhostElementSelected => self.ghost_element_selected,
ThemeColorField::GhostElementDisabled => self.ghost_element_disabled,
ThemeColorField::Text => self.text,
ThemeColorField::TextMuted => self.text_muted,
ThemeColorField::TextPlaceholder => self.text_placeholder,
ThemeColorField::TextDisabled => self.text_disabled,
ThemeColorField::TextAccent => self.text_accent,
ThemeColorField::Icon => self.icon,
ThemeColorField::IconMuted => self.icon_muted,
ThemeColorField::IconDisabled => self.icon_disabled,
ThemeColorField::IconPlaceholder => self.icon_placeholder,
ThemeColorField::IconAccent => self.icon_accent,
ThemeColorField::StatusBarBackground => self.status_bar_background,
ThemeColorField::TitleBarBackground => self.title_bar_background,
ThemeColorField::TitleBarInactiveBackground => self.title_bar_inactive_background,
ThemeColorField::ToolbarBackground => self.toolbar_background,
ThemeColorField::TabBarBackground => self.tab_bar_background,
ThemeColorField::TabInactiveBackground => self.tab_inactive_background,
ThemeColorField::TabActiveBackground => self.tab_active_background,
ThemeColorField::SearchMatchBackground => self.search_match_background,
ThemeColorField::PanelBackground => self.panel_background,
ThemeColorField::PanelFocusedBorder => self.panel_focused_border,
ThemeColorField::PanelIndentGuide => self.panel_indent_guide,
ThemeColorField::PanelIndentGuideHover => self.panel_indent_guide_hover,
ThemeColorField::PanelIndentGuideActive => self.panel_indent_guide_active,
ThemeColorField::PaneFocusedBorder => self.pane_focused_border,
ThemeColorField::PaneGroupBorder => self.pane_group_border,
ThemeColorField::ScrollbarThumbBackground => self.scrollbar_thumb_background,
ThemeColorField::ScrollbarThumbHoverBackground => self.scrollbar_thumb_hover_background,
ThemeColorField::ScrollbarThumbBorder => self.scrollbar_thumb_border,
ThemeColorField::ScrollbarTrackBackground => self.scrollbar_track_background,
ThemeColorField::ScrollbarTrackBorder => self.scrollbar_track_border,
ThemeColorField::EditorForeground => self.editor_foreground,
ThemeColorField::EditorBackground => self.editor_background,
ThemeColorField::EditorGutterBackground => self.editor_gutter_background,
ThemeColorField::EditorSubheaderBackground => self.editor_subheader_background,
ThemeColorField::EditorActiveLineBackground => self.editor_active_line_background,
ThemeColorField::EditorHighlightedLineBackground => {
self.editor_highlighted_line_background
}
ThemeColorField::EditorLineNumber => self.editor_line_number,
ThemeColorField::EditorActiveLineNumber => self.editor_active_line_number,
ThemeColorField::EditorInvisible => self.editor_invisible,
ThemeColorField::EditorWrapGuide => self.editor_wrap_guide,
ThemeColorField::EditorActiveWrapGuide => self.editor_active_wrap_guide,
ThemeColorField::EditorIndentGuide => self.editor_indent_guide,
ThemeColorField::EditorIndentGuideActive => self.editor_indent_guide_active,
ThemeColorField::EditorDocumentHighlightReadBackground => {
self.editor_document_highlight_read_background
}
ThemeColorField::EditorDocumentHighlightWriteBackground => {
self.editor_document_highlight_write_background
}
ThemeColorField::EditorDocumentHighlightBracketBackground => {
self.editor_document_highlight_bracket_background
}
ThemeColorField::TerminalBackground => self.terminal_background,
ThemeColorField::TerminalForeground => self.terminal_foreground,
ThemeColorField::TerminalBrightForeground => self.terminal_bright_foreground,
ThemeColorField::TerminalDimForeground => self.terminal_dim_foreground,
ThemeColorField::TerminalAnsiBackground => self.terminal_ansi_background,
ThemeColorField::TerminalAnsiBlack => self.terminal_ansi_black,
ThemeColorField::TerminalAnsiBrightBlack => self.terminal_ansi_bright_black,
ThemeColorField::TerminalAnsiDimBlack => self.terminal_ansi_dim_black,
ThemeColorField::TerminalAnsiRed => self.terminal_ansi_red,
ThemeColorField::TerminalAnsiBrightRed => self.terminal_ansi_bright_red,
ThemeColorField::TerminalAnsiDimRed => self.terminal_ansi_dim_red,
ThemeColorField::TerminalAnsiGreen => self.terminal_ansi_green,
ThemeColorField::TerminalAnsiBrightGreen => self.terminal_ansi_bright_green,
ThemeColorField::TerminalAnsiDimGreen => self.terminal_ansi_dim_green,
ThemeColorField::TerminalAnsiYellow => self.terminal_ansi_yellow,
ThemeColorField::TerminalAnsiBrightYellow => self.terminal_ansi_bright_yellow,
ThemeColorField::TerminalAnsiDimYellow => self.terminal_ansi_dim_yellow,
ThemeColorField::TerminalAnsiBlue => self.terminal_ansi_blue,
ThemeColorField::TerminalAnsiBrightBlue => self.terminal_ansi_bright_blue,
ThemeColorField::TerminalAnsiDimBlue => self.terminal_ansi_dim_blue,
ThemeColorField::TerminalAnsiMagenta => self.terminal_ansi_magenta,
ThemeColorField::TerminalAnsiBrightMagenta => self.terminal_ansi_bright_magenta,
ThemeColorField::TerminalAnsiDimMagenta => self.terminal_ansi_dim_magenta,
ThemeColorField::TerminalAnsiCyan => self.terminal_ansi_cyan,
ThemeColorField::TerminalAnsiBrightCyan => self.terminal_ansi_bright_cyan,
ThemeColorField::TerminalAnsiDimCyan => self.terminal_ansi_dim_cyan,
ThemeColorField::TerminalAnsiWhite => self.terminal_ansi_white,
ThemeColorField::TerminalAnsiBrightWhite => self.terminal_ansi_bright_white,
ThemeColorField::TerminalAnsiDimWhite => self.terminal_ansi_dim_white,
ThemeColorField::LinkTextHover => self.link_text_hover,
}
}

pub fn iter(&self) -> impl Iterator<Item = (ThemeColorField, Hsla)> + '_ {
ThemeColorField::iter().map(move |field| (field, self.color(field)))
}

pub fn to_vec(&self) -> Vec<(ThemeColorField, Hsla)> {
self.iter().collect()
}
}

pub fn all_theme_colors(cx: &WindowContext) -> Vec<(Hsla, SharedString)> {
let theme = cx.theme();
ThemeColorField::iter()
.map(|field| {
let color = theme.colors().color(field);
let name = field.as_ref().to_string();
(color, SharedString::from(name))
})
.collect()
}

#[derive(Refineable, Clone, PartialEq)]
pub struct ThemeStyles {
/// The background appearance of the window.
Expand Down
35 changes: 34 additions & 1 deletion crates/ui/src/styles/elevation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use gpui::{hsla, point, px, BoxShadow};
use std::fmt::{self, Display, Formatter};

use gpui::{hsla, point, px, BoxShadow, Hsla, WindowContext};
use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme;

/// Today, elevation is primarily used to add shadows to elements, and set the correct background for elements like buttons.
///
Expand All @@ -15,6 +18,8 @@ pub enum ElevationIndex {
Background,
/// The primary surface – Contains panels, panes, containers, etc.
Surface,
/// The same elevation as the primary surface, but used for the editable areas, like buffers
EditorSurface,
/// A surface that is elevated above the primary surface. but below washes, models, and dragged elements.
ElevatedSurface,
/// A surface that is above all non-modal surfaces, and separates the app from focused intents, like dialogs, alerts, modals, etc.
Expand All @@ -25,11 +30,26 @@ pub enum ElevationIndex {
DraggedElement,
}

impl Display for ElevationIndex {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ElevationIndex::Background => write!(f, "Background"),
ElevationIndex::Surface => write!(f, "Surface"),
ElevationIndex::EditorSurface => write!(f, "Editor Surface"),
ElevationIndex::ElevatedSurface => write!(f, "Elevated Surface"),
ElevationIndex::Wash => write!(f, "Wash"),
ElevationIndex::ModalSurface => write!(f, "Modal Surface"),
ElevationIndex::DraggedElement => write!(f, "Dragged Element"),
}
}
}

impl ElevationIndex {
/// Returns an appropriate shadow for the given elevation index.
pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
match self {
ElevationIndex::Surface => smallvec![],
ElevationIndex::EditorSurface => smallvec![],

ElevationIndex::ElevatedSurface => smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.12),
Expand Down Expand Up @@ -62,4 +82,17 @@ impl ElevationIndex {
_ => smallvec![],
}
}

/// Returns the background color for the given elevation index.
pub fn bg(&self, cx: &WindowContext) -> Hsla {
match self {
ElevationIndex::Background => cx.theme().colors().background,
ElevationIndex::Surface => cx.theme().colors().surface_background,
ElevationIndex::EditorSurface => cx.theme().colors().editor_background,
ElevationIndex::ElevatedSurface => cx.theme().colors().elevated_surface_background,
ElevationIndex::Wash => gpui::transparent_black(),
ElevationIndex::ModalSurface => cx.theme().colors().elevated_surface_background,
ElevationIndex::DraggedElement => gpui::transparent_black(),
}
}
}
2 changes: 2 additions & 0 deletions crates/ui/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! UI-related utilities

mod color_contrast;
mod format_distance;
mod with_rem_size;

pub use color_contrast::*;
pub use format_distance::*;
pub use with_rem_size::*;
Loading

0 comments on commit a347c4d

Please sign in to comment.