Add theme preview (#20039)
This PR adds a theme preview tab to help get an at a glance overview of the styles in a theme.  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.
This commit is contained in:
parent
9c77bcc827
commit
a347c4def7
9 changed files with 813 additions and 4 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -12093,6 +12093,7 @@ dependencies = [
|
||||||
"serde_json_lenient",
|
"serde_json_lenient",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"settings",
|
"settings",
|
||||||
|
"strum 0.25.0",
|
||||||
"util",
|
"util",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::{bail, Context};
|
use anyhow::{bail, Context};
|
||||||
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
use serde::de::{self, Deserialize, Deserializer, Visitor};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt::{self, Display, Formatter},
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
/// Construct an [`Hsla`] object from plain values
|
||||||
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
|
||||||
Hsla {
|
Hsla {
|
||||||
|
|
|
@ -35,6 +35,7 @@ serde_json.workspace = true
|
||||||
serde_json_lenient.workspace = true
|
serde_json_lenient.workspace = true
|
||||||
serde_repr.workspace = true
|
serde_repr.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
strum.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use gpui::{Hsla, WindowBackgroundAppearance};
|
use gpui::{Hsla, SharedString, WindowBackgroundAppearance, WindowContext};
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use strum::{AsRefStr, EnumIter, IntoEnumIterator};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AccentColors, PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors,
|
AccentColors, ActiveTheme, PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme,
|
||||||
|
SystemColors,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Refineable, Clone, Debug, PartialEq)]
|
#[derive(Refineable, Clone, Debug, PartialEq)]
|
||||||
|
@ -249,6 +251,237 @@ pub struct ThemeColors {
|
||||||
pub link_text_hover: Hsla,
|
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)]
|
#[derive(Refineable, Clone, PartialEq)]
|
||||||
pub struct ThemeStyles {
|
pub struct ThemeStyles {
|
||||||
/// The background appearance of the window.
|
/// The background appearance of the window.
|
||||||
|
|
|
@ -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 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.
|
/// Today, elevation is primarily used to add shadows to elements, and set the correct background for elements like buttons.
|
||||||
///
|
///
|
||||||
|
@ -15,6 +18,8 @@ pub enum ElevationIndex {
|
||||||
Background,
|
Background,
|
||||||
/// The primary surface – Contains panels, panes, containers, etc.
|
/// The primary surface – Contains panels, panes, containers, etc.
|
||||||
Surface,
|
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.
|
/// A surface that is elevated above the primary surface. but below washes, models, and dragged elements.
|
||||||
ElevatedSurface,
|
ElevatedSurface,
|
||||||
/// A surface that is above all non-modal surfaces, and separates the app from focused intents, like dialogs, alerts, modals, etc.
|
/// A surface that is above all non-modal surfaces, and separates the app from focused intents, like dialogs, alerts, modals, etc.
|
||||||
|
@ -25,11 +30,26 @@ pub enum ElevationIndex {
|
||||||
DraggedElement,
|
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 {
|
impl ElevationIndex {
|
||||||
/// Returns an appropriate shadow for the given elevation index.
|
/// Returns an appropriate shadow for the given elevation index.
|
||||||
pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
|
pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
|
||||||
match self {
|
match self {
|
||||||
ElevationIndex::Surface => smallvec![],
|
ElevationIndex::Surface => smallvec![],
|
||||||
|
ElevationIndex::EditorSurface => smallvec![],
|
||||||
|
|
||||||
ElevationIndex::ElevatedSurface => smallvec![BoxShadow {
|
ElevationIndex::ElevatedSurface => smallvec![BoxShadow {
|
||||||
color: hsla(0., 0., 0., 0.12),
|
color: hsla(0., 0., 0., 0.12),
|
||||||
|
@ -62,4 +82,17 @@ impl ElevationIndex {
|
||||||
_ => smallvec![],
|
_ => 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
//! UI-related utilities
|
//! UI-related utilities
|
||||||
|
|
||||||
|
mod color_contrast;
|
||||||
mod format_distance;
|
mod format_distance;
|
||||||
mod with_rem_size;
|
mod with_rem_size;
|
||||||
|
|
||||||
|
pub use color_contrast::*;
|
||||||
pub use format_distance::*;
|
pub use format_distance::*;
|
||||||
pub use with_rem_size::*;
|
pub use with_rem_size::*;
|
||||||
|
|
70
crates/ui/src/utils/color_contrast.rs
Normal file
70
crates/ui/src/utils/color_contrast.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
use gpui::{Hsla, Rgba};
|
||||||
|
|
||||||
|
/// Calculates the contrast ratio between two colors according to WCAG 2.0 standards.
|
||||||
|
///
|
||||||
|
/// The formula used is:
|
||||||
|
/// (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter of the two luminances and L2 is the darker.
|
||||||
|
///
|
||||||
|
/// Returns a float representing the contrast ratio. A higher value indicates more contrast.
|
||||||
|
/// The range of the returned value is 1 to 21 (commonly written as 1:1 to 21:1).
|
||||||
|
pub fn calculate_contrast_ratio(fg: Hsla, bg: Hsla) -> f32 {
|
||||||
|
let l1 = relative_luminance(fg);
|
||||||
|
let l2 = relative_luminance(bg);
|
||||||
|
|
||||||
|
let (lighter, darker) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
|
||||||
|
|
||||||
|
(lighter + 0.05) / (darker + 0.05)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the relative luminance of a color.
|
||||||
|
///
|
||||||
|
/// The relative luminance is the relative brightness of any point in a colorspace,
|
||||||
|
/// normalized to 0 for darkest black and 1 for lightest white.
|
||||||
|
fn relative_luminance(color: Hsla) -> f32 {
|
||||||
|
let rgba: Rgba = color.into();
|
||||||
|
let r = linearize(rgba.r);
|
||||||
|
let g = linearize(rgba.g);
|
||||||
|
let b = linearize(rgba.b);
|
||||||
|
|
||||||
|
0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Linearizes an RGB component.
|
||||||
|
fn linearize(component: f32) -> f32 {
|
||||||
|
if component <= 0.03928 {
|
||||||
|
component / 12.92
|
||||||
|
} else {
|
||||||
|
((component + 0.055) / 1.055).powf(2.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use gpui::hsla;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Test the contrast ratio formula with some common color combinations to
|
||||||
|
// prevent regressions in either the color conversions or the formula itself.
|
||||||
|
#[test]
|
||||||
|
fn test_contrast_ratio_formula() {
|
||||||
|
// White on Black (should be close to 21:1)
|
||||||
|
let white = hsla(0.0, 0.0, 1.0, 1.0);
|
||||||
|
let black = hsla(0.0, 0.0, 0.0, 1.0);
|
||||||
|
assert!((calculate_contrast_ratio(white, black) - 21.0).abs() < 0.1);
|
||||||
|
|
||||||
|
// Black on White (should be close to 21:1)
|
||||||
|
assert!((calculate_contrast_ratio(black, white) - 21.0).abs() < 0.1);
|
||||||
|
|
||||||
|
// Mid-gray on Black (should be close to 5.32:1)
|
||||||
|
let mid_gray = hsla(0.0, 0.0, 0.5, 1.0);
|
||||||
|
assert!((calculate_contrast_ratio(mid_gray, black) - 5.32).abs() < 0.1);
|
||||||
|
|
||||||
|
// White on Mid-gray (should be close to 3.95:1)
|
||||||
|
assert!((calculate_contrast_ratio(white, mid_gray) - 3.95).abs() < 0.1);
|
||||||
|
|
||||||
|
// Same color (should be 1:1)
|
||||||
|
let red = hsla(0.0, 1.0, 0.5, 1.0);
|
||||||
|
assert!((calculate_contrast_ratio(red, red) - 1.0).abs() < 0.01);
|
||||||
|
}
|
||||||
|
}
|
454
crates/workspace/src/theme_preview.rs
Normal file
454
crates/workspace/src/theme_preview.rs
Normal file
|
@ -0,0 +1,454 @@
|
||||||
|
#![allow(unused, dead_code)]
|
||||||
|
use gpui::{actions, AppContext, EventEmitter, FocusHandle, FocusableView, Hsla};
|
||||||
|
use theme::all_theme_colors;
|
||||||
|
use ui::{
|
||||||
|
prelude::*, utils::calculate_contrast_ratio, AudioStatus, Availability, Avatar,
|
||||||
|
AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, ElevationIndex, Facepile,
|
||||||
|
TintColor, Tooltip,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Item, Workspace};
|
||||||
|
|
||||||
|
actions!(debug, [OpenThemePreview]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||||
|
workspace.register_action(|workspace, _: &OpenThemePreview, cx| {
|
||||||
|
let theme_preview = cx.new_view(ThemePreview::new);
|
||||||
|
workspace.add_item_to_active_pane(Box::new(theme_preview), None, true, cx)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThemePreview {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemePreview {
|
||||||
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<()> for ThemePreview {}
|
||||||
|
|
||||||
|
impl FocusableView for ThemePreview {
|
||||||
|
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ThemePreview {}
|
||||||
|
|
||||||
|
impl Item for ThemePreview {
|
||||||
|
type Event = ();
|
||||||
|
|
||||||
|
fn to_item_events(_: &Self::Event, _: impl FnMut(crate::item::ItemEvent)) {}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
|
||||||
|
let name = cx.theme().name.clone();
|
||||||
|
Some(format!("{} Preview", name).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_on_split(
|
||||||
|
&self,
|
||||||
|
_workspace_id: Option<crate::WorkspaceId>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<gpui::View<Self>>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Some(cx.new_view(Self::new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVATAR_URL: &str = "https://avatars.githubusercontent.com/u/1714999?v=4";
|
||||||
|
|
||||||
|
impl ThemePreview {
|
||||||
|
fn preview_bg(cx: &WindowContext) -> Hsla {
|
||||||
|
cx.theme().colors().editor_background
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_avatars(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Headline::new("Avatars")
|
||||||
|
.size(HeadlineSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_start()
|
||||||
|
.gap_4()
|
||||||
|
.child(Avatar::new(AVATAR_URL).size(px(24.)))
|
||||||
|
.child(Avatar::new(AVATAR_URL).size(px(24.)).grayscale(true))
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.size(px(24.))
|
||||||
|
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.size(px(24.))
|
||||||
|
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.size(px(24.))
|
||||||
|
.indicator(AvatarAvailabilityIndicator::new(Availability::Free)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.size(px(24.))
|
||||||
|
.indicator(AvatarAvailabilityIndicator::new(Availability::Free)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Facepile::empty()
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.border_color(Self::preview_bg(cx))
|
||||||
|
.size(px(22.))
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.border_color(Self::preview_bg(cx))
|
||||||
|
.size(px(22.))
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.border_color(Self::preview_bg(cx))
|
||||||
|
.size(px(22.))
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Avatar::new(AVATAR_URL)
|
||||||
|
.border_color(Self::preview_bg(cx))
|
||||||
|
.size(px(22.))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_buttons(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Headline::new("Buttons")
|
||||||
|
.size(HeadlineSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_start()
|
||||||
|
.gap_px()
|
||||||
|
.child(
|
||||||
|
IconButton::new("icon_button_transparent", IconName::Check)
|
||||||
|
.style(ButtonStyle::Transparent),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("icon_button_subtle", IconName::Check)
|
||||||
|
.style(ButtonStyle::Subtle),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("icon_button_filled", IconName::Check)
|
||||||
|
.style(ButtonStyle::Filled),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("icon_button_selected_accent", IconName::Check)
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
|
.selected(true),
|
||||||
|
)
|
||||||
|
.child(IconButton::new("icon_button_selected", IconName::Check).selected(true))
|
||||||
|
.child(
|
||||||
|
IconButton::new("icon_button_positive", IconName::Check)
|
||||||
|
.style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("icon_button_warning", IconName::Check)
|
||||||
|
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("icon_button_negative", IconName::Check)
|
||||||
|
.style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_px()
|
||||||
|
.child(
|
||||||
|
Button::new("button_transparent", "Transparent")
|
||||||
|
.style(ButtonStyle::Transparent),
|
||||||
|
)
|
||||||
|
.child(Button::new("button_subtle", "Subtle").style(ButtonStyle::Subtle))
|
||||||
|
.child(Button::new("button_filled", "Filled").style(ButtonStyle::Filled))
|
||||||
|
.child(
|
||||||
|
Button::new("button_selected", "Selected")
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
|
.selected(true),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("button_selected_tinted", "Selected (Tinted)")
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
|
.selected(true),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("button_positive", "Tint::Positive")
|
||||||
|
.style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("button_warning", "Tint::Warning")
|
||||||
|
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("button_negative", "Tint::Negative")
|
||||||
|
.style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_text(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let bg = layer.bg(cx);
|
||||||
|
|
||||||
|
let label_with_contrast = |label: &str, fg: Hsla| {
|
||||||
|
let contrast = calculate_contrast_ratio(fg, bg);
|
||||||
|
format!("{} ({:.2})", label, contrast)
|
||||||
|
};
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Headline::new("Text").size(HeadlineSize::Small).color(Color::Muted))
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_start()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Headline::new("Headline Sizes").size(HeadlineSize::Small).color(Color::Muted))
|
||||||
|
.child(Headline::new("XLarge Headline").size(HeadlineSize::XLarge))
|
||||||
|
.child(Headline::new("Large Headline").size(HeadlineSize::Large))
|
||||||
|
.child(Headline::new("Medium Headline").size(HeadlineSize::Medium))
|
||||||
|
.child(Headline::new("Small Headline").size(HeadlineSize::Small))
|
||||||
|
.child(Headline::new("XSmall Headline").size(HeadlineSize::XSmall)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Headline::new("Text Colors").size(HeadlineSize::Small).color(Color::Muted))
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Default Text",
|
||||||
|
Color::Default.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Default),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Accent Text",
|
||||||
|
Color::Accent.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Accent),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Conflict Text",
|
||||||
|
Color::Conflict.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Conflict),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Created Text",
|
||||||
|
Color::Created.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Created),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Deleted Text",
|
||||||
|
Color::Deleted.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Deleted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Disabled Text",
|
||||||
|
Color::Disabled.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Disabled),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Error Text",
|
||||||
|
Color::Error.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Error),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Hidden Text",
|
||||||
|
Color::Hidden.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Hidden),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Hint Text",
|
||||||
|
Color::Hint.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Hint),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Ignored Text",
|
||||||
|
Color::Ignored.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Ignored),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Info Text",
|
||||||
|
Color::Info.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Info),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Modified Text",
|
||||||
|
Color::Modified.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Modified),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Muted Text",
|
||||||
|
Color::Muted.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Placeholder Text",
|
||||||
|
Color::Placeholder.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Placeholder),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Selected Text",
|
||||||
|
Color::Selected.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Selected),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Success Text",
|
||||||
|
Color::Success.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Success),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(label_with_contrast(
|
||||||
|
"Warning Text",
|
||||||
|
Color::Warning.color(cx),
|
||||||
|
))
|
||||||
|
.color(Color::Warning),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Headline::new("Wrapping Text").size(HeadlineSize::Small).color(Color::Muted))
|
||||||
|
.child(
|
||||||
|
div().max_w(px(200.)).child(
|
||||||
|
"This is a longer piece of text that should wrap to multiple lines. It demonstrates how text behaves when it exceeds the width of its container."
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_colors(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let bg = layer.bg(cx);
|
||||||
|
let all_colors = all_theme_colors(cx);
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
Headline::new("Colors")
|
||||||
|
.size(HeadlineSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.flex_wrap()
|
||||||
|
.gap_1()
|
||||||
|
.children(all_colors.into_iter().map(|(color, name)| {
|
||||||
|
let id = ElementId::Name(format!("{:?}-preview", color).into());
|
||||||
|
let name = name.clone();
|
||||||
|
div().size_8().flex_none().child(
|
||||||
|
ButtonLike::new(id)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.size_8()
|
||||||
|
.bg(color)
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.overflow_hidden(),
|
||||||
|
)
|
||||||
|
.size(ButtonSize::None)
|
||||||
|
.style(ButtonStyle::Transparent)
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
let name = name.clone();
|
||||||
|
Tooltip::with_meta(name, None, format!("{:?}", color), cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_theme_layer(
|
||||||
|
&self,
|
||||||
|
layer: ElevationIndex,
|
||||||
|
cx: &ViewContext<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.p_4()
|
||||||
|
.bg(layer.bg(cx))
|
||||||
|
.text_color(cx.theme().colors().text)
|
||||||
|
.gap_2()
|
||||||
|
.child(Headline::new(layer.clone().to_string()).size(HeadlineSize::Medium))
|
||||||
|
.child(self.render_avatars(cx))
|
||||||
|
.child(self.render_buttons(layer, cx))
|
||||||
|
.child(self.render_text(layer, cx))
|
||||||
|
.child(self.render_colors(layer, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ThemePreview {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl ui::IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.id("theme-preview")
|
||||||
|
.key_context("ThemePreview")
|
||||||
|
.overflow_scroll()
|
||||||
|
.size_full()
|
||||||
|
.max_h_full()
|
||||||
|
.p_4()
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.bg(Self::preview_bg(cx))
|
||||||
|
.gap_4()
|
||||||
|
.child(self.render_theme_layer(ElevationIndex::Background, cx))
|
||||||
|
.child(self.render_theme_layer(ElevationIndex::Surface, cx))
|
||||||
|
.child(self.render_theme_layer(ElevationIndex::EditorSurface, cx))
|
||||||
|
.child(self.render_theme_layer(ElevationIndex::ElevatedSurface, cx))
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ pub mod searchable;
|
||||||
pub mod shared_screen;
|
pub mod shared_screen;
|
||||||
mod status_bar;
|
mod status_bar;
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
|
mod theme_preview;
|
||||||
mod toolbar;
|
mod toolbar;
|
||||||
mod workspace_settings;
|
mod workspace_settings;
|
||||||
|
|
||||||
|
@ -323,6 +324,7 @@ pub fn init_settings(cx: &mut AppContext) {
|
||||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
init_settings(cx);
|
init_settings(cx);
|
||||||
notifications::init(cx);
|
notifications::init(cx);
|
||||||
|
theme_preview::init(cx);
|
||||||
|
|
||||||
cx.on_action(Workspace::close_global);
|
cx.on_action(Workspace::close_global);
|
||||||
cx.on_action(reload);
|
cx.on_action(reload);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue