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 @@
-
+
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(ui::get_theme());
@@ -1343,6 +1430,9 @@ private:
selectScalingItems();
}
}
+
+ if (recreateVariantsFields)
+ fillThemeVariants();
}
}
catch (const std::exception& ex) {
@@ -1618,6 +1708,7 @@ private:
std::vector 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 ExtensionItems;
+ // Key=id
+ // Value=path
+ using ExtensionItems = std::map;
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;
+ using DitheringMatrices = std::map;
+
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 m_ditheringMatrices;
+ DitheringMatrices m_ditheringMatrices;
#ifdef ENABLE_SCRIPTING
struct ScriptItem {