feat: basic vscode theme export support

This commit is contained in:
Ashley Wulber 2024-05-12 22:16:53 -04:00 committed by Ashley Wulber
parent 05a9e7639f
commit c1cfa024d6
6 changed files with 378 additions and 23 deletions

View file

@ -10,20 +10,23 @@ features = ["test_all_features"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
default = []
gtk4-output = []
default = ["export"]
export = ["serde_json"]
no-default = []
theme-from-image = ["kmeans_colors", "image"]
[dependencies]
palette = {version = "0.7.3", features = ["serializing"] }
palette = { version = "0.7.3", features = ["serializing"] }
almost = "0.2"
kmeans_colors = { version = "0.5", features = ["palette_color"], default-features = false, optional = true }
image = {version = "0.24.1", optional = true }
serde = { version = "1.0.129", features = ["derive"] }
serde_json = { version = "1.0.64", optional = true, features = [
"preserve_order",
] }
ron = "0.8"
lazy_static = "1.4.0"
csscolorparser = {version = "0.6.2", features = ["serde"]}
cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription", "macro"] }
csscolorparser = { version = "0.6.2", features = ["serde"] }
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
"subscription",
"macro",
] }
dirs.workspace = true
thiserror = "1.0.5"

View file

@ -1 +0,0 @@
// TODO theme from image

View file

@ -9,6 +9,8 @@
pub use model::*;
mod model;
#[cfg(feature = "export")]
mod output;
/// composite colors in srgb

View file

@ -5,15 +5,8 @@ use std::{
io::Write,
num::NonZeroUsize,
};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum OutputError {
#[error("IO Error: {0}")]
Io(std::io::Error),
#[error("Missing config directory")]
MissingConfigDir,
}
use super::{to_hex, OutputError};
impl Theme {
#[must_use]
@ -249,11 +242,6 @@ fn component_gtk4_css(prefix: &str, c: &Component) -> String {
)
}
fn to_hex(c: Srgba) -> String {
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
format!("{:02x}{:02x}{:02x}", c_u8.red, c_u8.green, c_u8.blue)
}
fn color_css(prefix: &str, c_3: Srgba) -> String {
let oklch: palette::Oklch = c_3.into_color();
let c_2: Srgba = oklch.lighten(0.1).into_color();

View file

@ -1,3 +1,46 @@
#[cfg(feature = "gtk4-output")]
use palette::{rgb::Rgba, Srgba};
use thiserror::Error;
use crate::Theme;
/// Module for outputting the Cosmic gtk4 theme type as CSS
pub mod gtk4_output;
pub mod vs_code;
#[derive(Error, Debug)]
pub enum OutputError {
#[error("IO Error: {0}")]
Io(std::io::Error),
#[error("Missing config directory")]
MissingConfigDir,
}
impl Theme {
pub fn apply_exports(&self) -> Result<(), OutputError> {
let gtk_res = Theme::apply_gtk(self.is_dark);
let vs_res = self.clone().apply_vs_code();
gtk_res?;
vs_res?;
Ok(())
}
pub fn write_exports(&self) -> Result<(), OutputError> {
let gtk_res = self.write_gtk4();
gtk_res?;
Ok(())
}
pub fn reset_exports() -> Result<(), OutputError> {
let gtk_res = Theme::reset_gtk();
let vs_res = Theme::reset_vs_code();
gtk_res?;
vs_res?;
Ok(())
}
}
pub fn to_hex(c: Srgba) -> String {
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
format!("{:02x}{:02x}{:02x}", c_u8.red, c_u8.green, c_u8.blue)
}

View file

@ -0,0 +1,320 @@
use serde::{Deserialize, Serialize};
use crate::Theme;
use super::{to_hex, OutputError};
/// Represents the workbench.colorCustomizations section of a VS Code settings.json file
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VsTheme {
#[serde(rename = "editor.background")]
editor_background: String,
#[serde(rename = "sideBar.background")]
sidebar_background: String,
#[serde(rename = "activityBar.background")]
activity_bar_background: String,
#[serde(rename = "notificationCenterHeader.background")]
notification_center_header_background: String,
#[serde(rename = "notifications.background")]
notifications_background: String,
#[serde(rename = "activityBarTop.activeBackground")]
activity_bar_top_active_background: String,
#[serde(rename = "editorGroupHeader.tabsBackground")]
editor_group_header_tabs_background: String,
#[serde(rename = "editorGroupHeader.noTabsBackground")]
editor_group_header_no_tabs_background: String,
#[serde(rename = "titleBar.activeBackground")]
title_bar_active_background: String,
#[serde(rename = "titleBar.inactiveBackground")]
title_bar_inactive_background: String,
#[serde(rename = "statusBar.background")]
status_bar_background: String,
#[serde(rename = "statusBar.noFolderBackground")]
status_bar_no_folder_background: String,
#[serde(rename = "statusBar.debuggingBackground")]
status_bar_debugging_background: String,
#[serde(rename = "tab.activeBackground")]
tab_active_background: String,
#[serde(rename = "tab.activeBorder")]
tab_active_border: String,
#[serde(rename = "tab.activeBorderTop")]
tab_active_border_top: String,
#[serde(rename = "tab.hoverBackground")]
tab_hover_background: String,
#[serde(rename = "quickInput.background")]
quick_input_background: String,
#[serde(rename = "tab.inactiveBackground")]
tab_inactive_background: String,
#[serde(rename = "sideBarSectionHeader.background")]
side_bar_section_header_background: String,
#[serde(rename = "list.focusOutline")]
list_focus_outline: String,
#[serde(rename = "banner.background")]
banner_background: String,
#[serde(rename = "breadcrumb.background")]
breadcrumb_background: String,
#[serde(rename = "commandCenter.background")]
command_center_background: String,
#[serde(rename = "terminal.background")]
terminal_background: String,
#[serde(rename = "menu.background")]
menu_background: String,
#[serde(rename = "panel.background")]
panel_background: String,
#[serde(rename = "peekViewEditorGutter.background")]
peek_view_editor_gutter_background: String,
#[serde(rename = "peekViewResult.background")]
peek_view_result_background: String,
#[serde(rename = "peekViewTitle.background")]
peek_view_title_background: String,
#[serde(rename = "peekViewEditor.background")]
peek_view_editor_background: String,
#[serde(rename = "peekViewResult.selectionBackground")]
peek_view_result_selection_background: String,
#[serde(rename = "editorWidget.background")]
editor_widget_background: String,
#[serde(rename = "editorSuggestWidget.background")]
editor_suggest_widget_background: String,
#[serde(rename = "editorHoverWidget.background")]
editor_hover_widget_background: String,
#[serde(rename = "input.background")]
input_background: String,
#[serde(rename = "dropdown.background")]
dropdown_background: String,
#[serde(rename = "settings.checkboxBackground")]
settings_checkbox_background: String,
#[serde(rename = "settings.textInputBackground")]
settings_text_input_background: String,
#[serde(rename = "settings.numberInputBackground")]
settings_number_input_background: String,
#[serde(rename = "settings.dropdownBackground")]
settings_dropdown_background: String,
#[serde(rename = "sideBar.dropBackground")]
side_bar_drop_background: String,
#[serde(rename = "list.activeSelectionBackground")]
list_active_selection_background: String,
#[serde(rename = "list.inactiveSelectionBackground")]
list_inactive_selection_background: String,
#[serde(rename = "list.focusBackground")]
list_focus_background: String,
#[serde(rename = "list.hoverBackground")]
list_hover_background: String,
// text colors
#[serde(rename = "editor.foreground")]
editor_foreground: String,
#[serde(rename = "editorLineNumber.foreground")]
editor_line_number_foreground: String,
#[serde(rename = "editorCursor.foreground")]
editor_cursor_foreground: String,
#[serde(rename = "sideBar.foreground")]
side_bar_foreground: String,
#[serde(rename = "activityBar.foreground")]
activity_bar_foreground: String,
#[serde(rename = "statusBar.foreground")]
status_bar_foreground: String,
#[serde(rename = "tab.activeForeground")]
tab_active_foreground: String,
#[serde(rename = "tab.inactiveForeground")]
tab_inactive_foreground: String,
#[serde(rename = "editorGroupHeader.tabsForeground")]
editor_group_header_tabs_foreground: String,
#[serde(rename = "sideBarSectionHeader.foreground")]
side_bar_section_header_foreground: String,
#[serde(rename = "statusBar.debuggingForeground")]
status_bar_debugging_foreground: String,
#[serde(rename = "statusBar.noFolderForeground")]
status_bar_no_folder_foreground: String,
#[serde(rename = "editorWidget.foreground")]
editor_widget_foreground: String,
#[serde(rename = "editorSuggestWidget.foreground")]
editor_suggest_widget_foreground: String,
#[serde(rename = "editorHoverWidget.foreground")]
editor_hover_widget_foreground: String,
#[serde(rename = "input.foreground")]
input_foreground: String,
#[serde(rename = "dropdown.foreground")]
dropdown_foreground: String,
#[serde(rename = "terminal.foreground")]
terminal_foreground: String,
#[serde(rename = "menu.foreground")]
menu_foreground: String,
#[serde(rename = "panel.foreground")]
panel_foreground: String,
#[serde(rename = "peekViewEditorGutter.foreground")]
peek_view_editor_gutter_foreground: String,
#[serde(rename = "peekViewResult.selectionForeground")]
peek_view_result_selection_foreground: String,
#[serde(rename = "inputOption.activeBorder")]
input_option_active_border: String,
// accent colors
#[serde(rename = "activityBarBadge.background")]
activity_bar_badge_background: String,
#[serde(rename = "statusBar.debuggingBorder")]
status_bar_debugging_border: String,
#[serde(rename = "button.background")]
button_background: String,
#[serde(rename = "button.hoverBackground")]
button_hover_background: String,
#[serde(rename = "statusBarItem.remoteBackground")]
status_bar_item_remote_background: String,
// accent fg colors
#[serde(rename = "activityBarBadge.foreground")]
activity_bar_badge_foreground: String,
#[serde(rename = "button.foreground")]
button_foreground: String,
#[serde(rename = "textLink.foreground")]
text_link_foreground: String,
#[serde(rename = "textLink.activeForeground")]
text_link_active_foreground: String,
#[serde(rename = "peekView.border")]
peek_view_border: String,
#[serde(rename = "settings.checkboxForeground")]
settings_checkbox_foreground: String,
}
impl From<Theme> for VsTheme {
fn from(theme: Theme) -> Self {
Self {
editor_background: format!("#{}", to_hex(theme.background.base)),
sidebar_background: format!("#{}", to_hex(theme.primary.base)),
activity_bar_background: format!("#{}", to_hex(theme.primary.base)),
notification_center_header_background: format!("#{}", to_hex(theme.background.base)),
notifications_background: format!("#{}", to_hex(theme.background.base)),
activity_bar_top_active_background: format!("#{}", to_hex(theme.primary.base)),
editor_group_header_tabs_background: format!("#{}", to_hex(theme.background.base)),
editor_group_header_no_tabs_background: format!("#{}", to_hex(theme.background.base)),
title_bar_active_background: format!("#{}", to_hex(theme.background.component.base)),
title_bar_inactive_background: format!(
"#{}",
to_hex(
theme
.background
.component.disabled
)
),
status_bar_background: format!("#{}", to_hex(theme.background.base)),
status_bar_no_folder_background: format!("#{}", to_hex(theme.background.base)),
status_bar_debugging_background: format!("#{}", to_hex(theme.background.base)),
tab_active_background: format!("#{}", to_hex(theme.primary.component.pressed)),
tab_active_border: format!("#{}", to_hex(theme.accent.base)),
tab_active_border_top: format!("#{}", to_hex(theme.accent.base)),
tab_hover_background: format!("#{}", to_hex(theme.primary.component.hover)),
tab_inactive_background: format!(
"#{}",
to_hex(
theme
.primary
.component
.base
)
),
quick_input_background: format!("#{}", to_hex(theme.primary.base)),
side_bar_section_header_background: format!("#{}", to_hex(theme.primary.base)),
banner_background: format!("#{}", to_hex(theme.primary.base)),
breadcrumb_background: format!("#{}", to_hex(theme.primary.base)),
command_center_background: format!("#{}", to_hex(theme.primary.base)),
terminal_background: format!("#{}", to_hex(theme.primary.base)),
menu_background: format!("#{}", to_hex(theme.primary.base)),
panel_background: format!("#{}", to_hex(theme.primary.base)),
peek_view_editor_gutter_background: format!("#{}", to_hex(theme.background.base)),
peek_view_result_background: format!("#{}", to_hex(theme.background.base)),
peek_view_title_background: format!("#{}", to_hex(theme.background.base)),
peek_view_editor_background: format!("#{}", to_hex(theme.background.base)),
peek_view_result_selection_background: format!("#{}", to_hex(theme.background.base)),
editor_widget_background: format!("#{}", to_hex(theme.background.base)),
editor_suggest_widget_background: format!("#{}", to_hex(theme.background.base)),
editor_hover_widget_background: format!("#{}", to_hex(theme.background.base)),
input_background: format!("#{}", to_hex(theme.background.base)),
dropdown_background: format!("#{}", to_hex(theme.background.base)),
settings_checkbox_background: format!("#{}", to_hex(theme.background.base)),
settings_text_input_background: format!("#{}", to_hex(theme.background.base)),
settings_number_input_background: format!("#{}", to_hex(theme.background.base)),
settings_dropdown_background: format!("#{}", to_hex(theme.background.base)),
side_bar_drop_background: format!("#{}", to_hex(theme.background.base)),
list_active_selection_background: format!("#{}", to_hex(theme.primary.base)),
list_inactive_selection_background: format!("#{}", to_hex(theme.primary.base)),
list_focus_background: format!("#{}", to_hex(theme.primary.base)),
list_hover_background: format!("#{}", to_hex(theme.primary.base)),
editor_foreground: format!("#{}", to_hex(theme.background.on)),
editor_line_number_foreground: format!("#{}", to_hex(theme.background.on)),
editor_cursor_foreground: format!("#{}", to_hex(theme.background.on)),
side_bar_foreground: format!("#{}", to_hex(theme.primary.on)),
activity_bar_foreground: format!("#{}", to_hex(theme.primary.on)),
status_bar_foreground: format!("#{}", to_hex(theme.primary.on)),
tab_active_foreground: format!("#{}", to_hex(theme.primary.on)),
tab_inactive_foreground: format!("#{}", to_hex(theme.primary.on)),
editor_group_header_tabs_foreground: format!("#{}", to_hex(theme.primary.on)),
side_bar_section_header_foreground: format!("#{}", to_hex(theme.primary.on)),
status_bar_debugging_foreground: format!("#{}", to_hex(theme.primary.on)),
status_bar_no_folder_foreground: format!("#{}", to_hex(theme.primary.on)),
editor_widget_foreground: format!("#{}", to_hex(theme.primary.on)),
editor_suggest_widget_foreground: format!("#{}", to_hex(theme.primary.on)),
editor_hover_widget_foreground: format!("#{}", to_hex(theme.primary.on)),
input_foreground: format!("#{}", to_hex(theme.primary.on)),
dropdown_foreground: format!("#{}", to_hex(theme.primary.on)),
terminal_foreground: format!("#{}", to_hex(theme.primary.on)),
menu_foreground: format!("#{}", to_hex(theme.primary.on)),
panel_foreground: format!("#{}", to_hex(theme.primary.on)),
peek_view_editor_gutter_foreground: format!("#{}", to_hex(theme.primary.on)),
peek_view_result_selection_foreground: format!("#{}", to_hex(theme.primary.on)),
input_option_active_border: format!("#{}", to_hex(theme.accent.base)),
activity_bar_badge_background: format!("#{}", to_hex(theme.accent.base)),
activity_bar_badge_foreground: format!("#{}", to_hex(theme.accent.on)),
status_bar_debugging_border: format!("#{}", to_hex(theme.accent.base)),
list_focus_outline: format!("#{}", to_hex(theme.accent.base)),
button_background: format!("#{}", to_hex(theme.accent_button.base)),
button_hover_background: format!("#{}", to_hex(theme.accent_button.hover)),
status_bar_item_remote_background: format!("#{}", to_hex(theme.accent.base)),
button_foreground: format!("#{}", to_hex(theme.accent_button.on)),
text_link_foreground: format!("#{}", to_hex(theme.accent.base)),
text_link_active_foreground: format!("#{}", to_hex(theme.accent.base)),
peek_view_border: format!("#{}", to_hex(theme.accent.base)),
settings_checkbox_foreground: format!("#{}", to_hex(theme.accent.base)),
}
}
}
impl Theme {
pub fn apply_vs_code(self) -> Result<(), OutputError> {
let vs_theme = VsTheme::from(self);
let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
let vs_code_dir = config_dir.join("Code").join("User");
if !vs_code_dir.exists() {
std::fs::create_dir_all(&vs_code_dir).map_err(OutputError::Io)?;
}
// just add the json entry for workbench.colorCustomizations
let settings_file = vs_code_dir.join("settings.json");
let settings = std::fs::read_to_string(&settings_file).unwrap_or_default();
let mut settings: serde_json::Value = serde_json::from_str(&settings).unwrap_or_default();
settings["workbench.colorCustomizations"] = serde_json::to_value(vs_theme).unwrap();
std::fs::write(
&settings_file,
serde_json::to_string_pretty(&settings).unwrap(),
)
.map_err(OutputError::Io)?;
Ok(())
}
pub fn reset_vs_code() -> Result<(), OutputError> {
let config_dir = dirs::config_dir().ok_or(OutputError::MissingConfigDir)?;
let vs_code_dir = config_dir.join("Code").join("User");
let settings_file = vs_code_dir.join("settings.json");
// just remove the json entry for workbench.colorCustomizations
let settings = std::fs::read_to_string(&settings_file).unwrap_or_default();
let mut settings: serde_json::Value = serde_json::from_str(&settings).unwrap_or_default();
settings["workbench.colorCustomizations"] = serde_json::Value::Null;
std::fs::write(
&settings_file,
serde_json::to_string_pretty(&settings).unwrap(),
)
.map_err(OutputError::Io)?;
Ok(())
}
}