theme.rs

  1mod highlight_map;
  2mod resolution;
  3mod theme_registry;
  4
  5use crate::editor::{EditorStyle, SelectionStyle};
  6use anyhow::Result;
  7use gpui::{
  8    color::Color,
  9    elements::{ContainerStyle, ImageStyle, LabelStyle},
 10    fonts::{HighlightStyle, TextStyle},
 11    Border,
 12};
 13use serde::Deserialize;
 14use std::collections::HashMap;
 15
 16pub use highlight_map::*;
 17pub use theme_registry::*;
 18
 19pub const DEFAULT_THEME_NAME: &'static str = "black";
 20
 21#[derive(Deserialize)]
 22pub struct Theme {
 23    #[serde(default)]
 24    pub name: String,
 25    pub workspace: Workspace,
 26    pub chat_panel: ChatPanel,
 27    pub people_panel: PeoplePanel,
 28    pub project_panel: ProjectPanel,
 29    pub selector: Selector,
 30    pub editor: EditorStyle,
 31    pub syntax: SyntaxTheme,
 32}
 33
 34pub struct SyntaxTheme {
 35    highlights: Vec<(String, HighlightStyle)>,
 36}
 37
 38#[derive(Deserialize)]
 39pub struct Workspace {
 40    pub background: Color,
 41    pub titlebar: Titlebar,
 42    pub tab: Tab,
 43    pub active_tab: Tab,
 44    pub pane_divider: Border,
 45    pub left_sidebar: Sidebar,
 46    pub right_sidebar: Sidebar,
 47}
 48
 49#[derive(Clone, Deserialize)]
 50pub struct Titlebar {
 51    #[serde(flatten)]
 52    pub container: ContainerStyle,
 53    pub title: TextStyle,
 54    pub avatar_width: f32,
 55    pub offline_icon: OfflineIcon,
 56    pub icon_color: Color,
 57    pub avatar: ImageStyle,
 58    pub outdated_warning: ContainedText,
 59}
 60
 61#[derive(Clone, Deserialize)]
 62pub struct OfflineIcon {
 63    #[serde(flatten)]
 64    pub container: ContainerStyle,
 65    pub width: f32,
 66}
 67
 68#[derive(Clone, Deserialize)]
 69pub struct Tab {
 70    #[serde(flatten)]
 71    pub container: ContainerStyle,
 72    #[serde(flatten)]
 73    pub label: LabelStyle,
 74    pub spacing: f32,
 75    pub icon_width: f32,
 76    pub icon_close: Color,
 77    pub icon_close_active: Color,
 78    pub icon_dirty: Color,
 79    pub icon_conflict: Color,
 80}
 81
 82#[derive(Deserialize)]
 83pub struct Sidebar {
 84    #[serde(flatten)]
 85    pub container: ContainerStyle,
 86    pub width: f32,
 87    pub icon: SidebarIcon,
 88    pub active_icon: SidebarIcon,
 89    pub resize_handle: ContainerStyle,
 90}
 91
 92#[derive(Deserialize)]
 93pub struct SidebarIcon {
 94    pub color: Color,
 95    pub height: f32,
 96}
 97
 98#[derive(Deserialize)]
 99pub struct ChatPanel {
100    #[serde(flatten)]
101    pub container: ContainerStyle,
102    pub message: ChatMessage,
103    pub pending_message: ChatMessage,
104    pub channel_select: ChannelSelect,
105    pub input_editor: InputEditorStyle,
106    pub sign_in_prompt: TextStyle,
107    pub hovered_sign_in_prompt: TextStyle,
108}
109
110#[derive(Deserialize)]
111pub struct ProjectPanel {
112    #[serde(flatten)]
113    pub container: ContainerStyle,
114    pub entry_base_padding: f32,
115    pub entry: ContainedText,
116    pub hovered_entry: ContainedText,
117    pub selected_entry: ContainedText,
118}
119
120#[derive(Deserialize)]
121pub struct PeoplePanel {
122    #[serde(flatten)]
123    pub container: ContainerStyle,
124    pub host_row_height: f32,
125    pub host_avatar: ImageStyle,
126    pub host_username: ContainedText,
127    pub tree_branch_width: f32,
128    pub tree_branch_color: Color,
129    pub shared_worktree: WorktreeRow,
130    pub hovered_shared_worktree: WorktreeRow,
131    pub unshared_worktree: WorktreeRow,
132    pub hovered_unshared_worktree: WorktreeRow,
133}
134
135#[derive(Deserialize)]
136pub struct WorktreeRow {
137    #[serde(flatten)]
138    pub container: ContainerStyle,
139    pub height: f32,
140    pub name: ContainedText,
141    pub guest_avatar: ImageStyle,
142    pub guest_avatar_spacing: f32,
143}
144
145#[derive(Deserialize)]
146pub struct ChatMessage {
147    #[serde(flatten)]
148    pub container: ContainerStyle,
149    pub body: TextStyle,
150    pub sender: ContainedText,
151    pub timestamp: ContainedText,
152}
153
154#[derive(Deserialize)]
155pub struct ChannelSelect {
156    #[serde(flatten)]
157    pub container: ContainerStyle,
158    pub header: ChannelName,
159    pub item: ChannelName,
160    pub active_item: ChannelName,
161    pub hovered_item: ChannelName,
162    pub hovered_active_item: ChannelName,
163    pub menu: ContainerStyle,
164}
165
166#[derive(Deserialize)]
167pub struct ChannelName {
168    #[serde(flatten)]
169    pub container: ContainerStyle,
170    pub hash: ContainedText,
171    pub name: TextStyle,
172}
173
174#[derive(Deserialize)]
175pub struct Selector {
176    #[serde(flatten)]
177    pub container: ContainerStyle,
178    pub empty: ContainedLabel,
179    pub input_editor: InputEditorStyle,
180    pub item: ContainedLabel,
181    pub active_item: ContainedLabel,
182}
183
184#[derive(Clone, Debug, Deserialize)]
185pub struct ContainedText {
186    #[serde(flatten)]
187    pub container: ContainerStyle,
188    #[serde(flatten)]
189    pub text: TextStyle,
190}
191
192#[derive(Deserialize)]
193pub struct ContainedLabel {
194    #[serde(flatten)]
195    pub container: ContainerStyle,
196    #[serde(flatten)]
197    pub label: LabelStyle,
198}
199
200#[derive(Clone, Deserialize)]
201pub struct InputEditorStyle {
202    #[serde(flatten)]
203    pub container: ContainerStyle,
204    pub text: TextStyle,
205    #[serde(default)]
206    pub placeholder_text: Option<TextStyle>,
207    pub selection: SelectionStyle,
208}
209
210impl SyntaxTheme {
211    pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
212        Self { highlights }
213    }
214
215    pub fn highlight_style(&self, id: HighlightId) -> Option<HighlightStyle> {
216        self.highlights
217            .get(id.0 as usize)
218            .map(|entry| entry.1.clone())
219    }
220
221    #[cfg(test)]
222    pub fn highlight_name(&self, id: HighlightId) -> Option<&str> {
223        self.highlights.get(id.0 as usize).map(|e| e.0.as_str())
224    }
225}
226
227impl InputEditorStyle {
228    pub fn as_editor(&self) -> EditorStyle {
229        EditorStyle {
230            text: self.text.clone(),
231            placeholder_text: self.placeholder_text.clone(),
232            background: self
233                .container
234                .background_color
235                .unwrap_or(Color::transparent_black()),
236            selection: self.selection,
237            gutter_background: Default::default(),
238            active_line_background: Default::default(),
239            line_number: Default::default(),
240            line_number_active: Default::default(),
241            guest_selections: Default::default(),
242        }
243    }
244}
245
246impl<'de> Deserialize<'de> for SyntaxTheme {
247    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
248    where
249        D: serde::Deserializer<'de>,
250    {
251        let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
252
253        let mut result = Self::new(Vec::new());
254        for (key, style) in syntax_data {
255            match result
256                .highlights
257                .binary_search_by(|(needle, _)| needle.cmp(&key))
258            {
259                Ok(i) | Err(i) => {
260                    result.highlights.insert(i, (key, style));
261                }
262            }
263        }
264
265        Ok(result)
266    }
267}