Skip to content

Commit

Permalink
Use default GTK theme option, and custom theme alternative
Browse files Browse the repository at this point in the history
  • Loading branch information
epilys committed Mar 20, 2023
1 parent e8dc9e0 commit 8908ed4
Show file tree
Hide file tree
Showing 15 changed files with 8,273 additions and 457 deletions.
43 changes: 29 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
.POSIX:
.SUFFIXES:
CARGOBIN = cargo
TAGREFBIN = tagref
BLACKBIN = black
SASSCBIN = sassc
SASSCOPTS = -a -M -t compact
PRINTF = /usr/bin/printf

fmt: pyfmt
cargo fmt
cargo sort
cargo clippy --bin gerb
$(CARGOBIN) fmt
$(CARGOBIN) sort
$(CARGOBIN) clippy --bin gerb

.PHONY: check
check: tags
cargo check --bin gerb
$(CARGOBIN) check --bin gerb

.PHONY: tags
tags:
@which tagref > /dev/null && tagref || (printf "Warning: tagref binary not in PATH.\n" 1>&2)
@which $(TAGREFBIN) > /dev/null && $(TAGREFBIN) || ($(PRINTF) "Warning: tagref binary not in PATH.\n" 1>&2)

.PHONY: pyfmt
pyfmt:
@which black > /dev/null && (find src -name "*.py" | xargs black) || (printf "Warning: black binary not in PATH.\n" 1>&2)
@which $(BLACKBIN) > /dev/null && (find src -name "*.py" | xargs $(BLACKBIN)) || ($(PRINTF) "Warning: black binary not in PATH.\n" 1>&2)

.PHONY: feature-check
feature-check: check
# No features
@sh -c 'cargo check --bin gerb --no-default-features || (export EXIT="$$?"; /usr/bin/printf "--no-default-features fails cargo check.\n" && exit $$EXIT)'
@cargo clippy --bin gerb --no-default-features
@sh -c '$(CARGOBIN) check --bin gerb --no-default-features || (export EXIT="$$?"; $(PRINTF) "--no-default-features fails cargo check.\n" && exit $$EXIT)'
@$(CARGOBIN) clippy --bin gerb --no-default-features
# `git`
@sh -c 'cargo check --bin gerb --no-default-features --features git || (export EXIT="$$?"; /usr/bin/printf "--features git fails cargo check.\n" && exit $$EXIT)'
@cargo clippy --bin gerb --no-default-features --features git
@sh -c '$(CARGOBIN) check --bin gerb --no-default-features --features git || (export EXIT="$$?"; $(PRINTF) "--features git fails cargo check.\n" && exit $$EXIT)'
@$(CARGOBIN) clippy --bin gerb --no-default-features --features git
# `python`
@sh -c 'cargo check --bin gerb --no-default-features --features python || (export EXIT="$$?"; /usr/bin/printf "--features python fails cargo check.\n" && exit $$EXIT)'
@cargo clippy --bin gerb --no-default-features --features python
@sh -c '$(CARGOBIN) check --bin gerb --no-default-features --features python || (export EXIT="$$?"; $(PRINTF) "--features python fails cargo check.\n" && exit $$EXIT)'
@$(CARGOBIN) clippy --bin gerb --no-default-features --features python
# all features
@sh -c 'cargo check --bin gerb --all-features || (export EXIT="$$?"; /usr/bin/printf "--all-features fails cargo check.\n" && exit $$EXIT)'
@cargo clippy --bin gerb --all-features
@sh -c '$(CARGOBIN) check --bin gerb --all-features || (export EXIT="$$?"; $(PRINTF) "--all-features fails cargo check.\n" && exit $$EXIT)'
@$(CARGOBIN) clippy --bin gerb --all-features

src/themes/paperwhite/gtk.css: src/themes/paperwhite/*.scss
$(SASSCBIN) $(SASSCOPTS) "src/themes/paperwhite/gtk.scss" "src/themes/paperwhite/gtk.css"

.PHONY:
themes: src/themes/paperwhite/gtk.css
33 changes: 32 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
fn main() {
use std::collections::VecDeque;
use std::path::{Path, PathBuf};

fn main() -> std::io::Result<()> {
println!("cargo:rerun-if-changed=src/themes");
{
fn read_fn(p: impl AsRef<Path>) -> Result<VecDeque<PathBuf>, std::io::Error> {
std::fs::read_dir(p)?
.map(|res| res.map(|e| e.path()))
.collect::<Result<VecDeque<_>, std::io::Error>>()
}
let theme_dirs = read_fn("src/themes/")?;
for theme in theme_dirs.into_iter().filter(|p| p.is_dir()) {
let theme_css = theme.join("gtk.css");
let mdata = std::fs::metadata(&theme_css)?.modified()?;
let mut entries = read_fn(&theme)?;
while let Some(p) = entries.pop_front() {
if p.is_dir() {
entries.extend(read_fn(&p)?.drain(..));
continue;
}
if p.extension() == Some("scss".as_ref()) && p.metadata()?.modified()? > mdata {
println!(
"cargo:warning=Theme {} might need to be recompiled from .scss sources:",
theme.file_name().unwrap().to_string_lossy()
);
println!("cargo:warning=File {} has a newer modified time than the compiled .css, {}.", p.display(), theme_css.display());
}
}
}
}
#[cfg(feature = "build-info")]
build_info_build::build_script();
Ok(())
}
9 changes: 0 additions & 9 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,6 @@ impl ApplicationImpl for ApplicationInner {
self.window.setup_actions();
self.build_system_menu(app);

let css_provider = gtk::CssProvider::new();
css_provider
.load_from_data(include_bytes!("./custom.css"))
.unwrap();
gtk::StyleContext::add_provider_for_screen(
&gtk::gdk::Screen::default().unwrap(),
&css_provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
self.window.show_all();
if let Some(path) = self.env_args.get().and_then(|args| args.clone().pop()) {
self.window.emit_by_name::<()>("open-project", &[&path]);
Expand Down
68 changes: 59 additions & 9 deletions src/app/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub struct SettingsInner {
pub document: Rc<RefCell<Document>>,
pub ui_font: Rc<RefCell<gtk::pango::FontDescription>>,
pub show_prerelease_warning: Cell<bool>,
pub theme: Cell<types::Theme>,
default_provider: OnceCell<gtk::CssProvider>,
}

impl SettingsInner {
Expand Down Expand Up @@ -139,6 +141,7 @@ impl SettingsInner {
document[Settings::GUIDELINE_WIDTH] = toml_value(self.guideline_width.get());
document[Settings::WARP_CURSOR] = toml_value(self.warp_cursor.get());
document[Settings::MARK_COLOR] = toml_value(self.mark_color.get().name());
document[Settings::THEME] = toml_value(self.theme.get().name());
document[Settings::SHOW_PRERELEASE_WARNING] =
toml_value(self.show_prerelease_warning.get());
file.rewind()?;
Expand Down Expand Up @@ -257,9 +260,9 @@ impl SettingsInner {
} else if prop.value_type() == i64::static_type() {
set_if_neq!(i64, document[&type_name][prop.name()].as_integer().unwrap());
} else if prop.value_type() == types::MarkColor::static_type() {
set_if_neq!(types::MarkColor, opt types::MarkColor::deserialize(document[&type_name].get(prop.name())));
set_if_neq!(types::MarkColor, opt types::MarkColor::toml_deserialize(document[&type_name].get(prop.name())));
} else if prop.value_type() == types::ShowMinimap::static_type() {
set_if_neq!(types::ShowMinimap, opt types::ShowMinimap::deserialize(document[&type_name].get(prop.name())));
set_if_neq!(types::ShowMinimap, opt types::ShowMinimap::toml_deserialize(document[&type_name].get(prop.name())));
}
}
}
Expand Down Expand Up @@ -297,7 +300,15 @@ impl SettingsInner {
}
/* enums */
for (prop, field) in [(Settings::MARK_COLOR, &self.mark_color)] {
if let Some(v) = types::MarkColor::deserialize(document.get(prop)) {
if let Some(v) = types::MarkColor::toml_deserialize(document.get(prop)) {
field.set(v);
} else {
document[prop] = toml_value(field.get().name());
save = true;
}
}
for (prop, field) in [(Settings::THEME, &self.theme)] {
if let Some(v) = types::Theme::toml_deserialize(document.get(prop)) {
field.set(v);
} else {
document[prop] = toml_value(field.get().name());
Expand All @@ -308,6 +319,7 @@ impl SettingsInner {
if save {
self.save_settings()?;
}
self.reload_theme();
Ok(())
}

Expand Down Expand Up @@ -346,15 +358,15 @@ impl SettingsInner {
document[&type_name][prop.name()].as_integer().unwrap(),
);
} else if prop.value_type() == types::MarkColor::static_type() {
if let Some(v) =
types::MarkColor::deserialize(document[&type_name].get(prop.name()))
{
if let Some(v) = types::MarkColor::toml_deserialize(
document[&type_name].get(prop.name()),
) {
obj.set_property(prop.name(), v);
}
} else if prop.value_type() == types::ShowMinimap::static_type() {
if let Some(v) =
types::ShowMinimap::deserialize(document[&type_name].get(prop.name()))
{
if let Some(v) = types::ShowMinimap::toml_deserialize(
document[&type_name].get(prop.name()),
) {
obj.set_property(prop.name(), v);
}
}
Expand Down Expand Up @@ -384,6 +396,24 @@ impl SettingsInner {
pub fn path(&self) -> Option<PathBuf> {
self.file.borrow().as_ref().map(|(p, _)| p.to_path_buf())
}

fn reload_theme(&self) {
match self.theme.get() {
types::Theme::SystemDefault => {
gtk::StyleContext::remove_provider_for_screen(
&gtk::gdk::Screen::default().unwrap(),
self.default_provider.get().unwrap(),
);
}
types::Theme::Paperwhite => {
gtk::StyleContext::add_provider_for_screen(
&gtk::gdk::Screen::default().unwrap(),
self.default_provider.get().unwrap(),
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
}
}
}
}

#[glib::object_subclass]
Expand All @@ -402,6 +432,11 @@ impl ObjectImpl for SettingsInner {
self.guideline_width.set(Self::GUIDELINE_WIDTH_INIT_VAL);
self.warp_cursor.set(Self::WARP_CURSOR_INIT_VAL);
self.show_prerelease_warning.set(true);
let css_provider = gtk::CssProvider::new();
css_provider
.load_from_data(types::Theme::PAPERWHITE_CSS)
.unwrap();
self.default_provider.set(css_provider).unwrap();

self.init_file().unwrap();
self.load_settings().unwrap();
Expand Down Expand Up @@ -460,6 +495,14 @@ impl ObjectImpl for SettingsInner {
types::MarkColor::None as i32,
glib::ParamFlags::READWRITE | UI_EDITABLE,
),
glib::ParamSpecEnum::new(
Settings::THEME,
Settings::THEME,
"UI theme.",
types::Theme::static_type(),
types::Theme::Paperwhite as i32,
glib::ParamFlags::READWRITE | UI_EDITABLE,
),
glib::ParamSpecBoxed::new(
Settings::UI_FONT,
Settings::UI_FONT,
Expand All @@ -480,6 +523,7 @@ impl ObjectImpl for SettingsInner {
Settings::WARP_CURSOR => self.warp_cursor.get().to_value(),
Settings::SHOW_PRERELEASE_WARNING => self.show_prerelease_warning.get().to_value(),
Settings::MARK_COLOR => self.mark_color.get().to_value(),
Settings::THEME => self.theme.get().to_value(),
Settings::UI_FONT => self.ui_font.borrow().to_value(),
_ => unimplemented!("{}", pspec.name()),
}
Expand Down Expand Up @@ -517,6 +561,11 @@ impl ObjectImpl for SettingsInner {
self.mark_color.set(value.get().unwrap());
self.save_settings().unwrap();
}
Settings::THEME => {
self.theme.set(value.get().unwrap());
self.reload_theme();
self.save_settings().unwrap();
}
Settings::UI_FONT => {
*self.ui_font.borrow_mut() = value.get().unwrap();
self.save_settings().unwrap();
Expand All @@ -540,6 +589,7 @@ impl Settings {
pub const MARK_COLOR: &str = "mark-color";
pub const UI_FONT: &str = "ui-font";
pub const SHOW_PRERELEASE_WARNING: &str = "show-prerelease-warning";
pub const THEME: &str = "theme";

pub fn new() -> Self {
glib::Object::new::<Self>(&[]).unwrap()
Expand Down
61 changes: 57 additions & 4 deletions src/app/settings/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub trait EnumValue: glib::value::ToValue + Sized {
v.nick().to_string()
}

fn deserialize<'de>(item: Option<&toml_edit::Item>) -> Option<Self>
fn toml_deserialize<'de>(item: Option<&toml_edit::Item>) -> Option<Self>
where
Self: Deserialize<'de>,
{
Expand All @@ -39,6 +39,19 @@ pub trait EnumValue: glib::value::ToValue + Sized {
.map(toml_edit::Value::into_deserializer)
.and_then(|p| <Self as Deserialize>::deserialize(p).ok())
}

fn kebab_str_deserialize<'de>(s: &str) -> Option<Self>
where
Self: Deserialize<'de>,
{
use serde::de::IntoDeserializer;
<Self as Deserialize>::deserialize(toml_edit::Value::into_deserializer(
toml_edit::value(s).into_value().ok()?,
))
.ok()
}

fn kebab_case_variants() -> &'static [&'static str];
}

#[derive(Debug, Deserialize, Serialize, Default, Copy, Clone, PartialEq, Eq, glib::Enum)]
Expand All @@ -51,7 +64,11 @@ pub enum MarkColor {
Icon,
}

impl EnumValue for MarkColor {}
impl EnumValue for MarkColor {
fn kebab_case_variants() -> &'static [&'static str] {
&["none", "background", "icon"]
}
}

#[derive(Debug, Deserialize, Serialize, Default, Copy, Clone, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "ShowMinimap")]
Expand All @@ -63,7 +80,30 @@ pub enum ShowMinimap {
WhenManipulating,
}

impl EnumValue for ShowMinimap {}
impl EnumValue for ShowMinimap {
fn kebab_case_variants() -> &'static [&'static str] {
&["never", "always", "when-manipulating"]
}
}

#[derive(Debug, Deserialize, Serialize, Default, Copy, Clone, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "Theme")]
#[serde(rename_all = "kebab-case")]
pub enum Theme {
SystemDefault,
#[default]
Paperwhite,
}

impl EnumValue for Theme {
fn kebab_case_variants() -> &'static [&'static str] {
&["system-default", "paperwhite"]
}
}

impl Theme {
pub const PAPERWHITE_CSS: &[u8] = include_bytes!("../../themes/paperwhite/gtk.css");
}

#[test]
fn test_parse_toml() {
Expand All @@ -83,11 +123,24 @@ color = "#E6E6E4"
assert_eq!(doc["line-width"].as_float().unwrap(), 1.0);
assert!(!doc["warp-cursor"].as_bool().unwrap());
assert_eq!(
<MarkColor as EnumValue>::deserialize(Some(&doc["mark-color"])).unwrap(),
<MarkColor as EnumValue>::toml_deserialize(Some(&doc["mark-color"])).unwrap(),
MarkColor::None
);
assert_eq!(
Color::from_hex(doc["canvas"]["color"].as_str().unwrap()),
Color::from_hex("#E6E6E4")
);
let doc = r#"theme = "system-default"
other-theme = "paperwhite"
"#
.parse::<Document>()
.unwrap();
assert_eq!(
<Theme as EnumValue>::toml_deserialize(Some(&doc["theme"])).unwrap(),
Theme::SystemDefault
);
assert_eq!(
<Theme as EnumValue>::toml_deserialize(Some(&doc["other-theme"])).unwrap(),
Theme::Paperwhite
);
}
Loading

0 comments on commit 8908ed4

Please sign in to comment.