From 04a32897559ec4b16256582223f64809a982247e Mon Sep 17 00:00:00 2001 From: GenevensiS <66968533+G-e-n-e-v-e-n-s-i-S@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:36:09 +0100 Subject: [PATCH] Add dark icon property --- src/data/field.cpp | 1 + src/data/field.hpp | 1 + src/data/installer.cpp | 15 +++++++--- src/data/installer.hpp | 21 ++++++------- src/data/statistics.cpp | 52 ++++++++++++++++++--------------- src/data/statistics.hpp | 48 +++++++++++++++--------------- src/gui/package_update_list.cpp | 9 +++--- src/gui/set/stats_panel.cpp | 20 ++++++++----- src/util/io/package.cpp | 16 ++++++++-- src/util/io/package.hpp | 19 ++++++------ 10 files changed, 118 insertions(+), 84 deletions(-) diff --git a/src/data/field.cpp b/src/data/field.cpp index c4805977..9eb6b0a3 100644 --- a/src/data/field.cpp +++ b/src/data/field.cpp @@ -52,6 +52,7 @@ IMPLEMENT_REFLECTION(Field) { REFLECT_LOCALIZED(caption); REFLECT_LOCALIZED(description); // FIXME: This field is both unused and uninitialized. REFLECT_N("icon", icon_filename); + REFLECT_N("dark_icon", dark_icon_filename); REFLECT(editable); REFLECT(save_value); REFLECT(show_statistics); diff --git a/src/data/field.hpp b/src/data/field.hpp index 140516c4..fd479b05 100644 --- a/src/data/field.hpp +++ b/src/data/field.hpp @@ -48,6 +48,7 @@ public: LocalizedString caption; ///< Caption for NativeLookEditor LocalizedString description; ///< Description, used in status bar String icon_filename; ///< Filename for an icon (for list of fields) + String dark_icon_filename; ///< Filename for an icon (for list of fields) when a variant for dark mode is necessary bool editable; ///< Can values of this field be edited? bool save_value; ///< Should values of this field be written to files? Can be false for script generated fields. bool show_statistics; ///< Should this field appear as a group by choice in the statistics panel? diff --git a/src/data/installer.cpp b/src/data/installer.cpp index c3e66c0b..83a8a8f1 100644 --- a/src/data/installer.cpp +++ b/src/data/installer.cpp @@ -37,16 +37,21 @@ IMPLEMENT_REFLECTION(Installer) { void Installer::validate(Version file_app_version) { Packaged::validate(file_app_version); // load icons where possible - FOR_EACH(p,packages) { - if (!p->icon_url.empty() && !starts_with(p->icon_url,_("http:"))) { + FOR_EACH(p,packages) { + String url = p->icon_url; + if (settings.darkMode() && !p->dark_icon_url.empty()) { + url = p->dark_icon_url; + } + if (!url.empty() && !starts_with(url,_("http:"))) { // TODO: support absolute icon names try{ - String filename = p->name + _("/") + p->icon_url; - auto img_stream = openIn(p->name + _("/") + p->icon_url); + String filename = p->name + _("/") + url; + auto img_stream = openIn(p->name + _("/") + url); image_load_file(p->icon, *img_stream); } catch (...) { // ignore errors, it's just an image p->icon_url.clear(); + p->dark_icon_url.clear(); } } } @@ -202,6 +207,7 @@ PackageDescription::PackageDescription(const Packaged& package) , short_name(package.short_name) , full_name(package.full_name) , icon_url(package.icon_filename) + , dark_icon_url(package.dark_icon_filename) , installer_group(package.installer_group) , position_hint(package.position_hint) //, description(package.description) @@ -237,6 +243,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(PackageDescription) { REFLECT(short_name); REFLECT(full_name); REFLECT(icon_url); + REFLECT(dark_icon_url); REFLECT(installer_group); REFLECT(position_hint); REFLECT(description); diff --git a/src/data/installer.hpp b/src/data/installer.hpp index 2fbccf2d..72f120b0 100644 --- a/src/data/installer.hpp +++ b/src/data/installer.hpp @@ -60,16 +60,17 @@ public: PackageDescription(); PackageDescription(const Packaged& package); - String name; ///< Filename of the package - Version version; ///< Version number of this package - String short_name; ///< Short name of this package - String full_name; ///< Name of this package, for menus etc. - String icon_url; ///< Filename or URL of icon to use in package lists - Image icon; ///< Icon for the package - String installer_group; ///< Where to put this package in the installer - int position_hint; ///< A hint for the package list - String description; ///< Changelog/description - vector dependencies; ///< Dependencies of this package + String name; ///< Filename of the package + Version version; ///< Version number of this package + String short_name; ///< Short name of this package + String full_name; ///< Name of this package, for menus etc. + String icon_url; ///< Filename or URL of icon to use in package lists + String dark_icon_url; ///< Filename or URL of icon to use in package lists + Image icon; ///< Icon for the package + String installer_group; ///< Where to put this package in the installer + int position_hint; ///< A hint for the package list + String description; ///< Changelog/description + vector dependencies; ///< Dependencies of this package /// Merge two descriptions a package. This package takes precedence /** Usually one of the descriptions will refer to the locally installed one, the other to the new one */ diff --git a/src/data/statistics.cpp b/src/data/statistics.cpp index a406d8ad..9e1c9b25 100644 --- a/src/data/statistics.cpp +++ b/src/data/statistics.cpp @@ -16,23 +16,24 @@ extern ScriptValueP script_primary_choice; // ----------------------------------------------------------------------------- : Statistics dimension StatsDimension::StatsDimension() - : automatic (false) - , position_hint(0) - , numeric (false) - , bin_size (0) - , show_empty (false) - , split_list (false) + : automatic (false) + , position_hint (0) + , numeric (false) + , bin_size (0) + , show_empty (false) + , split_list (false) {} StatsDimension::StatsDimension(const Field& field) - : automatic (true) - , name (field.name) - , description (field.description) - , position_hint(field.position_hint) - , icon_filename(field.icon_filename) - , numeric (false) - , show_empty (false) - , split_list (false) + : automatic (true) + , name (field.name) + , description (field.description) + , position_hint (field.position_hint) + , icon_filename (field.icon_filename) + , dark_icon_filename (field.dark_icon_filename) + , numeric (false) + , show_empty (false) + , split_list (false) { // choice field? const ChoiceField* choice_field = dynamic_cast(&field); @@ -67,6 +68,7 @@ IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsDimension) { REFLECT_LOCALIZED(description); REFLECT(position_hint); REFLECT_N("icon", icon_filename); + REFLECT_N("dark_icon", dark_icon_filename); REFLECT(script); REFLECT(global_script); REFLECT(numeric); @@ -81,19 +83,20 @@ IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsDimension) { // ----------------------------------------------------------------------------- : Statistics category StatsCategory::StatsCategory() - : automatic(false) - , position_hint(0) - , type(GRAPH_TYPE_BAR) + : automatic (false) + , position_hint (0) + , type (GRAPH_TYPE_BAR) {} StatsCategory::StatsCategory(const StatsDimensionP& dim) - : automatic(true) - , name (dim->name) - , description (dim->description) - , position_hint(dim->position_hint) - , icon_filename(dim->icon_filename) - , dimensions(1, dim) - , type(GRAPH_TYPE_BAR) + : automatic (true) + , name (dim->name) + , description (dim->description) + , position_hint (dim->position_hint) + , icon_filename (dim->icon_filename) + , dark_icon_filename (dim->dark_icon_filename) + , dimensions (1, dim) + , type (GRAPH_TYPE_BAR) {} IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsCategory) { @@ -102,6 +105,7 @@ IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsCategory) { REFLECT_LOCALIZED(description); REFLECT(position_hint); REFLECT_N("icon", icon_filename); + REFLECT_N("dark_icon", dark_icon_filename); REFLECT(type); REFLECT_N("dimensions", dimension_names); } diff --git a/src/data/statistics.hpp b/src/data/statistics.hpp index 56050200..6131570d 100644 --- a/src/data/statistics.hpp +++ b/src/data/statistics.hpp @@ -27,20 +27,21 @@ public: StatsDimension(); StatsDimension(const Field&); - const bool automatic; ///< Based on a card field? - String name; ///< Name of this dimension - LocalizedString description; ///< Description, used in status bar - int position_hint; ///< Hint for the ordering - String icon_filename; ///< Icon for lists - Bitmap icon; ///< The loaded icon (optional of course) - OptionalScript script; ///< Script that determines the value(s), ran on each card - OptionalScript global_script; ///< Script that determines the value(s), ran only once at the start - bool numeric; ///< Are the values numeric? If so, they require special sorting - double bin_size; ///< Bin adjecent numbers? - bool show_empty; ///< Should "" be shown? - bool split_list; ///< Split values into multiple ones separated by commas - map colors; ///< Colors for the categories - vector groups; ///< Order of the items + const bool automatic; ///< Based on a card field? + String name; ///< Name of this dimension + LocalizedString description; ///< Description, used in status bar + int position_hint; ///< Hint for the ordering + String icon_filename; ///< Icon for lists + String dark_icon_filename; ///< Icon for lists, if a variant for dark mode is needed + Bitmap icon; ///< The loaded icon (optional of course) + OptionalScript script; ///< Script that determines the value(s), ran on each card + OptionalScript global_script; ///< Script that determines the value(s), ran only once at the start + bool numeric; ///< Are the values numeric? If so, they require special sorting + double bin_size; ///< Bin adjecent numbers? + bool show_empty; ///< Should "" be shown? + bool split_list; ///< Split values into multiple ones separated by commas + map colors; ///< Colors for the categories + vector groups; ///< Order of the items DECLARE_REFLECTION(); }; @@ -54,15 +55,16 @@ public: StatsCategory(); StatsCategory(const StatsDimensionP&); - const bool automatic; ///< Automatically generated? - String name; ///< Name/label - LocalizedString description; ///< Description, used in status bar - int position_hint; ///< Hint for the ordering - String icon_filename; ///< Icon for lists - Bitmap icon; ///< The loaded icon (optional of course) - vector dimension_names; ///< Names of the dimensions to use - vector dimensions; ///< Actual dimensions - GraphType type; ///< Type of graph to use + const bool automatic; ///< Automatically generated? + String name; ///< Name/label + LocalizedString description; ///< Description, used in status bar + int position_hint; ///< Hint for the ordering + String icon_filename; ///< Icon for lists + String dark_icon_filename; ///< Icon for lists when a variant for dark mode is needed + Bitmap icon; ///< The loaded icon (optional of course) + vector dimension_names; ///< Names of the dimensions to use + vector dimensions; ///< Actual dimensions + GraphType type; ///< Type of graph to use /// Initialize dimensions from dimension_names void find_dimensions(const vector& available); diff --git a/src/gui/package_update_list.cpp b/src/gui/package_update_list.cpp index 8669bb1d..2377e14a 100644 --- a/src/gui/package_update_list.cpp +++ b/src/gui/package_update_list.cpp @@ -159,13 +159,13 @@ public: PackageIconRequest(PackageUpdateList* list, PackageUpdateList::TreeItem* ti) : ThumbnailRequest( list, - _("package_") + ti->package->description->icon_url + _("_") + ti->package->description->version.toString(), + _("package_") + (settings.darkMode() && !ti->package->description->dark_icon_url.empty() ? ti->package->description->dark_icon_url : ti->package->description->icon_url) + _("_") + ti->package->description->version.toString(), wxDateTime(1,wxDateTime::Jan,2000)) , list(list), ti(ti) {} Image generate() override { - wxURL url(ti->package->description->icon_url); + wxURL url(settings.darkMode() && !ti->package->description->dark_icon_url.empty() ? ti->package->description->dark_icon_url : ti->package->description->icon_url); unique_ptr isP(url.GetInputStream()); if (!isP) return wxImage(); SeekAtStartInputStream is2(*isP); @@ -219,8 +219,9 @@ void PackageUpdateList::initItems() { if (p && p->description->icon.Ok()) { // it has an icon ti.setIcon(p->description->icon); } else if (p) { // it doesn't have an icon (yet) - ti.setIcon(load_resource_image(_("installer_package"))); - if (!p->description->icon_url.empty()) { + ti.setIcon(load_resource_image(_("installer_package"))); + String icon_url = settings.darkMode() && !p->description->dark_icon_url.empty() ? p->description->dark_icon_url : p->description->icon_url; + if (!icon_url.empty()) { // download icon thumbnail_thread.request(make_intrusive(this,&ti)); } diff --git a/src/gui/set/stats_panel.cpp b/src/gui/set/stats_panel.cpp index c579e7c7..963a7d3e 100644 --- a/src/gui/set/stats_panel.cpp +++ b/src/gui/set/stats_panel.cpp @@ -228,13 +228,19 @@ void StatDimensionList::drawItem(DC& dc, int x, int y, size_t item) { } StatsDimension& dim = *dimensions.at(item - show_empty); // draw icon - if (!dim.icon_filename.empty() && !dim.icon.Ok()) { - auto file = game->openIn(dim.icon_filename); - Image img(*file); - if (img.HasMask()) img.InitAlpha(); // we can't handle masks - Image resampled(21, 21); - resample_preserve_aspect(img, resampled); - if (img.Ok()) dim.icon = Bitmap(resampled); + if(!dim.icon.Ok()) { + String filename = dim.icon_filename; + if (settings.darkMode() && !dim.dark_icon_filename.empty()) { + filename = dim.dark_icon_filename; + } + if (!filename.empty()) { + auto file = game->openIn(filename); + Image img(*file); + if (img.HasMask()) img.InitAlpha(); // we can't handle masks + Image resampled(21, 21); + resample_preserve_aspect(img, resampled); + if (img.Ok()) dim.icon = Bitmap(resampled); + } } if (dim.icon.Ok()) { dc.DrawBitmap(dim.icon, x+1, y+1); diff --git a/src/util/io/package.cpp b/src/util/io/package.cpp index 9fc986f5..988982ee 100644 --- a/src/util/io/package.cpp +++ b/src/util/io/package.cpp @@ -572,6 +572,7 @@ IMPLEMENT_REFLECTION(Packaged) { REFLECT(full_name); REFLECT(folder_name); REFLECT_N("icon", icon_filename); + REFLECT_N("dark_icon", dark_icon_filename); REFLECT_NO_SCRIPT(position_hint); REFLECT(installer_group); REFLECT(version); @@ -584,9 +585,18 @@ Packaged::Packaged() , fully_loaded(true) {} -unique_ptr Packaged::openIconFile() { - if (!icon_filename.empty()) { - return openIn(icon_filename); +unique_ptr Packaged::openIconFile() { + String filename = icon_filename; + if (!dark_icon_filename.empty()) { + if (settings.darkMode()) { + wxFileName fn (dark_icon_filename); + String extension = fn.GetExt(); + filename = dark_icon_filename.Replace(extension, _("")) + "_dark" + extension; + } + else filename = dark_icon_filename; + } + if (!filename.empty()) { + return openIn(filename); } else { return unique_ptr(); } diff --git a/src/util/io/package.hpp b/src/util/io/package.hpp index c62e99d6..683ab26a 100644 --- a/src/util/io/package.hpp +++ b/src/util/io/package.hpp @@ -305,15 +305,16 @@ public: Packaged(); virtual ~Packaged() {} - Version version; ///< Version number of this package - Version compatible_version; ///< Earliest version number this package is compatible with - String installer_group; ///< Group to place this package in in the installer - String short_name; ///< Short name of this package - String full_name; ///< Name of this package, for menus etc. - String folder_name; ///< Name of the folder this package is loaded from. - String icon_filename; ///< Filename of icon to use in package lists - vector dependencies; ///< Dependencies of this package - int position_hint; ///< A hint for the package list + Version version; ///< Version number of this package + Version compatible_version; ///< Earliest version number this package is compatible with + String installer_group; ///< Group to place this package in in the installer + String short_name; ///< Short name of this package + String full_name; ///< Name of this package, for menus etc. + String folder_name; ///< Name of the folder this package is loaded from. + String icon_filename; ///< Filename of icon to use in package lists + String dark_icon_filename; ///< Filename of icon to use in package lists, when a variant for dark mode is needed + vector dependencies; ///< Dependencies of this package + int position_hint; ///< A hint for the package list /// Get an input stream for the package icon, if there is any unique_ptr openIconFile();