From c99000a2c387377eefb19ffe17923c73489315a9 Mon Sep 17 00:00:00 2001 From: David Capello <david@igarastudio.com> Date: Sat, 22 May 2021 00:42:36 -0300 Subject: [PATCH] Add theme variants to switch easily between Light/Dark themes --- data/extensions/aseprite-theme/package.json | 8 +- data/widgets/options.xml | 2 +- src/app/commands/cmd_options.cpp | 107 ++++++++++++++++++-- src/app/extensions.cpp | 26 ++--- src/app/extensions.h | 34 +++++-- 5 files changed, 143 insertions(+), 34 deletions(-) diff --git a/data/extensions/aseprite-theme/package.json b/data/extensions/aseprite-theme/package.json index e37e744fc..079bec673 100644 --- a/data/extensions/aseprite-theme/package.json +++ b/data/extensions/aseprite-theme/package.json @@ -1,9 +1,9 @@ { "name": "aseprite-theme", "displayName": "Aseprite Default Theme", - "description": "Default Aseprite Pixel-Art Theme", + "description": "Default Aseprite Pixel-Art Themes", "version": "1.0", - "author": { "name": "David Capello", "email": "davidcapello@gmail.com", "url": "http://davidcapello.com/" }, + "author": { "name": "David Capello", "email": "david@igarastudio.com", "url": "http://davidcapello.com/" }, "contributors": [ { "name": "Ilija Melentijevic", "url": "http://ilkke.blogspot.com/" }, { "name": "Nicolas Desilets", "url": "https://twitter.com/MapleGecko" } @@ -15,8 +15,8 @@ ], "contributes": { "themes": [ - { "id": "default", "path": "." }, - { "id": "default-dark", "path": "./dark" } + { "id": "default", "path": ".", "variant": "Light" }, + { "id": "default-dark", "path": "./dark", "variant": "Dark" } ] } } diff --git a/data/widgets/options.xml b/data/widgets/options.xml index 8a0c8f425..53b543193 100644 --- a/data/widgets/options.xml +++ b/data/widgets/options.xml @@ -39,7 +39,7 @@ <listitem text="300%" value="3" /> <listitem text="400%" value="4" /> </combobox> - <boxfiller /> + <hbox id="theme_variants" /> <label text="@.ui_scaling" /> <combobox id="ui_scale"> diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 622826863..c5d851d13 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -110,11 +110,13 @@ class OptionsWindow : public app::gen::Options { class ThemeItem : public ListItem { public: - ThemeItem(const std::string& path, - const std::string& name) - : ListItem(name.empty() ? "-- " + path + " --": name), + ThemeItem(const std::string& id, + const std::string& path, + const std::string& displayName = std::string(), + const std::string& variant = std::string()) + : ListItem(createLabel(path, id, displayName, variant)), m_path(path), - m_name(name) { + m_name(id) { } const std::string& themePath() const { return m_path; } @@ -129,6 +131,30 @@ class OptionsWindow : public app::gen::Options { } private: + static std::string createLabel(const std::string& path, + const std::string& id, + const std::string& displayName, + const std::string& variant) { + if (displayName.empty()) { + if (id.empty()) + return fmt::format("-- {} --", path); + else + return id; + } + else if (id == displayName) { + if (variant.empty()) + return id; + else + return fmt::format("{} - {}", id, variant); + } + else { + if (variant.empty()) + return displayName; + else + return fmt::format("{} - {}", displayName, variant); + } + } + std::string m_path; std::string m_name; }; @@ -190,6 +216,24 @@ class OptionsWindow : public app::gen::Options { Extension* m_extension; }; + class ThemeVariantItem : public ButtonSet::Item { + public: + ThemeVariantItem(OptionsWindow* options, + const std::string& id, + const std::string& variant) + : m_options(options) + , m_themeId(id) { + setText(variant); + } + private: + void onClick() override { + m_options->setUITheme(m_themeId, true, + false); // Don't recreate variants + } + OptionsWindow* m_options; + std::string m_themeId; + }; + public: OptionsWindow(Context* context, int& curSection) : m_context(context) @@ -204,6 +248,9 @@ public: { sectionListbox()->Change.connect([this]{ onChangeSection(); }); + // Theme variants + fillThemeVariants(); + // Default extension to save files fillExtensionsCombobox(defaultExtension(), m_pref.saveFile.defaultExtension()); fillExtensionsCombobox(exportImageDefaultExtension(), m_pref.exportFile.imageDefaultExtension()); @@ -823,6 +870,42 @@ public: private: + void fillThemeVariants() { + ButtonSet* list = nullptr; + for (Extension* ext : App::instance()->extensions()) { + if (ext->isCurrentTheme()) { + // Number of variants + int c = 0; + for (auto it : ext->themes()) { + if (!it.second.variant.empty()) + ++c; + } + + if (c >= 2) { + list = new ButtonSet(c); + for (auto it : ext->themes()) { + if (!it.second.variant.empty()) { + auto item = list->addItem( + new ThemeVariantItem(this, it.first, it.second.variant)); + + if (it.first == Preferences::instance().theme.selected()) + list->setSelectedItem(item, false); + } + } + } + break; + } + } + if (list) { + themeVariants()->addChild(list); + } + if (m_themeVars) { + themeVariants()->removeChild(m_themeVars); + m_themeVars->deferDelete(); + } + m_themeVars = list; + } + void fillExtensionsCombobox(ui::ComboBox* combobox, const std::string& defExt) { base::paths exts = get_writable_extensions(); @@ -1196,7 +1279,7 @@ private: new SeparatorInView(base::normalize_path(path), HORIZONTAL)); } - ThemeItem* item = new ThemeItem(fullPath, fn); + ThemeItem* item = new ThemeItem(fn, fullPath); themeList()->addChild(item); // Selected theme @@ -1221,11 +1304,14 @@ private: } for (auto it : ext->themes()) { - ThemeItem* item = new ThemeItem(it.second, it.first); + ThemeItem* item = new ThemeItem(it.first, + it.second.path, + ext->displayName(), + it.second.variant); themeList()->addChild(item); // Selected theme - if (it.second == selectedPath) + if (it.second.path == selectedPath) themeList()->selectChild(item); } } @@ -1296,7 +1382,8 @@ private: } void setUITheme(const std::string& themeName, - const bool updateScaling) { + const bool updateScaling, + const bool recreateVariantsFields = true) { try { if (themeName != m_pref.theme.selected()) { auto theme = static_cast<skin::SkinTheme*>(ui::get_theme()); @@ -1343,6 +1430,9 @@ private: selectScalingItems(); } } + + if (recreateVariantsFields) + fillThemeVariants(); } } catch (const std::exception& ex) { @@ -1618,6 +1708,7 @@ private: std::vector<os::ColorSpaceRef> m_colorSpaces; std::string m_templateTextForDisplayCS; RgbMapAlgorithmSelector m_rgbmapAlgorithmSelector; + ButtonSet* m_themeVars = nullptr; }; class OptionsCommand : public Command { diff --git a/src/app/extensions.cpp b/src/app/extensions.cpp index cde81cd99..84f451567 100644 --- a/src/app/extensions.cpp +++ b/src/app/extensions.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -263,9 +263,9 @@ void Extension::addLanguage(const std::string& id, const std::string& path) updateCategory(Category::Languages); } -void Extension::addTheme(const std::string& id, const std::string& path) +void Extension::addTheme(const std::string& id, const std::string& path, const std::string& variant) { - m_themes[id] = path; + m_themes[id] = ThemeInfo(path, variant); updateCategory(Category::Themes); } @@ -789,7 +789,7 @@ std::string Extensions::themePath(const std::string& themeId) auto it = ext->themes().find(themeId); if (it != ext->themes().end()) - return it->second; + return it->second.path; } return std::string(); } @@ -1026,7 +1026,7 @@ Extension* Extensions::loadExtension(const std::string& path, // The path must be always relative to the extension langPath = base::join_path(path, langPath); - LOG("EXT: New language '%s' in '%s'\n", + LOG("EXT: New language id=%s path=%s\n", langId.c_str(), langPath.c_str()); @@ -1040,15 +1040,17 @@ Extension* Extensions::loadExtension(const std::string& path, for (const auto& theme : themes.array_items()) { std::string themeId = theme["id"].string_value(); std::string themePath = theme["path"].string_value(); + std::string themeVariant = theme["variant"].string_value(); // The path must be always relative to the extension themePath = base::join_path(path, themePath); - LOG("EXT: New theme '%s' in '%s'\n", + LOG("EXT: New theme id=%s path=%s variant=%s\n", themeId.c_str(), - themePath.c_str()); + themePath.c_str(), + themeVariant.c_str()); - extension->addTheme(themeId, themePath); + extension->addTheme(themeId, themePath, themeVariant); } } @@ -1062,7 +1064,7 @@ Extension* Extensions::loadExtension(const std::string& path, // The path must be always relative to the extension palPath = base::join_path(path, palPath); - LOG("EXT: New palette '%s' in '%s'\n", + LOG("EXT: New palette id=%s path=%s\n", palId.c_str(), palPath.c_str()); @@ -1083,7 +1085,7 @@ Extension* Extensions::loadExtension(const std::string& path, // The path must be always relative to the extension matPath = base::join_path(path, matPath); - LOG("EXT: New dithering matrix '%s' in '%s'\n", + LOG("EXT: New dithering matrix id=%s path=%s\n", matId.c_str(), matPath.c_str()); @@ -1103,7 +1105,7 @@ Extension* Extensions::loadExtension(const std::string& path, // The path must be always relative to the extension scriptPath = base::join_path(path, scriptPath); - LOG("EXT: New script '%s'\n", scriptPath.c_str()); + LOG("EXT: New script path=%s\n", scriptPath.c_str()); extension->addScript(scriptPath); } @@ -1116,7 +1118,7 @@ Extension* Extensions::loadExtension(const std::string& path, // The path must be always relative to the extension scriptPath = base::join_path(path, scriptPath); - LOG("EXT: New script '%s'\n", scriptPath.c_str()); + LOG("EXT: New script path=%s\n", scriptPath.c_str()); extension->addScript(scriptPath); } diff --git a/src/app/extensions.h b/src/app/extensions.h index ab2e7492b..8070e5551 100644 --- a/src/app/extensions.h +++ b/src/app/extensions.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -18,9 +18,9 @@ namespace app { - // Key=theme/palette/etc. id - // Value=theme/palette/etc. path - typedef std::map<std::string, std::string> ExtensionItems; + // Key=id + // Value=path + using ExtensionItems = std::map<std::string, std::string>; class Extensions; @@ -34,6 +34,7 @@ namespace app { class Extension { friend class Extensions; public: + enum class Category { None, Languages, @@ -61,6 +62,20 @@ namespace app { mutable bool m_loaded = false; }; + struct ThemeInfo { + std::string path; + std::string variant; + + ThemeInfo() = default; + ThemeInfo(const std::string& path, + const std::string& variant) + : path(path) + , variant(variant) { } + }; + + using Themes = std::map<std::string, ThemeInfo>; + using DitheringMatrices = std::map<std::string, DitheringMatrixInfo>; + Extension(const std::string& path, const std::string& name, const std::string& version, @@ -79,11 +94,11 @@ namespace app { const Category category() const { return m_category; } const ExtensionItems& languages() const { return m_languages; } - const ExtensionItems& themes() const { return m_themes; } + const Themes& themes() const { return m_themes; } const ExtensionItems& palettes() const { return m_palettes; } void addLanguage(const std::string& id, const std::string& path); - void addTheme(const std::string& id, const std::string& path); + void addTheme(const std::string& id, const std::string& path, const std::string& variant); void addPalette(const std::string& id, const std::string& path); void addDitheringMatrix(const std::string& id, const std::string& path, @@ -107,11 +122,12 @@ namespace app { void addScript(const std::string& fn); #endif + bool isCurrentTheme() const; + private: void enable(const bool state); void uninstall(); void uninstallFiles(const std::string& path); - bool isCurrentTheme() const; bool isDefaultTheme() const; void updateCategory(const Category newCategory); #ifdef ENABLE_SCRIPTING @@ -120,9 +136,9 @@ namespace app { #endif ExtensionItems m_languages; - ExtensionItems m_themes; + Themes m_themes; ExtensionItems m_palettes; - std::map<std::string, DitheringMatrixInfo> m_ditheringMatrices; + DitheringMatrices m_ditheringMatrices; #ifdef ENABLE_SCRIPTING struct ScriptItem {