libcosmic/cosmic-theme/src/config/mod.rs
Ashley Wulber e056e8c830
Cosmic advanced text (#103)
* wip: update to use cosmic-advanced-text

* use cosmic-advanced-text branch of iced

* fix: line height and spacing for segmented button and update to get svg fix

* fix: spin button styling & spacing

* update iced to fix segmented button border radius

* feat: example improvements

* feat: helper for loading fonts

* feat: add focus style to button

* fix: slider height and iced fixed

* feat: hash icon width and height

* cleanup

* update ci

* refactor: always use lazy feature of iced

* update iced

* update iced

* cleanup & update iced

* update iced: new slider & tiny-skia quad updates

* update iced: fixes for tiny-skia quad rendering with edge case border radius

* re-export iced_runtime & iced_widget

* merge master

* udpate iced

* update iced

* update iced

* update iced

* fix: make rectangle_tracker subscription only return update if there is some

* feat: derive macro for loading a cosmic-config

* feat (cosmic-config): iced subscription

* fix (example): update to rectangle tracker subscription

* fix (cosmic-config)

* refactor(cosmic-config-derive): add support for types with generic parameters

* fix (cosmic-config): feature gate updates for subscription helpers

* feat: support for custom & system themes + move cosmic-theme to libcosmic

* feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk

* update iced

* update and reexport sctk

* fix: applet border radius

* feat (cosmic-theme): add id and name methods

* fix(cosmic-theme): reexport palette from cosmic-theme

* fix(cosmic-config-derive): allow use with reexported cosmic-config

* feat: update iced with fix and refactor applet env vars

* update iced
2023-05-30 12:03:15 -04:00

196 lines
5.9 KiB
Rust

// SPDX-License-Identifier: MPL-2.0-only
use crate::{util::CssColor, Theme, NAME, THEME_DIR};
use anyhow::{bail, Context, Result};
use directories::{BaseDirsExt, ProjectDirsExt};
use palette::Srgba;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
fmt,
fs::File,
io::{prelude::*, BufReader},
path::PathBuf,
};
/// Cosmic Theme config
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Config {
/// whether high contrast mode is activated
pub is_high_contrast: bool,
/// active
pub is_dark: bool,
/// Selected light theme name
pub light: String,
/// Selected dark theme name
pub dark: String,
}
impl Default for Config {
fn default() -> Self {
Self {
is_dark: true,
light: "cosmic-light".to_string(),
dark: "cosmic-dark".to_string(),
is_high_contrast: false,
}
}
}
/// name of the config file
pub const CONFIG_NAME: &str = "config";
impl Config {
/// create a new cosmic theme config
pub fn new(is_dark: bool, high_contrast: bool, light: String, dark: String) -> Self {
Self {
is_dark,
light,
dark,
is_high_contrast: high_contrast,
}
}
/// save the cosmic theme config
pub fn save(&self) -> Result<()> {
let xdg_dirs = directories::ProjectDirs::from_path(PathBuf::from(NAME))
.context("Failed to find project directory.")?;
if let Ok(path) = xdg_dirs.place_config_file(PathBuf::from(format!("{CONFIG_NAME}.ron"))) {
let mut f = File::create(path)?;
let ron = ron::ser::to_string_pretty(&self, Default::default())?;
f.write_all(ron.as_bytes())?;
Ok(())
} else {
bail!("failed to save theme config")
}
}
/// init the config directory
pub fn init() -> anyhow::Result<PathBuf> {
let base_dirs = directories::BaseDirs::new().context("Failed to get base directories.")?;
let res = Ok(base_dirs.create_config_directory(NAME)?);
Theme::<Srgba>::init()?;
if Self::load().is_ok() {
res
} else {
Self::default().save()?;
Theme::dark_default().save()?;
Theme::light_default().save()?;
res
}
}
/// load the cosmic theme config
pub fn load() -> Result<Self> {
let xdg_dirs = directories::ProjectDirs::from_path(PathBuf::from(NAME))
.context("Failed to find project directory.")?;
let path = xdg_dirs.config_dir();
std::fs::create_dir_all(&path)?;
let path = xdg_dirs.find_config_file(PathBuf::from(format!("{CONFIG_NAME}.ron")));
if path.is_none() {
let s = Self::default();
s.save()?;
}
if let Some(path) = xdg_dirs.find_config_file(PathBuf::from(format!("{CONFIG_NAME}.ron"))) {
let mut f = File::open(&path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(ron::from_str(s.as_str())?)
} else {
anyhow::bail!("Failed to load config")
}
}
/// get the name of the active theme
pub fn active_name(&self) -> Option<String> {
if self.is_dark && self.dark.is_empty() {
Some(self.dark.clone())
} else if !self.is_dark && !self.light.is_empty() {
Some(self.light.clone())
} else {
None
}
// if *high_contrast {
// if let Some(palette) = palette.take() {
// // TODO enforce high contrast constraints
// *palette = palette.to_high_contrast();
// todo!()
// }
// }
}
/// get the active theme
pub fn get_active(&self) -> anyhow::Result<Theme<CssColor>> {
let active = match self.active_name() {
Some(n) => n,
_ => anyhow::bail!("No configured active overrides"),
};
let css_path: PathBuf = [NAME, THEME_DIR].iter().collect();
let css_dirs = directories::ProjectDirs::from_path(PathBuf::from(css_path))
.context("Failed to find project directory.")?;
let active_theme_path = match css_dirs.find_data_file(format!("{active}.ron")) {
Some(p) => p,
_ => anyhow::bail!("Could not find theme"),
};
match File::open(active_theme_path) {
Ok(active_theme_file) => {
let reader = BufReader::new(active_theme_file);
Ok(ron::de::from_reader::<_, Theme<CssColor>>(reader)?)
}
Err(_) => {
if self.is_dark {
Ok(Theme::dark_default())
} else {
Ok(Theme::light_default())
}
}
}
}
/// set the name of the active light theme
pub fn set_active_light(new: &str) -> Result<()> {
let mut self_ = Self::load()?;
self_.light = new.to_string();
self_.save()
}
/// set the name of the active dark theme
pub fn set_active_dark(new: &str) -> Result<()> {
let mut self_ = Self::load()?;
self_.dark = new.to_string();
self_.save()
}
}
impl<C> From<(Theme<C>, Theme<C>)> for Config
where
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
{
fn from((light, dark): (Theme<C>, Theme<C>)) -> Self {
Self {
light: light.name,
dark: dark.name,
is_dark: true,
is_high_contrast: false,
}
}
}
impl<C> From<Theme<C>> for Config
where
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
{
fn from(t: Theme<C>) -> Self {
Self {
light: t.clone().name,
dark: t.name,
is_dark: true,
is_high_contrast: true,
}
}
}