diff --git a/.clang-format b/.clang-format index 61441ef6..d68fa440 100644 --- a/.clang-format +++ b/.clang-format @@ -5,7 +5,8 @@ Standard: Cpp11 ColumnLimit: 0 # We want a space between the type and the star for pointer types. -PointerBindsToType: false +DerivePointerAlignment: false +PointerAlignment: Right # We use template< without space. SpaceAfterTemplateKeyword: false @@ -43,7 +44,7 @@ NamespaceIndentation: None # The coding style does not specify the following, but this is what gives # results closest to the existing code. AlignAfterOpenBracket: true -AlwaysBreakTemplateDeclarations: true +BreakTemplateDeclarations: Yes # Ideally we should also allow less short function in a single line, but # clang-format does not handle that. @@ -60,3 +61,6 @@ ForEachMacros: [ foreach, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENC # Break constructor initializers before the colon and after the commas. BreakConstructorInitializers: BeforeColon + +# Empty blocks should be {} +SpaceInEmptyBraces: Always diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d866bd15..d3ba5313 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,6 +45,9 @@ jobs: - name: Install dependencies run: brew install clang-format + - name: Print clang-format version + run: clang-format --version + - name: Run clang-format run: | find . \( -name '*.h' -or -name '*.cpp' -or -name '*.c' -or -name '*.mm' -or -name '*.m' \) -print0 | xargs -0 clang-format -style=file -i diff --git a/YACReader/CMakeLists.txt b/YACReader/CMakeLists.txt index 51c55b61..62cf5864 100644 --- a/YACReader/CMakeLists.txt +++ b/YACReader/CMakeLists.txt @@ -56,9 +56,13 @@ target_compile_definitions(YACReader PRIVATE YACREADER) # Resources qt_add_resources(yacreader_images_rcc "${CMAKE_CURRENT_SOURCE_DIR}/yacreader_images.qrc") qt_add_resources(yacreader_files_rcc "${CMAKE_CURRENT_SOURCE_DIR}/yacreader_files.qrc") +qt_add_resources(yacreader_themes_rcc "${CMAKE_CURRENT_SOURCE_DIR}/themes/themes.qrc") +qt_add_resources(yacreader_common_images_rcc "${CMAKE_SOURCE_DIR}/common/themes/appearance_config_images.qrc") target_sources(YACReader PRIVATE ${yacreader_images_rcc} ${yacreader_files_rcc} + ${yacreader_themes_rcc} + ${yacreader_common_images_rcc} ) # Translations diff --git a/YACReader/main.cpp b/YACReader/main.cpp index e71f6791..d1c98e22 100644 --- a/YACReader/main.cpp +++ b/YACReader/main.cpp @@ -7,7 +7,10 @@ #include "main_window_viewer.h" #include "configuration.h" #include "exit_check.h" +#include "appearance_configuration.h" #include "theme_manager.h" +#include "theme_repository.h" +#include "yacreader_global.h" #include "QsLog.h" #include "QsLogDest.h" @@ -114,7 +117,11 @@ int main(int argc, char *argv[]) app.setApplicationName("YACReader"); app.setOrganizationName("YACReader"); - ThemeManager::instance().initialize(); + auto *appearanceConfig = new AppearanceConfiguration( + YACReader::getSettingsPath() + "/YACReader.ini", qApp); + auto *themeRepo = new ThemeRepository( + ":/themes", YACReader::getSettingsPath() + "/themes/user"); + ThemeManager::instance().initialize(appearanceConfig, themeRepo); if (QIcon::hasThemeIcon("YACReader")) { app.setWindowIcon(QIcon::fromTheme("YACReader")); diff --git a/YACReader/options_dialog.cpp b/YACReader/options_dialog.cpp index 4df071c3..43f50e67 100644 --- a/YACReader/options_dialog.cpp +++ b/YACReader/options_dialog.cpp @@ -12,7 +12,10 @@ #include #include #include +#include #include "theme_manager.h" +#include "theme_factory.h" +#include "appearance_tab_widget.h" #include "yacreader_spin_slider_widget.h" #include "yacreader_3d_flow_config_widget.h" @@ -203,9 +206,21 @@ OptionsDialog::OptionsDialog(QWidget *parent) pageFlow->setLayout(layoutFlow); pageImage->setLayout(layoutImageV); + // APPEARANCE ---------------------------------------- + + auto *pageAppearance = new AppearanceTabWidget( + ThemeManager::instance().getAppearanceConfiguration(), + ThemeManager::instance().getRepository(), + []() { return ThemeManager::instance().getCurrentTheme().sourceJson; }, + [](const QJsonObject &json) { ThemeManager::instance().setTheme(makeTheme(json)); }, + this); + + // APPEARANCE END ------------------------------------ + tabWidget->addTab(pageGeneral, tr("General")); tabWidget->addTab(pageFlow, tr("Page Flow")); tabWidget->addTab(pageImage, tr("Image adjustment")); + tabWidget->addTab(pageAppearance, tr("Appearance")); layout->addWidget(tabWidget); diff --git a/YACReader/options_dialog.h b/YACReader/options_dialog.h index 7c2ef6cc..20467f77 100644 --- a/YACReader/options_dialog.h +++ b/YACReader/options_dialog.h @@ -4,6 +4,8 @@ #include "yacreader_options_dialog.h" #include "themable.h" +#include + class QDialog; class QLabel; class QLineEdit; diff --git a/YACReader/render.cpp b/YACReader/render.cpp index 595df27c..59996fea 100644 --- a/YACReader/render.cpp +++ b/YACReader/render.cpp @@ -1000,7 +1000,7 @@ void Render::fillBuffer() pageRenders[currentPageBufferedIndex + i]->start(); } - if ((currentIndex - i > 0) && + if ((currentIndex - i >= 0) && buffer[currentPageBufferedIndex - i]->isNull() && i <= numLeftPages && pageRenders[currentPageBufferedIndex - i] == 0 && diff --git a/YACReader/themes/builtin_classic.json b/YACReader/themes/builtin_classic.json new file mode 100644 index 00000000..f3edf088 --- /dev/null +++ b/YACReader/themes/builtin_classic.json @@ -0,0 +1,51 @@ +{ + "goToFlowWidget": { + "editBackgroundColor": "#55000000", + "editBorderColor": "#77000000", + "editTextColor": "#ffffff", + "flowBackgroundColor": "#282828", + "flowTextColor": "#ffffff", + "iconColor": "#ffffff", + "labelTextColor": "#ffffff", + "sliderBorderColor": "#22ffffff", + "sliderGrooveColor": "#77000000", + "sliderHandleColor": "#55ffffff", + "toolbarBackgroundColor": "#99000000" + }, + "helpAboutDialog": { + "headingColor": "#302f2d", + "linkColor": "#c19441" + }, + "meta": { + "displayName": "Default Classic", + "id": "builtin/classic", + "variant": "dark" + }, + "shortcutsIcons": { + "iconColor": "#404040" + }, + "toolbar": { + "backgroundColor": "#f3f3f3", + "checkedButtonColor": "#cccccc", + "iconCheckedColor": "#5a5a5a", + "iconColor": "#404040", + "iconDisabledColor": "#858585", + "menuIndicatorColor": "#404040", + "separatorColor": "#cccccc" + }, + "viewer": { + "defaultBackgroundColor": "#282828", + "defaultTextColor": "#ffffff", + "infoBackgroundColor": "#bb000000", + "infoTextColor": "#ffffff" + }, + "whatsNewDialog": { + "backgroundColor": "#ffffff", + "closeButtonColor": "#444444", + "contentTextColor": "#0a0a0a", + "headerDecorationColor": "#e8b800", + "headerTextColor": "#0a0a0a", + "linkColor": "#e8b800", + "versionTextColor": "#858585" + } +} diff --git a/YACReader/themes/builtin_dark.json b/YACReader/themes/builtin_dark.json new file mode 100644 index 00000000..61038dfa --- /dev/null +++ b/YACReader/themes/builtin_dark.json @@ -0,0 +1,51 @@ +{ + "goToFlowWidget": { + "editBackgroundColor": "#55000000", + "editBorderColor": "#77000000", + "editTextColor": "#ffffff", + "flowBackgroundColor": "#282828", + "flowTextColor": "#ffffff", + "iconColor": "#cccccc", + "labelTextColor": "#ffffff", + "sliderBorderColor": "#22ffffff", + "sliderGrooveColor": "#77000000", + "sliderHandleColor": "#55ffffff", + "toolbarBackgroundColor": "#99000000" + }, + "helpAboutDialog": { + "headingColor": "#e0e0e0", + "linkColor": "#d4a84b" + }, + "meta": { + "displayName": "Default Dark", + "id": "builtin/dark", + "variant": "dark" + }, + "shortcutsIcons": { + "iconColor": "#d0d0d0" + }, + "toolbar": { + "backgroundColor": "#202020", + "checkedButtonColor": "#3a3a3a", + "iconCheckedColor": "#dadada", + "iconColor": "#cccccc", + "iconDisabledColor": "#444444", + "menuIndicatorColor": "#cccccc", + "separatorColor": "#444444" + }, + "viewer": { + "defaultBackgroundColor": "#282828", + "defaultTextColor": "#ffffff", + "infoBackgroundColor": "#bb000000", + "infoTextColor": "#b0b0b0" + }, + "whatsNewDialog": { + "backgroundColor": "#2a2a2a", + "closeButtonColor": "#dddddd", + "contentTextColor": "#e0e0e0", + "headerDecorationColor": "#e8b800", + "headerTextColor": "#e0e0e0", + "linkColor": "#e8b800", + "versionTextColor": "#858585" + } +} diff --git a/YACReader/themes/builtin_light.json b/YACReader/themes/builtin_light.json new file mode 100644 index 00000000..dbd77f07 --- /dev/null +++ b/YACReader/themes/builtin_light.json @@ -0,0 +1,51 @@ +{ + "goToFlowWidget": { + "editBackgroundColor": "#22000000", + "editBorderColor": "#33000000", + "editTextColor": "#202020", + "flowBackgroundColor": "#f6f6f6", + "flowTextColor": "#202020", + "iconColor": "#404040", + "labelTextColor": "#202020", + "sliderBorderColor": "#22000000", + "sliderGrooveColor": "#33000000", + "sliderHandleColor": "#55000000", + "toolbarBackgroundColor": "#bbffffff" + }, + "helpAboutDialog": { + "headingColor": "#302f2d", + "linkColor": "#c19441" + }, + "meta": { + "displayName": "Default Light", + "id": "builtin/light", + "variant": "light" + }, + "shortcutsIcons": { + "iconColor": "#606060" + }, + "toolbar": { + "backgroundColor": "#f3f3f3", + "checkedButtonColor": "#cccccc", + "iconCheckedColor": "#5a5a5a", + "iconColor": "#404040", + "iconDisabledColor": "#b0b0b0", + "menuIndicatorColor": "#404040", + "separatorColor": "#cccccc" + }, + "viewer": { + "defaultBackgroundColor": "#f6f6f6", + "defaultTextColor": "#202020", + "infoBackgroundColor": "#bbffffff", + "infoTextColor": "#404040" + }, + "whatsNewDialog": { + "backgroundColor": "#ffffff", + "closeButtonColor": "#444444", + "contentTextColor": "#0a0a0a", + "headerDecorationColor": "#e8b800", + "headerTextColor": "#0a0a0a", + "linkColor": "#e8b800", + "versionTextColor": "#858585" + } +} diff --git a/YACReader/themes/theme.h b/YACReader/themes/theme.h index 2cc5e8cf..964e619f 100644 --- a/YACReader/themes/theme.h +++ b/YACReader/themes/theme.h @@ -2,9 +2,11 @@ #define THEME_H #include +#include #include "help_about_dialog_theme.h" #include "whats_new_dialog_theme.h" +#include "theme_meta.h" struct ToolbarThemeTemplates { QString toolbarQSS = "QToolBar { border: none; background: %1; }\n" @@ -142,6 +144,9 @@ struct DialogIconsTheme { }; struct Theme { + ThemeMeta meta; + QJsonObject sourceJson; + ToolbarTheme toolbar; ViewerTheme viewer; GoToFlowWidgetTheme goToFlowWidget; diff --git a/YACReader/themes/theme_factory.cpp b/YACReader/themes/theme_factory.cpp index d798a5a4..a0cb207f 100644 --- a/YACReader/themes/theme_factory.cpp +++ b/YACReader/themes/theme_factory.cpp @@ -3,6 +3,7 @@ #include #include "icon_utils.h" +#include "theme_meta.h" struct ToolbarParams { ToolbarThemeTemplates t; @@ -56,7 +57,7 @@ struct ShortcutsIconsParams { }; struct ThemeParams { - QString themeName; + ThemeMeta meta; ToolbarParams toolbarParams; ViewerParams viewerParams; @@ -72,7 +73,7 @@ void setToolbarIconPair(QIcon &icon, const QColor &iconColor, const QColor &disabledColor, const QColor &checkedColor, - const QString &themeName) + const QString &themeId) { QString path18 = basePath; if (path18.endsWith(".svg")) @@ -81,14 +82,14 @@ void setToolbarIconPair(QIcon &icon, path18.append("_18x18"); // Normal - const QString normalPath = recoloredSvgToThemeFile(basePath, iconColor, themeName); - const QString normalPath18 = recoloredSvgToThemeFile(path18, iconColor, themeName); + const QString normalPath = recoloredSvgToThemeFile(basePath, iconColor, themeId); + const QString normalPath18 = recoloredSvgToThemeFile(path18, iconColor, themeId); // Disabled - const QString disabledPath = recoloredSvgToThemeFile(basePath, disabledColor, themeName, { .suffix = "_disabled" }); - const QString disabledPath18 = recoloredSvgToThemeFile(path18, disabledColor, themeName, { .suffix = "_disabled" }); + const QString disabledPath = recoloredSvgToThemeFile(basePath, disabledColor, themeId, { .suffix = "_disabled" }); + const QString disabledPath18 = recoloredSvgToThemeFile(path18, disabledColor, themeId, { .suffix = "_disabled" }); // Checked (On state) - const QString checkedPath = recoloredSvgToThemeFile(basePath, checkedColor, themeName, { .suffix = "_checked" }); - const QString checkedPath18 = recoloredSvgToThemeFile(path18, checkedColor, themeName, { .suffix = "_checked" }); + const QString checkedPath = recoloredSvgToThemeFile(basePath, checkedColor, themeId, { .suffix = "_checked" }); + const QString checkedPath18 = recoloredSvgToThemeFile(path18, checkedColor, themeId, { .suffix = "_checked" }); icon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); icon.addFile(disabledPath, QSize(), QIcon::Disabled, QIcon::Off); @@ -105,11 +106,13 @@ Theme makeTheme(const ThemeParams ¶ms) { Theme theme; + theme.meta = params.meta; + // Toolbar & actions - theme.toolbar.toolbarQSS = params.toolbarParams.t.toolbarQSS.arg(params.toolbarParams.backgroundColor.name(), params.toolbarParams.separatorColor.name(), params.toolbarParams.checkedButtonColor.name(), recoloredSvgToThemeFile(params.toolbarParams.t.menuArrowPath, params.toolbarParams.menuIndicatorColor, params.themeName)); + theme.toolbar.toolbarQSS = params.toolbarParams.t.toolbarQSS.arg(params.toolbarParams.backgroundColor.name(), params.toolbarParams.separatorColor.name(), params.toolbarParams.checkedButtonColor.name(), recoloredSvgToThemeFile(params.toolbarParams.t.menuArrowPath, params.toolbarParams.menuIndicatorColor, params.meta.id)); auto setToolbarIconPairT = [&](QIcon &icon, QIcon &icon18, const QString &basePath) { - setToolbarIconPair(icon, icon18, basePath, params.toolbarParams.iconColor, params.toolbarParams.iconDisabledColor, params.toolbarParams.iconCheckedColor, params.themeName); + setToolbarIconPair(icon, icon18, basePath, params.toolbarParams.iconColor, params.toolbarParams.iconDisabledColor, params.toolbarParams.iconCheckedColor, params.meta.id); }; setToolbarIconPairT(theme.toolbar.openAction, theme.toolbar.openAction18x18, ":/images/viewer_toolbar/open.svg"); @@ -166,8 +169,8 @@ Theme makeTheme(const ThemeParams ¶ms) theme.goToFlowWidget.buttonQSS = gotoParams.t.buttonQSS; theme.goToFlowWidget.labelQSS = gotoParams.t.labelQSS.arg(gotoParams.labelTextColor.name()); - const QString centerIconPath = recoloredSvgToThemeFile(":/images/centerFlow.svg", gotoParams.iconColor, params.themeName); - const QString goToIconPath = recoloredSvgToThemeFile(":/images/gotoFlow.svg", gotoParams.iconColor, params.themeName); + const QString centerIconPath = recoloredSvgToThemeFile(":/images/centerFlow.svg", gotoParams.iconColor, params.meta.id); + const QString goToIconPath = recoloredSvgToThemeFile(":/images/gotoFlow.svg", gotoParams.iconColor, params.meta.id); theme.goToFlowWidget.centerIcon = QIcon(centerIconPath); theme.goToFlowWidget.goToIcon = QIcon(goToIconPath); // end GoToFlowWidget @@ -183,14 +186,14 @@ Theme makeTheme(const ThemeParams ¶ms) theme.whatsNewDialog.versionTextColor = wn.versionTextColor; theme.whatsNewDialog.contentTextColor = wn.contentTextColor; theme.whatsNewDialog.linkColor = wn.linkColor; - theme.whatsNewDialog.closeButtonIcon = QPixmap(recoloredSvgToThemeFile(":/images/custom_dialog/custom_close_button.svg", wn.closeButtonColor, params.themeName)); - theme.whatsNewDialog.headerDecoration = QPixmap(recoloredSvgToThemeFile(":/images/whats_new/whatsnew_header.svg", wn.headerDecorationColor, params.themeName)); + theme.whatsNewDialog.closeButtonIcon = QPixmap(recoloredSvgToThemeFile(":/images/custom_dialog/custom_close_button.svg", wn.closeButtonColor, params.meta.id)); + theme.whatsNewDialog.headerDecoration = QPixmap(recoloredSvgToThemeFile(":/images/whats_new/whatsnew_header.svg", wn.headerDecorationColor, params.meta.id)); // end WhatsNewDialog // ShortcutsIcons const auto &sci = params.shortcutsIconsParams; auto makeShortcutsIcon = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, sci.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(basePath, sci.iconColor, params.meta.id); return QIcon(path); }; @@ -203,7 +206,7 @@ Theme makeTheme(const ThemeParams ¶ms) // FindFolder icon (used in OptionsDialog) { - const QString path = recoloredSvgToThemeFile(":/images/find_folder.svg", params.toolbarParams.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(":/images/find_folder.svg", params.toolbarParams.iconColor, params.meta.id); const qreal dpr = qApp->devicePixelRatio(); theme.dialogIcons.findFolderIcon = QIcon(renderSvgToPixmap(path, 13, 13, dpr)); } @@ -211,204 +214,92 @@ Theme makeTheme(const ThemeParams ¶ms) return theme; } -ThemeParams classicThemeParams(); -ThemeParams lightThemeParams(); -ThemeParams darkThemeParams(); +// JSON helpers --------------------------------------------------------------- -Theme makeTheme(ThemeId themeId) +static QColor colorFromJson(const QJsonObject &obj, const QString &key, const QColor &fallback) { - switch (themeId) { - case ThemeId::Classic: - return makeTheme(classicThemeParams()); - case ThemeId::Light: - return makeTheme(lightThemeParams()); - case ThemeId::Dark: - return makeTheme(darkThemeParams()); + if (!obj.contains(key)) + return fallback; + QColor c(obj[key].toString()); + return c.isValid() ? c : fallback; +} + +Theme makeTheme(const QJsonObject &json) +{ + ThemeParams p; + + if (json.contains("toolbar")) { + const auto t = json["toolbar"].toObject(); + auto &tp = p.toolbarParams; + tp.iconColor = colorFromJson(t, "iconColor", tp.iconColor); + tp.iconDisabledColor = colorFromJson(t, "iconDisabledColor", tp.iconDisabledColor); + tp.iconCheckedColor = colorFromJson(t, "iconCheckedColor", tp.iconCheckedColor); + tp.backgroundColor = colorFromJson(t, "backgroundColor", tp.backgroundColor); + tp.separatorColor = colorFromJson(t, "separatorColor", tp.separatorColor); + tp.checkedButtonColor = colorFromJson(t, "checkedButtonColor", tp.checkedButtonColor); + tp.menuIndicatorColor = colorFromJson(t, "menuIndicatorColor", tp.menuIndicatorColor); } -} - -ThemeParams classicThemeParams() -{ - ThemeParams params; - params.themeName = "classic"; - - ToolbarParams toolbarParams; - toolbarParams.iconColor = QColor(0x404040); - toolbarParams.iconDisabledColor = QColor(0x858585); - toolbarParams.iconCheckedColor = QColor(0x5A5A5A); - toolbarParams.backgroundColor = QColor(0xF3F3F3); - toolbarParams.separatorColor = QColor(0xCCCCCC); - toolbarParams.checkedButtonColor = QColor(0xCCCCCC); - toolbarParams.menuIndicatorColor = QColor(0x404040); - - params.toolbarParams = toolbarParams; - - ViewerParams viewerParams; - viewerParams.defaultBackgroundColor = QColor(0x282828); - viewerParams.defaultTextColor = Qt::white; - viewerParams.infoBackgroundColor = QColor::fromRgba(0xBB000000); - viewerParams.infoTextColor = Qt::white; - viewerParams.t = ViewerThemeTemplates(); - - params.viewerParams = viewerParams; - - GoToFlowWidgetParams goToFlowWidgetParams; - goToFlowWidgetParams.flowBackgroundColor = QColor(0x282828); - goToFlowWidgetParams.flowTextColor = Qt::white; - goToFlowWidgetParams.toolbarBackgroundColor = QColor::fromRgba(0x99000000); - goToFlowWidgetParams.sliderBorderColor = QColor::fromRgba(0x22FFFFFF); - goToFlowWidgetParams.sliderGrooveColor = QColor::fromRgba(0x77000000); - goToFlowWidgetParams.sliderHandleColor = QColor::fromRgba(0x55FFFFFF); - goToFlowWidgetParams.editBorderColor = QColor::fromRgba(0x77000000); - goToFlowWidgetParams.editBackgroundColor = QColor::fromRgba(0x55000000); - goToFlowWidgetParams.editTextColor = Qt::white; - goToFlowWidgetParams.labelTextColor = Qt::white; - goToFlowWidgetParams.iconColor = Qt::white; - goToFlowWidgetParams.t = GoToFlowWidgetThemeTemplates(); - - params.goToFlowWidgetParams = goToFlowWidgetParams; - - params.helpAboutDialogParams.headingColor = QColor(0x302f2d); - params.helpAboutDialogParams.linkColor = QColor(0xC19441); - - params.whatsNewDialogParams.backgroundColor = QColor(0xFFFFFF); - params.whatsNewDialogParams.headerTextColor = QColor(0x0A0A0A); - params.whatsNewDialogParams.versionTextColor = QColor(0x858585); - params.whatsNewDialogParams.contentTextColor = QColor(0x0A0A0A); - params.whatsNewDialogParams.linkColor = QColor(0xE8B800); - params.whatsNewDialogParams.closeButtonColor = QColor(0x444444); - params.whatsNewDialogParams.headerDecorationColor = QColor(0xE8B800); - - // ShortcutsIcons - ShortcutsIconsParams sci; - sci.iconColor = QColor(0x404040); // Dark icons for light background - params.shortcutsIconsParams = sci; - - return params; -} - -ThemeParams lightThemeParams() -{ - ThemeParams params; - params.themeName = "light"; - - ToolbarParams toolbarParams; - toolbarParams.iconColor = QColor(0x404040); - toolbarParams.iconDisabledColor = QColor(0xB0B0B0); - toolbarParams.iconCheckedColor = QColor(0x5A5A5A); - toolbarParams.backgroundColor = QColor(0xF3F3F3); - toolbarParams.separatorColor = QColor(0xCCCCCC); - toolbarParams.checkedButtonColor = QColor(0xCCCCCC); - toolbarParams.menuIndicatorColor = QColor(0x404040); - - params.toolbarParams = toolbarParams; - - ViewerParams viewerParams; - viewerParams.defaultBackgroundColor = QColor(0xF6F6F6); - viewerParams.defaultTextColor = QColor(0x202020); - viewerParams.infoBackgroundColor = QColor::fromRgba(0xBBFFFFFF); - viewerParams.infoTextColor = QColor(0x404040); - viewerParams.t = ViewerThemeTemplates(); - - params.viewerParams = viewerParams; - - GoToFlowWidgetParams goToFlowWidgetParams; - goToFlowWidgetParams.flowBackgroundColor = QColor(0xF6F6F6); - goToFlowWidgetParams.flowTextColor = QColor(0x202020); - goToFlowWidgetParams.toolbarBackgroundColor = QColor::fromRgba(0xBBFFFFFF); - goToFlowWidgetParams.sliderBorderColor = QColor::fromRgba(0x22000000); - goToFlowWidgetParams.sliderGrooveColor = QColor::fromRgba(0x33000000); - goToFlowWidgetParams.sliderHandleColor = QColor::fromRgba(0x55000000); - goToFlowWidgetParams.editBorderColor = QColor::fromRgba(0x33000000); - goToFlowWidgetParams.editBackgroundColor = QColor::fromRgba(0x22000000); - goToFlowWidgetParams.editTextColor = QColor(0x202020); - goToFlowWidgetParams.labelTextColor = QColor(0x202020); - goToFlowWidgetParams.iconColor = QColor(0x404040); - goToFlowWidgetParams.t = GoToFlowWidgetThemeTemplates(); - - params.goToFlowWidgetParams = goToFlowWidgetParams; - - params.helpAboutDialogParams.headingColor = QColor(0x302f2d); - params.helpAboutDialogParams.linkColor = QColor(0xC19441); - - params.whatsNewDialogParams.backgroundColor = QColor(0xFFFFFF); - params.whatsNewDialogParams.headerTextColor = QColor(0x0A0A0A); - params.whatsNewDialogParams.versionTextColor = QColor(0x858585); - params.whatsNewDialogParams.contentTextColor = QColor(0x0A0A0A); - params.whatsNewDialogParams.linkColor = QColor(0xE8B800); - params.whatsNewDialogParams.closeButtonColor = QColor(0x444444); - params.whatsNewDialogParams.headerDecorationColor = QColor(0xE8B800); - - // ShortcutsIcons - ShortcutsIconsParams sci; - sci.iconColor = QColor(0x606060); // Dark icons for light background - params.shortcutsIconsParams = sci; - - return params; -} - -ThemeParams darkThemeParams() -{ - ThemeParams params; - params.themeName = "dark"; - - ToolbarParams toolbarParams; - toolbarParams.iconColor = QColor(0xCCCCCC); - toolbarParams.iconDisabledColor = QColor(0x444444); - toolbarParams.iconCheckedColor = QColor(0xDADADA); - toolbarParams.backgroundColor = QColor(0x202020); - toolbarParams.separatorColor = QColor(0x444444); - toolbarParams.checkedButtonColor = QColor(0x3A3A3A); - toolbarParams.menuIndicatorColor = QColor(0xCCCCCC); - - params.toolbarParams = toolbarParams; - - ViewerParams viewerParams; - viewerParams.defaultBackgroundColor = QColor(40, 40, 40); - viewerParams.defaultTextColor = Qt::white; - viewerParams.infoBackgroundColor = QColor::fromRgba(0xBB000000); - viewerParams.infoTextColor = QColor(0xB0B0B0); - viewerParams.t = ViewerThemeTemplates(); - - params.viewerParams = viewerParams; - - GoToFlowWidgetParams goToFlowWidgetParams; - goToFlowWidgetParams.flowBackgroundColor = QColor(40, 40, 40); - goToFlowWidgetParams.flowTextColor = Qt::white; - goToFlowWidgetParams.toolbarBackgroundColor = QColor::fromRgba(0x99000000); - goToFlowWidgetParams.sliderBorderColor = QColor::fromRgba(0x22FFFFFF); - goToFlowWidgetParams.sliderGrooveColor = QColor::fromRgba(0x77000000); - goToFlowWidgetParams.sliderHandleColor = QColor::fromRgba(0x55FFFFFF); - goToFlowWidgetParams.editBorderColor = QColor::fromRgba(0x77000000); - goToFlowWidgetParams.editBackgroundColor = QColor::fromRgba(0x55000000); - goToFlowWidgetParams.editTextColor = Qt::white; - goToFlowWidgetParams.labelTextColor = Qt::white; - goToFlowWidgetParams.iconColor = QColor(0xCCCCCC); - goToFlowWidgetParams.t = GoToFlowWidgetThemeTemplates(); - - params.goToFlowWidgetParams = goToFlowWidgetParams; - - params.helpAboutDialogParams.headingColor = QColor(0xE0E0E0); - params.helpAboutDialogParams.linkColor = QColor(0xD4A84B); - - params.whatsNewDialogParams.backgroundColor = QColor(0x2A2A2A); - params.whatsNewDialogParams.headerTextColor = QColor(0xE0E0E0); - params.whatsNewDialogParams.versionTextColor = QColor(0x858585); - params.whatsNewDialogParams.contentTextColor = QColor(0xE0E0E0); - params.whatsNewDialogParams.linkColor = QColor(0xE8B800); - params.whatsNewDialogParams.closeButtonColor = QColor(0xDDDDDD); - params.whatsNewDialogParams.headerDecorationColor = QColor(0xE8B800); - - // ShortcutsIcons - ShortcutsIconsParams sci; - sci.iconColor = QColor(0xD0D0D0); // Light icons for dark background - params.shortcutsIconsParams = sci; - - return params; -} - -// TODO -ThemeParams paramsFromFile(const QString &filePath) -{ - return {}; + + if (json.contains("viewer")) { + const auto v = json["viewer"].toObject(); + auto &vp = p.viewerParams; + vp.defaultBackgroundColor = colorFromJson(v, "defaultBackgroundColor", vp.defaultBackgroundColor); + vp.defaultTextColor = colorFromJson(v, "defaultTextColor", vp.defaultTextColor); + vp.infoBackgroundColor = colorFromJson(v, "infoBackgroundColor", vp.infoBackgroundColor); + vp.infoTextColor = colorFromJson(v, "infoTextColor", vp.infoTextColor); + } + + if (json.contains("goToFlowWidget")) { + const auto g = json["goToFlowWidget"].toObject(); + auto &gp = p.goToFlowWidgetParams; + gp.flowBackgroundColor = colorFromJson(g, "flowBackgroundColor", gp.flowBackgroundColor); + gp.flowTextColor = colorFromJson(g, "flowTextColor", gp.flowTextColor); + gp.toolbarBackgroundColor = colorFromJson(g, "toolbarBackgroundColor", gp.toolbarBackgroundColor); + gp.sliderBorderColor = colorFromJson(g, "sliderBorderColor", gp.sliderBorderColor); + gp.sliderGrooveColor = colorFromJson(g, "sliderGrooveColor", gp.sliderGrooveColor); + gp.sliderHandleColor = colorFromJson(g, "sliderHandleColor", gp.sliderHandleColor); + gp.editBorderColor = colorFromJson(g, "editBorderColor", gp.editBorderColor); + gp.editBackgroundColor = colorFromJson(g, "editBackgroundColor", gp.editBackgroundColor); + gp.editTextColor = colorFromJson(g, "editTextColor", gp.editTextColor); + gp.labelTextColor = colorFromJson(g, "labelTextColor", gp.labelTextColor); + gp.iconColor = colorFromJson(g, "iconColor", gp.iconColor); + } + + if (json.contains("helpAboutDialog")) { + const auto h = json["helpAboutDialog"].toObject(); + p.helpAboutDialogParams.headingColor = colorFromJson(h, "headingColor", p.helpAboutDialogParams.headingColor); + p.helpAboutDialogParams.linkColor = colorFromJson(h, "linkColor", p.helpAboutDialogParams.linkColor); + } + + if (json.contains("whatsNewDialog")) { + const auto w = json["whatsNewDialog"].toObject(); + auto &wn = p.whatsNewDialogParams; + wn.backgroundColor = colorFromJson(w, "backgroundColor", wn.backgroundColor); + wn.headerTextColor = colorFromJson(w, "headerTextColor", wn.headerTextColor); + wn.versionTextColor = colorFromJson(w, "versionTextColor", wn.versionTextColor); + wn.contentTextColor = colorFromJson(w, "contentTextColor", wn.contentTextColor); + wn.linkColor = colorFromJson(w, "linkColor", wn.linkColor); + wn.closeButtonColor = colorFromJson(w, "closeButtonColor", wn.closeButtonColor); + wn.headerDecorationColor = colorFromJson(w, "headerDecorationColor", wn.headerDecorationColor); + } + + if (json.contains("shortcutsIcons")) { + const auto s = json["shortcutsIcons"].toObject(); + p.shortcutsIconsParams.iconColor = colorFromJson(s, "iconColor", p.shortcutsIconsParams.iconColor); + } + + if (json.contains("meta")) { + const auto o = json["meta"].toObject(); + p.meta.id = o["id"].toString(p.meta.id); + p.meta.displayName = o["displayName"].toString(p.meta.displayName); + const QString variantStr = o["variant"].toString(); + if (variantStr == "light") + p.meta.variant = ThemeVariant::Light; + else if (variantStr == "dark") + p.meta.variant = ThemeVariant::Dark; + } + + Theme theme = makeTheme(p); + theme.sourceJson = json; + return theme; } diff --git a/YACReader/themes/theme_factory.h b/YACReader/themes/theme_factory.h index 72cb4ca2..ebed9bbb 100644 --- a/YACReader/themes/theme_factory.h +++ b/YACReader/themes/theme_factory.h @@ -2,8 +2,9 @@ #define THEME_FACTORY_H #include "theme.h" -#include "theme_id.h" -Theme makeTheme(ThemeId themeId); +#include + +Theme makeTheme(const QJsonObject &json); #endif // THEME_FACTORY_H diff --git a/YACReader/themes/themes.qrc b/YACReader/themes/themes.qrc new file mode 100644 index 00000000..62ec21fe --- /dev/null +++ b/YACReader/themes/themes.qrc @@ -0,0 +1,7 @@ + + + builtin_classic.json + builtin_light.json + builtin_dark.json + + diff --git a/YACReaderLibrary/CMakeLists.txt b/YACReaderLibrary/CMakeLists.txt index cae71e34..485c1191 100644 --- a/YACReaderLibrary/CMakeLists.txt +++ b/YACReaderLibrary/CMakeLists.txt @@ -203,10 +203,14 @@ target_compile_definitions(YACReaderLibrary PRIVATE qt_add_resources(yacreaderlibrary_images_rcc "${CMAKE_CURRENT_SOURCE_DIR}/images.qrc") qt_add_resources(yacreaderlibrary_files_rcc "${CMAKE_CURRENT_SOURCE_DIR}/files.qrc") qt_add_resources(yacreaderlibrary_qml_rcc "${CMAKE_CURRENT_SOURCE_DIR}/qml.qrc") +qt_add_resources(yacreaderlibrary_themes_rcc "${CMAKE_CURRENT_SOURCE_DIR}/themes/themes.qrc") +qt_add_resources(yacreaderlibrary_common_images_rcc "${CMAKE_SOURCE_DIR}/common/themes/appearance_config_images.qrc") target_sources(YACReaderLibrary PRIVATE ${yacreaderlibrary_images_rcc} ${yacreaderlibrary_files_rcc} ${yacreaderlibrary_qml_rcc} + ${yacreaderlibrary_themes_rcc} + ${yacreaderlibrary_common_images_rcc} ) if(WIN32 OR (UNIX AND NOT APPLE)) qt_add_resources(yacreaderlibrary_images_win_rcc "${CMAKE_CURRENT_SOURCE_DIR}/images_win.qrc") diff --git a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp index 23d74cd3..5efab527 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp @@ -649,16 +649,16 @@ void ComicVineDialog::launchSearchComic() void ComicVineDialog::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - setStyleSheet(comicVineTheme.dialogQSS); + setStyleSheet(metadataScraperDialogTheme.dialogQSS); - skipButton->setStyleSheet(comicVineTheme.dialogButtonsQSS); - backButton->setStyleSheet(comicVineTheme.dialogButtonsQSS); - nextButton->setStyleSheet(comicVineTheme.dialogButtonsQSS); - searchButton->setStyleSheet(comicVineTheme.dialogButtonsQSS); - closeButton->setStyleSheet(comicVineTheme.dialogButtonsQSS); + skipButton->setStyleSheet(metadataScraperDialogTheme.dialogButtonsQSS); + backButton->setStyleSheet(metadataScraperDialogTheme.dialogButtonsQSS); + nextButton->setStyleSheet(metadataScraperDialogTheme.dialogButtonsQSS); + searchButton->setStyleSheet(metadataScraperDialogTheme.dialogButtonsQSS); + closeButton->setStyleSheet(metadataScraperDialogTheme.dialogButtonsQSS); - loadingMessage->setStyleSheet(comicVineTheme.defaultLabelQSS); - busyWidget->setColor(comicVineTheme.busyIndicatorColor); + loadingMessage->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); + busyWidget->setColor(metadataScraperDialogTheme.busyIndicatorColor); } diff --git a/YACReaderLibrary/comic_vine/scraper_checkbox.cpp b/YACReaderLibrary/comic_vine/scraper_checkbox.cpp index 2aa78b9b..e15b3ac9 100644 --- a/YACReaderLibrary/comic_vine/scraper_checkbox.cpp +++ b/YACReaderLibrary/comic_vine/scraper_checkbox.cpp @@ -8,7 +8,7 @@ ScraperCheckBox::ScraperCheckBox(const QString &text, QWidget *parent) void ScraperCheckBox::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - setStyleSheet(comicVineTheme.checkBoxQSS); + setStyleSheet(metadataScraperDialogTheme.checkBoxQSS); } diff --git a/YACReaderLibrary/comic_vine/scraper_lineedit.cpp b/YACReaderLibrary/comic_vine/scraper_lineedit.cpp index fb305c05..a67c8004 100644 --- a/YACReaderLibrary/comic_vine/scraper_lineedit.cpp +++ b/YACReaderLibrary/comic_vine/scraper_lineedit.cpp @@ -20,8 +20,8 @@ void ScraperLineEdit::resizeEvent(QResizeEvent *) void ScraperLineEdit::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - titleLabel->setStyleSheet(comicVineTheme.scraperLineEditTitleLabelQSS); - setStyleSheet(comicVineTheme.scraperLineEditQSS.arg(titleLabel->sizeHint().width() + 6)); + titleLabel->setStyleSheet(metadataScraperDialogTheme.scraperLineEditTitleLabelQSS); + setStyleSheet(metadataScraperDialogTheme.scraperLineEditQSS.arg(titleLabel->sizeHint().width() + 6)); } diff --git a/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp b/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp index 637b7fb9..8594656f 100644 --- a/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp +++ b/YACReaderLibrary/comic_vine/scraper_results_paginator.cpp @@ -63,16 +63,16 @@ void ScraperResultsPaginator::setCustomLabel(const QString &label) void ScraperResultsPaginator::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - numElements->setStyleSheet(comicVineTheme.defaultLabelQSS); - numPages->setStyleSheet(comicVineTheme.defaultLabelQSS); + numElements->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); + numPages->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); - nextPage->setStyleSheet(comicVineTheme.noBorderToolButtonQSS); - nextPage->setIconSize(comicVineTheme.nextPageIcon.size); - nextPage->setIcon(comicVineTheme.nextPageIcon.icon); + nextPage->setStyleSheet(metadataScraperDialogTheme.noBorderToolButtonQSS); + nextPage->setIconSize(metadataScraperDialogTheme.nextPageIcon.size); + nextPage->setIcon(metadataScraperDialogTheme.nextPageIcon.icon); - previousPage->setStyleSheet(comicVineTheme.noBorderToolButtonQSS); - previousPage->setIconSize(comicVineTheme.previousPageIcon.size); - previousPage->setIcon(comicVineTheme.previousPageIcon.icon); + previousPage->setStyleSheet(metadataScraperDialogTheme.noBorderToolButtonQSS); + previousPage->setIconSize(metadataScraperDialogTheme.previousPageIcon.size); + previousPage->setIcon(metadataScraperDialogTheme.previousPageIcon.icon); } diff --git a/YACReaderLibrary/comic_vine/scraper_scroll_label.cpp b/YACReaderLibrary/comic_vine/scraper_scroll_label.cpp index 314a7ac3..f9422890 100644 --- a/YACReaderLibrary/comic_vine/scraper_scroll_label.cpp +++ b/YACReaderLibrary/comic_vine/scraper_scroll_label.cpp @@ -41,8 +41,8 @@ void ScraperScrollLabel::openLink(const QString &link) void ScraperScrollLabel::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - textLabel->setStyleSheet(comicVineTheme.scraperScrollLabelTextQSS); - setStyleSheet(comicVineTheme.scraperScrollLabelScrollAreaQSS); + textLabel->setStyleSheet(metadataScraperDialogTheme.scraperScrollLabelTextQSS); + setStyleSheet(metadataScraperDialogTheme.scraperScrollLabelScrollAreaQSS); } diff --git a/YACReaderLibrary/comic_vine/scraper_tableview.cpp b/YACReaderLibrary/comic_vine/scraper_tableview.cpp index 0b528802..47efab2c 100644 --- a/YACReaderLibrary/comic_vine/scraper_tableview.cpp +++ b/YACReaderLibrary/comic_vine/scraper_tableview.cpp @@ -32,7 +32,7 @@ ScraperTableView::ScraperTableView(QWidget *parent) void ScraperTableView::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - setStyleSheet(comicVineTheme.scraperTableViewQSS); + setStyleSheet(metadataScraperDialogTheme.scraperTableViewQSS); } diff --git a/YACReaderLibrary/comic_vine/search_single_comic.cpp b/YACReaderLibrary/comic_vine/search_single_comic.cpp index edf18782..9dd7c7db 100644 --- a/YACReaderLibrary/comic_vine/search_single_comic.cpp +++ b/YACReaderLibrary/comic_vine/search_single_comic.cpp @@ -74,7 +74,7 @@ void SearchSingleComic::clean() void SearchSingleComic::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - label->setStyleSheet(comicVineTheme.defaultLabelQSS); + label->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); } diff --git a/YACReaderLibrary/comic_vine/search_volume.cpp b/YACReaderLibrary/comic_vine/search_volume.cpp index e520f02c..7a0a4cfa 100644 --- a/YACReaderLibrary/comic_vine/search_volume.cpp +++ b/YACReaderLibrary/comic_vine/search_volume.cpp @@ -49,7 +49,7 @@ QString SearchVolume::getVolumeInfo() const void SearchVolume::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - label->setStyleSheet(comicVineTheme.defaultLabelQSS); + label->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); } diff --git a/YACReaderLibrary/comic_vine/select_comic.cpp b/YACReaderLibrary/comic_vine/select_comic.cpp index aa5eb668..67dd1ac2 100644 --- a/YACReaderLibrary/comic_vine/select_comic.cpp +++ b/YACReaderLibrary/comic_vine/select_comic.cpp @@ -165,8 +165,8 @@ QString SelectComic::getSelectedComicId() void SelectComic::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - label->setStyleSheet(comicVineTheme.defaultLabelQSS); - cover->setStyleSheet(comicVineTheme.coverLabelQSS); + label->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); + cover->setStyleSheet(metadataScraperDialogTheme.coverLabelQSS); } diff --git a/YACReaderLibrary/comic_vine/select_volume.cpp b/YACReaderLibrary/comic_vine/select_volume.cpp index 2ed1656d..66e6c5ee 100644 --- a/YACReaderLibrary/comic_vine/select_volume.cpp +++ b/YACReaderLibrary/comic_vine/select_volume.cpp @@ -219,8 +219,8 @@ SelectedVolumeInfo SelectVolume::getSelectedVolumeInfo() void SelectVolume::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - label->setStyleSheet(comicVineTheme.defaultLabelQSS); - cover->setStyleSheet(comicVineTheme.coverLabelQSS); + label->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); + cover->setStyleSheet(metadataScraperDialogTheme.coverLabelQSS); } diff --git a/YACReaderLibrary/comic_vine/series_question.cpp b/YACReaderLibrary/comic_vine/series_question.cpp index caab57fe..34c53c7e 100644 --- a/YACReaderLibrary/comic_vine/series_question.cpp +++ b/YACReaderLibrary/comic_vine/series_question.cpp @@ -40,9 +40,9 @@ void SeriesQuestion::setYes(bool y) void SeriesQuestion::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - questionLabel->setStyleSheet(comicVineTheme.defaultLabelQSS); - yes->setStyleSheet(comicVineTheme.radioButtonQSS); - no->setStyleSheet(comicVineTheme.radioButtonQSS); + questionLabel->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); + yes->setStyleSheet(metadataScraperDialogTheme.radioButtonQSS); + no->setStyleSheet(metadataScraperDialogTheme.radioButtonQSS); } diff --git a/YACReaderLibrary/comic_vine/sort_volume_comics.cpp b/YACReaderLibrary/comic_vine/sort_volume_comics.cpp index d44774d4..5bb2d6c5 100644 --- a/YACReaderLibrary/comic_vine/sort_volume_comics.cpp +++ b/YACReaderLibrary/comic_vine/sort_volume_comics.cpp @@ -22,8 +22,8 @@ QWidget *ScrapperToolButton::getSeparator() { QWidget *w = new QWidget; w->setFixedWidth(1); - auto comicVineTheme = ThemeManager::instance().getCurrentTheme().comicVine; - w->setStyleSheet(comicVineTheme.scraperToolButtonSeparatorQSS); + auto metadataScraperDialogTheme = ThemeManager::instance().getCurrentTheme().metadataScraperDialog; + w->setStyleSheet(metadataScraperDialogTheme.scraperToolButtonSeparatorQSS); return w; } @@ -47,9 +47,9 @@ void ScrapperToolButton::paintEvent(QPaintEvent *e) void ScrapperToolButton::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; - setStyleSheet(comicVineTheme.scraperToolButtonQSS); - fillColor = comicVineTheme.scraperToolButtonFillColor; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; + setStyleSheet(metadataScraperDialogTheme.scraperToolButtonQSS); + fillColor = metadataScraperDialogTheme.scraperToolButtonFillColor; update(); } @@ -267,13 +267,13 @@ QList> SortVolumeComics::getMatchingInfo() void SortVolumeComics::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - label->setStyleSheet(comicVineTheme.defaultLabelQSS); - sortLabel->setStyleSheet(comicVineTheme.defaultLabelQSS); + label->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); + sortLabel->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); - moveUpButtonCL->setIconSize(comicVineTheme.rowUpIcon.size); - moveUpButtonCL->setIcon(comicVineTheme.rowUpIcon.icon); - moveDownButtonCL->setIconSize(comicVineTheme.rowDownIcon.size); - moveDownButtonCL->setIcon(comicVineTheme.rowDownIcon.icon); + moveUpButtonCL->setIconSize(metadataScraperDialogTheme.rowUpIcon.size); + moveUpButtonCL->setIcon(metadataScraperDialogTheme.rowUpIcon.icon); + moveDownButtonCL->setIconSize(metadataScraperDialogTheme.rowDownIcon.size); + moveDownButtonCL->setIcon(metadataScraperDialogTheme.rowDownIcon.icon); } diff --git a/YACReaderLibrary/comic_vine/title_header.cpp b/YACReaderLibrary/comic_vine/title_header.cpp index 06585908..8f54c5b3 100644 --- a/YACReaderLibrary/comic_vine/title_header.cpp +++ b/YACReaderLibrary/comic_vine/title_header.cpp @@ -49,8 +49,8 @@ void TitleHeader::showButtons(bool show) void TitleHeader::applyTheme(const Theme &theme) { - auto comicVineTheme = theme.comicVine; + auto metadataScraperDialogTheme = theme.metadataScraperDialog; - mainTitleLabel->setStyleSheet(comicVineTheme.titleLabelQSS); - subTitleLabel->setStyleSheet(comicVineTheme.defaultLabelQSS); + mainTitleLabel->setStyleSheet(metadataScraperDialogTheme.titleLabelQSS); + subTitleLabel->setStyleSheet(metadataScraperDialogTheme.defaultLabelQSS); } diff --git a/YACReaderLibrary/comics_view_transition.cpp b/YACReaderLibrary/comics_view_transition.cpp index 0c083d93..886292ac 100644 --- a/YACReaderLibrary/comics_view_transition.cpp +++ b/YACReaderLibrary/comics_view_transition.cpp @@ -10,12 +10,12 @@ ComicsViewTransition::ComicsViewTransition(QWidget *parent) void ComicsViewTransition::applyTheme(const Theme &theme) { - setStyleSheet(QString("QWidget {background:%1}").arg(theme.defaultContentBackgroundColor.name())); + setStyleSheet(QString("QWidget {background:%1}").arg(theme.gridAndInfoView.backgroundColor.name())); update(); } void ComicsViewTransition::paintEvent(QPaintEvent *) { QPainter painter(this); - painter.fillRect(0, 0, width(), height(), theme.defaultContentBackgroundColor); + painter.fillRect(0, 0, width(), height(), theme.gridAndInfoView.backgroundColor); } diff --git a/YACReaderLibrary/db/comic_model.cpp b/YACReaderLibrary/db/comic_model.cpp index b0a40129..d85339b0 100644 --- a/YACReaderLibrary/db/comic_model.cpp +++ b/YACReaderLibrary/db/comic_model.cpp @@ -378,7 +378,7 @@ QVariant ComicModel::data(const QModelIndex &index, int role) const Qt::ItemFlags ComicModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return {}; + return { }; if (index.column() == ComicModel::Rating) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled; diff --git a/YACReaderLibrary/db/folder_model.cpp b/YACReaderLibrary/db/folder_model.cpp index d659a19c..17b70cf6 100644 --- a/YACReaderLibrary/db/folder_model.cpp +++ b/YACReaderLibrary/db/folder_model.cpp @@ -144,8 +144,8 @@ void FolderModel::applyTheme(const Theme &theme) folderIcon = QFileIconProvider().icon(QFileIconProvider::Folder); folderFinishedIcon = drawFinishedFolderIcon(sidebarIcons.folderReadOverlay); } else { - folderIcon = sidebarIcons.folderIcon; - folderFinishedIcon = sidebarIcons.folderFinishedIcon; + folderIcon = theme.navigationTree.folderIcon; + folderFinishedIcon = theme.navigationTree.folderFinishedIcon; } } @@ -434,7 +434,7 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const Qt::ItemFlags FolderModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return {}; + return { }; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; } diff --git a/YACReaderLibrary/db/query_lexer.h b/YACReaderLibrary/db/query_lexer.h index d3136d0b..ece898e9 100644 --- a/YACReaderLibrary/db/query_lexer.h +++ b/YACReaderLibrary/db/query_lexer.h @@ -36,8 +36,8 @@ public: } private: - Type _type {}; - std::string _lexeme {}; + Type _type { }; + std::string _lexeme { }; }; class QueryLexer diff --git a/YACReaderLibrary/db/query_parser.cpp b/YACReaderLibrary/db/query_parser.cpp index 35fd77e3..d6691930 100644 --- a/YACReaderLibrary/db/query_parser.cpp +++ b/YACReaderLibrary/db/query_parser.cpp @@ -316,9 +316,9 @@ QueryParser::TreeNode QueryParser::expression() } auto right = token(true); - return TreeNode("expression", { TreeNode(toLower(left), {}), TreeNode(right, {}) }, expOperator); + return TreeNode("expression", { TreeNode(toLower(left), { }), TreeNode(right, { }) }, expOperator); } else { - return TreeNode("expression", { TreeNode("all", {}), TreeNode(left, {}) }); + return TreeNode("expression", { TreeNode("all", { }), TreeNode(left, { }) }); } } @@ -328,12 +328,12 @@ QueryParser::TreeNode QueryParser::expression() QueryParser::TreeNode QueryParser::baseToken() { if (tokenType() == Token::Type::quotedWord) { - return TreeNode("expression", { TreeNode("all", {}), TreeNode(token(true), {}) }); + return TreeNode("expression", { TreeNode("all", { }), TreeNode(token(true), { }) }); } if (tokenType() == Token::Type::word) { - return TreeNode("expression", { TreeNode("all", {}), TreeNode(token(true), {}) }); + return TreeNode("expression", { TreeNode("all", { }), TreeNode(token(true), { }) }); } - return TreeNode("expression", { TreeNode("all", {}), TreeNode(token(true), {}) }); + return TreeNode("expression", { TreeNode("all", { }), TreeNode(token(true), { }) }); } diff --git a/YACReaderLibrary/db/reading_list_item.cpp b/YACReaderLibrary/db/reading_list_item.cpp index 98e3bf57..0fd02f3d 100644 --- a/YACReaderLibrary/db/reading_list_item.cpp +++ b/YACReaderLibrary/db/reading_list_item.cpp @@ -153,7 +153,7 @@ QIcon ReadingListItem::getIcon() const else if (theme.sidebarIcons.useSystemFolderIcons) return QFileIconProvider().icon(QFileIconProvider::Folder); else - return theme.sidebarIcons.folderIcon; // sublist + return theme.navigationTree.folderIcon; // sublist } int ReadingListItem::childCount() const diff --git a/YACReaderLibrary/db/reading_list_model.cpp b/YACReaderLibrary/db/reading_list_model.cpp index 856fa87b..55aa626e 100644 --- a/YACReaderLibrary/db/reading_list_model.cpp +++ b/YACReaderLibrary/db/reading_list_model.cpp @@ -101,11 +101,11 @@ QVariant ReadingListModel::data(const QModelIndex &index, int role) const Qt::ItemFlags ReadingListModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return {}; + return { }; auto item = static_cast(index.internalPointer()); if (typeid(*item) == typeid(ReadingListSeparatorItem)) - return {}; + return { }; if (typeid(*item) == typeid(ReadingListItem) && static_cast(item)->parent->getId() != 0) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; // only sublists are dragable diff --git a/YACReaderLibrary/folder_content_view.cpp b/YACReaderLibrary/folder_content_view.cpp index 79d87fa3..28535922 100644 --- a/YACReaderLibrary/folder_content_view.cpp +++ b/YACReaderLibrary/folder_content_view.cpp @@ -263,39 +263,36 @@ void FolderContentView::droppedFiles(const QList &urls, Qt::DropAction act void FolderContentView::applyTheme(const Theme &theme) { QQmlContext *ctxt = view->rootContext(); - const auto &qv = theme.qmlView; + const auto &giv = theme.gridAndInfoView; toolbar->setStyleSheet(theme.comicsViewToolbar.toolbarQSS); // Continue reading section colors - ctxt->setContextProperty("continueReadingBackgroundColor", qv.continueReadingBackgroundColor); - ctxt->setContextProperty("continueReadingColor", qv.continueReadingColor); + ctxt->setContextProperty("continueReadingBackgroundColor", giv.continueReadingBackgroundColor); + ctxt->setContextProperty("continueReadingColor", giv.continueReadingColor); // Grid colors - ctxt->setContextProperty("backgroundColor", qv.backgroundColor); - ctxt->setContextProperty("cellColor", qv.cellColor); - ctxt->setContextProperty("selectedColor", qv.selectedColor); - ctxt->setContextProperty("selectedBorderColor", qv.selectedBorderColor); - ctxt->setContextProperty("borderColor", qv.borderColor); - ctxt->setContextProperty("titleColor", qv.titleColor); - ctxt->setContextProperty("textColor", qv.textColor); - ctxt->setContextProperty("dropShadow", QVariant(qv.showDropShadow)); + ctxt->setContextProperty("backgroundColor", giv.backgroundColor); + ctxt->setContextProperty("cellColor", giv.cellColor); + ctxt->setContextProperty("selectedColor", giv.selectedColor); + ctxt->setContextProperty("selectedBorderColor", giv.selectedBorderColor); + ctxt->setContextProperty("borderColor", giv.borderColor); + ctxt->setContextProperty("titleColor", giv.titleColor); + ctxt->setContextProperty("textColor", giv.textColor); + ctxt->setContextProperty("dropShadow", QVariant(giv.showDropShadow)); // Info panel colors - ctxt->setContextProperty("infoBackgroundColor", qv.infoBackgroundColor); - ctxt->setContextProperty("topShadow", qv.topShadow.isEmpty() ? QUrl() : QUrl(qv.topShadow)); - ctxt->setContextProperty("infoShadow", qv.infoShadow); - ctxt->setContextProperty("infoIndicator", qv.infoIndicator); - ctxt->setContextProperty("infoTextColor", qv.infoTextColor); - ctxt->setContextProperty("infoTitleColor", qv.infoTitleColor); + ctxt->setContextProperty("infoBackgroundColor", giv.infoBackgroundColor); + ctxt->setContextProperty("infoTextColor", giv.infoTextColor); + ctxt->setContextProperty("infoTitleColor", giv.infoTitleColor); // Rating and favorite colors - ctxt->setContextProperty("ratingUnselectedColor", qv.ratingUnselectedColor); - ctxt->setContextProperty("ratingSelectedColor", qv.ratingSelectedColor); - ctxt->setContextProperty("favUncheckedColor", qv.favUncheckedColor); - ctxt->setContextProperty("favCheckedColor", qv.favCheckedColor); - ctxt->setContextProperty("readTickUncheckedColor", qv.readTickUncheckedColor); - ctxt->setContextProperty("readTickCheckedColor", qv.readTickCheckedColor); + ctxt->setContextProperty("ratingUnselectedColor", giv.ratingUnselectedColor); + ctxt->setContextProperty("ratingSelectedColor", giv.ratingSelectedColor); + ctxt->setContextProperty("favUncheckedColor", giv.favUncheckedColor); + ctxt->setContextProperty("favCheckedColor", giv.favCheckedColor); + ctxt->setContextProperty("readTickUncheckedColor", giv.readTickUncheckedColor); + ctxt->setContextProperty("readTickCheckedColor", giv.readTickCheckedColor); // Update zoom slider icons if (smallZoomLabel) { diff --git a/YACReaderLibrary/grid_comics_view.cpp b/YACReaderLibrary/grid_comics_view.cpp index f30e4f90..2542f85c 100644 --- a/YACReaderLibrary/grid_comics_view.cpp +++ b/YACReaderLibrary/grid_comics_view.cpp @@ -219,9 +219,10 @@ void GridComicsView::updateBackgroundConfig() } // Use theme colors for cell and selected colors - const auto &qv = theme.qmlView; - ctxt->setContextProperty("cellColor", useBackgroundImage ? qv.cellColorWithBackground : qv.cellColor); - ctxt->setContextProperty("selectedColor", qv.selectedColor); + const auto &giv = theme.gridAndInfoView; + ctxt->setContextProperty("backgroundColor", useBackgroundImage ? giv.backgroundBlurOverlayColor : giv.backgroundColor); + ctxt->setContextProperty("cellColor", useBackgroundImage ? giv.cellColorWithBackground : giv.cellColor); + ctxt->setContextProperty("selectedColor", giv.selectedColor); } void GridComicsView::showInfo() @@ -487,36 +488,34 @@ void GridComicsView::selectedItem(int index) void GridComicsView::applyTheme(const Theme &theme) { QQmlContext *ctxt = view->rootContext(); - const auto &qv = theme.qmlView; + const auto &giv = theme.gridAndInfoView; // Grid colors - ctxt->setContextProperty("backgroundColor", qv.backgroundColor); - ctxt->setContextProperty("cellColor", qv.cellColor); - ctxt->setContextProperty("selectedColor", qv.selectedColor); - ctxt->setContextProperty("selectedBorderColor", qv.selectedBorderColor); - ctxt->setContextProperty("borderColor", qv.borderColor); - ctxt->setContextProperty("titleColor", qv.titleColor); - ctxt->setContextProperty("textColor", qv.textColor); - ctxt->setContextProperty("showDropShadow", QVariant(qv.showDropShadow)); + ctxt->setContextProperty("backgroundColor", giv.backgroundColor); + ctxt->setContextProperty("backgroundBlurOverlayColor", giv.backgroundBlurOverlayColor); + ctxt->setContextProperty("cellColor", giv.cellColor); + ctxt->setContextProperty("selectedColor", giv.selectedColor); + ctxt->setContextProperty("selectedBorderColor", giv.selectedBorderColor); + ctxt->setContextProperty("borderColor", giv.borderColor); + ctxt->setContextProperty("titleColor", giv.titleColor); + ctxt->setContextProperty("textColor", giv.textColor); + ctxt->setContextProperty("showDropShadow", QVariant(giv.showDropShadow)); // Info panel colors - ctxt->setContextProperty("infoBackgroundColor", qv.infoBackgroundColor); - ctxt->setContextProperty("topShadow", qv.topShadow.isEmpty() ? QUrl() : QUrl(qv.topShadow)); - ctxt->setContextProperty("infoShadow", qv.infoShadow); - ctxt->setContextProperty("infoIndicator", qv.infoIndicator); - ctxt->setContextProperty("infoTextColor", qv.infoTextColor); - ctxt->setContextProperty("infoTitleColor", qv.infoTitleColor); + ctxt->setContextProperty("infoBackgroundColor", giv.infoBackgroundColor); + ctxt->setContextProperty("infoTextColor", giv.infoTextColor); + ctxt->setContextProperty("infoTitleColor", giv.infoTitleColor); // Rating and favorite colors - ctxt->setContextProperty("ratingUnselectedColor", qv.ratingUnselectedColor); - ctxt->setContextProperty("ratingSelectedColor", qv.ratingSelectedColor); - ctxt->setContextProperty("favUncheckedColor", qv.favUncheckedColor); - ctxt->setContextProperty("favCheckedColor", qv.favCheckedColor); - ctxt->setContextProperty("readTickUncheckedColor", qv.readTickUncheckedColor); - ctxt->setContextProperty("readTickCheckedColor", qv.readTickCheckedColor); + ctxt->setContextProperty("ratingUnselectedColor", giv.ratingUnselectedColor); + ctxt->setContextProperty("ratingSelectedColor", giv.ratingSelectedColor); + ctxt->setContextProperty("favUncheckedColor", giv.favUncheckedColor); + ctxt->setContextProperty("favCheckedColor", giv.favCheckedColor); + ctxt->setContextProperty("readTickUncheckedColor", giv.readTickUncheckedColor); + ctxt->setContextProperty("readTickCheckedColor", giv.readTickCheckedColor); // Current comic banner - ctxt->setContextProperty("currentComicBackgroundColor", qv.currentComicBackgroundColor); + ctxt->setContextProperty("currentComicBackgroundColor", giv.currentComicBackgroundColor); // Update background config to apply theme cell colors updateBackgroundConfig(); diff --git a/YACReaderLibrary/images.qrc b/YACReaderLibrary/images.qrc index d0749b7f..788d45d9 100644 --- a/YACReaderLibrary/images.qrc +++ b/YACReaderLibrary/images.qrc @@ -36,8 +36,6 @@ ../images/empty_container/empty_reading_list.svg ../images/library_dialogs/exportComicsInfo.svg ../images/library_dialogs/exportLibrary.svg - ../images/f_overlayed.png - ../images/f_overlayed_retina.png ../images/find_folder.svg ../images/flow1.png ../images/flow2.png diff --git a/YACReaderLibrary/info_comics_view.cpp b/YACReaderLibrary/info_comics_view.cpp index 87e35a53..9aba46c9 100644 --- a/YACReaderLibrary/info_comics_view.cpp +++ b/YACReaderLibrary/info_comics_view.cpp @@ -224,23 +224,32 @@ void InfoComicsView::selectedItem(int index) void InfoComicsView::applyTheme(const Theme &theme) { QQmlContext *ctxt = view->rootContext(); - const auto &qv = theme.qmlView; + const auto &giv = theme.gridAndInfoView; // Info panel colors - ctxt->setContextProperty("infoBackgroundColor", qv.infoBackgroundColor); - ctxt->setContextProperty("topShadow", qv.topShadow.isEmpty() ? QUrl() : QUrl(qv.topShadow)); - ctxt->setContextProperty("infoShadow", qv.infoShadow); - ctxt->setContextProperty("infoIndicator", qv.infoIndicator); - ctxt->setContextProperty("infoTextColor", qv.infoTextColor); - ctxt->setContextProperty("infoTitleColor", qv.infoTitleColor); + // Cache-bust the SVG file URLs so QML's image cache doesn't serve stale + // files when the theme is updated (the same file path is rewritten each time). + const QString bust = QString::number(QDateTime::currentMSecsSinceEpoch()); + auto svgUrl = [&bust](const QString &path) { + QUrl url = QUrl::fromLocalFile(path); + url.setQuery(bust); + return url; + }; + ctxt->setContextProperty("infoBackgroundColor", giv.infoBackgroundColor); + ctxt->setContextProperty("topShadow", svgUrl(giv.topShadow)); + ctxt->setContextProperty("infoShadow", svgUrl(giv.infoShadow)); + ctxt->setContextProperty("infoIndicator", svgUrl(giv.infoIndicator)); + ctxt->setContextProperty("infoTextColor", giv.infoTextColor); + ctxt->setContextProperty("infoTitleColor", giv.infoTitleColor); // Rating and favorite colors - ctxt->setContextProperty("ratingUnselectedColor", qv.ratingUnselectedColor); - ctxt->setContextProperty("ratingSelectedColor", qv.ratingSelectedColor); - ctxt->setContextProperty("favUncheckedColor", qv.favUncheckedColor); - ctxt->setContextProperty("favCheckedColor", qv.favCheckedColor); - ctxt->setContextProperty("readTickUncheckedColor", qv.readTickUncheckedColor); - ctxt->setContextProperty("readTickCheckedColor", qv.readTickCheckedColor); + ctxt->setContextProperty("ratingUnselectedColor", giv.ratingUnselectedColor); + ctxt->setContextProperty("ratingSelectedColor", giv.ratingSelectedColor); + ctxt->setContextProperty("favUncheckedColor", giv.favUncheckedColor); + ctxt->setContextProperty("favCheckedColor", giv.favCheckedColor); + ctxt->setContextProperty("readTickUncheckedColor", giv.readTickUncheckedColor); + ctxt->setContextProperty("readTickCheckedColor", giv.readTickCheckedColor); - ctxt->setContextProperty("showDropShadow", QVariant(qv.showDropShadow)); + ctxt->setContextProperty("showDropShadow", QVariant(giv.showDropShadow)); + ctxt->setContextProperty("backgroundBlurOverlayColor", giv.backgroundBlurOverlayColor); } diff --git a/YACReaderLibrary/main.cpp b/YACReaderLibrary/main.cpp index 5507e25e..4d82ea86 100644 --- a/YACReaderLibrary/main.cpp +++ b/YACReaderLibrary/main.cpp @@ -21,7 +21,9 @@ #include "db_helper.h" #include "yacreader_libraries.h" #include "exit_check.h" +#include "appearance_configuration.h" #include "theme_manager.h" +#include "theme_repository.h" #ifdef Q_OS_MACOS #include "trayhandler.h" #endif @@ -140,7 +142,11 @@ int main(int argc, char **argv) app.setApplicationVersion(VERSION); // Theme initialization - ThemeManager::instance().initialize(); + auto *appearanceConfig = new AppearanceConfiguration( + YACReader::getSettingsPath() + "/YACReaderLibrary.ini", qApp); + auto *themeRepo = new ThemeRepository( + ":/themes", YACReader::getSettingsPath() + "/themes/user"); + ThemeManager::instance().initialize(appearanceConfig, themeRepo); // Set window icon according to Freedesktop icon specification // This is mostly relevant for Linux and other Unix systems diff --git a/YACReaderLibrary/options_dialog.cpp b/YACReaderLibrary/options_dialog.cpp index 53210ac8..f2cdb4bf 100644 --- a/YACReaderLibrary/options_dialog.cpp +++ b/YACReaderLibrary/options_dialog.cpp @@ -4,6 +4,11 @@ #include "api_key_dialog.h" #include "yacreader_global_gui.h" +#include "theme_manager.h" +#include "theme_factory.h" +#include "appearance_tab_widget.h" + +#include FlowType flowType = Strip; @@ -15,11 +20,14 @@ OptionsDialog::OptionsDialog(QWidget *parent) auto comicFlowW = createFlowTab(); auto gridViewW = createGridTab(); + auto appearanceW = createAppearanceTab(); + auto tabWidget = new QTabWidget(); tabWidget->addTab(generalW, tr("General")); tabWidget->addTab(librariesW, tr("Libraries")); tabWidget->addTab(comicFlowW, tr("Comic Flow")); tabWidget->addTab(gridViewW, tr("Grid view")); + tabWidget->addTab(appearanceW, tr("Appearance")); auto buttons = new QHBoxLayout(); buttons->addStretch(); @@ -406,3 +414,13 @@ QWidget *OptionsDialog::createGridTab() return gridViewW; } + +QWidget *OptionsDialog::createAppearanceTab() +{ + return new AppearanceTabWidget( + ThemeManager::instance().getAppearanceConfiguration(), + ThemeManager::instance().getRepository(), + []() { return ThemeManager::instance().getCurrentTheme().sourceJson; }, + [](const QJsonObject &json) { ThemeManager::instance().setTheme(makeTheme(json)); }, + this); +} diff --git a/YACReaderLibrary/options_dialog.h b/YACReaderLibrary/options_dialog.h index ccf8a2ff..84caae53 100644 --- a/YACReaderLibrary/options_dialog.h +++ b/YACReaderLibrary/options_dialog.h @@ -61,6 +61,7 @@ private: QWidget *createLibrariesTab(); QWidget *createFlowTab(); QWidget *createGridTab(); + QWidget *createAppearanceTab(); }; #endif diff --git a/YACReaderLibrary/qml.qrc b/YACReaderLibrary/qml.qrc index d216b4f8..2d767d06 100644 --- a/YACReaderLibrary/qml.qrc +++ b/YACReaderLibrary/qml.qrc @@ -11,13 +11,9 @@ qml/reading.svg qml/star.svg qml/page.svg - qml/info-indicator.png - qml/info-shadow.png - qml/info-indicator-light.png - qml/info-shadow-light.png - qml/info-indicator-light@2x.png - qml/info-shadow-light@2x.png - qml/info-top-shadow.png + qml/info-indicator.svg + qml/info-shadow.svg + qml/info-top-shadow.svg qml/ComicInfoView.qml qml/info-favorites.svg qml/info-rating.svg diff --git a/YACReaderLibrary/qml/FlowView.qml b/YACReaderLibrary/qml/FlowView.qml index 9ece421a..6ebc35a7 100644 --- a/YACReaderLibrary/qml/FlowView.qml +++ b/YACReaderLibrary/qml/FlowView.qml @@ -25,7 +25,7 @@ Rectangle { Rectangle { id: background - color: "#2A2A2A" + color: backgroundBlurOverlayColor anchors.fill: backgroundImg } @@ -48,7 +48,7 @@ Rectangle { source: backgroundImg blurEnabled: true blur: 1.0 - blurMax: 64 + blurMax: Math.max(2, mainFlowContainer.backgroundBlurRadius) opacity: backgroundBlurOpacity visible: backgroundBlurVisible } diff --git a/YACReaderLibrary/qml/GridComicsView.qml b/YACReaderLibrary/qml/GridComicsView.qml index 1c5edc01..95ef165a 100644 --- a/YACReaderLibrary/qml/GridComicsView.qml +++ b/YACReaderLibrary/qml/GridComicsView.qml @@ -43,7 +43,7 @@ SplitView { source: backgroundImg blurEnabled: true blur: 1.0 - blurMax: 64 + blurMax: Math.max(2, backgroundBlurRadius) opacity: backgroundBlurOpacity visible: backgroundBlurVisible } diff --git a/YACReaderLibrary/qml/info-indicator-light.png b/YACReaderLibrary/qml/info-indicator-light.png deleted file mode 100644 index b08a8ead..00000000 Binary files a/YACReaderLibrary/qml/info-indicator-light.png and /dev/null differ diff --git a/YACReaderLibrary/qml/info-indicator-light@2x.png b/YACReaderLibrary/qml/info-indicator-light@2x.png deleted file mode 100644 index 38abb8c8..00000000 Binary files a/YACReaderLibrary/qml/info-indicator-light@2x.png and /dev/null differ diff --git a/YACReaderLibrary/qml/info-indicator.png b/YACReaderLibrary/qml/info-indicator.png deleted file mode 100644 index af4a616a..00000000 Binary files a/YACReaderLibrary/qml/info-indicator.png and /dev/null differ diff --git a/YACReaderLibrary/qml/info-indicator.svg b/YACReaderLibrary/qml/info-indicator.svg new file mode 100644 index 00000000..84d28a33 --- /dev/null +++ b/YACReaderLibrary/qml/info-indicator.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YACReaderLibrary/qml/info-shadow-light.png b/YACReaderLibrary/qml/info-shadow-light.png deleted file mode 100644 index e52f2c7a..00000000 Binary files a/YACReaderLibrary/qml/info-shadow-light.png and /dev/null differ diff --git a/YACReaderLibrary/qml/info-shadow-light@2x.png b/YACReaderLibrary/qml/info-shadow-light@2x.png deleted file mode 100644 index 3abb75af..00000000 Binary files a/YACReaderLibrary/qml/info-shadow-light@2x.png and /dev/null differ diff --git a/YACReaderLibrary/qml/info-shadow.png b/YACReaderLibrary/qml/info-shadow.png deleted file mode 100644 index 3508da2e..00000000 Binary files a/YACReaderLibrary/qml/info-shadow.png and /dev/null differ diff --git a/YACReaderLibrary/qml/info-shadow.svg b/YACReaderLibrary/qml/info-shadow.svg new file mode 100644 index 00000000..67957428 --- /dev/null +++ b/YACReaderLibrary/qml/info-shadow.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YACReaderLibrary/qml/info-top-shadow.png b/YACReaderLibrary/qml/info-top-shadow.png deleted file mode 100644 index 1fd60281..00000000 Binary files a/YACReaderLibrary/qml/info-top-shadow.png and /dev/null differ diff --git a/YACReaderLibrary/qml/info-top-shadow.svg b/YACReaderLibrary/qml/info-top-shadow.svg new file mode 100644 index 00000000..f8092b2a --- /dev/null +++ b/YACReaderLibrary/qml/info-top-shadow.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YACReaderLibrary/themes/builtin_classic.json b/YACReaderLibrary/themes/builtin_classic.json new file mode 100644 index 00000000..ea571a11 --- /dev/null +++ b/YACReaderLibrary/themes/builtin_classic.json @@ -0,0 +1,237 @@ +{ + "comicFlow": { + "backgroundColor": "#000000", + "textColor": "#4c4c4c" + }, + "comicsViewTable": { + "alternateBackgroundColor": "#f2f2f2", + "backgroundColor": "#fafafa", + "headerBackgroundColor": "#f5f5f5", + "headerBorderColor": "#b8bdc4", + "headerGradientColor": "#d1d1d1", + "headerTextColor": "#313232", + "itemBorderBottomColor": "#dfdfdf", + "itemBorderBottomWidth": 1, + "itemBorderTopColor": "#fefefe", + "itemBorderTopWidth": 1, + "itemTextColor": "#252626", + "selectedColor": "#d4d4d4", + "selectedTextColor": "#252626", + "starRatingColor": "#e9be0f", + "starRatingSelectedColor": "#ffffff" + }, + "comicsViewToolbar": { + "backgroundColor": "#f0f0f0", + "checkedBackgroundColor": "#cccccc", + "iconColor": "#404040", + "separatorColor": "#cccccc" + }, + "contentSplitter": { + "handleColor": "#b8b8b8", + "horizontalHandleHeight": 4, + "verticalHandleWidth": 4 + }, + "dialogIcons": { + "iconColor": "#f7f7f7" + }, + "emptyContainer": { + "backgroundColor": "#2a2a2a", + "descriptionTextColor": "#aaaaaa", + "searchIconColor": "#4c4c4c", + "textColor": "#cccccc", + "titleTextColor": "#cccccc" + }, + "gridAndInfoView": { + "backgroundBlurOverlayColor": "#2a2a2a", + "backgroundColor": "#2a2a2a", + "borderColor": "#121212", + "cellColor": "#212121", + "cellColorWithBackground": "#99212121", + "continueReadingBackgroundColor": "#88000000", + "continueReadingColor": "#ffffff", + "currentComicBackgroundColor": "#88000000", + "favCheckedColor": "#e84852", + "favUncheckedColor": "#1c1c1c", + "infoBackgroundColor": "#2e2e2e", + "infoBorderColor": "#404040", + "infoShadowColor": "#000000", + "infoTextColor": "#b0b0b0", + "infoTitleColor": "#ffffff", + "ratingSelectedColor": "#ffffff", + "ratingUnselectedColor": "#1c1c1c", + "readTickCheckedColor": "#e84852", + "readTickUncheckedColor": "#1c1c1c", + "selectedBorderColor": "#ffcc00", + "selectedColor": "#121212", + "showDropShadow": true, + "textColor": "#a8a8a8", + "titleColor": "#ffffff" + }, + "helpAboutDialog": { + "headingColor": "#302f2d", + "linkColor": "#c19441" + }, + "importWidget": { + "backgroundColor": "#2a2a2a", + "coversDecorationBgColor": "#3a3a3a", + "coversDecorationShadowColor": "#1a1a1a", + "coversLabelColor": "#aaaaaa", + "coversViewBackgroundColor": "#3a3a3a", + "currentComicTextColor": "#aaaaaa", + "descriptionTextColor": "#aaaaaa", + "iconCheckedColor": "#aaaaaa", + "iconColor": "#cccccc", + "modeIconColor": "#4a4a4a", + "titleTextColor": "#cccccc" + }, + "libraryItem": { + "libraryIconColor": "#dddfdf", + "libraryIconSelectedColor": "#ffffff", + "libraryIconShadowColor": "#000000", + "libraryOptionsIconColor": "#ffffff", + "selectedBackgroundColor": "#2e2e2e", + "selectedTextColor": "#ffffff", + "textColor": "#dddfdf" + }, + "mainToolbar": { + "backgroundColor": "#f0f0f0", + "dividerColor": "#b8bdc4", + "folderNameColor": "#404040", + "iconColor": "#404040", + "iconDisabledColor": "#b0b0b0" + }, + "menuIcons": { + "iconColor": "#f7f7f7" + }, + "meta": { + "displayName": "Default Classic", + "id": "builtin/classic", + "variant": "dark" + }, + "metadataScraperDialog": { + "busyIndicatorColor": "#ffffff", + "buttonBackgroundColor": "#2e2e2e", + "buttonBorderColor": "#242424", + "buttonTextColor": "#ffffff", + "checkBoxTickColor": "#ffffff", + "contentAltBackgroundColor": "#2b2b2b", + "contentBackgroundColor": "#2b2b2b", + "contentTextColor": "#ffffff", + "dialogBackgroundColor": "#404040", + "downArrowColor": "#9f9f9f", + "hyperlinkColor": "#ffcc00", + "labelBackgroundColor": "#2b2b2b", + "labelTextColor": "#ffffff", + "navIconColor": "#ffffff", + "radioCheckedBackgroundColor": "#e5e5e5", + "radioCheckedIndicatorColor": "#5f5f5f", + "radioUncheckedColor": "#e5e5e5", + "rowIconColor": "#e5e5e5", + "tableAltBackgroundColor": "#2e2e2e", + "tableBackgroundColor": "#2b2b2b", + "tableBorderColor": "#242424", + "tableHeaderBackgroundColor": "#292929", + "tableHeaderBorderColor": "#1f1f1f", + "tableHeaderGradientColor": "#292929", + "tableHeaderTextColor": "#ebebeb", + "tableScrollBackgroundColor": "#404040", + "tableScrollHandleColor": "#dddddd", + "tableSectionBorderDark": "#dfdfdf", + "tableSectionBorderLight": "#fefefe", + "tableSelectedColor": "#555555", + "toolButtonAccentColor": "#282828", + "upArrowColor": "#9f9f9f" + }, + "navigationTree": { + "branchIndicatorColor": "#e0e0e0", + "branchIndicatorSelectedColor": "#ffffff", + "folderIconColor": "#e0e0e0", + "folderIconSelectedColor": "#e0e0e0", + "folderIconSelectedShadowColor": "#000000", + "folderIconShadowColor": "#000000", + "folderIndicatorColor": "#edc518", + "folderReadOverlayColor": "#464646", + "folderReadOverlaySelectedColor": "#464646", + "scrollBackgroundColor": "#404040", + "scrollHandleColor": "#dddddd", + "selectedTextColor": "#ffffff", + "selectionBackgroundColor": "#2e2e2e", + "textColor": "#dddfdf" + }, + "readingListIcons": { + "currentlyReadingMainColor": "#ffcc00", + "currentlyReadingMainSelectedColor": "#ffcc00", + "currentlyReadingOuterColor": "#000000", + "currentlyReadingOuterSelectedColor": "#000000", + "favoritesMainColor": "#e15055", + "favoritesMainSelectedColor": "#e15055", + "labelColors": { + "blue": "#82c7ff", + "cyan": "#a0fddb", + "dark": "#b7b7b7", + "green": "#ade738", + "light": "#cbcbcb", + "orange": "#f5c240", + "pink": "#fd9cda", + "purple": "#d692fc", + "red": "#f67a7b", + "violet": "#8f95ff", + "white": "#fcfcfc", + "yellow": "#f2e446" + }, + "labelShadowColor": "#000000", + "labelShadowSelectedColor": "#000000", + "listDetailColor": "#464646", + "listDetailSelectedColor": "#464646", + "listMainColor": "#e7e7e7", + "listMainSelectedColor": "#e7e7e7", + "listShadowColor": "#000000", + "listShadowSelectedColor": "#000000", + "readingListMainColor": "#e7e7e7", + "readingListMainSelectedColor": "#e7e7e7", + "specialListShadowColor": "#000000", + "specialListShadowSelectedColor": "#000000" + }, + "searchLineEdit": { + "backgroundColor": "#404040", + "iconColor": "#f7f7f7", + "textColor": "#ababab" + }, + "serverConfigDialog": { + "backgroundColor": "#2a2a2a", + "checkBoxTextColor": "#262626", + "decorationColor": "#f7f7f7", + "labelTextColor": "#575757", + "propagandaTextColor": "#4d4d4d", + "qrBackgroundColor": "#2a2a2a", + "qrForegroundColor": "#ffffff", + "qrMessageTextColor": "#a3a3a3", + "titleTextColor": "#474747" + }, + "shortcutsIcons": { + "iconColor": "#f7f7f7" + }, + "sidebar": { + "backgroundColor": "#454545", + "busyIndicatorColor": "#ffffff", + "sectionSeparatorColor": "#575757", + "separatorColor": "#bdbfbf", + "titleDropShadowColor": "#000000", + "titleTextColor": "#bdbfbf", + "uppercaseLabels": true + }, + "sidebarIcons": { + "iconColor": "#e0e0e0", + "shadowColor": "#000000", + "useSystemFolderIcons": false + }, + "whatsNewDialog": { + "backgroundColor": "#2a2a2a", + "closeButtonColor": "#dddddd", + "contentTextColor": "#e0e0e0", + "headerDecorationColor": "#e8b800", + "headerTextColor": "#e0e0e0", + "linkColor": "#e8b800", + "versionTextColor": "#858585" + } +} diff --git a/YACReaderLibrary/themes/builtin_dark.json b/YACReaderLibrary/themes/builtin_dark.json new file mode 100644 index 00000000..6a9a7a28 --- /dev/null +++ b/YACReaderLibrary/themes/builtin_dark.json @@ -0,0 +1,237 @@ +{ + "comicFlow": { + "backgroundColor": "#111111", + "textColor": "#888888" + }, + "comicsViewTable": { + "alternateBackgroundColor": "#2c2c2c", + "backgroundColor": "#232323", + "headerBackgroundColor": "#2a2a2a", + "headerBorderColor": "#1f1f1f", + "headerGradientColor": "#252525", + "headerTextColor": "#dddddd", + "itemBorderBottomColor": "#1f1f1f", + "itemBorderBottomWidth": 0, + "itemBorderTopColor": "#353535", + "itemBorderTopWidth": 0, + "itemTextColor": "#dddddd", + "selectedColor": "#555555", + "selectedTextColor": "#ffffff", + "starRatingColor": "#e9be0f", + "starRatingSelectedColor": "#ffffff" + }, + "comicsViewToolbar": { + "backgroundColor": "#1f1f1f", + "checkedBackgroundColor": "#555555", + "iconColor": "#dddddd", + "separatorColor": "#444444" + }, + "contentSplitter": { + "handleColor": "#1f1f1f", + "horizontalHandleHeight": 4, + "verticalHandleWidth": 4 + }, + "dialogIcons": { + "iconColor": "#f7f7f7" + }, + "emptyContainer": { + "backgroundColor": "#2a2a2a", + "descriptionTextColor": "#aaaaaa", + "searchIconColor": "#4c4c4c", + "textColor": "#cccccc", + "titleTextColor": "#cccccc" + }, + "gridAndInfoView": { + "backgroundBlurOverlayColor": "#2a2a2a", + "backgroundColor": "#2a2a2a", + "borderColor": "#121212", + "cellColor": "#212121", + "cellColorWithBackground": "#99212121", + "continueReadingBackgroundColor": "#88000000", + "continueReadingColor": "#ffffff", + "currentComicBackgroundColor": "#55000000", + "favCheckedColor": "#e84852", + "favUncheckedColor": "#1c1c1c", + "infoBackgroundColor": "#2e2e2e", + "infoBorderColor": "#404040", + "infoShadowColor": "#000000", + "infoTextColor": "#b0b0b0", + "infoTitleColor": "#ffffff", + "ratingSelectedColor": "#ffffff", + "ratingUnselectedColor": "#1c1c1c", + "readTickCheckedColor": "#e84852", + "readTickUncheckedColor": "#1c1c1c", + "selectedBorderColor": "#ffcc00", + "selectedColor": "#121212", + "showDropShadow": true, + "textColor": "#a8a8a8", + "titleColor": "#ffffff" + }, + "helpAboutDialog": { + "headingColor": "#e0e0e0", + "linkColor": "#d4a84b" + }, + "importWidget": { + "backgroundColor": "#2a2a2a", + "coversDecorationBgColor": "#3a3a3a", + "coversDecorationShadowColor": "#1a1a1a", + "coversLabelColor": "#aaaaaa", + "coversViewBackgroundColor": "#3a3a3a", + "currentComicTextColor": "#aaaaaa", + "descriptionTextColor": "#aaaaaa", + "iconCheckedColor": "#aaaaaa", + "iconColor": "#cccccc", + "modeIconColor": "#4a4a4a", + "titleTextColor": "#cccccc" + }, + "libraryItem": { + "libraryIconColor": "#dddfdf", + "libraryIconSelectedColor": "#ffffff", + "libraryIconShadowColor": "#000000", + "libraryOptionsIconColor": "#ffffff", + "selectedBackgroundColor": "#2e2e2e", + "selectedTextColor": "#ffffff", + "textColor": "#dddfdf" + }, + "mainToolbar": { + "backgroundColor": "#1f1f1f", + "dividerColor": "#555555", + "folderNameColor": "#dddddd", + "iconColor": "#dddddd", + "iconDisabledColor": "#666666" + }, + "menuIcons": { + "iconColor": "#f7f7f7" + }, + "meta": { + "displayName": "Default Dark", + "id": "builtin/dark", + "variant": "dark" + }, + "metadataScraperDialog": { + "busyIndicatorColor": "#ffffff", + "buttonBackgroundColor": "#2e2e2e", + "buttonBorderColor": "#242424", + "buttonTextColor": "#ffffff", + "checkBoxTickColor": "#ffffff", + "contentAltBackgroundColor": "#2e2e2e", + "contentBackgroundColor": "#2b2b2b", + "contentTextColor": "#ffffff", + "dialogBackgroundColor": "#404040", + "downArrowColor": "#9f9f9f", + "hyperlinkColor": "#ffcc00", + "labelBackgroundColor": "#2b2b2b", + "labelTextColor": "#ffffff", + "navIconColor": "#ffffff", + "radioCheckedBackgroundColor": "#e5e5e5", + "radioCheckedIndicatorColor": "#5f5f5f", + "radioUncheckedColor": "#e5e5e5", + "rowIconColor": "#e5e5e5", + "tableAltBackgroundColor": "#2e2e2e", + "tableBackgroundColor": "#2b2b2b", + "tableBorderColor": "#242424", + "tableHeaderBackgroundColor": "#292929", + "tableHeaderBorderColor": "#1f1f1f", + "tableHeaderGradientColor": "#292929", + "tableHeaderTextColor": "#ebebeb", + "tableScrollBackgroundColor": "#404040", + "tableScrollHandleColor": "#dddddd", + "tableSectionBorderDark": "#dfdfdf", + "tableSectionBorderLight": "#fefefe", + "tableSelectedColor": "#555555", + "toolButtonAccentColor": "#282828", + "upArrowColor": "#9f9f9f" + }, + "navigationTree": { + "branchIndicatorColor": "#e0e0e0", + "branchIndicatorSelectedColor": "#ffffff", + "folderIconColor": "#e0e0e0", + "folderIconSelectedColor": "#e0e0e0", + "folderIconSelectedShadowColor": "#000000", + "folderIconShadowColor": "#000000", + "folderIndicatorColor": "#edc518", + "folderReadOverlayColor": "#222222", + "folderReadOverlaySelectedColor": "#222222", + "scrollBackgroundColor": "#404040", + "scrollHandleColor": "#dddddd", + "selectedTextColor": "#ffffff", + "selectionBackgroundColor": "#2e2e2e", + "textColor": "#dddfdf" + }, + "readingListIcons": { + "currentlyReadingMainColor": "#ffcc00", + "currentlyReadingMainSelectedColor": "#ffcc00", + "currentlyReadingOuterColor": "#000000", + "currentlyReadingOuterSelectedColor": "#000000", + "favoritesMainColor": "#e15055", + "favoritesMainSelectedColor": "#e15055", + "labelColors": { + "blue": "#82c7ff", + "cyan": "#a0fddb", + "dark": "#b7b7b7", + "green": "#ade738", + "light": "#cbcbcb", + "orange": "#f5c240", + "pink": "#fd9cda", + "purple": "#d692fc", + "red": "#f67a7b", + "violet": "#8f95ff", + "white": "#fcfcfc", + "yellow": "#f2e446" + }, + "labelShadowColor": "#000000", + "labelShadowSelectedColor": "#000000", + "listDetailColor": "#464646", + "listDetailSelectedColor": "#464646", + "listMainColor": "#e7e7e7", + "listMainSelectedColor": "#e7e7e7", + "listShadowColor": "#000000", + "listShadowSelectedColor": "#000000", + "readingListMainColor": "#e7e7e7", + "readingListMainSelectedColor": "#e7e7e7", + "specialListShadowColor": "#000000", + "specialListShadowSelectedColor": "#000000" + }, + "searchLineEdit": { + "backgroundColor": "#404040", + "iconColor": "#f7f7f7", + "textColor": "#ababab" + }, + "serverConfigDialog": { + "backgroundColor": "#2a2a2a", + "checkBoxTextColor": "#dddddd", + "decorationColor": "#f7f7f7", + "labelTextColor": "#c0c0c0", + "propagandaTextColor": "#b0b0b0", + "qrBackgroundColor": "#2a2a2a", + "qrForegroundColor": "#ffffff", + "qrMessageTextColor": "#a3a3a3", + "titleTextColor": "#d0d0d0" + }, + "shortcutsIcons": { + "iconColor": "#f7f7f7" + }, + "sidebar": { + "backgroundColor": "#454545", + "busyIndicatorColor": "#ffffff", + "sectionSeparatorColor": "#575757", + "separatorColor": "#bdbfbf", + "titleDropShadowColor": "#000000", + "titleTextColor": "#bdbfbf", + "uppercaseLabels": true + }, + "sidebarIcons": { + "iconColor": "#e0e0e0", + "shadowColor": "#000000", + "useSystemFolderIcons": false + }, + "whatsNewDialog": { + "backgroundColor": "#2a2a2a", + "closeButtonColor": "#dddddd", + "contentTextColor": "#e0e0e0", + "headerDecorationColor": "#e8b800", + "headerTextColor": "#e0e0e0", + "linkColor": "#e8b800", + "versionTextColor": "#858585" + } +} diff --git a/YACReaderLibrary/themes/builtin_light.json b/YACReaderLibrary/themes/builtin_light.json new file mode 100644 index 00000000..9d514d4b --- /dev/null +++ b/YACReaderLibrary/themes/builtin_light.json @@ -0,0 +1,237 @@ +{ + "comicFlow": { + "backgroundColor": "#dcdcdc", + "textColor": "#303030" + }, + "comicsViewTable": { + "alternateBackgroundColor": "#f2f2f2", + "backgroundColor": "#fafafa", + "headerBackgroundColor": "#f5f5f5", + "headerBorderColor": "#b8bdc4", + "headerGradientColor": "#f5f5f5", + "headerTextColor": "#313232", + "itemBorderBottomColor": "#dfdfdf", + "itemBorderBottomWidth": 0, + "itemBorderTopColor": "#fefefe", + "itemBorderTopWidth": 0, + "itemTextColor": "#252626", + "selectedColor": "#595959", + "selectedTextColor": "#ffffff", + "starRatingColor": "#e9be0f", + "starRatingSelectedColor": "#ffffff" + }, + "comicsViewToolbar": { + "backgroundColor": "#f0f0f0", + "checkedBackgroundColor": "#cccccc", + "iconColor": "#404040", + "separatorColor": "#cccccc" + }, + "contentSplitter": { + "handleColor": "#f0f0f0", + "horizontalHandleHeight": 4, + "verticalHandleWidth": 4 + }, + "dialogIcons": { + "iconColor": "#606060" + }, + "emptyContainer": { + "backgroundColor": "#ffffff", + "descriptionTextColor": "#565959", + "searchIconColor": "#cccccc", + "textColor": "#495252", + "titleTextColor": "#888888" + }, + "gridAndInfoView": { + "backgroundBlurOverlayColor": "#9e9e9e", + "backgroundColor": "#f6f6f6", + "borderColor": "#dbdbdb", + "cellColor": "#ffffff", + "cellColorWithBackground": "#99ffffff", + "continueReadingBackgroundColor": "#e8e8e8", + "continueReadingColor": "#000000", + "currentComicBackgroundColor": "#88ffffff", + "favCheckedColor": "#e84852", + "favUncheckedColor": "#dedede", + "infoBackgroundColor": "#ffffff", + "infoBorderColor": "#808080", + "infoShadowColor": "#444444", + "infoTextColor": "#404040", + "infoTitleColor": "#2e2e2e", + "ratingSelectedColor": "#2b2b2b", + "ratingUnselectedColor": "#dedede", + "readTickCheckedColor": "#e84852", + "readTickUncheckedColor": "#dedede", + "selectedBorderColor": "#ffcc00", + "selectedColor": "#ffffff", + "showDropShadow": true, + "textColor": "#636363", + "titleColor": "#121212" + }, + "helpAboutDialog": { + "headingColor": "#302f2d", + "linkColor": "#c19441" + }, + "importWidget": { + "backgroundColor": "#fafafa", + "coversDecorationBgColor": "#e6e6e6", + "coversDecorationShadowColor": "#a1a1a1", + "coversLabelColor": "#565959", + "coversViewBackgroundColor": "#e6e6e6", + "currentComicTextColor": "#565959", + "descriptionTextColor": "#565959", + "iconCheckedColor": "#565959", + "iconColor": "#495252", + "modeIconColor": "#e6e6e6", + "titleTextColor": "#495252" + }, + "libraryItem": { + "libraryIconColor": "#606060", + "libraryIconSelectedColor": "#ffffff", + "libraryIconShadowColor": "#ffffff", + "libraryOptionsIconColor": "#ffffff", + "selectedBackgroundColor": "#333133", + "selectedTextColor": "#ffffff", + "textColor": "#000000" + }, + "mainToolbar": { + "backgroundColor": "#f0f0f0", + "dividerColor": "#b8bdc4", + "folderNameColor": "#333133", + "iconColor": "#333133", + "iconDisabledColor": "#b0b0b0" + }, + "menuIcons": { + "iconColor": "#606060" + }, + "meta": { + "displayName": "Default Light", + "id": "builtin/light", + "variant": "light" + }, + "metadataScraperDialog": { + "busyIndicatorColor": "#000000", + "buttonBackgroundColor": "#e0e0e0", + "buttonBorderColor": "#cccccc", + "buttonTextColor": "#000000", + "checkBoxTickColor": "#000000", + "contentAltBackgroundColor": "#e0e0e0", + "contentBackgroundColor": "#ececec", + "contentTextColor": "#000000", + "dialogBackgroundColor": "#fbfbfb", + "downArrowColor": "#222222", + "hyperlinkColor": "#ffcc00", + "labelBackgroundColor": "#ececec", + "labelTextColor": "#000000", + "navIconColor": "#222222", + "radioCheckedBackgroundColor": "#e0e0e0", + "radioCheckedIndicatorColor": "#222222", + "radioUncheckedColor": "#e0e0e0", + "rowIconColor": "#222222", + "tableAltBackgroundColor": "#fafafa", + "tableBackgroundColor": "#f4f4f4", + "tableBorderColor": "#cccccc", + "tableHeaderBackgroundColor": "#e0e0e0", + "tableHeaderBorderColor": "#c0c0c0", + "tableHeaderGradientColor": "#e0e0e0", + "tableHeaderTextColor": "#333333", + "tableScrollBackgroundColor": "#d0d0d0", + "tableScrollHandleColor": "#888888", + "tableSectionBorderDark": "#cccccc", + "tableSectionBorderLight": "#ffffff", + "tableSelectedColor": "#dddddd", + "toolButtonAccentColor": "#a0a0a0", + "upArrowColor": "#222222" + }, + "navigationTree": { + "branchIndicatorColor": "#606060", + "branchIndicatorSelectedColor": "#ffffff", + "folderIconColor": "#606060", + "folderIconSelectedColor": "#ffffff", + "folderIconSelectedShadowColor": "#161616", + "folderIconShadowColor": "#ffffff", + "folderIndicatorColor": "#555f7f", + "folderReadOverlayColor": "#ffffff", + "folderReadOverlaySelectedColor": "#161616", + "scrollBackgroundColor": "#e0e0e0", + "scrollHandleColor": "#888888", + "selectedTextColor": "#ffffff", + "selectionBackgroundColor": "#333133", + "textColor": "#000000" + }, + "readingListIcons": { + "currentlyReadingMainColor": "#ffcc00", + "currentlyReadingMainSelectedColor": "#ffcc00", + "currentlyReadingOuterColor": "#000000", + "currentlyReadingOuterSelectedColor": "#000000", + "favoritesMainColor": "#e15055", + "favoritesMainSelectedColor": "#e15055", + "labelColors": { + "blue": "#82c7ff", + "cyan": "#a0fddb", + "dark": "#b7b7b7", + "green": "#ade738", + "light": "#cbcbcb", + "orange": "#f5c240", + "pink": "#fd9cda", + "purple": "#d692fc", + "red": "#f67a7b", + "violet": "#8f95ff", + "white": "#fcfcfc", + "yellow": "#f2e446" + }, + "labelShadowColor": "#8f8f8f", + "labelShadowSelectedColor": "#161616", + "listDetailColor": "#ffffff", + "listDetailSelectedColor": "#161616", + "listMainColor": "#808080", + "listMainSelectedColor": "#ffffff", + "listShadowColor": "#8f8f8f", + "listShadowSelectedColor": "#161616", + "readingListMainColor": "#808080", + "readingListMainSelectedColor": "#808080", + "specialListShadowColor": "#8f8f8f", + "specialListShadowSelectedColor": "#161616" + }, + "searchLineEdit": { + "backgroundColor": "#333133", + "iconColor": "#efefef", + "textColor": "#ffffff" + }, + "serverConfigDialog": { + "backgroundColor": "#ffffff", + "checkBoxTextColor": "#262626", + "decorationColor": "#606060", + "labelTextColor": "#575757", + "propagandaTextColor": "#4d4d4d", + "qrBackgroundColor": "#ffffff", + "qrForegroundColor": "#606060", + "qrMessageTextColor": "#a3a3a3", + "titleTextColor": "#474747" + }, + "shortcutsIcons": { + "iconColor": "#606060" + }, + "sidebar": { + "backgroundColor": "#fbfbfb", + "busyIndicatorColor": "#808080", + "sectionSeparatorColor": "#e0e0e0", + "separatorColor": "#808080", + "titleDropShadowColor": "#ffffff", + "titleTextColor": "#4a494a", + "uppercaseLabels": true + }, + "sidebarIcons": { + "iconColor": "#4f4e4f", + "shadowColor": "#fbfbfb", + "useSystemFolderIcons": false + }, + "whatsNewDialog": { + "backgroundColor": "#ffffff", + "closeButtonColor": "#444444", + "contentTextColor": "#0a0a0a", + "headerDecorationColor": "#e8b800", + "headerTextColor": "#0a0a0a", + "linkColor": "#e8b800", + "versionTextColor": "#858585" + } +} diff --git a/YACReaderLibrary/themes/theme.h b/YACReaderLibrary/themes/theme.h index bf3b7a1d..76764508 100644 --- a/YACReaderLibrary/themes/theme.h +++ b/YACReaderLibrary/themes/theme.h @@ -2,12 +2,14 @@ #define THEME_H #include +#include #include "yacreader_icon.h" #include "help_about_dialog_theme.h" #include "whats_new_dialog_theme.h" +#include "theme_meta.h" -struct ComicVineThemeTemplates { +struct MetadataScraperDialogThemeTemplates { QString defaultLabelQSS = "QLabel {color:%1; font-size:12px;font-family:Arial;}"; QString titleLabelQSS = "QLabel {color:%1; font-size:18px;font-family:Arial;}"; QString coverLabelQSS = "QLabel {background-color: %1; color:%2; font-size:12px; font-family:Arial; }"; @@ -63,7 +65,7 @@ struct ComicVineThemeTemplates { QString scraperTableViewQSS = "QTableView {color:%1; border:0px;alternate-background-color: %2;background-color: %3; outline: 0px;}" "QTableView::item {outline: 0px; border: 0px; color:%1;}" "QTableView::item:selected {outline: 0px; background-color: %4; }" - "QHeaderView::section:horizontal {background-color:%5; border-bottom:1px solid %6; border-right:1px solid qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %5, stop: 1 %6); border-left:none; border-top:none; padding:4px; color:%7;}" + "QHeaderView::section:horizontal {background-color:%5; border-bottom:1px solid %6; border-right:1px solid qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %14, stop: 1 %6); border-left:none; border-top:none; padding:4px; color:%7;}" "QHeaderView::section:vertical {border-bottom: 1px solid %8;border-top: 1px solid %9;}" "QHeaderView::down-arrow {image: url('%12');width: 8px;height: 7px;padding-right: 10px;}" "QHeaderView::up-arrow {image: url('%13');width: 8px;height: 7px; padding-right: 10px;}" @@ -96,16 +98,16 @@ struct ComicFlowColors { QColor textColor; }; -struct TableViewThemeTemplates { +struct ComicsViewTableThemeTemplates { QString tableViewQSS = "QTableView {alternate-background-color: %1; background-color: %2; outline: 0px; border: none;}" "QTableCornerButton::section {background-color:%3; border:none; border-bottom:1px solid %4; border-right:1px solid qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %5, stop: 1 %4);}" - "QTableView::item {outline: 0px; border-bottom: 1px solid %6; border-top: 1px solid %7; padding-bottom:1px; color:%8;}" - "QTableView::item:selected {outline: 0px; border-bottom: 1px solid %9; border-top: 1px solid %9; padding-bottom:1px; background-color: %9; color: %10; }" + "QTableView::item {outline: 0px; border-bottom: %12px solid %6; border-top: %13px solid %7; padding-bottom:1px; color:%8;}" + "QTableView::item:selected {outline: 0px; border-bottom: %12px solid %9; border-top: %13px solid %9; padding-bottom:1px; background-color: %9; color: %10; }" "QHeaderView::section:horizontal {background-color:%3; border-bottom:1px solid %4; border-right:1px solid qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %5, stop: 1 %4); border-left:none; border-top:none; padding:4px; color:%11;}" "QHeaderView::section:vertical {border-bottom: 1px solid %6; border-top: 1px solid %7;}"; }; -struct TableViewTheme { +struct ComicsViewTableTheme { QString tableViewQSS; QColor starRatingColor; QColor starRatingSelectedColor; @@ -136,20 +138,12 @@ struct EmptyContainerTheme { QMap emptyLabelIcons; // Keyed by YACReader::LabelColors enum value }; -struct SidebarThemeTemplates { - // Non-macOS splitter template: %1 = background color, %2 = height - QString styledSplitterQSS = "QSplitter::handle { image: none; background-color: %1; }" - "QSplitter::handle:vertical { height: %2px;}"; - - // macOS splitter template: %1 = height, %2 = background color - QString nativeSplitterQSS = "QSplitter::handle:vertical { height: %1px; background-color: %2;}"; -}; - struct SidebarTheme { QColor backgroundColor; QColor separatorColor; QColor sectionSeparatorColor; // Horizontal separators between sidebar sections - QString splitterQSS; + QString splitterQSS = "QSplitter::handle { image: none; background-color: transparent; }" + "QSplitter::handle:vertical { height: 39px; }"; // When true, section title strings are uppercased after translation bool uppercaseLabels; @@ -179,10 +173,10 @@ struct ImportWidgetTheme { QIcon coversToggleIcon; }; -struct TreeViewThemeTemplates { +struct NavigationTreeThemeTemplates { // Styled tree view template with custom scroll bars // %1 = text color, %2 = selection/hover background, %3 = scroll background, %4 = scroll handle, %5 = selected text color - QString styledTreeViewQSS = "QTreeView {background-color:transparent; border: none; color:%1; outline:0; show-decoration-selected: 0;}" + QString navigationTreeQSS = "QTreeView {background-color:transparent; border: none; color:%1; outline:0; show-decoration-selected: 0;}" "QTreeView::item:selected {background-color: %2; color:%5; font:bold;}" "QTreeView::item:hover {background-color:%2; color:%5; font:bold;}" "QTreeView::branch:selected {background-color:%2;}" @@ -194,25 +188,28 @@ struct TreeViewThemeTemplates { "QScrollBar::down-arrow:vertical {border:none;width: 9px;height: 6px;background: url(':/images/folders_view/line-down.png') center top no-repeat;}" "QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {background: none; }" "QTreeView::branch:has-children:!has-siblings:closed,QTreeView::branch:closed:has-children:has-siblings {border-image: none;image: url('%6');}" - "QTreeView::branch:has-children:selected:!has-siblings:closed,QTreeView::branch:closed:selected:has-children:has-siblings {border-image: none;image: url('%6');}" + "QTreeView::branch:has-children:selected:!has-siblings:closed,QTreeView::branch:closed:selected:has-children:has-siblings {border-image: none;image: url('%8');}" "QTreeView::branch:open:has-children:!has-siblings,QTreeView::branch:open:has-children:has-siblings {border-image: none;image: url('%7');}" - "QTreeView::branch:open:has-children:selected:!has-siblings,QTreeView::branch:open:has-children:selected:has-siblings {border-image: none;image: url('%7');}"; - - // Native tree view template (uses system scroll bars) - for macOS - QString nativeTreeViewQSS = "QTreeView {background-color:transparent; border: none;}" - "QTreeView::item:selected {background-color:%1; border-top: 1px solid %1; border-left:none;border-right:none;border-bottom:1px solid %1;}" - "QTreeView::branch:selected {background-color:%1; border-top: 1px solid %1; border-left:none;border-right:none;border-bottom:1px solid %1;}" - "QTreeView::branch:open:selected:has-children {image: url(':/images/sidebar/expanded_branch_osx.png');}" - "QTreeView::branch:closed:selected:has-children {image: url(':/images/sidebar/collapsed_branch_osx.png');}"; + "QTreeView::branch:open:has-children:selected:!has-siblings,QTreeView::branch:open:has-children:selected:has-siblings {border-image: none;image: url('%9');}"; }; -struct TreeViewTheme { - QString treeViewQSS; +struct NavigationTreeTheme { + QString navigationTreeQSS; QColor folderIndicatorColor; // For incomplete folders and recently updated folders + + // Branch indicator icon paths (used by QSS url()) + QString branchClosedIconPath; + QString branchOpenIconPath; + QString branchClosedIconSelectedPath; + QString branchOpenIconSelectedPath; + + // Folder icons (normal and selected states, including finished/read tick variant) + QIcon folderIcon; + QIcon folderFinishedIcon; }; -// QML view theme colors (used by GridComicsView, FolderContentView, InfoComicsView) -struct QmlViewTheme { +// Grid and info view theme colors (used by GridComicsView, FolderContentView, InfoComicsView) +struct GridAndInfoViewTheme { // Grid colors QColor backgroundColor; QColor cellColor; @@ -226,9 +223,9 @@ struct QmlViewTheme { // Info panel colors QColor infoBackgroundColor; - QString topShadow; // Image filename - QString infoShadow; // Image filename - QString infoIndicator; // Image filename + QString topShadow; // Recolored SVG path + QString infoShadow; // Recolored SVG path + QString infoIndicator; // Recolored SVG path QColor infoTextColor; QColor infoTitleColor; @@ -246,6 +243,9 @@ struct QmlViewTheme { // Continue reading section (FolderContentView) QColor continueReadingBackgroundColor; QColor continueReadingColor; + + // Blur overlay background (FlowView always, GridView when background image enabled) + QColor backgroundBlurOverlayColor; }; struct MainToolbarThemeTemplates { @@ -262,10 +262,9 @@ struct ContentSplitterThemeTemplates { }; struct ComicsViewToolbarThemeTemplates { - QString toolbarQSS = R"( - QToolBar { border: none; } - QToolButton:checked { background-color: %1; } - )"; + QString toolbarQSS = "QToolBar { border: none; background: %1; }\n" + "QToolBar::separator { background: %2; width: 1px; margin: 5px 4px; }\n" + "QToolButton:checked { background-color: %3; }\n"; }; struct SearchLineEditThemeTemplates { @@ -282,14 +281,10 @@ struct ContentSplitterTheme { struct SidebarIconsTheme { // When true, use QFileIconProvider for folder icons and overlay folderReadOverlay for finished folders - // When false, use the themed folderIcon and folderFinishedIcon SVGs + // When false, use the themed folderIcon and folderFinishedIcon from NavigationTreeTheme bool useSystemFolderIcons; QPixmap folderReadOverlay; // Tick overlay drawn on system folder icons when useSystemFolderIcons is true - // Folder icons (for FolderModel, ReadingListItem) - QIcon folderIcon; - QIcon folderFinishedIcon; - // Library icon (for YACReaderLibraryItemWidget - unselected state) QIcon libraryIcon; @@ -303,10 +298,6 @@ struct SidebarIconsTheme { QIcon colapseIcon; QIcon addLabelIcon; QIcon renameListIcon; - - // Branch icons (for TreeView QSS) - QString branchClosedIconPath; - QString branchOpenIconPath; }; struct LibraryItemTheme { @@ -429,7 +420,7 @@ struct MainToolbarTheme { QString folderNameLabelQSS; }; -struct ComicVineTheme { +struct MetadataScraperDialogTheme { QString defaultLabelQSS; QString titleLabelQSS; QString coverLabelQSS; @@ -463,10 +454,11 @@ struct ComicVineTheme { }; struct Theme { - QColor defaultContentBackgroundColor; + ThemeMeta meta; + QJsonObject sourceJson; ComicFlowColors comicFlow; - ComicVineTheme comicVine; + MetadataScraperDialogTheme metadataScraperDialog; HelpAboutDialogTheme helpAboutDialog; WhatsNewDialogTheme whatsNewDialog; EmptyContainerTheme emptyContainer; @@ -475,9 +467,9 @@ struct Theme { LibraryItemTheme libraryItem; ImportWidgetTheme importWidget; ServerConfigDialogTheme serverConfigDialog; - TreeViewTheme treeView; - TableViewTheme tableView; - QmlViewTheme qmlView; + NavigationTreeTheme navigationTree; + ComicsViewTableTheme comicsViewTable; + GridAndInfoViewTheme gridAndInfoView; MainToolbarTheme mainToolbar; ContentSplitterTheme contentSplitter; ComicsViewToolbarTheme comicsViewToolbar; diff --git a/YACReaderLibrary/themes/theme_factory.cpp b/YACReaderLibrary/themes/theme_factory.cpp index 5f7c4969..eaba930d 100644 --- a/YACReaderLibrary/themes/theme_factory.cpp +++ b/YACReaderLibrary/themes/theme_factory.cpp @@ -5,8 +5,8 @@ #include "icon_utils.h" #include "yacreader_global.h" -struct ComicVineParams { - ComicVineThemeTemplates t; +struct MetadataScraperDialogParams { + MetadataScraperDialogThemeTemplates t; QColor contentTextColor; QColor contentBackgroundColor; @@ -18,6 +18,7 @@ struct ComicVineParams { QColor tableBorderColor; QColor tableSelectedColor; QColor tableHeaderBackgroundColor; + QColor tableHeaderGradientColor; QColor tableHeaderBorderColor; QColor tableHeaderTextColor; QColor tableScrollHandleColor; @@ -33,10 +34,8 @@ struct ComicVineParams { QColor buttonTextColor; QColor buttonBorderColor; - QString radioUncheckedPath; QColor radioUncheckedColor; - QString radioCheckedPath; QColor radioCheckedBackgroundColor; QColor radioCheckedIndicatorColor; @@ -66,17 +65,10 @@ struct EmptyContainerParams { }; struct SidebarParams { - SidebarThemeTemplates t; - bool useStyledSplitter; // true for non-macOS, false for macOS - QColor backgroundColor; QColor separatorColor; QColor sectionSeparatorColor; // Horizontal separators between sidebar sections - // Splitter parameters - QColor splitterBackgroundColor; - int splitterHeight; - bool uppercaseLabels; // Title bar colors @@ -101,33 +93,41 @@ struct ImportWidgetParams { QColor iconCheckedColor; }; -struct TreeViewParams { - TreeViewThemeTemplates t; - bool useStyledTemplate; // true for non-macOS themes, false for macOS themes +struct NavigationTreeParams { + NavigationTreeThemeTemplates t; - // For styled template: %1=text, %2=selection bg, %3=scroll bg, %4=scroll handle, %5=selected text QColor textColor; QColor selectionBackgroundColor; QColor scrollBackgroundColor; QColor scrollHandleColor; QColor selectedTextColor; - - // For native (macOS) template: %1=selection color - QColor nativeSelectionColor; - QColor folderIndicatorColor; + + // Branch indicator icon colors (independent of sidebarIcons.iconColor) + QColor branchIndicatorColor; + QColor branchIndicatorSelectedColor; + + // Folder icon colors (own parameters, independent of sidebarIcons) + QColor folderIconColor; // Main color for unselected folder (#f0f) + QColor folderIconShadowColor; // Shadow color for unselected folder (#0ff) + QColor folderIconSelectedColor; // Main color for selected folder (#f0f) + QColor folderIconSelectedShadowColor; // Shadow color for selected folder (#0ff) + QColor folderReadOverlayColor; // Tick/checkmark color for unselected folder (#ff0 in folder_finished, #f0f in folder_read_overlay.svg) + QColor folderReadOverlaySelectedColor; // Tick/checkmark color for selected folder }; -struct TableViewParams { - TableViewThemeTemplates t; +struct ComicsViewTableParams { + ComicsViewTableThemeTemplates t; QColor alternateBackgroundColor; QColor backgroundColor; - QColor cornerButtonBackgroundColor; - QColor cornerButtonBorderColor; - QColor cornerButtonGradientColor; + QColor headerBackgroundColor; + QColor headerBorderColor; + QColor headerGradientColor; QColor itemBorderBottomColor; QColor itemBorderTopColor; + int itemBorderBottomWidth; // px + int itemBorderTopWidth; // px QColor itemTextColor; QColor selectedColor; QColor selectedTextColor; @@ -137,7 +137,7 @@ struct TableViewParams { QColor starRatingSelectedColor; }; -struct QmlViewParams { +struct GridAndInfoViewParams { // Grid colors QColor backgroundColor; QColor cellColor; @@ -151,9 +151,8 @@ struct QmlViewParams { // Info panel colors QColor infoBackgroundColor; - QString topShadow; // Image filename - QString infoShadow; // Image filename - QString infoIndicator; // Image filename + QColor infoBorderColor; + QColor infoShadowColor; QColor infoTextColor; QColor infoTitleColor; @@ -171,6 +170,9 @@ struct QmlViewParams { // Continue reading section (FolderContentView) QColor continueReadingBackgroundColor; QColor continueReadingColor; + + // Blur overlay background (FlowView always, GridView when background image enabled) + QColor backgroundBlurOverlayColor; }; struct MainToolbarParams { @@ -195,11 +197,9 @@ struct SidebarIconsParams { // Icon colors - #f0f placeholder gets replaced with these QColor iconColor; // Main icon color (replaces #f0f) QColor shadowColor; // Shadow color (replaces #0ff) - QColor extraColor; // Extra info like ticks (replaces #ff0) // When true, use QFileIconProvider for folder icons and overlay a tick for finished folders bool useSystemFolderIcons; - QColor folderReadOverlayColor; // Color for the tick overlay (replaces #f0f in folder_read_overlay.svg) }; struct ServerConfigDialogThemeTemplates { @@ -215,15 +215,19 @@ struct LibraryItemParams { QColor textColor; QColor selectedTextColor; QColor selectedBackgroundColor; - QColor libraryIconSelectedColor; // Color for the library icon when selected + QColor libraryIconColor; + QColor libraryIconShadowColor; + QColor libraryIconSelectedColor; QColor libraryOptionsIconColor; // Color for the options icon (shown only when selected) }; struct ComicsViewToolbarParams { ComicsViewToolbarThemeTemplates t; + QColor backgroundColor; + QColor separatorColor; QColor checkedBackgroundColor; - QColor iconColor; // Main icon color (replaces #f0f) + QColor iconColor; }; struct SearchLineEditParams { @@ -237,18 +241,27 @@ struct SearchLineEditParams { struct ReadingListIconsParams { QMap labelColors; // Label colors by name (e.g., "red" -> #f67a7b) QColor labelShadowColor; // Shadow color for labels (replaces #0ff) + QColor labelShadowSelectedColor; // Shadow color for labels when selected/hovered // Special list icon colors QColor readingListMainColor; // default_0 main color (replaces #f0f) + QColor readingListMainSelectedColor; // default_0 main color when selected/hovered QColor favoritesMainColor; // default_1 main color (replaces #f0f) + QColor favoritesMainSelectedColor; // default_1 main color when selected/hovered QColor currentlyReadingMainColor; // default_2 main color (replaces #f0f) + QColor currentlyReadingMainSelectedColor; // default_2 main color when selected/hovered QColor currentlyReadingOuterColor; // default_2 outer circle (replaces #ff0) + QColor currentlyReadingOuterSelectedColor; // default_2 outer circle when selected/hovered QColor specialListShadowColor; // Shadow color for special lists (replaces #0ff) + QColor specialListShadowSelectedColor; // Shadow color for special lists when selected/hovered // List icon colors QColor listMainColor; // main color (replaces #f0f) + QColor listMainSelectedColor; // main color when selected/hovered QColor listShadowColor; // shadow color (replaces #0ff) + QColor listShadowSelectedColor; // shadow color when selected/hovered QColor listDetailColor; // detail/checkbox color (replaces #ff0) + QColor listDetailSelectedColor; // detail/checkbox color when selected/hovered }; struct DialogIconsParams { @@ -287,11 +300,10 @@ struct WhatsNewDialogParams { }; struct ThemeParams { - QString themeName; - QColor defaultContentBackgroundColor; + ThemeMeta meta; ComicFlowColors comicFlowColors; - ComicVineParams comicVineParams; + MetadataScraperDialogParams metadataScraperDialogParams; HelpAboutDialogTheme helpAboutDialogParams; EmptyContainerParams emptyContainerParams; SidebarParams sidebarParams; @@ -301,9 +313,9 @@ struct ThemeParams { ServerConfigDialogParams serverConfigDialogParams; MainToolbarParams mainToolbarParams; ContentSplitterParams contentSplitterParams; - TreeViewParams treeViewParams; - TableViewParams tableViewParams; - QmlViewParams qmlViewParams; + NavigationTreeParams navigationTreeParams; + ComicsViewTableParams comicsViewTableParams; + GridAndInfoViewParams gridAndInfoViewParams; ComicsViewToolbarParams comicsViewToolbarParams; SearchLineEditParams searchLineEditParams; ReadingListIconsParams readingListIconsParams; @@ -317,62 +329,61 @@ Theme makeTheme(const ThemeParams ¶ms) { Theme theme; - theme.defaultContentBackgroundColor = params.defaultContentBackgroundColor; - // Comic Flow const auto &cf = params.comicFlowColors; theme.comicFlow.backgroundColor = cf.backgroundColor; theme.comicFlow.textColor = cf.textColor; - // Comic Vine - const auto &cv = params.comicVineParams; - const auto &t = cv.t; + // MetadataScraperDialog + const auto &msd = params.metadataScraperDialogParams; + const auto &t = msd.t; auto recolor = [&](const QString &path, const QColor &color) { - return recoloredSvgToThemeFile(path, color, params.themeName); + return recoloredSvgToThemeFile(path, color, params.meta.id); }; - theme.comicVine.defaultLabelQSS = t.defaultLabelQSS.arg(cv.labelTextColor.name()); - theme.comicVine.titleLabelQSS = t.titleLabelQSS.arg(cv.labelTextColor.name()); - theme.comicVine.coverLabelQSS = t.coverLabelQSS.arg(cv.labelBackgroundColor.name(), cv.labelTextColor.name()); - theme.comicVine.radioButtonQSS = t.radioButtonQSS.arg(cv.buttonTextColor.name(), recolor(cv.radioUncheckedPath, cv.radioUncheckedColor), recoloredSvgToThemeFile(cv.radioCheckedPath, cv.radioCheckedBackgroundColor, cv.radioCheckedIndicatorColor, params.themeName)); - theme.comicVine.checkBoxQSS = t.checkBoxQSS.arg(cv.buttonTextColor.name(), cv.buttonBorderColor.name(), cv.buttonBackgroundColor.name(), recolor(":/images/comic_vine/checkBoxTick.svg", cv.checkBoxTickColor)); + theme.metadataScraperDialog.defaultLabelQSS = t.defaultLabelQSS.arg(msd.labelTextColor.name()); + theme.metadataScraperDialog.titleLabelQSS = t.titleLabelQSS.arg(msd.labelTextColor.name()); + theme.metadataScraperDialog.coverLabelQSS = t.coverLabelQSS.arg(msd.labelBackgroundColor.name(), msd.labelTextColor.name()); + theme.metadataScraperDialog.radioButtonQSS = t.radioButtonQSS.arg(msd.buttonTextColor.name(), recolor(":/images/comic_vine/radioUnchecked.svg", msd.radioUncheckedColor), recoloredSvgToThemeFile(":/images/comic_vine/radioChecked.svg", msd.radioCheckedBackgroundColor, msd.radioCheckedIndicatorColor, params.meta.id)); + theme.metadataScraperDialog.checkBoxQSS = t.checkBoxQSS.arg(msd.buttonTextColor.name(), msd.buttonBorderColor.name(), msd.buttonBackgroundColor.name(), recolor(":/images/comic_vine/checkBoxTick.svg", msd.checkBoxTickColor)); - theme.comicVine.scraperLineEditTitleLabelQSS = t.scraperLineEditTitleLabelQSS.arg(cv.contentTextColor.name()); - theme.comicVine.scraperLineEditQSS = t.scraperLineEditQSS.arg(cv.contentAltBackgroundColor.name(), cv.contentTextColor.name(), "%1"); + theme.metadataScraperDialog.scraperLineEditTitleLabelQSS = t.scraperLineEditTitleLabelQSS.arg(msd.contentTextColor.name()); + theme.metadataScraperDialog.scraperLineEditQSS = t.scraperLineEditQSS.arg(msd.contentAltBackgroundColor.name(), msd.contentTextColor.name(), "%1"); - theme.comicVine.scraperToolButtonQSS = t.scraperToolButtonQSS.arg(cv.buttonBackgroundColor.name(), cv.buttonTextColor.name(), cv.toolButtonAccentColor.name()); - theme.comicVine.scraperToolButtonSeparatorQSS = t.scraperToolButtonSeparatorQSS.arg(cv.toolButtonAccentColor.name()); - theme.comicVine.scraperToolButtonFillColor = cv.buttonBackgroundColor; + theme.metadataScraperDialog.scraperToolButtonQSS = t.scraperToolButtonQSS.arg(msd.buttonBackgroundColor.name(), msd.buttonTextColor.name(), msd.toolButtonAccentColor.name()); + theme.metadataScraperDialog.scraperToolButtonSeparatorQSS = t.scraperToolButtonSeparatorQSS.arg(msd.toolButtonAccentColor.name()); + theme.metadataScraperDialog.scraperToolButtonFillColor = msd.buttonBackgroundColor; - theme.comicVine.scraperScrollLabelTextQSS = t.scraperScrollLabelTextQSS.arg(cv.contentBackgroundColor.name(), cv.contentTextColor.name(), cv.hyperlinkColor.name()); - theme.comicVine.scraperScrollLabelScrollAreaQSS = t.scraperScrollLabelScrollAreaQSS.arg(cv.contentBackgroundColor.name(), cv.tableScrollHandleColor.name(), cv.tableScrollBackgroundColor.name()); + theme.metadataScraperDialog.scraperScrollLabelTextQSS = t.scraperScrollLabelTextQSS.arg(msd.contentBackgroundColor.name(), msd.contentTextColor.name(), msd.hyperlinkColor.name()); + theme.metadataScraperDialog.scraperScrollLabelScrollAreaQSS = t.scraperScrollLabelScrollAreaQSS.arg(msd.contentBackgroundColor.name(), msd.tableScrollHandleColor.name(), msd.tableScrollBackgroundColor.name()); - theme.comicVine.scraperTableViewQSS = t.scraperTableViewQSS - .arg(cv.tableHeaderTextColor.name(), - cv.tableAltBackgroundColor.name(), - cv.tableBackgroundColor.name(), - cv.tableSelectedColor.name(), - cv.tableHeaderBackgroundColor.name(), - cv.tableHeaderBorderColor.name(), - cv.tableHeaderTextColor.name(), - cv.tableSectionBorderDark.name(), - cv.tableSectionBorderLight.name(), - cv.tableScrollHandleColor.name(), - cv.tableScrollBackgroundColor.name(), - recolor(":/images/comic_vine/downArrow.svg", cv.downArrowColor), - recolor(":/images/comic_vine/upArrow.svg", cv.upArrowColor)); + theme.metadataScraperDialog.scraperTableViewQSS = t.scraperTableViewQSS + .arg(msd.tableHeaderTextColor.name(), + msd.tableAltBackgroundColor.name(), + msd.tableBackgroundColor.name(), + msd.tableSelectedColor.name(), + msd.tableHeaderBackgroundColor.name(), + msd.tableHeaderBorderColor.name(), + msd.tableHeaderTextColor.name(), + msd.tableSectionBorderDark.name(), + msd.tableSectionBorderLight.name(), + msd.tableScrollHandleColor.name(), + msd.tableScrollBackgroundColor.name(), + recolor(":/images/comic_vine/downArrow.svg", msd.downArrowColor), + recolor(":/images/comic_vine/upArrow.svg", msd.upArrowColor), + msd.tableHeaderGradientColor.name()); - theme.comicVine.dialogQSS = t.dialogQSS.arg(cv.dialogBackgroundColor.name()); - theme.comicVine.dialogButtonsQSS = t.dialogButtonsQSS.arg(cv.buttonBorderColor.name(), cv.buttonBackgroundColor.name(), cv.buttonTextColor.name()); + theme.metadataScraperDialog.dialogQSS = t.dialogQSS.arg(msd.dialogBackgroundColor.name()); + theme.metadataScraperDialog.dialogButtonsQSS = t.dialogButtonsQSS.arg(msd.buttonBorderColor.name(), msd.buttonBackgroundColor.name(), msd.buttonTextColor.name()); - theme.comicVine.busyIndicatorColor = cv.busyIndicatorColor; + theme.metadataScraperDialog.busyIndicatorColor = msd.busyIndicatorColor; - theme.comicVine.nextPageIcon = { QIcon(recolor(t.nextPageIcon, cv.navIconColor)), t.pageIconSize }; - theme.comicVine.previousPageIcon = { QIcon(recolor(t.previousPageIcon, cv.navIconColor)), t.pageIconSize }; + theme.metadataScraperDialog.nextPageIcon = { QIcon(recolor(t.nextPageIcon, msd.navIconColor)), t.pageIconSize }; + theme.metadataScraperDialog.previousPageIcon = { QIcon(recolor(t.previousPageIcon, msd.navIconColor)), t.pageIconSize }; - theme.comicVine.rowUpIcon = { QIcon(recolor(t.rowUpIcon, cv.rowIconColor)), t.rowIconSize }; - theme.comicVine.rowDownIcon = { QIcon(recolor(t.rowDownIcon, cv.rowIconColor)), t.rowIconSize }; + theme.metadataScraperDialog.rowUpIcon = { QIcon(recolor(t.rowUpIcon, msd.rowIconColor)), t.rowIconSize }; + theme.metadataScraperDialog.rowDownIcon = { QIcon(recolor(t.rowDownIcon, msd.rowIconColor)), t.rowIconSize }; // HelpAboutDialog theme.helpAboutDialog = params.helpAboutDialogParams; @@ -385,8 +396,8 @@ Theme makeTheme(const ThemeParams ¶ms) theme.whatsNewDialog.versionTextColor = wn.versionTextColor; theme.whatsNewDialog.contentTextColor = wn.contentTextColor; theme.whatsNewDialog.linkColor = wn.linkColor; - theme.whatsNewDialog.closeButtonIcon = QPixmap(recoloredSvgToThemeFile(":/images/custom_dialog/custom_close_button.svg", wn.closeButtonColor, params.themeName)); - theme.whatsNewDialog.headerDecoration = QPixmap(recoloredSvgToThemeFile(":/images/whats_new/whatsnew_header.svg", wn.headerDecorationColor, params.themeName)); + theme.whatsNewDialog.closeButtonIcon = QPixmap(recoloredSvgToThemeFile(":/images/custom_dialog/custom_close_button.svg", wn.closeButtonColor, params.meta.id)); + theme.whatsNewDialog.headerDecoration = QPixmap(recoloredSvgToThemeFile(":/images/whats_new/whatsnew_header.svg", wn.headerDecorationColor, params.meta.id)); // end WhatsNewDialog // EmptyContainer @@ -397,16 +408,16 @@ Theme makeTheme(const ThemeParams ¶ms) theme.emptyContainer.titleLabelQSS = ect.titleLabelQSS.arg(ec.titleTextColor.name()); theme.emptyContainer.textColor = ec.textColor; theme.emptyContainer.descriptionTextColor = ec.descriptionTextColor; - theme.emptyContainer.noLibrariesIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/noLibrariesIcon.svg", ec.searchIconColor, params.themeName), 165, 160, qApp->devicePixelRatio()); + theme.emptyContainer.noLibrariesIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/noLibrariesIcon.svg", ec.searchIconColor, params.meta.id), 165, 160, qApp->devicePixelRatio()); { const qreal dpr = qApp->devicePixelRatio(); - theme.emptyContainer.searchingIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/search_result.svg", ec.searchIconColor, params.themeName, { .suffix = "_searching" }), 97, dpr); - theme.emptyContainer.noSearchResultsIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/search_result.svg", ec.searchIconColor, params.themeName, { .suffix = "_no_results" }), 239, dpr); + theme.emptyContainer.searchingIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/search_result.svg", ec.searchIconColor, params.meta.id, { .suffix = "_searching" }), 97, dpr); + theme.emptyContainer.noSearchResultsIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/search_result.svg", ec.searchIconColor, params.meta.id, { .suffix = "_no_results" }), 239, dpr); - theme.emptyContainer.emptyFolderIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_folder.svg", ec.searchIconColor, params.themeName), 319, 243, dpr); - theme.emptyContainer.emptyFavoritesIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_favorites.svg", QColor(0xe84853), params.themeName), 238, 223, dpr); - theme.emptyContainer.emptyCurrentReadingsIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_current_readings.svg", ec.searchIconColor, params.themeName), 167, 214, dpr); - theme.emptyContainer.emptyReadingListIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_reading_list.svg", ec.searchIconColor, params.themeName), 248, 187, dpr); + theme.emptyContainer.emptyFolderIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_folder.svg", ec.searchIconColor, params.meta.id), 319, 243, dpr); + theme.emptyContainer.emptyFavoritesIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_favorites.svg", QColor(0xe84853), params.meta.id), 238, 223, dpr); + theme.emptyContainer.emptyCurrentReadingsIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_current_readings.svg", ec.searchIconColor, params.meta.id), 167, 214, dpr); + theme.emptyContainer.emptyReadingListIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/empty_container/empty_reading_list.svg", ec.searchIconColor, params.meta.id), 248, 187, dpr); // Generate empty label icons for each label color const auto &rli = params.readingListIconsParams; @@ -416,7 +427,7 @@ Theme makeTheme(const ThemeParams ¶ms) auto it = rli.labelColors.find(colorName); if (it != rli.labelColors.end()) { theme.emptyContainer.emptyLabelIcons[c] = renderSvgToPixmap( - recoloredSvgToThemeFile(":/images/empty_container/empty_label.svg", it.value(), params.themeName, { .suffix = "_" + colorName }), 243, 243, dpr); + recoloredSvgToThemeFile(":/images/empty_container/empty_label.svg", it.value(), params.meta.id, { .suffix = "_" + colorName }), 243, 243, dpr); } } } @@ -427,15 +438,6 @@ Theme makeTheme(const ThemeParams ¶ms) theme.sidebar.backgroundColor = sb.backgroundColor; theme.sidebar.separatorColor = sb.separatorColor; theme.sidebar.sectionSeparatorColor = sb.sectionSeparatorColor; - if (sb.useStyledSplitter) { - theme.sidebar.splitterQSS = sb.t.styledSplitterQSS - .arg(sb.splitterBackgroundColor.name()) - .arg(sb.splitterHeight); - } else { - theme.sidebar.splitterQSS = sb.t.nativeSplitterQSS - .arg(sb.splitterHeight) - .arg(sb.splitterBackgroundColor.name()); - } theme.sidebar.uppercaseLabels = sb.uppercaseLabels; theme.sidebar.titleTextColor = sb.titleTextColor; theme.sidebar.titleDropShadowColor = sb.titleDropShadowColor; @@ -451,66 +453,68 @@ Theme makeTheme(const ThemeParams ¶ms) theme.importWidget.currentComicTextColor = iw.currentComicTextColor; theme.importWidget.coversViewBackgroundColor = iw.coversViewBackgroundColor; theme.importWidget.coversLabelColor = iw.coversLabelColor; - theme.importWidget.topCoversDecoration = QPixmap(recoloredSvgToThemeFile(":/images/import/importTopCoversDecoration.svg", iw.coversDecorationBgColor, iw.coversDecorationShadowColor, params.themeName)); - theme.importWidget.bottomCoversDecoration = QPixmap(recoloredSvgToThemeFile(":/images/import/importBottomCoversDecoration.svg", iw.coversDecorationBgColor, iw.coversDecorationShadowColor, params.themeName)); - theme.importWidget.importingIcon = QPixmap(recoloredSvgToThemeFile(":/images/import/importingIcon.svg", iw.modeIconColor, params.themeName)); - theme.importWidget.updatingIcon = QPixmap(recoloredSvgToThemeFile(":/images/import/updatingIcon.svg", iw.modeIconColor, params.themeName)); + theme.importWidget.topCoversDecoration = QPixmap(recoloredSvgToThemeFile(":/images/import/importTopCoversDecoration.svg", iw.coversDecorationBgColor, iw.coversDecorationShadowColor, params.meta.id)); + theme.importWidget.bottomCoversDecoration = QPixmap(recoloredSvgToThemeFile(":/images/import/importBottomCoversDecoration.svg", iw.coversDecorationBgColor, iw.coversDecorationShadowColor, params.meta.id)); + theme.importWidget.importingIcon = QPixmap(recoloredSvgToThemeFile(":/images/import/importingIcon.svg", iw.modeIconColor, params.meta.id)); + theme.importWidget.updatingIcon = QPixmap(recoloredSvgToThemeFile(":/images/import/updatingIcon.svg", iw.modeIconColor, params.meta.id)); { QIcon coversToggle; - const QString normalPath = recoloredSvgToThemeFile(":/images/import/coversToggle.svg", iw.iconColor, params.themeName); - const QString checkedPath = recoloredSvgToThemeFile(":/images/import/coversToggle.svg", iw.iconCheckedColor, params.themeName, { .suffix = "_checked" }); + const QString normalPath = recoloredSvgToThemeFile(":/images/import/coversToggle.svg", iw.iconColor, params.meta.id); + const QString checkedPath = recoloredSvgToThemeFile(":/images/import/coversToggle.svg", iw.iconCheckedColor, params.meta.id, { .suffix = "_checked" }); coversToggle.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); coversToggle.addFile(checkedPath, QSize(), QIcon::Normal, QIcon::On); theme.importWidget.coversToggleIcon = coversToggle; } // end ImportWidget - // TableView - const auto &tav = params.tableViewParams; - const auto &tavt = tav.t; - theme.tableView.tableViewQSS = tavt.tableViewQSS - .arg(tav.alternateBackgroundColor.name(), - tav.backgroundColor.name(), - tav.cornerButtonBackgroundColor.name(), - tav.cornerButtonBorderColor.name(), - tav.cornerButtonGradientColor.name(), - tav.itemBorderBottomColor.name(), - tav.itemBorderTopColor.name(), - tav.itemTextColor.name(), - tav.selectedColor.name(), - tav.selectedTextColor.name(), - tav.headerTextColor.name()); - theme.tableView.starRatingColor = tav.starRatingColor; - theme.tableView.starRatingSelectedColor = tav.starRatingSelectedColor; - // end TableView + // ComicsViewTable + const auto &cvta = params.comicsViewTableParams; + theme.comicsViewTable.tableViewQSS = cvta.t.tableViewQSS + .arg(cvta.alternateBackgroundColor.name(), + cvta.backgroundColor.name(), + cvta.headerBackgroundColor.name(), + cvta.headerBorderColor.name(), + cvta.headerGradientColor.name(), + cvta.itemBorderBottomColor.name(), + cvta.itemBorderTopColor.name(), + cvta.itemTextColor.name(), + cvta.selectedColor.name(), + cvta.selectedTextColor.name(), + cvta.headerTextColor.name(), + QString::number(cvta.itemBorderBottomWidth), + QString::number(cvta.itemBorderTopWidth)); + theme.comicsViewTable.starRatingColor = cvta.starRatingColor; + theme.comicsViewTable.starRatingSelectedColor = cvta.starRatingSelectedColor; + // end ComicsViewTable - // QmlView - const auto &qv = params.qmlViewParams; - theme.qmlView.backgroundColor = qv.backgroundColor; - theme.qmlView.cellColor = qv.cellColor; - theme.qmlView.cellColorWithBackground = qv.cellColorWithBackground; - theme.qmlView.selectedColor = qv.selectedColor; - theme.qmlView.selectedBorderColor = qv.selectedBorderColor; - theme.qmlView.borderColor = qv.borderColor; - theme.qmlView.titleColor = qv.titleColor; - theme.qmlView.textColor = qv.textColor; - theme.qmlView.showDropShadow = qv.showDropShadow; - theme.qmlView.infoBackgroundColor = qv.infoBackgroundColor; - theme.qmlView.topShadow = qv.topShadow; - theme.qmlView.infoShadow = qv.infoShadow; - theme.qmlView.infoIndicator = qv.infoIndicator; - theme.qmlView.infoTextColor = qv.infoTextColor; - theme.qmlView.infoTitleColor = qv.infoTitleColor; - theme.qmlView.ratingUnselectedColor = qv.ratingUnselectedColor; - theme.qmlView.ratingSelectedColor = qv.ratingSelectedColor; - theme.qmlView.favUncheckedColor = qv.favUncheckedColor; - theme.qmlView.favCheckedColor = qv.favCheckedColor; - theme.qmlView.readTickUncheckedColor = qv.readTickUncheckedColor; - theme.qmlView.readTickCheckedColor = qv.readTickCheckedColor; - theme.qmlView.currentComicBackgroundColor = qv.currentComicBackgroundColor; - theme.qmlView.continueReadingBackgroundColor = qv.continueReadingBackgroundColor; - theme.qmlView.continueReadingColor = qv.continueReadingColor; - // end QmlView + // GridAndInfoView + const auto &giv = params.gridAndInfoViewParams; + theme.gridAndInfoView.backgroundColor = giv.backgroundColor; + theme.gridAndInfoView.cellColor = giv.cellColor; + theme.gridAndInfoView.cellColorWithBackground = giv.cellColorWithBackground; + theme.gridAndInfoView.selectedColor = giv.selectedColor; + theme.gridAndInfoView.selectedBorderColor = giv.selectedBorderColor; + theme.gridAndInfoView.borderColor = giv.borderColor; + theme.gridAndInfoView.titleColor = giv.titleColor; + theme.gridAndInfoView.textColor = giv.textColor; + theme.gridAndInfoView.showDropShadow = giv.showDropShadow; + theme.gridAndInfoView.infoBackgroundColor = giv.infoBackgroundColor; + theme.gridAndInfoView.topShadow = recoloredSvgToThemeFile(":/qml/info-top-shadow.svg", giv.infoBackgroundColor, giv.infoBorderColor, giv.infoShadowColor, params.meta.id); + theme.gridAndInfoView.infoShadow = recoloredSvgToThemeFile(":/qml/info-shadow.svg", giv.infoBackgroundColor, giv.infoBorderColor, giv.infoShadowColor, params.meta.id); + theme.gridAndInfoView.infoIndicator = recoloredSvgToThemeFile(":/qml/info-indicator.svg", giv.infoBackgroundColor, giv.infoBorderColor, giv.infoShadowColor, params.meta.id); + theme.gridAndInfoView.infoTextColor = giv.infoTextColor; + theme.gridAndInfoView.infoTitleColor = giv.infoTitleColor; + theme.gridAndInfoView.ratingUnselectedColor = giv.ratingUnselectedColor; + theme.gridAndInfoView.ratingSelectedColor = giv.ratingSelectedColor; + theme.gridAndInfoView.favUncheckedColor = giv.favUncheckedColor; + theme.gridAndInfoView.favCheckedColor = giv.favCheckedColor; + theme.gridAndInfoView.readTickUncheckedColor = giv.readTickUncheckedColor; + theme.gridAndInfoView.readTickCheckedColor = giv.readTickCheckedColor; + theme.gridAndInfoView.currentComicBackgroundColor = giv.currentComicBackgroundColor; + theme.gridAndInfoView.continueReadingBackgroundColor = giv.continueReadingBackgroundColor; + theme.gridAndInfoView.continueReadingColor = giv.continueReadingColor; + theme.gridAndInfoView.backgroundBlurOverlayColor = giv.backgroundBlurOverlayColor; + // end GridAndInfoView // MainToolbar const auto &mt = params.mainToolbarParams; @@ -549,8 +553,8 @@ Theme makeTheme(const ThemeParams ¶ms) // Build icons with Normal and Disabled states auto makeToolbarIcon = [&](const QString &basePath) { QIcon icon; - const QString normalPath = recoloredSvgToThemeFile(basePath, mt.iconColor, params.themeName); - const QString disabledPath = recoloredSvgToThemeFile(basePath, mt.iconDisabledColor, params.themeName, { .suffix = "_disabled" }); + const QString normalPath = recoloredSvgToThemeFile(basePath, mt.iconColor, params.meta.id); + const QString disabledPath = recoloredSvgToThemeFile(basePath, mt.iconDisabledColor, params.meta.id, { .suffix = "_disabled" }); icon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); icon.addFile(disabledPath, QSize(), QIcon::Disabled, QIcon::Off); return icon; @@ -573,16 +577,7 @@ Theme makeTheme(const ThemeParams ¶ms) // Helper to create icons with shadow (two-color: #f0f main, #0ff shadow) // Adds both Normal and Selected modes to prevent Qt from applying a selection tint auto makeSidebarIcon = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, si.iconColor, si.shadowColor, params.themeName); - QIcon icon; - icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); - icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); - return icon; - }; - - // Helper for icons with extra color (three-color: #f0f main, #0ff shadow, #ff0 extra) - auto makeSidebarIconWithExtra = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, si.iconColor, si.shadowColor, si.extraColor, params.themeName); + const QString path = recoloredSvgToThemeFile(basePath, si.iconColor, si.shadowColor, params.meta.id); QIcon icon; icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); @@ -591,7 +586,7 @@ Theme makeTheme(const ThemeParams ¶ms) // Helper for single-color icons (only #f0f main) auto makeSingleColorIcon = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, si.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(basePath, si.iconColor, params.meta.id); QIcon icon; icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); @@ -601,16 +596,19 @@ Theme makeTheme(const ThemeParams ¶ms) // System folder icons flag and overlay theme.sidebarIcons.useSystemFolderIcons = si.useSystemFolderIcons; if (si.useSystemFolderIcons) { - const QString overlayPath = recoloredSvgToThemeFile(":/images/sidebar/folder_read_overlay.svg", si.folderReadOverlayColor, params.themeName); + const QString overlayPath = recoloredSvgToThemeFile(":/images/sidebar/folder_read_overlay.svg", params.navigationTreeParams.folderReadOverlayColor, params.meta.id); theme.sidebarIcons.folderReadOverlay = QPixmap(overlayPath); } - // Folder icons - theme.sidebarIcons.folderIcon = makeSidebarIcon(":/images/sidebar/folder.svg"); - theme.sidebarIcons.folderFinishedIcon = makeSidebarIconWithExtra(":/images/sidebar/folder_finished.svg"); - - // Library icon (unselected state uses sidebar colors) - theme.sidebarIcons.libraryIcon = makeSidebarIcon(":/images/sidebar/libraryIcon.svg"); + // Library icon (unselected state, two-color: #f0f main, #0ff shadow) + { + const auto &li = params.libraryItemParams; + const QString libraryIconPath = recoloredSvgToThemeFile(":/images/sidebar/libraryIcon.svg", li.libraryIconColor, li.libraryIconShadowColor, params.meta.id); + QIcon icon; + icon.addFile(libraryIconPath, QSize(), QIcon::Normal, QIcon::Off); + icon.addFile(libraryIconPath, QSize(), QIcon::Selected, QIcon::Off); + theme.sidebarIcons.libraryIcon = icon; + } // Action icons theme.sidebarIcons.newLibraryIcon = makeSidebarIcon(":/images/sidebar/newLibraryIcon.svg"); @@ -623,9 +621,6 @@ Theme makeTheme(const ThemeParams ¶ms) theme.sidebarIcons.addLabelIcon = makeSidebarIcon(":/images/sidebar/addLabelIcon.svg"); theme.sidebarIcons.renameListIcon = makeSidebarIcon(":/images/sidebar/renameListIcon.svg"); - // Branch icons (paths for QSS) - theme.sidebarIcons.branchClosedIconPath = recoloredSvgToThemeFile(":/images/sidebar/branch-closed.svg", si.iconColor, params.themeName); - theme.sidebarIcons.branchOpenIconPath = recoloredSvgToThemeFile(":/images/sidebar/branch-open.svg", si.iconColor, params.themeName); // end SidebarIcons // LibraryItem @@ -635,30 +630,51 @@ Theme makeTheme(const ThemeParams ¶ms) theme.libraryItem.selectedBackgroundColor = li.selectedBackgroundColor; // Library icon when selected (uses its own color to contrast with selected background) - const QString libraryIconSelectedPath = recoloredSvgToThemeFile(":/images/sidebar/libraryIconSelected.svg", li.libraryIconSelectedColor, params.themeName); + const QString libraryIconSelectedPath = recoloredSvgToThemeFile(":/images/sidebar/libraryIconSelected.svg", li.libraryIconSelectedColor, params.meta.id); theme.libraryItem.libraryIconSelected = QIcon(libraryIconSelectedPath); // Library options icon (shown only when selected, uses its own color) - const QString libraryOptionsPath = recoloredSvgToThemeFile(":/images/sidebar/libraryOptions.svg", li.libraryOptionsIconColor, params.themeName); + const QString libraryOptionsPath = recoloredSvgToThemeFile(":/images/sidebar/libraryOptions.svg", li.libraryOptionsIconColor, params.meta.id); theme.libraryItem.libraryOptionsIcon = QIcon(libraryOptionsPath); // end LibraryItem - // TreeView (must come after SidebarIcons for branch icon paths) - const auto &tv = params.treeViewParams; - if (tv.useStyledTemplate) { - theme.treeView.treeViewQSS = tv.t.styledTreeViewQSS - .arg(tv.textColor.name(), - tv.selectionBackgroundColor.name(), - tv.scrollBackgroundColor.name(), - tv.scrollHandleColor.name(), - tv.selectedTextColor.name(), - theme.sidebarIcons.branchClosedIconPath, - theme.sidebarIcons.branchOpenIconPath); - } else { - theme.treeView.treeViewQSS = tv.t.nativeTreeViewQSS.arg(tv.nativeSelectionColor.name()); + // NavigationTree + const auto &nt = params.navigationTreeParams; + + // Branch indicator icons — own colors, independent of the sidebar icon color + theme.navigationTree.branchClosedIconPath = recoloredSvgToThemeFile(":/images/sidebar/branch-closed.svg", nt.branchIndicatorColor, params.meta.id); + theme.navigationTree.branchOpenIconPath = recoloredSvgToThemeFile(":/images/sidebar/branch-open.svg", nt.branchIndicatorColor, params.meta.id); + theme.navigationTree.branchClosedIconSelectedPath = recoloredSvgToThemeFile(":/images/sidebar/branch-closed.svg", nt.branchIndicatorSelectedColor, params.meta.id, { .suffix = "_selected" }); + theme.navigationTree.branchOpenIconSelectedPath = recoloredSvgToThemeFile(":/images/sidebar/branch-open.svg", nt.branchIndicatorSelectedColor, params.meta.id, { .suffix = "_selected" }); + + theme.navigationTree.navigationTreeQSS = nt.t.navigationTreeQSS + .arg(nt.textColor.name(), + nt.selectionBackgroundColor.name(), + nt.scrollBackgroundColor.name(), + nt.scrollHandleColor.name(), + nt.selectedTextColor.name(), + theme.navigationTree.branchClosedIconPath, + theme.navigationTree.branchOpenIconPath, + theme.navigationTree.branchClosedIconSelectedPath, + theme.navigationTree.branchOpenIconSelectedPath); + theme.navigationTree.folderIndicatorColor = nt.folderIndicatorColor; + + // Folder icon — normal and selected states with independent colors + { + const QString normalPath = recoloredSvgToThemeFile(":/images/sidebar/folder.svg", nt.folderIconColor, nt.folderIconShadowColor, params.meta.id); + const QString selectedPath = recoloredSvgToThemeFile(":/images/sidebar/folder.svg", nt.folderIconSelectedColor, nt.folderIconSelectedShadowColor, params.meta.id, { .suffix = "_selected" }); + theme.navigationTree.folderIcon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); + theme.navigationTree.folderIcon.addFile(selectedPath, QSize(), QIcon::Selected, QIcon::Off); } - theme.treeView.folderIndicatorColor = tv.folderIndicatorColor; - // end TreeView + + // Folder finished icon — same but with tick (#ff0) recolored independently for each state + { + const QString normalPath = recoloredSvgToThemeFile(":/images/sidebar/folder_finished.svg", nt.folderIconColor, nt.folderIconShadowColor, nt.folderReadOverlayColor, params.meta.id); + const QString selectedPath = recoloredSvgToThemeFile(":/images/sidebar/folder_finished.svg", nt.folderIconSelectedColor, nt.folderIconSelectedShadowColor, nt.folderReadOverlaySelectedColor, params.meta.id, { .suffix = "_selected" }); + theme.navigationTree.folderFinishedIcon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); + theme.navigationTree.folderFinishedIcon.addFile(selectedPath, QSize(), QIcon::Selected, QIcon::Off); + } + // end NavigationTree // ContentSplitter const auto &cs = params.contentSplitterParams; @@ -675,14 +691,17 @@ Theme makeTheme(const ThemeParams ¶ms) // Helper to create single-color icons for comics view toolbar auto makeComicsViewIcon = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, cvt.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(basePath, cvt.iconColor, params.meta.id); QIcon icon; icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); return icon; }; - theme.comicsViewToolbar.toolbarQSS = cvt.t.toolbarQSS.arg(cvt.checkedBackgroundColor.name()); + theme.comicsViewToolbar.toolbarQSS = cvt.t.toolbarQSS.arg( + cvt.backgroundColor.name(), + cvt.separatorColor.name(), + cvt.checkedBackgroundColor.name()); theme.comicsViewToolbar.openInYACReaderIcon = makeComicsViewIcon(":/images/comics_view_toolbar/openInYACReader.svg"); theme.comicsViewToolbar.setAsReadIcon = makeComicsViewIcon(":/images/comics_view_toolbar/setReadButton.svg"); theme.comicsViewToolbar.setAsUnreadIcon = makeComicsViewIcon(":/images/comics_view_toolbar/setUnread.svg"); @@ -711,8 +730,8 @@ Theme makeTheme(const ThemeParams ¶ms) theme.searchLineEdit.clearButtonQSS = sle.t.clearButtonQSS; const qreal dpr = qApp->devicePixelRatio(); - theme.searchLineEdit.searchIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/iconSearchNew.svg", sle.iconColor, params.themeName), 15, dpr); - theme.searchLineEdit.clearIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/clearSearchNew.svg", sle.iconColor, params.themeName), 12, dpr); + theme.searchLineEdit.searchIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/iconSearchNew.svg", sle.iconColor, params.meta.id), 15, dpr); + theme.searchLineEdit.clearIcon = renderSvgToPixmap(recoloredSvgToThemeFile(":/images/clearSearchNew.svg", sle.iconColor, params.meta.id), 12, dpr); // end SearchLineEdit // ReadingListIcons @@ -720,10 +739,11 @@ Theme makeTheme(const ThemeParams ¶ms) // Helper to create label icons from template (uses color name to generate label_.svg files) auto makeLabelIcon = [&](const QString &colorName, const QColor &mainColor) { - const QString path = recoloredSvgToThemeFile(":/images/lists/label_template.svg", mainColor, rli.labelShadowColor, params.themeName, { .fileName = "label_" + colorName }); + const QString normalPath = recoloredSvgToThemeFile(":/images/lists/label_template.svg", mainColor, rli.labelShadowColor, params.meta.id, { .fileName = "label_" + colorName }); + const QString selectedPath = recoloredSvgToThemeFile(":/images/lists/label_template.svg", mainColor, rli.labelShadowSelectedColor, params.meta.id, { .suffix = "_selected", .fileName = "label_" + colorName }); QIcon icon; - icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); - icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); + icon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); + icon.addFile(selectedPath, QSize(), QIcon::Selected, QIcon::Off); return icon; }; @@ -732,32 +752,35 @@ Theme makeTheme(const ThemeParams ¶ms) } // Special list icons - auto makeSpecialIcon = [&](const QString &basePath, const QColor &mainColor) { - const QString path = recoloredSvgToThemeFile(basePath, mainColor, rli.specialListShadowColor, params.themeName); + auto makeSpecialIcon = [&](const QString &basePath, const QColor &mainColor, const QColor &mainSelectedColor) { + const QString normalPath = recoloredSvgToThemeFile(basePath, mainColor, rli.specialListShadowColor, params.meta.id); + const QString selectedPath = recoloredSvgToThemeFile(basePath, mainSelectedColor, rli.specialListShadowSelectedColor, params.meta.id, { .suffix = "_selected" }); QIcon icon; - icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); - icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); + icon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); + icon.addFile(selectedPath, QSize(), QIcon::Selected, QIcon::Off); return icon; }; - theme.readingListIcons.readingListIcon = makeSpecialIcon(":/images/lists/default_0.svg", rli.readingListMainColor); - theme.readingListIcons.favoritesIcon = makeSpecialIcon(":/images/lists/default_1.svg", rli.favoritesMainColor); + theme.readingListIcons.readingListIcon = makeSpecialIcon(":/images/lists/default_0.svg", rli.readingListMainColor, rli.readingListMainSelectedColor); + theme.readingListIcons.favoritesIcon = makeSpecialIcon(":/images/lists/default_1.svg", rli.favoritesMainColor, rli.favoritesMainSelectedColor); // Currently reading has 3 colors { - const QString path = recoloredSvgToThemeFile(":/images/lists/default_2.svg", rli.currentlyReadingMainColor, rli.specialListShadowColor, rli.currentlyReadingOuterColor, params.themeName); + const QString normalPath = recoloredSvgToThemeFile(":/images/lists/default_2.svg", rli.currentlyReadingMainColor, rli.specialListShadowColor, rli.currentlyReadingOuterColor, params.meta.id); + const QString selectedPath = recoloredSvgToThemeFile(":/images/lists/default_2.svg", rli.currentlyReadingMainSelectedColor, rli.specialListShadowSelectedColor, rli.currentlyReadingOuterSelectedColor, params.meta.id, { .suffix = "_selected" }); QIcon icon; - icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); - icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); + icon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); + icon.addFile(selectedPath, QSize(), QIcon::Selected, QIcon::Off); theme.readingListIcons.currentlyReadingIcon = icon; } // List icon (3 colors) { - const QString path = recoloredSvgToThemeFile(":/images/lists/list.svg", rli.listMainColor, rli.listShadowColor, rli.listDetailColor, params.themeName); + const QString normalPath = recoloredSvgToThemeFile(":/images/lists/list.svg", rli.listMainColor, rli.listShadowColor, rli.listDetailColor, params.meta.id); + const QString selectedPath = recoloredSvgToThemeFile(":/images/lists/list.svg", rli.listMainSelectedColor, rli.listShadowSelectedColor, rli.listDetailSelectedColor, params.meta.id, { .suffix = "_selected" }); QIcon icon; - icon.addFile(path, QSize(), QIcon::Normal, QIcon::Off); - icon.addFile(path, QSize(), QIcon::Selected, QIcon::Off); + icon.addFile(normalPath, QSize(), QIcon::Normal, QIcon::Off); + icon.addFile(selectedPath, QSize(), QIcon::Selected, QIcon::Off); theme.readingListIcons.listIcon = icon; } // end ReadingListIcons @@ -765,7 +788,7 @@ Theme makeTheme(const ThemeParams ¶ms) // MenuIcons const auto &mi = params.menuIconsParams; auto makeMenuIcon = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, mi.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(basePath, mi.iconColor, params.meta.id); return QIcon(path); }; @@ -784,7 +807,7 @@ Theme makeTheme(const ThemeParams ¶ms) // DialogIcons const auto &di = params.dialogIconsParams; auto makeDialogIcon = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, di.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(basePath, di.iconColor, params.meta.id); return QPixmap(path); }; theme.dialogIcons.newLibraryIcon = makeDialogIcon(":/images/library_dialogs/new.svg"); @@ -795,7 +818,7 @@ Theme makeTheme(const ThemeParams ¶ms) theme.dialogIcons.exportLibraryIcon = makeDialogIcon(":/images/library_dialogs/exportLibrary.svg"); theme.dialogIcons.importLibraryIcon = makeDialogIcon(":/images/library_dialogs/importLibrary.svg"); { - const QString path = recoloredSvgToThemeFile(":/images/find_folder.svg", di.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(":/images/find_folder.svg", di.iconColor, params.meta.id); const qreal dpr = qApp->devicePixelRatio(); theme.dialogIcons.findFolderIcon = QIcon(renderSvgToPixmap(path, 13, 13, dpr)); } @@ -804,7 +827,7 @@ Theme makeTheme(const ThemeParams ¶ms) // ShortcutsIcons const auto &sci = params.shortcutsIconsParams; auto makeShortcutsIcon = [&](const QString &basePath) { - const QString path = recoloredSvgToThemeFile(basePath, sci.iconColor, params.themeName); + const QString path = recoloredSvgToThemeFile(basePath, sci.iconColor, params.meta.id); return QIcon(path); }; @@ -825,791 +848,326 @@ Theme makeTheme(const ThemeParams ¶ms) theme.serverConfigDialog.checkBoxQSS = scd.t.checkBoxQSS.arg(scd.checkBoxTextColor.name()); theme.serverConfigDialog.qrBackgroundColor = scd.qrBackgroundColor; theme.serverConfigDialog.qrForegroundColor = scd.qrForegroundColor; - theme.serverConfigDialog.backgroundDecoration = QPixmap(recoloredSvgToThemeFile(":/images/serverConfigBackground.svg", scd.decorationColor, params.themeName)); + theme.serverConfigDialog.backgroundDecoration = QPixmap(recoloredSvgToThemeFile(":/images/serverConfigBackground.svg", scd.decorationColor, params.meta.id)); + + theme.meta = params.meta; return theme; } -ThemeParams classicThemeParams(); -ThemeParams lightThemeParams(); -ThemeParams darkThemeParams(); +// JSON helpers --------------------------------------------------------------- -Theme makeTheme(ThemeId themeId) +static QColor colorFromJson(const QJsonObject &obj, const QString &key, const QColor &fallback) { + if (!obj.contains(key)) + return fallback; + QColor c(obj[key].toString()); + return c.isValid() ? c : fallback; +} - switch (themeId) { - case ThemeId::Classic: - return makeTheme(classicThemeParams()); - case ThemeId::Light: - return makeTheme(lightThemeParams()); - case ThemeId::Dark: - return makeTheme(darkThemeParams()); +Theme makeTheme(const QJsonObject &json) +{ + ThemeParams p; + + if (json.contains("comicFlow")) { + const auto o = json["comicFlow"].toObject(); + p.comicFlowColors.backgroundColor = colorFromJson(o, "backgroundColor", p.comicFlowColors.backgroundColor); + p.comicFlowColors.textColor = colorFromJson(o, "textColor", p.comicFlowColors.textColor); } - return makeTheme(classicThemeParams()); -} - -ThemeParams classicThemeParams() -{ - ThemeParams params; - params.themeName = "classic"; - params.defaultContentBackgroundColor = QColor(0x2A2A2A); - - ComicFlowColors cf; - cf.backgroundColor = Qt::black; - cf.textColor = QColor(0x4C4C4C); - - ComicVineParams cv; - cv.contentTextColor = Qt::white; - cv.contentBackgroundColor = QColor(0x2B2B2B); - cv.contentAltBackgroundColor = QColor(0x2B2B2B); - cv.dialogBackgroundColor = QColor(0x404040); - - cv.tableBackgroundColor = QColor(0x2B2B2B); - cv.tableAltBackgroundColor = QColor(0x2E2E2E); - cv.tableBorderColor = QColor(0x242424); - cv.tableSelectedColor = QColor(0x555555); - cv.tableHeaderBackgroundColor = QColor(0x292929); - cv.tableHeaderBorderColor = QColor(0x1F1F1F); - cv.tableHeaderTextColor = QColor(0xEBEBEB); - cv.tableScrollHandleColor = QColor(0xDDDDDD); - cv.tableScrollBackgroundColor = QColor(0x404040); - cv.tableSectionBorderLight = QColor(0xFEFEFE); - cv.tableSectionBorderDark = QColor(0xDFDFDF); - - cv.labelTextColor = Qt::white; - cv.labelBackgroundColor = QColor(0x2B2B2B); - cv.hyperlinkColor = QColor(0xFFCC00); - - cv.buttonBackgroundColor = QColor(0x2E2E2E); - cv.buttonTextColor = Qt::white; - cv.buttonBorderColor = QColor(0x242424); - - cv.radioUncheckedPath = ":/images/comic_vine/radioUnchecked.svg"; - cv.radioUncheckedColor = QColor(0xE5E5E5); - - cv.radioCheckedPath = ":/images/comic_vine/radioChecked.svg"; - cv.radioCheckedBackgroundColor = QColor(0xE5E5E5); - cv.radioCheckedIndicatorColor = QColor(0x5F5F5F); - - cv.checkBoxTickColor = Qt::white; - - cv.toolButtonAccentColor = QColor(0x282828); - - cv.downArrowColor = QColor(0x9F9F9F); - cv.upArrowColor = QColor(0x9F9F9F); - - cv.busyIndicatorColor = Qt::white; - cv.navIconColor = Qt::white; - cv.rowIconColor = QColor(0xE5E5E5); - - cv.t = ComicVineThemeTemplates(); - - params.comicFlowColors = cf; - params.comicVineParams = cv; - - params.helpAboutDialogParams.headingColor = QColor(0x302f2d); - params.helpAboutDialogParams.linkColor = QColor(0xC19441); - - WhatsNewDialogParams wnp; - wnp.backgroundColor = QColor(0x2A2A2A); - wnp.headerTextColor = QColor(0xE0E0E0); - wnp.versionTextColor = QColor(0x858585); - wnp.contentTextColor = QColor(0xE0E0E0); - wnp.linkColor = QColor(0xE8B800); - wnp.closeButtonColor = QColor(0xDDDDDD); - wnp.headerDecorationColor = QColor(0xE8B800); - params.whatsNewDialogParams = wnp; - - EmptyContainerParams ec; - ec.backgroundColor = QColor(0x2A2A2A); - ec.titleTextColor = QColor(0xCCCCCC); - ec.textColor = QColor(0xCCCCCC); - ec.descriptionTextColor = QColor(0xAAAAAA); - ec.searchIconColor = QColor(0x4C4C4C); - ec.t = EmptyContainerThemeTemplates(); - params.emptyContainerParams = ec; - - SidebarParams sb; - sb.useStyledSplitter = true; - sb.backgroundColor = QColor(0x454545); - sb.separatorColor = QColor(0xBDBFBF); - sb.sectionSeparatorColor = QColor(0x575757); - sb.splitterBackgroundColor = QColor(0x454545); - sb.splitterHeight = 39; - sb.uppercaseLabels = true; - sb.titleTextColor = QColor(0xBDBFBF); - sb.titleDropShadowColor = Qt::black; - sb.busyIndicatorColor = Qt::white; - params.sidebarParams = sb; - - ImportWidgetParams iw; - iw.backgroundColor = QColor(0x2A2A2A); - iw.titleTextColor = QColor(0xCCCCCC); - iw.descriptionTextColor = QColor(0xAAAAAA); - iw.currentComicTextColor = QColor(0xAAAAAA); - iw.coversViewBackgroundColor = QColor(0x3A3A3A); - iw.coversLabelColor = QColor(0xAAAAAA); - iw.coversDecorationBgColor = QColor(0x3A3A3A); - iw.coversDecorationShadowColor = QColor(0x1A1A1A); - iw.modeIconColor = QColor(0x4A4A4A); - iw.iconColor = QColor(0xCCCCCC); - iw.iconCheckedColor = QColor(0xAAAAAA); - params.importWidgetParams = iw; - - TreeViewParams tv; - tv.useStyledTemplate = true; - tv.textColor = QColor(0xDDDFDF); - tv.selectionBackgroundColor = QColor(0x2E2E2E); - tv.scrollBackgroundColor = QColor(0x404040); - tv.scrollHandleColor = QColor(0xDDDDDD); - tv.selectedTextColor = Qt::white; - tv.folderIndicatorColor = QColor(237, 197, 24); - params.treeViewParams = tv; - - TableViewParams tav; - tav.alternateBackgroundColor = QColor(0xF2F2F2); - tav.backgroundColor = QColor(0xFAFAFA); - tav.cornerButtonBackgroundColor = QColor(0xF5F5F5); - tav.cornerButtonBorderColor = QColor(0xB8BDC4); - tav.cornerButtonGradientColor = QColor(0xD1D1D1); - tav.itemBorderBottomColor = QColor(0xDFDFDF); - tav.itemBorderTopColor = QColor(0xFEFEFE); - tav.itemTextColor = QColor(0x252626); - tav.selectedColor = QColor(0xD4D4D4); - tav.selectedTextColor = QColor(0x252626); - tav.headerTextColor = QColor(0x313232); - tav.starRatingColor = QColor(0xE9BE0F); - tav.starRatingSelectedColor = QColor(0xFFFFFF); - tav.t = TableViewThemeTemplates(); - params.tableViewParams = tav; - - QmlViewParams qv; - qv.backgroundColor = QColor(0x2A2A2A); - qv.cellColor = QColor(0x212121); - qv.cellColorWithBackground = QColor(0x21, 0x21, 0x21, 0x99); - qv.selectedColor = QColor(0x121212); - qv.selectedBorderColor = QColor(0xFFCC00); - qv.borderColor = QColor(0x121212); - qv.titleColor = QColor(0xFFFFFF); - qv.textColor = QColor(0xA8A8A8); - qv.showDropShadow = true; - qv.infoBackgroundColor = QColor(0x2E2E2E); - qv.topShadow = "info-top-shadow.png"; - qv.infoShadow = "info-shadow.png"; - qv.infoIndicator = "info-indicator.png"; - qv.infoTextColor = QColor(0xB0B0B0); - qv.infoTitleColor = QColor(0xFFFFFF); - qv.ratingUnselectedColor = QColor(0x1C1C1C); - qv.ratingSelectedColor = QColor(0xFFFFFF); - qv.favUncheckedColor = QColor(0x1C1C1C); - qv.favCheckedColor = QColor(0xE84852); - qv.readTickUncheckedColor = QColor(0x1C1C1C); - qv.readTickCheckedColor = QColor(0xE84852); - qv.currentComicBackgroundColor = QColor(0x00, 0x00, 0x00, 0x88); - qv.continueReadingBackgroundColor = QColor(0x00, 0x00, 0x00, 0x88); - qv.continueReadingColor = QColor(0xFFFFFF); - params.qmlViewParams = qv; - - MainToolbarParams mt; - mt.backgroundColor = QColor(0xF0F0F0); - mt.folderNameColor = QColor(0x404040); - mt.dividerColor = QColor(0xB8BDC4); - mt.iconColor = QColor(0x404040); - mt.iconDisabledColor = QColor(0xB0B0B0); - params.mainToolbarParams = mt; - - ContentSplitterParams cs; - cs.handleColor = QColor(0xB8B8B8); - cs.horizontalHandleHeight = 4; - cs.verticalHandleWidth = 4; - params.contentSplitterParams = cs; - - SidebarIconsParams si; - si.iconColor = QColor(0xE0E0E0); - si.shadowColor = QColor(0xFF000000); - si.extraColor = QColor(0x464646); - si.useSystemFolderIcons = false; - params.sidebarIconsParams = si; - - LibraryItemParams li; - li.textColor = QColor(0xDDDFDF); - li.selectedTextColor = Qt::white; - li.selectedBackgroundColor = QColor(0x2E2E2E); - li.libraryIconSelectedColor = Qt::white; - li.libraryOptionsIconColor = Qt::white; - params.libraryItemParams = li; - - ComicsViewToolbarParams cvt; - cvt.checkedBackgroundColor = QColor(0xCCCCCC); - cvt.iconColor = QColor(0x404040); - params.comicsViewToolbarParams = cvt; - - SearchLineEditParams sle; - sle.textColor = QColor(0xABABAB); - sle.backgroundColor = QColor(0x404040); - sle.iconColor = QColor(0xF7F7F7); - params.searchLineEditParams = sle; - - ReadingListIconsParams rli; - rli.labelColors = { - { "red", QColor(0xf67a7b) }, - { "orange", QColor(0xf5c240) }, - { "yellow", QColor(0xf2e446) }, - { "green", QColor(0xade738) }, - { "cyan", QColor(0xa0fddb) }, - { "blue", QColor(0x82c7ff) }, - { "violet", QColor(0x8f95ff) }, - { "purple", QColor(0xd692fc) }, - { "pink", QColor(0xfd9cda) }, - { "white", QColor(0xfcfcfc) }, - { "light", QColor(0xcbcbcb) }, - { "dark", QColor(0xb7b7b7) } - }; - rli.labelShadowColor = Qt::black; - rli.readingListMainColor = QColor(0xe7e7e7); - rli.favoritesMainColor = QColor(0xe15055); - rli.currentlyReadingMainColor = QColor(0xffcc00); - rli.currentlyReadingOuterColor = Qt::black; - rli.specialListShadowColor = Qt::black; - rli.listMainColor = QColor(0xe7e7e7); - rli.listShadowColor = Qt::black; - rli.listDetailColor = QColor(0x464646); - params.readingListIconsParams = rli; - - MenuIconsParams mi; - mi.iconColor = QColor(0xF7F7F7); - params.menuIconsParams = mi; - - DialogIconsParams dip; - dip.iconColor = mi.iconColor; - params.dialogIconsParams = dip; - - ShortcutsIconsParams sci; - sci.iconColor = QColor(0xF7F7F7); - params.shortcutsIconsParams = sci; - - ServerConfigDialogParams scd; - scd.backgroundColor = QColor(0x2A2A2A); - scd.titleTextColor = QColor(0x474747); - scd.qrMessageTextColor = QColor(0xA3A3A3); - scd.propagandaTextColor = QColor(0x4D4D4D); - scd.labelTextColor = QColor(0x575757); - scd.checkBoxTextColor = QColor(0x262626); - scd.qrBackgroundColor = QColor(0x2A2A2A); - scd.qrForegroundColor = Qt::white; - scd.decorationColor = QColor(0xF7F7F7); - params.serverConfigDialogParams = scd; - - return params; -} - -ThemeParams lightThemeParams() -{ - ThemeParams params; - params.themeName = "light"; - params.defaultContentBackgroundColor = QColor(0xFFFFFF); - - ComicFlowColors cf; - cf.backgroundColor = Qt::white; - cf.textColor = Qt::black; - - ComicVineParams cv; - cv.contentTextColor = Qt::black; - cv.contentBackgroundColor = QColor(0xECECEC); - cv.contentAltBackgroundColor = QColor(0xE0E0E0); - cv.dialogBackgroundColor = QColor(0xFBFBFB); - - cv.tableBackgroundColor = QColor(0xF4F4F4); - cv.tableAltBackgroundColor = QColor(0xFAFAFA); - cv.tableBorderColor = QColor(0xCCCCCC); - cv.tableSelectedColor = QColor(0xDDDDDD); - cv.tableHeaderBackgroundColor = QColor(0xE0E0E0); - cv.tableHeaderBorderColor = QColor(0xC0C0C0); - cv.tableHeaderTextColor = QColor(0x333333); - cv.tableScrollHandleColor = QColor(0x888888); - cv.tableScrollBackgroundColor = QColor(0xD0D0D0); - cv.tableSectionBorderLight = QColor(0xFFFFFF); - cv.tableSectionBorderDark = QColor(0xCCCCCC); - - cv.labelTextColor = Qt::black; - cv.labelBackgroundColor = QColor(0xECECEC); - cv.hyperlinkColor = QColor(0xFFCC00); - - cv.buttonBackgroundColor = QColor(0xE0E0E0); - cv.buttonTextColor = Qt::black; - cv.buttonBorderColor = QColor(0xCCCCCC); - - cv.radioUncheckedPath = ":/images/comic_vine/radioUnchecked.svg"; - cv.radioUncheckedColor = QColor(0xE0E0E0); - - cv.radioCheckedPath = ":/images/comic_vine/radioChecked.svg"; - cv.radioCheckedBackgroundColor = QColor(0xE0E0E0); - cv.radioCheckedIndicatorColor = QColor(0x222222); - - cv.checkBoxTickColor = Qt::black; - - cv.toolButtonAccentColor = QColor(0xA0A0A0); - - cv.downArrowColor = QColor(0x222222); - cv.upArrowColor = QColor(0x222222); - - cv.busyIndicatorColor = Qt::black; - cv.navIconColor = QColor(0x222222); - cv.rowIconColor = QColor(0x222222); - - cv.t = ComicVineThemeTemplates(); - - params.comicFlowColors = cf; - params.comicVineParams = cv; - - params.helpAboutDialogParams.headingColor = QColor(0x302f2d); - params.helpAboutDialogParams.linkColor = QColor(0xC19441); - - WhatsNewDialogParams wnp; - wnp.backgroundColor = QColor(0xFFFFFF); - wnp.headerTextColor = QColor(0x0A0A0A); - wnp.versionTextColor = QColor(0x858585); - wnp.contentTextColor = QColor(0x0A0A0A); - wnp.linkColor = QColor(0xE8B800); - wnp.closeButtonColor = QColor(0x444444); - wnp.headerDecorationColor = QColor(0xE8B800); - params.whatsNewDialogParams = wnp; - - EmptyContainerParams ec; - ec.backgroundColor = QColor(0xFFFFFF); - ec.titleTextColor = QColor(0x888888); - ec.textColor = QColor(0x495252); - ec.descriptionTextColor = QColor(0x565959); - ec.searchIconColor = QColor(0xCCCCCC); - ec.t = EmptyContainerThemeTemplates(); - params.emptyContainerParams = ec; - - SidebarParams sb; - sb.useStyledSplitter = true; - sb.backgroundColor = QColor(0xF1F1F1); - sb.separatorColor = QColor(0x808080); - sb.sectionSeparatorColor = QColor(0xD0D0D0); - sb.splitterBackgroundColor = QColor(0xF1F1F1); - sb.splitterHeight = 39; - sb.uppercaseLabels = true; - sb.titleTextColor = QColor(0x808080); - sb.titleDropShadowColor = QColor(0xFFFFFF); - sb.busyIndicatorColor = QColor(0x808080); - params.sidebarParams = sb; - - ImportWidgetParams iw; - iw.backgroundColor = QColor(0xFAFAFA); - iw.titleTextColor = QColor(0x495252); - iw.descriptionTextColor = QColor(0x565959); - iw.currentComicTextColor = QColor(0x565959); - iw.coversViewBackgroundColor = QColor(0xE6E6E6); - iw.coversLabelColor = QColor(0x565959); - iw.coversDecorationBgColor = QColor(0xE6E6E6); - iw.coversDecorationShadowColor = QColor(0xA1A1A1); - iw.modeIconColor = QColor(0xE6E6E6); - iw.iconColor = QColor(0x495252); - iw.iconCheckedColor = QColor(0x565959); - params.importWidgetParams = iw; - - TreeViewParams tv; - tv.useStyledTemplate = true; - tv.textColor = Qt::black; - tv.selectionBackgroundColor = QColor(0xD0D0D0); - tv.scrollBackgroundColor = QColor(0xE0E0E0); - tv.scrollHandleColor = QColor(0x888888); - tv.selectedTextColor = QColor(0x1A1A1A); - tv.folderIndicatorColor = QColor(85, 95, 127); - params.treeViewParams = tv; - - TableViewParams tav; - tav.alternateBackgroundColor = QColor(0xF2F2F2); - tav.backgroundColor = QColor(0xFAFAFA); - tav.cornerButtonBackgroundColor = QColor(0xF5F5F5); - tav.cornerButtonBorderColor = QColor(0xB8BDC4); - tav.cornerButtonGradientColor = QColor(0xD1D1D1); - tav.itemBorderBottomColor = QColor(0xDFDFDF); - tav.itemBorderTopColor = QColor(0xFEFEFE); - tav.itemTextColor = QColor(0x252626); - tav.selectedColor = QColor(0x3875D7); - tav.selectedTextColor = QColor(0xFFFFFF); - tav.headerTextColor = QColor(0x313232); - tav.starRatingColor = QColor(0xE9BE0F); - tav.starRatingSelectedColor = QColor(0xFFFFFF); - tav.t = TableViewThemeTemplates(); - params.tableViewParams = tav; - - QmlViewParams qv; - qv.backgroundColor = QColor(0xF6F6F6); - qv.cellColor = QColor(0xFFFFFF); - qv.cellColorWithBackground = QColor(0xFF, 0xFF, 0xFF, 0x99); - qv.selectedColor = QColor(0xFFFFFF); - qv.selectedBorderColor = QColor(0xFFCC00); - qv.borderColor = QColor(0xDBDBDB); - qv.titleColor = QColor(0x121212); - qv.textColor = QColor(0x636363); - qv.showDropShadow = true; - qv.infoBackgroundColor = QColor(0xFFFFFF); - qv.topShadow = ""; - qv.infoShadow = "info-shadow-light.png"; - qv.infoIndicator = "info-indicator-light.png"; - qv.infoTextColor = QColor(0x404040); - qv.infoTitleColor = QColor(0x2E2E2E); - qv.ratingUnselectedColor = QColor(0xDEDEDE); - qv.ratingSelectedColor = QColor(0x2B2B2B); - qv.favUncheckedColor = QColor(0xDEDEDE); - qv.favCheckedColor = QColor(0xE84852); - qv.readTickUncheckedColor = QColor(0xDEDEDE); - qv.readTickCheckedColor = QColor(0xE84852); - qv.currentComicBackgroundColor = QColor(0xFF, 0xFF, 0xFF, 0x88); - qv.continueReadingBackgroundColor = QColor(0xE8E8E8); - qv.continueReadingColor = QColor::fromRgb(0x000000); - params.qmlViewParams = qv; - - MainToolbarParams mt; - mt.backgroundColor = QColor(0xF0F0F0); - mt.folderNameColor = QColor(0x404040); - mt.dividerColor = QColor(0xB8BDC4); - mt.iconColor = QColor(0x404040); - mt.iconDisabledColor = QColor(0xB0B0B0); - params.mainToolbarParams = mt; - - ContentSplitterParams cs; - cs.handleColor = QColor(0xB8B8B8); - cs.horizontalHandleHeight = 4; - cs.verticalHandleWidth = 4; - params.contentSplitterParams = cs; - - SidebarIconsParams si; - si.iconColor = QColor(0x606060); - si.shadowColor = QColor(0xFFFFFF); - si.extraColor = QColor(0xFFFFFF); - si.useSystemFolderIcons = false; - params.sidebarIconsParams = si; - - LibraryItemParams li; - li.textColor = QColor(0x404040); - li.selectedTextColor = QColor(0x1A1A1A); - li.selectedBackgroundColor = QColor(0xD0D0D0); - li.libraryIconSelectedColor = QColor(0x404040); - li.libraryOptionsIconColor = QColor(0x404040); - params.libraryItemParams = li; - - ComicsViewToolbarParams cvt; - cvt.checkedBackgroundColor = QColor(0xCCCCCC); - cvt.iconColor = QColor(0x404040); - params.comicsViewToolbarParams = cvt; - - SearchLineEditParams sle; - sle.textColor = QColor(0x606060); - sle.backgroundColor = QColor(0xE0E0E0); - sle.iconColor = QColor(0x808080); - params.searchLineEditParams = sle; - - ReadingListIconsParams rli; - rli.labelColors = { - { "red", QColor(0xf67a7b) }, - { "orange", QColor(0xf5c240) }, - { "yellow", QColor(0xf2e446) }, - { "green", QColor(0xade738) }, - { "cyan", QColor(0xa0fddb) }, - { "blue", QColor(0x82c7ff) }, - { "violet", QColor(0x8f95ff) }, - { "purple", QColor(0xd692fc) }, - { "pink", QColor(0xfd9cda) }, - { "white", QColor(0xfcfcfc) }, - { "light", QColor(0xcbcbcb) }, - { "dark", QColor(0xb7b7b7) } - }; - rli.labelShadowColor = QColor(0xa0a0a0); - rli.readingListMainColor = QColor(0x808080); - rli.favoritesMainColor = QColor(0xe15055); - rli.currentlyReadingMainColor = QColor(0xffcc00); - rli.currentlyReadingOuterColor = Qt::black; - rli.specialListShadowColor = QColor(0xa0a0a0); - rli.listMainColor = QColor(0x808080); - rli.listShadowColor = QColor(0xc0c0c0); - rli.listDetailColor = QColor(0xFFFFFF); - params.readingListIconsParams = rli; - - MenuIconsParams mi; - mi.iconColor = QColor(0x606060); - params.menuIconsParams = mi; - - DialogIconsParams dip; - dip.iconColor = mi.iconColor; - params.dialogIconsParams = dip; - - ShortcutsIconsParams sci; - sci.iconColor = QColor(0x606060); - params.shortcutsIconsParams = sci; - - ServerConfigDialogParams scd; - scd.backgroundColor = QColor(0xFFFFFF); - scd.titleTextColor = QColor(0x474747); - scd.qrMessageTextColor = QColor(0xA3A3A3); - scd.propagandaTextColor = QColor(0x4D4D4D); - scd.labelTextColor = QColor(0x575757); - scd.checkBoxTextColor = QColor(0x262626); - scd.qrBackgroundColor = Qt::white; - scd.qrForegroundColor = QColor(0x606060); - scd.decorationColor = QColor(0x606060); - params.serverConfigDialogParams = scd; - - return params; -} - -ThemeParams darkThemeParams() -{ - ThemeParams params; - params.themeName = "dark"; - params.defaultContentBackgroundColor = QColor(0x2A2A2A); - - ComicFlowColors cf; - cf.backgroundColor = QColor(0x111111); - cf.textColor = QColor(0x888888); - - ComicVineParams cv; - cv.contentTextColor = Qt::white; - cv.contentBackgroundColor = QColor(0x2B2B2B); - cv.contentAltBackgroundColor = QColor(0x2E2E2E); - cv.dialogBackgroundColor = QColor(0x404040); - - cv.tableBackgroundColor = QColor(0x2B2B2B); - cv.tableAltBackgroundColor = QColor(0x2E2E2E); - cv.tableBorderColor = QColor(0x242424); - cv.tableSelectedColor = QColor(0x555555); - cv.tableHeaderBackgroundColor = QColor(0x292929); - cv.tableHeaderBorderColor = QColor(0x1F1F1F); - cv.tableHeaderTextColor = QColor(0xEBEBEB); - cv.tableScrollHandleColor = QColor(0xDDDDDD); - cv.tableScrollBackgroundColor = QColor(0x404040); - cv.tableSectionBorderLight = QColor(0xFEFEFE); - cv.tableSectionBorderDark = QColor(0xDFDFDF); - - cv.labelTextColor = Qt::white; - cv.labelBackgroundColor = QColor(0x2B2B2B); - cv.hyperlinkColor = QColor(0xFFCC00); - - cv.buttonBackgroundColor = QColor(0x2E2E2E); - cv.buttonTextColor = Qt::white; - cv.buttonBorderColor = QColor(0x242424); - - cv.radioUncheckedPath = ":/images/comic_vine/radioUnchecked.svg"; - cv.radioUncheckedColor = QColor(0xE5E5E5); - - cv.radioCheckedPath = ":/images/comic_vine/radioChecked.svg"; - cv.radioCheckedBackgroundColor = QColor(0xE5E5E5); - cv.radioCheckedIndicatorColor = QColor(0x5F5F5F); - - cv.checkBoxTickColor = Qt::white; - - cv.toolButtonAccentColor = QColor(0x282828); - - cv.downArrowColor = QColor(0x9F9F9F); - cv.upArrowColor = QColor(0x9F9F9F); - - cv.busyIndicatorColor = Qt::white; - cv.navIconColor = Qt::white; - cv.rowIconColor = QColor(0xE5E5E5); - - cv.t = ComicVineThemeTemplates(); - - params.comicFlowColors = cf; - params.comicVineParams = cv; - - params.helpAboutDialogParams.headingColor = QColor(0xE0E0E0); - params.helpAboutDialogParams.linkColor = QColor(0xD4A84B); - - WhatsNewDialogParams wnp; - wnp.backgroundColor = QColor(0x2A2A2A); - wnp.headerTextColor = QColor(0xE0E0E0); - wnp.versionTextColor = QColor(0x858585); - wnp.contentTextColor = QColor(0xE0E0E0); - wnp.linkColor = QColor(0xE8B800); - wnp.closeButtonColor = QColor(0xDDDDDD); - wnp.headerDecorationColor = QColor(0xE8B800); - params.whatsNewDialogParams = wnp; - - EmptyContainerParams ec; - ec.backgroundColor = QColor(0x2A2A2A); - ec.titleTextColor = QColor(0xCCCCCC); - ec.textColor = QColor(0xCCCCCC); - ec.descriptionTextColor = QColor(0xAAAAAA); - ec.searchIconColor = QColor(0x4C4C4C); - ec.t = EmptyContainerThemeTemplates(); - params.emptyContainerParams = ec; - - SidebarParams sb; - sb.useStyledSplitter = true; - sb.backgroundColor = QColor(0x454545); - sb.separatorColor = QColor(0xBDBFBF); - sb.sectionSeparatorColor = QColor(0x575757); - sb.splitterBackgroundColor = QColor(0x454545); - sb.splitterHeight = 39; - sb.uppercaseLabels = true; - sb.titleTextColor = QColor(0xBDBFBF); - sb.titleDropShadowColor = Qt::black; - sb.busyIndicatorColor = Qt::white; - params.sidebarParams = sb; - - ImportWidgetParams iw; - iw.backgroundColor = QColor(0x2A2A2A); - iw.titleTextColor = QColor(0xCCCCCC); - iw.descriptionTextColor = QColor(0xAAAAAA); - iw.currentComicTextColor = QColor(0xAAAAAA); - iw.coversViewBackgroundColor = QColor(0x3A3A3A); - iw.coversLabelColor = QColor(0xAAAAAA); - iw.coversDecorationBgColor = QColor(0x3A3A3A); - iw.coversDecorationShadowColor = QColor(0x1A1A1A); - iw.modeIconColor = QColor(0x4A4A4A); - iw.iconColor = QColor(0xCCCCCC); - iw.iconCheckedColor = QColor(0xAAAAAA); - params.importWidgetParams = iw; - - TreeViewParams tv; - tv.useStyledTemplate = true; - tv.textColor = QColor(0xDDDFDF); - tv.selectionBackgroundColor = QColor(0x2E2E2E); - tv.scrollBackgroundColor = QColor(0x404040); - tv.scrollHandleColor = QColor(0xDDDDDD); - tv.selectedTextColor = Qt::white; - tv.folderIndicatorColor = QColor(237, 197, 24); - params.treeViewParams = tv; - - TableViewParams tav; - tav.alternateBackgroundColor = QColor(0x2E2E2E); - tav.backgroundColor = QColor(0x2A2A2A); - tav.cornerButtonBackgroundColor = QColor(0x2A2A2A); - tav.cornerButtonBorderColor = QColor(0x1F1F1F); - tav.cornerButtonGradientColor = QColor(0x252525); - tav.itemBorderBottomColor = QColor(0x1F1F1F); - tav.itemBorderTopColor = QColor(0x353535); - tav.itemTextColor = QColor(0xDDDDDD); - tav.selectedColor = QColor(0x555555); - tav.selectedTextColor = QColor(0xFFFFFF); - tav.headerTextColor = QColor(0xDDDDDD); - tav.starRatingColor = QColor(0xE9BE0F); - tav.starRatingSelectedColor = QColor(0xFFFFFF); - tav.t = TableViewThemeTemplates(); - params.tableViewParams = tav; - - QmlViewParams qv; - qv.backgroundColor = QColor(0x2A2A2A); - qv.cellColor = QColor(0x212121); - qv.cellColorWithBackground = QColor(0x21, 0x21, 0x21, 0x99); - qv.selectedColor = QColor(0x121212); - qv.selectedBorderColor = QColor(0xFFCC00); - qv.borderColor = QColor(0x121212); - qv.titleColor = QColor(0xFFFFFF); - qv.textColor = QColor(0xA8A8A8); - qv.showDropShadow = true; - qv.infoBackgroundColor = QColor(0x2E2E2E); - qv.topShadow = "info-top-shadow.png"; - qv.infoShadow = "info-shadow.png"; - qv.infoIndicator = "info-indicator.png"; - qv.infoTextColor = QColor(0xB0B0B0); - qv.infoTitleColor = QColor(0xFFFFFF); - qv.ratingUnselectedColor = QColor(0x1C1C1C); - qv.ratingSelectedColor = QColor(0xFFFFFF); - qv.favUncheckedColor = QColor(0x1C1C1C); - qv.favCheckedColor = QColor(0xE84852); - qv.readTickUncheckedColor = QColor(0x1C1C1C); - qv.readTickCheckedColor = QColor(0xE84852); - qv.currentComicBackgroundColor = QColor(0x00, 0x00, 0x00, 0x88); - qv.continueReadingBackgroundColor = QColor(0x00, 0x00, 0x00, 0x88); - qv.continueReadingColor = QColor(0xFFFFFF); - params.qmlViewParams = qv; - - MainToolbarParams mt; - mt.backgroundColor = QColor(0x2A2A2A); - mt.folderNameColor = QColor(0xDDDDDD); - mt.dividerColor = QColor(0x555555); - mt.iconColor = QColor(0xDDDDDD); - mt.iconDisabledColor = QColor(0x666666); - params.mainToolbarParams = mt; - - ContentSplitterParams cs; - cs.handleColor = QColor(0x1F1F1F); - cs.horizontalHandleHeight = 4; - cs.verticalHandleWidth = 4; - params.contentSplitterParams = cs; - - SidebarIconsParams si; - si.iconColor = QColor(0xE0E0E0); - si.shadowColor = QColor(0xFF000000); - si.extraColor = QColor(0x222222); - si.useSystemFolderIcons = false; - params.sidebarIconsParams = si; - - LibraryItemParams li; - li.textColor = QColor(0xDDDFDF); - li.selectedTextColor = Qt::white; - li.selectedBackgroundColor = QColor(0x2E2E2E); - li.libraryIconSelectedColor = Qt::white; - li.libraryOptionsIconColor = Qt::white; - params.libraryItemParams = li; - - ComicsViewToolbarParams cvt; - cvt.checkedBackgroundColor = QColor(0x555555); - cvt.iconColor = QColor(0xDDDDDD); - params.comicsViewToolbarParams = cvt; - - SearchLineEditParams sle; - sle.textColor = QColor(0xABABAB); - sle.backgroundColor = QColor(0x404040); - sle.iconColor = QColor(0xF7F7F7); - params.searchLineEditParams = sle; - - ReadingListIconsParams rli; - rli.labelColors = { - { "red", QColor(0xf67a7b) }, - { "orange", QColor(0xf5c240) }, - { "yellow", QColor(0xf2e446) }, - { "green", QColor(0xade738) }, - { "cyan", QColor(0xa0fddb) }, - { "blue", QColor(0x82c7ff) }, - { "violet", QColor(0x8f95ff) }, - { "purple", QColor(0xd692fc) }, - { "pink", QColor(0xfd9cda) }, - { "white", QColor(0xfcfcfc) }, - { "light", QColor(0xcbcbcb) }, - { "dark", QColor(0xb7b7b7) } - }; - rli.labelShadowColor = Qt::black; - rli.readingListMainColor = QColor(0xe7e7e7); - rli.favoritesMainColor = QColor(0xe15055); - rli.currentlyReadingMainColor = QColor(0xffcc00); - rli.currentlyReadingOuterColor = Qt::black; - rli.specialListShadowColor = Qt::black; - rli.listMainColor = QColor(0xe7e7e7); - rli.listShadowColor = Qt::black; - rli.listDetailColor = QColor(0x464646); - params.readingListIconsParams = rli; - - MenuIconsParams mi; - mi.iconColor = QColor(0xF7F7F7); - params.menuIconsParams = mi; - - DialogIconsParams dip; - dip.iconColor = mi.iconColor; - params.dialogIconsParams = dip; - - ShortcutsIconsParams sci; - sci.iconColor = QColor(0xF7F7F7); - params.shortcutsIconsParams = sci; - - ServerConfigDialogParams scd; - scd.backgroundColor = QColor(0x2A2A2A); - scd.titleTextColor = QColor(0xD0D0D0); - scd.qrMessageTextColor = QColor(0xA3A3A3); - scd.propagandaTextColor = QColor(0xB0B0B0); - scd.labelTextColor = QColor(0xC0C0C0); - scd.checkBoxTextColor = QColor(0xDDDDDD); - scd.qrBackgroundColor = QColor(0x2A2A2A); - scd.qrForegroundColor = Qt::white; - scd.decorationColor = QColor(0xF7F7F7); - params.serverConfigDialogParams = scd; - - return params; + if (json.contains("metadataScraperDialog")) { + const auto o = json["metadataScraperDialog"].toObject(); + auto &msd = p.metadataScraperDialogParams; + msd.contentTextColor = colorFromJson(o, "contentTextColor", msd.contentTextColor); + msd.contentBackgroundColor = colorFromJson(o, "contentBackgroundColor", msd.contentBackgroundColor); + msd.contentAltBackgroundColor = colorFromJson(o, "contentAltBackgroundColor", msd.contentAltBackgroundColor); + msd.dialogBackgroundColor = colorFromJson(o, "dialogBackgroundColor", msd.dialogBackgroundColor); + msd.tableBackgroundColor = colorFromJson(o, "tableBackgroundColor", msd.tableBackgroundColor); + msd.tableAltBackgroundColor = colorFromJson(o, "tableAltBackgroundColor", msd.tableAltBackgroundColor); + msd.tableBorderColor = colorFromJson(o, "tableBorderColor", msd.tableBorderColor); + msd.tableSelectedColor = colorFromJson(o, "tableSelectedColor", msd.tableSelectedColor); + msd.tableHeaderBackgroundColor = colorFromJson(o, "tableHeaderBackgroundColor", msd.tableHeaderBackgroundColor); + msd.tableHeaderGradientColor = colorFromJson(o, "tableHeaderGradientColor", msd.tableHeaderGradientColor); + msd.tableHeaderBorderColor = colorFromJson(o, "tableHeaderBorderColor", msd.tableHeaderBorderColor); + msd.tableHeaderTextColor = colorFromJson(o, "tableHeaderTextColor", msd.tableHeaderTextColor); + msd.tableScrollHandleColor = colorFromJson(o, "tableScrollHandleColor", msd.tableScrollHandleColor); + msd.tableScrollBackgroundColor = colorFromJson(o, "tableScrollBackgroundColor", msd.tableScrollBackgroundColor); + msd.tableSectionBorderLight = colorFromJson(o, "tableSectionBorderLight", msd.tableSectionBorderLight); + msd.tableSectionBorderDark = colorFromJson(o, "tableSectionBorderDark", msd.tableSectionBorderDark); + msd.labelTextColor = colorFromJson(o, "labelTextColor", msd.labelTextColor); + msd.labelBackgroundColor = colorFromJson(o, "labelBackgroundColor", msd.labelBackgroundColor); + msd.hyperlinkColor = colorFromJson(o, "hyperlinkColor", msd.hyperlinkColor); + msd.buttonBackgroundColor = colorFromJson(o, "buttonBackgroundColor", msd.buttonBackgroundColor); + msd.buttonTextColor = colorFromJson(o, "buttonTextColor", msd.buttonTextColor); + msd.buttonBorderColor = colorFromJson(o, "buttonBorderColor", msd.buttonBorderColor); + msd.radioUncheckedColor = colorFromJson(o, "radioUncheckedColor", msd.radioUncheckedColor); + msd.radioCheckedBackgroundColor = colorFromJson(o, "radioCheckedBackgroundColor", msd.radioCheckedBackgroundColor); + msd.radioCheckedIndicatorColor = colorFromJson(o, "radioCheckedIndicatorColor", msd.radioCheckedIndicatorColor); + msd.checkBoxTickColor = colorFromJson(o, "checkBoxTickColor", msd.checkBoxTickColor); + msd.toolButtonAccentColor = colorFromJson(o, "toolButtonAccentColor", msd.toolButtonAccentColor); + msd.downArrowColor = colorFromJson(o, "downArrowColor", msd.downArrowColor); + msd.upArrowColor = colorFromJson(o, "upArrowColor", msd.upArrowColor); + msd.busyIndicatorColor = colorFromJson(o, "busyIndicatorColor", msd.busyIndicatorColor); + msd.navIconColor = colorFromJson(o, "navIconColor", msd.navIconColor); + msd.rowIconColor = colorFromJson(o, "rowIconColor", msd.rowIconColor); + } + + if (json.contains("helpAboutDialog")) { + const auto o = json["helpAboutDialog"].toObject(); + p.helpAboutDialogParams.headingColor = colorFromJson(o, "headingColor", p.helpAboutDialogParams.headingColor); + p.helpAboutDialogParams.linkColor = colorFromJson(o, "linkColor", p.helpAboutDialogParams.linkColor); + } + + if (json.contains("whatsNewDialog")) { + const auto o = json["whatsNewDialog"].toObject(); + auto &wn = p.whatsNewDialogParams; + wn.backgroundColor = colorFromJson(o, "backgroundColor", wn.backgroundColor); + wn.headerTextColor = colorFromJson(o, "headerTextColor", wn.headerTextColor); + wn.versionTextColor = colorFromJson(o, "versionTextColor", wn.versionTextColor); + wn.contentTextColor = colorFromJson(o, "contentTextColor", wn.contentTextColor); + wn.linkColor = colorFromJson(o, "linkColor", wn.linkColor); + wn.closeButtonColor = colorFromJson(o, "closeButtonColor", wn.closeButtonColor); + wn.headerDecorationColor = colorFromJson(o, "headerDecorationColor", wn.headerDecorationColor); + } + + if (json.contains("emptyContainer")) { + const auto o = json["emptyContainer"].toObject(); + auto &ec = p.emptyContainerParams; + ec.backgroundColor = colorFromJson(o, "backgroundColor", ec.backgroundColor); + ec.titleTextColor = colorFromJson(o, "titleTextColor", ec.titleTextColor); + ec.textColor = colorFromJson(o, "textColor", ec.textColor); + ec.descriptionTextColor = colorFromJson(o, "descriptionTextColor", ec.descriptionTextColor); + ec.searchIconColor = colorFromJson(o, "searchIconColor", ec.searchIconColor); + } + + if (json.contains("sidebar")) { + const auto o = json["sidebar"].toObject(); + auto &sb = p.sidebarParams; + sb.backgroundColor = colorFromJson(o, "backgroundColor", sb.backgroundColor); + sb.separatorColor = colorFromJson(o, "separatorColor", sb.separatorColor); + sb.sectionSeparatorColor = colorFromJson(o, "sectionSeparatorColor", sb.sectionSeparatorColor); + if (o.contains("uppercaseLabels")) + sb.uppercaseLabels = o["uppercaseLabels"].toBool(sb.uppercaseLabels); + sb.titleTextColor = colorFromJson(o, "titleTextColor", sb.titleTextColor); + sb.titleDropShadowColor = colorFromJson(o, "titleDropShadowColor", sb.titleDropShadowColor); + sb.busyIndicatorColor = colorFromJson(o, "busyIndicatorColor", sb.busyIndicatorColor); + } + + if (json.contains("sidebarIcons")) { + const auto o = json["sidebarIcons"].toObject(); + auto &si = p.sidebarIconsParams; + si.iconColor = colorFromJson(o, "iconColor", si.iconColor); + si.shadowColor = colorFromJson(o, "shadowColor", si.shadowColor); + if (o.contains("useSystemFolderIcons")) + si.useSystemFolderIcons = o["useSystemFolderIcons"].toBool(si.useSystemFolderIcons); + } + + if (json.contains("libraryItem")) { + const auto o = json["libraryItem"].toObject(); + auto &li = p.libraryItemParams; + li.textColor = colorFromJson(o, "textColor", li.textColor); + li.libraryIconColor = colorFromJson(o, "libraryIconColor", li.libraryIconColor); + li.libraryIconShadowColor = colorFromJson(o, "libraryIconShadowColor", li.libraryIconShadowColor); + li.selectedTextColor = colorFromJson(o, "selectedTextColor", li.selectedTextColor); + li.selectedBackgroundColor = colorFromJson(o, "selectedBackgroundColor", li.selectedBackgroundColor); + li.libraryIconSelectedColor = colorFromJson(o, "libraryIconSelectedColor", li.libraryIconSelectedColor); + li.libraryOptionsIconColor = colorFromJson(o, "libraryOptionsIconColor", li.libraryOptionsIconColor); + } + + if (json.contains("importWidget")) { + const auto o = json["importWidget"].toObject(); + auto &iw = p.importWidgetParams; + iw.backgroundColor = colorFromJson(o, "backgroundColor", iw.backgroundColor); + iw.titleTextColor = colorFromJson(o, "titleTextColor", iw.titleTextColor); + iw.descriptionTextColor = colorFromJson(o, "descriptionTextColor", iw.descriptionTextColor); + iw.currentComicTextColor = colorFromJson(o, "currentComicTextColor", iw.currentComicTextColor); + iw.coversViewBackgroundColor = colorFromJson(o, "coversViewBackgroundColor", iw.coversViewBackgroundColor); + iw.coversLabelColor = colorFromJson(o, "coversLabelColor", iw.coversLabelColor); + iw.coversDecorationBgColor = colorFromJson(o, "coversDecorationBgColor", iw.coversDecorationBgColor); + iw.coversDecorationShadowColor = colorFromJson(o, "coversDecorationShadowColor", iw.coversDecorationShadowColor); + iw.modeIconColor = colorFromJson(o, "modeIconColor", iw.modeIconColor); + iw.iconColor = colorFromJson(o, "iconColor", iw.iconColor); + iw.iconCheckedColor = colorFromJson(o, "iconCheckedColor", iw.iconCheckedColor); + } + + if (json.contains("serverConfigDialog")) { + const auto o = json["serverConfigDialog"].toObject(); + auto &scd2 = p.serverConfigDialogParams; + scd2.backgroundColor = colorFromJson(o, "backgroundColor", scd2.backgroundColor); + scd2.titleTextColor = colorFromJson(o, "titleTextColor", scd2.titleTextColor); + scd2.qrMessageTextColor = colorFromJson(o, "qrMessageTextColor", scd2.qrMessageTextColor); + scd2.propagandaTextColor = colorFromJson(o, "propagandaTextColor", scd2.propagandaTextColor); + scd2.labelTextColor = colorFromJson(o, "labelTextColor", scd2.labelTextColor); + scd2.checkBoxTextColor = colorFromJson(o, "checkBoxTextColor", scd2.checkBoxTextColor); + scd2.qrBackgroundColor = colorFromJson(o, "qrBackgroundColor", scd2.qrBackgroundColor); + scd2.qrForegroundColor = colorFromJson(o, "qrForegroundColor", scd2.qrForegroundColor); + scd2.decorationColor = colorFromJson(o, "decorationColor", scd2.decorationColor); + } + + if (json.contains("mainToolbar")) { + const auto o = json["mainToolbar"].toObject(); + auto &mt = p.mainToolbarParams; + mt.backgroundColor = colorFromJson(o, "backgroundColor", mt.backgroundColor); + mt.folderNameColor = colorFromJson(o, "folderNameColor", mt.folderNameColor); + mt.dividerColor = colorFromJson(o, "dividerColor", mt.dividerColor); + mt.iconColor = colorFromJson(o, "iconColor", mt.iconColor); + mt.iconDisabledColor = colorFromJson(o, "iconDisabledColor", mt.iconDisabledColor); + } + + if (json.contains("contentSplitter")) { + const auto o = json["contentSplitter"].toObject(); + auto &cs = p.contentSplitterParams; + cs.handleColor = colorFromJson(o, "handleColor", cs.handleColor); + if (o.contains("horizontalHandleHeight")) + cs.horizontalHandleHeight = o["horizontalHandleHeight"].toInt(cs.horizontalHandleHeight); + if (o.contains("verticalHandleWidth")) + cs.verticalHandleWidth = o["verticalHandleWidth"].toInt(cs.verticalHandleWidth); + } + + if (json.contains("navigationTree")) { + const auto o = json["navigationTree"].toObject(); + auto &nt = p.navigationTreeParams; + nt.textColor = colorFromJson(o, "textColor", nt.textColor); + nt.selectionBackgroundColor = colorFromJson(o, "selectionBackgroundColor", nt.selectionBackgroundColor); + nt.scrollBackgroundColor = colorFromJson(o, "scrollBackgroundColor", nt.scrollBackgroundColor); + nt.scrollHandleColor = colorFromJson(o, "scrollHandleColor", nt.scrollHandleColor); + nt.selectedTextColor = colorFromJson(o, "selectedTextColor", nt.selectedTextColor); + nt.folderIndicatorColor = colorFromJson(o, "folderIndicatorColor", nt.folderIndicatorColor); + nt.branchIndicatorColor = colorFromJson(o, "branchIndicatorColor", nt.branchIndicatorColor); + nt.branchIndicatorSelectedColor = colorFromJson(o, "branchIndicatorSelectedColor", nt.branchIndicatorSelectedColor); + nt.folderIconColor = colorFromJson(o, "folderIconColor", nt.folderIconColor); + nt.folderIconShadowColor = colorFromJson(o, "folderIconShadowColor", nt.folderIconShadowColor); + nt.folderIconSelectedColor = colorFromJson(o, "folderIconSelectedColor", nt.folderIconSelectedColor); + nt.folderIconSelectedShadowColor = colorFromJson(o, "folderIconSelectedShadowColor", nt.folderIconSelectedShadowColor); + nt.folderReadOverlayColor = colorFromJson(o, "folderReadOverlayColor", nt.folderReadOverlayColor); + nt.folderReadOverlaySelectedColor = colorFromJson(o, "folderReadOverlaySelectedColor", nt.folderReadOverlaySelectedColor); + } + + if (json.contains("comicsViewTable")) { + const auto o = json["comicsViewTable"].toObject(); + auto &cvta = p.comicsViewTableParams; + cvta.alternateBackgroundColor = colorFromJson(o, "alternateBackgroundColor", cvta.alternateBackgroundColor); + cvta.backgroundColor = colorFromJson(o, "backgroundColor", cvta.backgroundColor); + cvta.headerBackgroundColor = colorFromJson(o, "headerBackgroundColor", cvta.headerBackgroundColor); + cvta.headerBorderColor = colorFromJson(o, "headerBorderColor", cvta.headerBorderColor); + cvta.headerGradientColor = colorFromJson(o, "headerGradientColor", cvta.headerGradientColor); + cvta.itemBorderBottomColor = colorFromJson(o, "itemBorderBottomColor", cvta.itemBorderBottomColor); + cvta.itemBorderTopColor = colorFromJson(o, "itemBorderTopColor", cvta.itemBorderTopColor); + cvta.itemBorderBottomWidth = o["itemBorderBottomWidth"].toInt(cvta.itemBorderBottomWidth); + cvta.itemBorderTopWidth = o["itemBorderTopWidth"].toInt(cvta.itemBorderTopWidth); + cvta.itemTextColor = colorFromJson(o, "itemTextColor", cvta.itemTextColor); + cvta.selectedColor = colorFromJson(o, "selectedColor", cvta.selectedColor); + cvta.selectedTextColor = colorFromJson(o, "selectedTextColor", cvta.selectedTextColor); + cvta.headerTextColor = colorFromJson(o, "headerTextColor", cvta.headerTextColor); + cvta.starRatingColor = colorFromJson(o, "starRatingColor", cvta.starRatingColor); + cvta.starRatingSelectedColor = colorFromJson(o, "starRatingSelectedColor", cvta.starRatingSelectedColor); + } + + if (json.contains("gridAndInfoView")) { + const auto o = json["gridAndInfoView"].toObject(); + auto &giv = p.gridAndInfoViewParams; + giv.backgroundColor = colorFromJson(o, "backgroundColor", giv.backgroundColor); + giv.cellColor = colorFromJson(o, "cellColor", giv.cellColor); + giv.cellColorWithBackground = colorFromJson(o, "cellColorWithBackground", giv.cellColorWithBackground); + giv.selectedColor = colorFromJson(o, "selectedColor", giv.selectedColor); + giv.selectedBorderColor = colorFromJson(o, "selectedBorderColor", giv.selectedBorderColor); + giv.borderColor = colorFromJson(o, "borderColor", giv.borderColor); + giv.titleColor = colorFromJson(o, "titleColor", giv.titleColor); + giv.textColor = colorFromJson(o, "textColor", giv.textColor); + if (o.contains("showDropShadow")) + giv.showDropShadow = o["showDropShadow"].toBool(giv.showDropShadow); + giv.infoBackgroundColor = colorFromJson(o, "infoBackgroundColor", giv.infoBackgroundColor); + giv.infoBorderColor = colorFromJson(o, "infoBorderColor", giv.infoBorderColor); + giv.infoShadowColor = colorFromJson(o, "infoShadowColor", giv.infoShadowColor); + giv.infoTextColor = colorFromJson(o, "infoTextColor", giv.infoTextColor); + giv.infoTitleColor = colorFromJson(o, "infoTitleColor", giv.infoTitleColor); + giv.ratingUnselectedColor = colorFromJson(o, "ratingUnselectedColor", giv.ratingUnselectedColor); + giv.ratingSelectedColor = colorFromJson(o, "ratingSelectedColor", giv.ratingSelectedColor); + giv.favUncheckedColor = colorFromJson(o, "favUncheckedColor", giv.favUncheckedColor); + giv.favCheckedColor = colorFromJson(o, "favCheckedColor", giv.favCheckedColor); + giv.readTickUncheckedColor = colorFromJson(o, "readTickUncheckedColor", giv.readTickUncheckedColor); + giv.readTickCheckedColor = colorFromJson(o, "readTickCheckedColor", giv.readTickCheckedColor); + giv.currentComicBackgroundColor = colorFromJson(o, "currentComicBackgroundColor", giv.currentComicBackgroundColor); + giv.continueReadingBackgroundColor = colorFromJson(o, "continueReadingBackgroundColor", giv.continueReadingBackgroundColor); + giv.continueReadingColor = colorFromJson(o, "continueReadingColor", giv.continueReadingColor); + giv.backgroundBlurOverlayColor = colorFromJson(o, "backgroundBlurOverlayColor", giv.backgroundBlurOverlayColor); + } + + if (json.contains("comicsViewToolbar")) { + const auto o = json["comicsViewToolbar"].toObject(); + auto &cvt = p.comicsViewToolbarParams; + cvt.backgroundColor = colorFromJson(o, "backgroundColor", cvt.backgroundColor); + cvt.separatorColor = colorFromJson(o, "separatorColor", cvt.separatorColor); + cvt.checkedBackgroundColor = colorFromJson(o, "checkedBackgroundColor", cvt.checkedBackgroundColor); + cvt.iconColor = colorFromJson(o, "iconColor", cvt.iconColor); + } + + if (json.contains("searchLineEdit")) { + const auto o = json["searchLineEdit"].toObject(); + auto &sle = p.searchLineEditParams; + sle.textColor = colorFromJson(o, "textColor", sle.textColor); + sle.backgroundColor = colorFromJson(o, "backgroundColor", sle.backgroundColor); + sle.iconColor = colorFromJson(o, "iconColor", sle.iconColor); + } + + if (json.contains("readingListIcons")) { + const auto o = json["readingListIcons"].toObject(); + auto &rli = p.readingListIconsParams; + if (o.contains("labelColors")) { + const auto lc = o["labelColors"].toObject(); + for (auto it = lc.constBegin(); it != lc.constEnd(); ++it) { + QColor c(it.value().toString()); + if (c.isValid()) + rli.labelColors[it.key()] = c; + } + } + rli.labelShadowColor = colorFromJson(o, "labelShadowColor", rli.labelShadowColor); + rli.labelShadowSelectedColor = colorFromJson(o, "labelShadowSelectedColor", rli.labelShadowSelectedColor); + rli.readingListMainColor = colorFromJson(o, "readingListMainColor", rli.readingListMainColor); + rli.readingListMainSelectedColor = colorFromJson(o, "readingListMainSelectedColor", rli.readingListMainSelectedColor); + rli.favoritesMainColor = colorFromJson(o, "favoritesMainColor", rli.favoritesMainColor); + rli.favoritesMainSelectedColor = colorFromJson(o, "favoritesMainSelectedColor", rli.favoritesMainSelectedColor); + rli.currentlyReadingMainColor = colorFromJson(o, "currentlyReadingMainColor", rli.currentlyReadingMainColor); + rli.currentlyReadingMainSelectedColor = colorFromJson(o, "currentlyReadingMainSelectedColor", rli.currentlyReadingMainSelectedColor); + rli.currentlyReadingOuterColor = colorFromJson(o, "currentlyReadingOuterColor", rli.currentlyReadingOuterColor); + rli.currentlyReadingOuterSelectedColor = colorFromJson(o, "currentlyReadingOuterSelectedColor", rli.currentlyReadingOuterSelectedColor); + rli.specialListShadowColor = colorFromJson(o, "specialListShadowColor", rli.specialListShadowColor); + rli.specialListShadowSelectedColor = colorFromJson(o, "specialListShadowSelectedColor", rli.specialListShadowSelectedColor); + rli.listMainColor = colorFromJson(o, "listMainColor", rli.listMainColor); + rli.listMainSelectedColor = colorFromJson(o, "listMainSelectedColor", rli.listMainSelectedColor); + rli.listShadowColor = colorFromJson(o, "listShadowColor", rli.listShadowColor); + rli.listShadowSelectedColor = colorFromJson(o, "listShadowSelectedColor", rli.listShadowSelectedColor); + rli.listDetailColor = colorFromJson(o, "listDetailColor", rli.listDetailColor); + rli.listDetailSelectedColor = colorFromJson(o, "listDetailSelectedColor", rli.listDetailSelectedColor); + } + + if (json.contains("dialogIcons")) { + const auto o = json["dialogIcons"].toObject(); + p.dialogIconsParams.iconColor = colorFromJson(o, "iconColor", p.dialogIconsParams.iconColor); + } + + if (json.contains("menuIcons")) { + const auto o = json["menuIcons"].toObject(); + p.menuIconsParams.iconColor = colorFromJson(o, "iconColor", p.menuIconsParams.iconColor); + } + + if (json.contains("shortcutsIcons")) { + const auto o = json["shortcutsIcons"].toObject(); + p.shortcutsIconsParams.iconColor = colorFromJson(o, "iconColor", p.shortcutsIconsParams.iconColor); + } + + if (json.contains("meta")) { + const auto o = json["meta"].toObject(); + p.meta.id = o["id"].toString(p.meta.id); + p.meta.displayName = o["displayName"].toString(p.meta.displayName); + const QString variantStr = o["variant"].toString(); + if (variantStr == "light") + p.meta.variant = ThemeVariant::Light; + else if (variantStr == "dark") + p.meta.variant = ThemeVariant::Dark; + } + + Theme theme = makeTheme(p); + theme.sourceJson = json; + return theme; } diff --git a/YACReaderLibrary/themes/theme_factory.h b/YACReaderLibrary/themes/theme_factory.h index 72cb4ca2..ebed9bbb 100644 --- a/YACReaderLibrary/themes/theme_factory.h +++ b/YACReaderLibrary/themes/theme_factory.h @@ -2,8 +2,9 @@ #define THEME_FACTORY_H #include "theme.h" -#include "theme_id.h" -Theme makeTheme(ThemeId themeId); +#include + +Theme makeTheme(const QJsonObject &json); #endif // THEME_FACTORY_H diff --git a/YACReaderLibrary/themes/themes.qrc b/YACReaderLibrary/themes/themes.qrc new file mode 100644 index 00000000..62ec21fe --- /dev/null +++ b/YACReaderLibrary/themes/themes.qrc @@ -0,0 +1,7 @@ + + + builtin_classic.json + builtin_light.json + builtin_dark.json + + diff --git a/YACReaderLibrary/yacreader_folders_view.cpp b/YACReaderLibrary/yacreader_folders_view.cpp index 57f6f5b8..ce116bf6 100644 --- a/YACReaderLibrary/yacreader_folders_view.cpp +++ b/YACReaderLibrary/yacreader_folders_view.cpp @@ -81,6 +81,12 @@ YACReaderFoldersViewItemDeletegate::YACReaderFoldersViewItemDeletegate(QObject * void YACReaderFoldersViewItemDeletegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + // Promote hover to selected so QIcon::Selected mode activates on mouse-over, + // matching the QSS which already uses the same background for hover and selected. + QStyleOptionViewItem opt = option; + if (opt.state & QStyle::State_MouseOver) + opt.state |= QStyle::State_Selected; + // Get indicator color from parent tree view QColor indicatorColor(237, 197, 24); // Default fallback if (auto treeView = qobject_cast(parent())) { @@ -91,11 +97,11 @@ void YACReaderFoldersViewItemDeletegate::paint(QPainter *painter, const QStyleOp painter->save(); painter->setBrush(QBrush(indicatorColor)); painter->setPen(QPen(QBrush(), 0)); - painter->drawRect(0, option.rect.y(), 2, option.rect.height()); + painter->drawRect(0, opt.rect.y(), 2, opt.rect.height()); painter->restore(); } - QStyledItemDelegate::paint(painter, option, index); + QStyledItemDelegate::paint(painter, opt, index); auto showRecent = index.data(FolderModel::ShowRecentRole).toBool(); @@ -110,7 +116,7 @@ void YACReaderFoldersViewItemDeletegate::paint(QPainter *painter, const QStyleOp painter->setRenderHint(QPainter::Antialiasing); painter->setBrush(QBrush(indicatorColor)); painter->setPen(QPen(QBrush(), 0)); - painter->drawEllipse(option.rect.x() + 13, option.rect.y() + 2, 7, 7); + painter->drawEllipse(opt.rect.x() + 13, opt.rect.y() + 2, 7, 7); painter->restore(); } } diff --git a/YACReaderLibrary/yacreader_reading_lists_view.cpp b/YACReaderLibrary/yacreader_reading_lists_view.cpp index 6d3d3b40..62b4aea2 100644 --- a/YACReaderLibrary/yacreader_reading_lists_view.cpp +++ b/YACReaderLibrary/yacreader_reading_lists_view.cpp @@ -50,7 +50,13 @@ void YACReaderReadingListsViewItemDeletegate::paint(QPainter *painter, const QSt return; } - QStyledItemDelegate::paint(painter, option, index); + // Promote hover to selected so QIcon::Selected mode activates on mouse-over, + // matching the QSS which already uses the same background for hover and selected. + QStyleOptionViewItem opt = option; + if (opt.state & QStyle::State_MouseOver) + opt.state |= QStyle::State_Selected; + + QStyledItemDelegate::paint(painter, opt, index); } QSize YACReaderReadingListsViewItemDeletegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const diff --git a/YACReaderLibraryServer/CMakeLists.txt b/YACReaderLibraryServer/CMakeLists.txt index 619f10a8..fd70e77e 100644 --- a/YACReaderLibraryServer/CMakeLists.txt +++ b/YACReaderLibraryServer/CMakeLists.txt @@ -19,8 +19,6 @@ target_compile_definitions(YACReaderLibraryServer PRIVATE YACREADER_LIBRARY ) -# Resources -qt_add_resources(yacreaderlibraryserver_images_rcc "${CMAKE_CURRENT_SOURCE_DIR}/images.qrc") target_sources(YACReaderLibraryServer PRIVATE ${yacreaderlibraryserver_images_rcc}) # Translations diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index c754f552..ae0c1639 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -111,17 +111,27 @@ add_library(common_gui STATIC # themes infrastructure (does NOT depend on app-specific theme.h) themes/icon_utils.h themes/icon_utils.cpp - themes/theme_id.h + themes/appearance_configuration.h + themes/appearance_configuration.cpp + themes/theme_variant.h themes/themable.h themes/yacreader_icon.h themes/shared/help_about_dialog_theme.h themes/shared/whats_new_dialog_theme.h + themes/theme_editor_dialog.h + themes/theme_editor_dialog.cpp + themes/theme_meta.h + themes/theme_repository.h + themes/theme_repository.cpp + themes/appearance_tab_widget.h + themes/appearance_tab_widget.cpp ) target_include_directories(common_gui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/themes ${CMAKE_CURRENT_SOURCE_DIR}/themes/shared ) + target_link_libraries(common_gui PUBLIC Qt::Core Qt::Core5Compat diff --git a/common/themes/appearance_config_images.qrc b/common/themes/appearance_config_images.qrc new file mode 100644 index 00000000..b67edae7 --- /dev/null +++ b/common/themes/appearance_config_images.qrc @@ -0,0 +1,8 @@ + + + ../../images/appearance_config/theme-mode-system.svg + ../../images/appearance_config/theme-mode-light.svg + ../../images/appearance_config/theme-mode-dark.svg + ../../images/appearance_config/theme-mode-custom.svg + + diff --git a/common/themes/appearance_configuration.cpp b/common/themes/appearance_configuration.cpp new file mode 100644 index 00000000..dade4c26 --- /dev/null +++ b/common/themes/appearance_configuration.cpp @@ -0,0 +1,96 @@ +#include "appearance_configuration.h" + +#include + +static constexpr auto kGroup = "Appearance"; +static constexpr auto kMode = "ThemeMode"; +static constexpr auto kLightId = "LightThemeId"; +static constexpr auto kDarkId = "DarkThemeId"; +static constexpr auto kFixedId = "FixedThemeId"; + +static QString themeModeToString(ThemeMode mode) +{ + switch (mode) { + case ThemeMode::FollowSystem: + return "FollowSystem"; + case ThemeMode::Light: + return "Light"; + case ThemeMode::Dark: + return "Dark"; + case ThemeMode::ForcedTheme: + return "ForcedTheme"; + } + return "FollowSystem"; +} + +static ThemeMode themeModeFromString(const QString &s) +{ + if (s == "Light") + return ThemeMode::Light; + if (s == "Dark") + return ThemeMode::Dark; + if (s == "ForcedTheme") + return ThemeMode::ForcedTheme; + return ThemeMode::FollowSystem; +} + +AppearanceConfiguration::AppearanceConfiguration(const QString &settingsFilePath, QObject *parent) + : QObject(parent), path(settingsFilePath) +{ + load(); +} + +void AppearanceConfiguration::load() +{ + QSettings s(path, QSettings::IniFormat); + s.beginGroup(kGroup); + sel.mode = themeModeFromString(s.value(kMode, "FollowSystem").toString()); + sel.lightThemeId = s.value(kLightId, sel.lightThemeId).toString(); + sel.darkThemeId = s.value(kDarkId, sel.darkThemeId).toString(); + sel.fixedThemeId = s.value(kFixedId, sel.fixedThemeId).toString(); + s.endGroup(); +} + +void AppearanceConfiguration::write(const QString &key, const QString &value) +{ + QSettings s(path, QSettings::IniFormat); + s.beginGroup(kGroup); + s.setValue(key, value); + s.endGroup(); +} + +void AppearanceConfiguration::setMode(ThemeMode mode) +{ + if (sel.mode == mode) + return; + sel.mode = mode; + write(kMode, themeModeToString(mode)); + emit selectionChanged(); +} + +void AppearanceConfiguration::setLightThemeId(const QString &id) +{ + if (sel.lightThemeId == id) + return; + sel.lightThemeId = id; + write(kLightId, id); + emit selectionChanged(); +} + +void AppearanceConfiguration::setDarkThemeId(const QString &id) +{ + if (sel.darkThemeId == id) + return; + sel.darkThemeId = id; + write(kDarkId, id); + emit selectionChanged(); +} + +void AppearanceConfiguration::setFixedThemeId(const QString &id) +{ + if (sel.fixedThemeId == id) + return; + sel.fixedThemeId = id; + write(kFixedId, id); + emit selectionChanged(); +} diff --git a/common/themes/appearance_configuration.h b/common/themes/appearance_configuration.h new file mode 100644 index 00000000..bcc4d501 --- /dev/null +++ b/common/themes/appearance_configuration.h @@ -0,0 +1,48 @@ +#ifndef APPEARANCE_CONFIGURATION_H +#define APPEARANCE_CONFIGURATION_H + +#include +#include + +enum class ThemeMode { + FollowSystem, + Light, + Dark, + ForcedTheme, +}; + +struct ThemeSelection { + ThemeMode mode = ThemeMode::FollowSystem; + QString lightThemeId = "builtin/light"; + QString darkThemeId = "builtin/dark"; + QString fixedThemeId = "builtin/classic"; +}; + +// Persists theme selection settings to a QSettings INI file under the +// [Appearance] group. All access is on-demand (no persistent QSettings handle) +// so the caller does not need to manage a QSettings lifetime. +class AppearanceConfiguration : public QObject +{ + Q_OBJECT +public: + explicit AppearanceConfiguration(const QString &settingsFilePath, QObject *parent = nullptr); + + ThemeSelection selection() const { return sel; } + + void setMode(ThemeMode mode); + void setLightThemeId(const QString &id); + void setDarkThemeId(const QString &id); + void setFixedThemeId(const QString &id); + +signals: + void selectionChanged(); + +private: + QString path; + ThemeSelection sel; + + void load(); + void write(const QString &key, const QString &value); +}; + +#endif // APPEARANCE_CONFIGURATION_H diff --git a/common/themes/appearance_tab_widget.cpp b/common/themes/appearance_tab_widget.cpp new file mode 100644 index 00000000..9dcb49c2 --- /dev/null +++ b/common/themes/appearance_tab_widget.cpp @@ -0,0 +1,313 @@ +#include "appearance_tab_widget.h" + +#include "appearance_configuration.h" +#include "theme_editor_dialog.h" +#include "theme_repository.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Select the item in combo whose UserRole data matches id (no-op if not found). +static void selectInCombo(QComboBox *combo, const QString &id) +{ + for (int i = 0; i < combo->count(); ++i) { + if (combo->itemData(i).toString() == id) { + combo->setCurrentIndex(i); + return; + } + } +} + +AppearanceTabWidget::AppearanceTabWidget( + AppearanceConfiguration *config, + ThemeRepository *repository, + std::function currentThemeJson, + std::function applyTheme, + QWidget *parent) + : QWidget(parent), config(config), repository(repository), currentThemeJson(std::move(currentThemeJson)), applyTheme(std::move(applyTheme)) +{ + // --- Color scheme selector --- + auto *modeBox = new QGroupBox(tr("Color scheme"), this); + auto *modeLayout = new QHBoxLayout(); + + auto *sysBtn = new QToolButton(); + auto *lightBtn = new QToolButton(); + auto *darkBtn = new QToolButton(); + auto *customBtn = new QToolButton(); + + sysBtn->setText(tr("System")); + lightBtn->setText(tr("Light")); + darkBtn->setText(tr("Dark")); + customBtn->setText(tr("Custom")); + + sysBtn->setIcon(QIcon(":/images/appearance_config/theme-mode-system.svg")); + lightBtn->setIcon(QIcon(":/images/appearance_config/theme-mode-light.svg")); + darkBtn->setIcon(QIcon(":/images/appearance_config/theme-mode-dark.svg")); + customBtn->setIcon(QIcon(":/images/appearance_config/theme-mode-custom.svg")); + + for (auto *btn : { sysBtn, lightBtn, darkBtn, customBtn }) { + btn->setCheckable(true); + btn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + btn->setIconSize(QSize(93, 58)); + btn->setMinimumSize(115, 90); + } + + auto *modeGroup = new QButtonGroup(this); + modeGroup->addButton(sysBtn, static_cast(ThemeMode::FollowSystem)); + modeGroup->addButton(lightBtn, static_cast(ThemeMode::Light)); + modeGroup->addButton(darkBtn, static_cast(ThemeMode::Dark)); + modeGroup->addButton(customBtn, static_cast(ThemeMode::ForcedTheme)); + modeGroup->setExclusive(true); + + if (this->config) { + const auto mode = this->config->selection().mode; + if (auto *btn = modeGroup->button(static_cast(mode))) + btn->setChecked(true); + } + + connect(modeGroup, &QButtonGroup::idClicked, this, [this](int id) { + if (this->config) { + this->config->setMode(static_cast(id)); + updateModeRows(); + } + }); + + modeLayout->addStretch(); + modeLayout->addWidget(sysBtn); + modeLayout->addWidget(lightBtn); + modeLayout->addWidget(darkBtn); + modeLayout->addWidget(customBtn); + modeLayout->addStretch(); + modeBox->setLayout(modeLayout); + + // --- Theme selection --- + // Each row: [label fixed] [combo expanding] [Remove btn] + // Rows are shown/hidden based on mode — only relevant ones are visible. + auto makeRow = [](const QString &label, QComboBox *&combo, QPushButton *&deleteBtn) { + auto *row = new QWidget(); + auto *hl = new QHBoxLayout(row); + hl->setContentsMargins(0, 0, 0, 0); + + auto *lbl = new QLabel(label); + lbl->setFixedWidth(52); + + combo = new QComboBox(); + combo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + deleteBtn = new QPushButton(tr("Remove")); + deleteBtn->setEnabled(false); + deleteBtn->setToolTip(tr("Remove this user-imported theme")); + + hl->addWidget(lbl); + hl->addWidget(combo); + hl->addWidget(deleteBtn); + return row; + }; + + lightRow = makeRow(tr("Light:"), lightCombo, lightDeleteBtn); + darkRow = makeRow(tr("Dark:"), darkCombo, darkDeleteBtn); + customRow = makeRow(tr("Custom:"), customCombo, customDeleteBtn); + + auto *importBtn = new QPushButton(tr("Import theme...")); + + auto *themeSelBox = new QGroupBox(tr("Theme"), this); + auto *themeSelLayout = new QVBoxLayout(); + themeSelLayout->addWidget(lightRow); + themeSelLayout->addWidget(darkRow); + themeSelLayout->addWidget(customRow); + themeSelLayout->addWidget(importBtn, 0, Qt::AlignRight); + themeSelBox->setLayout(themeSelLayout); + + // Populate combos and set initial row visibility + if (this->config && this->repository) { + const auto &sel = this->config->selection(); + populateCombo(lightCombo, ThemeVariant::Light, sel.lightThemeId); + populateCombo(darkCombo, ThemeVariant::Dark, sel.darkThemeId); + populateCombo(customCombo, std::nullopt, sel.fixedThemeId); + updateDeleteButton(lightCombo, lightDeleteBtn); + updateDeleteButton(darkCombo, darkDeleteBtn); + updateDeleteButton(customCombo, customDeleteBtn); + updateModeRows(); + } + + // Combo selection → update config (live theme preview via selectionChanged chain) + connect(lightCombo, &QComboBox::currentIndexChanged, this, [this](int) { + if (!this->config) + return; + this->config->setLightThemeId(lightCombo->currentData().toString()); + updateDeleteButton(lightCombo, lightDeleteBtn); + }); + connect(darkCombo, &QComboBox::currentIndexChanged, this, [this](int) { + if (!this->config) + return; + this->config->setDarkThemeId(darkCombo->currentData().toString()); + updateDeleteButton(darkCombo, darkDeleteBtn); + }); + connect(customCombo, &QComboBox::currentIndexChanged, this, [this](int) { + if (!this->config) + return; + this->config->setFixedThemeId(customCombo->currentData().toString()); + updateDeleteButton(customCombo, customDeleteBtn); + }); + + // Delete buttons + connect(lightDeleteBtn, &QPushButton::clicked, this, + [this]() { deleteTheme(lightCombo, lightDeleteBtn); }); + connect(darkDeleteBtn, &QPushButton::clicked, this, + [this]() { deleteTheme(darkCombo, darkDeleteBtn); }); + connect(customDeleteBtn, &QPushButton::clicked, this, + [this]() { deleteTheme(customCombo, customDeleteBtn); }); + + // Import + connect(importBtn, &QPushButton::clicked, this, &AppearanceTabWidget::importTheme); + + // --- Theme editor --- + auto *themeEditorBox = new QGroupBox(tr("Theme editor"), this); + auto *themeEditorLayout = new QVBoxLayout(); + auto *openBtn = new QPushButton(tr("Open Theme Editor...")); + themeEditorLayout->addWidget(openBtn); + themeEditorBox->setLayout(themeEditorLayout); + + connect(openBtn, &QPushButton::clicked, this, [this]() { + if (!themeEditor) { + QJsonObject json = this->currentThemeJson(); + if (json.isEmpty()) { + QMessageBox::critical(this, + tr("Theme editor error"), + tr("The current theme JSON could not be loaded.")); + return; + } + themeEditor = new ThemeEditorDialog(json, this); + themeEditor->setAttribute(Qt::WA_DeleteOnClose); + connect(themeEditor, &ThemeEditorDialog::themeJsonChanged, this, + [this](const QJsonObject &json) { this->applyTheme(json); }); + } + themeEditor->show(); + themeEditor->raise(); + themeEditor->activateWindow(); + }); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(modeBox); + layout->addWidget(themeSelBox); + layout->addWidget(themeEditorBox); + layout->addStretch(); +} + +void AppearanceTabWidget::populateCombo(QComboBox *combo, + std::optional variantFilter, + const QString &selectedId) +{ + QSignalBlocker blocker(combo); + combo->clear(); + if (!repository) + return; + + for (const auto &entry : repository->availableThemes()) { + if (variantFilter && entry.variant != *variantFilter) + continue; + combo->addItem(entry.displayName, entry.id); + } + + // Restore selection; fall back to first item if the saved ID is no longer present + for (int i = 0; i < combo->count(); ++i) { + if (combo->itemData(i).toString() == selectedId) { + combo->setCurrentIndex(i); + return; + } + } + if (combo->count() > 0) + combo->setCurrentIndex(0); +} + +void AppearanceTabWidget::repopulateCombos() +{ + if (!config) + return; + const auto &sel = config->selection(); + populateCombo(lightCombo, ThemeVariant::Light, sel.lightThemeId); + populateCombo(darkCombo, ThemeVariant::Dark, sel.darkThemeId); + populateCombo(customCombo, std::nullopt, sel.fixedThemeId); + updateDeleteButton(lightCombo, lightDeleteBtn); + updateDeleteButton(darkCombo, darkDeleteBtn); + updateDeleteButton(customCombo, customDeleteBtn); +} + +void AppearanceTabWidget::updateDeleteButton(QComboBox *combo, QPushButton *btn) +{ + const QString id = combo->currentData().toString(); + btn->setEnabled(!id.isEmpty() && id.startsWith(QLatin1String("user/"))); +} + +void AppearanceTabWidget::updateModeRows() +{ + if (!config) + return; + const auto mode = config->selection().mode; + lightRow->setVisible(mode == ThemeMode::FollowSystem || mode == ThemeMode::Light); + darkRow->setVisible(mode == ThemeMode::FollowSystem || mode == ThemeMode::Dark); + customRow->setVisible(mode == ThemeMode::ForcedTheme); +} + +void AppearanceTabWidget::importTheme() +{ + const QString path = QFileDialog::getOpenFileName( + this, tr("Import theme"), QString(), tr("JSON files (*.json);;All files (*)")); + if (path.isEmpty() || !repository) + return; + + const QString id = repository->importThemeFromFile(path); + if (id.isEmpty()) { + QMessageBox::warning(this, tr("Import failed"), + tr("Could not import theme from:\n%1").arg(path)); + return; + } + + // Detect variant of the imported theme to auto-select it in the right combo + const QJsonObject json = repository->loadThemeJson(id); + const bool isLight = (json["meta"].toObject()["variant"].toString() == "light"); + + repopulateCombos(); + + // Select in the appropriate combo → triggers currentIndexChanged → config update + if (isLight) + selectInCombo(lightCombo, id); + else + selectInCombo(darkCombo, id); + + // If in Custom mode, also select in customCombo + if (config && config->selection().mode == ThemeMode::ForcedTheme) + selectInCombo(customCombo, id); +} + +void AppearanceTabWidget::deleteTheme(QComboBox *combo, QPushButton *deleteBtn) +{ + if (!repository) + return; + const QString id = combo->currentData().toString(); + if (!id.startsWith(QLatin1String("user/"))) + return; + + repository->deleteUserTheme(id); + repopulateCombos(); + + // repopulateCombos() blocked signals; manually push the new selection into config + // so the theme resolves correctly (important when the deleted theme was active). + const QString newId = combo->currentData().toString(); + if (combo == lightCombo && config) + config->setLightThemeId(newId); + else if (combo == darkCombo && config) + config->setDarkThemeId(newId); + else if (combo == customCombo && config) + config->setFixedThemeId(newId); + + updateDeleteButton(combo, deleteBtn); +} diff --git a/common/themes/appearance_tab_widget.h b/common/themes/appearance_tab_widget.h new file mode 100644 index 00000000..98195058 --- /dev/null +++ b/common/themes/appearance_tab_widget.h @@ -0,0 +1,58 @@ +#ifndef APPEARANCE_TAB_WIDGET_H +#define APPEARANCE_TAB_WIDGET_H + +#include "theme_variant.h" + +#include +#include +#include +#include +#include + +class AppearanceConfiguration; +class QComboBox; +class QPushButton; +class ThemeEditorDialog; +class ThemeRepository; + +class AppearanceTabWidget : public QWidget +{ + Q_OBJECT +public: + explicit AppearanceTabWidget( + AppearanceConfiguration *config, + ThemeRepository *repository, + std::function currentThemeJson, + std::function applyTheme, + QWidget *parent = nullptr); + +private: + AppearanceConfiguration *config; + ThemeRepository *repository; + std::function currentThemeJson; + std::function applyTheme; + QPointer themeEditor; + + // One row per picker; shown/hidden based on active mode + QWidget *lightRow = nullptr; + QWidget *darkRow = nullptr; + QWidget *customRow = nullptr; + + QComboBox *lightCombo = nullptr; + QComboBox *darkCombo = nullptr; + QComboBox *customCombo = nullptr; + + QPushButton *lightDeleteBtn = nullptr; + QPushButton *darkDeleteBtn = nullptr; + QPushButton *customDeleteBtn = nullptr; + + // Populate a combo with themes, filtered strictly by variant (or all if nullopt). + void populateCombo(QComboBox *combo, std::optional variantFilter, const QString &selectedId); + void repopulateCombos(); + void updateDeleteButton(QComboBox *combo, QPushButton *btn); + void updateModeRows(); + void importTheme(); + void deleteTheme(QComboBox *combo, QPushButton *deleteBtn); +}; + +#endif // APPEARANCE_TAB_WIDGET_H diff --git a/common/themes/icon_utils.cpp b/common/themes/icon_utils.cpp index f0ca5f9d..395347dd 100644 --- a/common/themes/icon_utils.cpp +++ b/common/themes/icon_utils.cpp @@ -28,7 +28,7 @@ QString readSvg(const QString &resourcePath) QFile in(resourcePath); if (!in.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "Failed to open SVG resource:" << resourcePath; - return {}; + return { }; } QString svg = QString::fromUtf8(in.readAll()); @@ -53,7 +53,7 @@ QString writeSvg(const QString &svg, const QString &resourcePath, const QString QFile out(outPath); if (!out.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { qWarning() << "Failed to write SVG:" << outPath; - return {}; + return { }; } out.write(svg.toUtf8()); @@ -66,6 +66,9 @@ QString recolorSvgXML(QString &svg, const QString &placeHolder, const QColor &color) { + // TODO: svg colors can work without ;, we need to update all the svg files to inlude ; + // Include the trailing ';' so e.g. "#ff0;" never accidentally matches + // inside a previously-substituted value like "#ff0000;". return svg.replace(placeHolder, color.name(QColor::HexRgb), Qt::CaseInsensitive); } diff --git a/common/themes/icon_utils.h b/common/themes/icon_utils.h index 48b4462c..aef05bd7 100644 --- a/common/themes/icon_utils.h +++ b/common/themes/icon_utils.h @@ -14,23 +14,23 @@ struct RecolorOptions { }; QString readSvg(const QString &resourcePath); -QString writeSvg(const QString &svg, const QString &resourcePath, const QString &themeName, const RecolorOptions &options = {}); +QString writeSvg(const QString &svg, const QString &resourcePath, const QString &themeName, const RecolorOptions &options = { }); QString recolorSvgXML(QString &svg, const QString &placeHolder, const QColor &color); QString recoloredSvgToThemeFile(const QString &resourcePath, const QColor &color, // #f0f (magenta) const QString &themeName, - const RecolorOptions &options = {}); + const RecolorOptions &options = { }); QString recoloredSvgToThemeFile(const QString &resourcePath, const QColor &color1, // #f0f (magenta) const QColor &color2, // #0ff (cyan) const QString &themeName, - const RecolorOptions &options = {}); + const RecolorOptions &options = { }); QString recoloredSvgToThemeFile(const QString &resourcePath, const QColor &color1, // #f0f (magenta) const QColor &color2, // #0ff (cyan) const QColor &color3, // #ff0 (yellow) const QString &themeName, - const RecolorOptions &options = {}); + const RecolorOptions &options = { }); #endif // ICON_UTILS_H diff --git a/common/themes/theme_editor_dialog.cpp b/common/themes/theme_editor_dialog.cpp new file mode 100644 index 00000000..72d03327 --- /dev/null +++ b/common/themes/theme_editor_dialog.cpp @@ -0,0 +1,495 @@ +#include "theme_editor_dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Role used to store the JSON path (QStringList) on each leaf item. +static const int PathRole = Qt::UserRole; +// Role used to distinguish color items from others. +static const int IsColorRole = Qt::UserRole + 1; +// Role used to distinguish boolean items. +static const int IsBoolRole = Qt::UserRole + 2; +// Role used to distinguish numeric items. +static const int IsNumberRole = Qt::UserRole + 3; + +// Converts a camelCase JSON key to a human-readable display string. +// "metadataScraperDialog" → "Metadata scraper dialog" +// "navigationTreeQSS" → "Navigation tree qss" +// Consecutive uppercase letters (acronyms) are kept together as one word. +static QString displayKey(const QString &key) +{ + QString result; + for (int i = 0; i < key.size(); ++i) { + const bool isUpper = key[i].isUpper(); + const bool prevIsLower = (i > 0) && key[i - 1].isLower(); + const bool nextIsLower = (i + 1 < key.size()) && key[i + 1].isLower(); + if (i > 0 && isUpper && (prevIsLower || nextIsLower)) + result += ' '; + result += result.isEmpty() ? key[i].toUpper() : key[i].toLower(); + } + return result; +} + +static bool isColorString(const QString &s) +{ + // Accepts #RGB, #RRGGBB, #AARRGGBB + if (!s.startsWith('#')) + return false; + const int len = s.length(); + return len == 4 || len == 7 || len == 9; +} + +ThemeEditorDialog::ThemeEditorDialog(const QJsonObject ¶ms, QWidget *parent) + : QDialog(parent), params(params) +{ + setWindowTitle(tr("Theme Editor")); + resize(520, 700); + + // --- top toolbar --- + auto *expandBtn = new QPushButton(tr("+"), this); + auto *collapseBtn = new QPushButton(tr("-"), this); + auto *identifyBtn = new QPushButton(tr("i"), this); + expandBtn->setFixedWidth(28); + collapseBtn->setFixedWidth(28); + identifyBtn->setFixedWidth(28); + expandBtn->setToolTip(tr("Expand all")); + collapseBtn->setToolTip(tr("Collapse all")); + identifyBtn->setToolTip(tr("Hold to flash the selected value in the UI (magenta / toggled / 0↔10). Releases restore the original.")); + // NoFocus so clicking the button doesn't steal the tree's current item + identifyBtn->setFocusPolicy(Qt::NoFocus); + + searchEdit = new QLineEdit(this); + searchEdit->setPlaceholderText(tr("Search…")); + searchEdit->setClearButtonEnabled(true); + + auto *toolbar = new QHBoxLayout(); + toolbar->addWidget(expandBtn); + toolbar->addWidget(collapseBtn); + toolbar->addWidget(identifyBtn); + toolbar->addStretch(); + toolbar->addWidget(searchEdit); + + connect(identifyBtn, &QPushButton::pressed, this, &ThemeEditorDialog::identifyPressed); + connect(identifyBtn, &QPushButton::released, this, &ThemeEditorDialog::identifyReleased); + + // --- meta section --- + idLabel = new QLabel(this); + idLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + nameEdit = new QLineEdit(this); + variantCombo = new QComboBox(this); + variantCombo->addItem(tr("Light"), "light"); + variantCombo->addItem(tr("Dark"), "dark"); + + auto *metaForm = new QFormLayout(); + metaForm->addRow(tr("ID:"), idLabel); + metaForm->addRow(tr("Display name:"), nameEdit); + metaForm->addRow(tr("Variant:"), variantCombo); + + auto *metaBox = new QGroupBox(tr("Theme info"), this); + metaBox->setLayout(metaForm); + + syncMetaFromParams(); + + connect(nameEdit, &QLineEdit::textEdited, this, [this](const QString &text) { + auto meta = this->params["meta"].toObject(); + meta["displayName"] = text; + this->params["meta"] = meta; + emit themeJsonChanged(this->params); + }); + connect(variantCombo, &QComboBox::currentIndexChanged, this, [this](int index) { + auto meta = this->params["meta"].toObject(); + meta["variant"] = variantCombo->itemData(index).toString(); + this->params["meta"] = meta; + emit themeJsonChanged(this->params); + }); + + // --- tree --- + tree = new QTreeWidget(this); + tree->setColumnCount(2); + tree->setHeaderLabels({ tr("Parameter"), tr("Value") }); + tree->header()->setSectionResizeMode(0, QHeaderView::Stretch); + tree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + tree->setRootIsDecorated(true); + tree->setUniformRowHeights(true); + tree->setAlternatingRowColors(true); + + populate(nullptr, params, { }); + tree->expandAll(); + + connect(expandBtn, &QPushButton::clicked, tree, &QTreeWidget::expandAll); + connect(collapseBtn, &QPushButton::clicked, tree, &QTreeWidget::collapseAll); + connect(searchEdit, &QLineEdit::textChanged, this, &ThemeEditorDialog::filterTree); + + connect(tree, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem *item, int) { + if (item->data(0, IsColorRole).toBool()) + editColorItem(item); + else if (item->data(0, IsBoolRole).toBool()) + toggleBoolItem(item); + else if (item->data(0, IsNumberRole).toBool()) + editNumberItem(item); + }); + + // --- bottom buttons --- + auto *saveBtn = new QPushButton(tr("Save to file..."), this); + auto *loadBtn = new QPushButton(tr("Load from file..."), this); + auto *closeBtn = new QPushButton(tr("Close"), this); + connect(saveBtn, &QPushButton::clicked, this, &ThemeEditorDialog::saveToFile); + connect(loadBtn, &QPushButton::clicked, this, &ThemeEditorDialog::loadFromFile); + connect(closeBtn, &QPushButton::clicked, this, &QDialog::close); + auto *buttons = new QHBoxLayout(); + buttons->addWidget(saveBtn); + buttons->addWidget(loadBtn); + buttons->addStretch(); + buttons->addWidget(closeBtn); + + auto *layout = new QVBoxLayout(this); + layout->addLayout(toolbar); + layout->addWidget(metaBox); + layout->addWidget(tree); + layout->addLayout(buttons); + + setLayout(layout); +} + +void ThemeEditorDialog::populate(QTreeWidgetItem *parent, const QJsonObject &obj, const QStringList &path) +{ + for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) { + const QString key = it.key(); + + // "meta" is handled by the dedicated UI above the tree + if (path.isEmpty() && key == "meta") + continue; + const QJsonValue val = it.value(); + const QStringList childPath = path + QStringList(key); + + if (val.isObject()) { + // Group row + QTreeWidgetItem *group = parent ? new QTreeWidgetItem(parent) + : new QTreeWidgetItem(tree); + QFont bold = group->font(0); + bold.setBold(true); + group->setFont(0, bold); + group->setText(0, displayKey(key)); + group->setFlags(group->flags() & ~Qt::ItemIsSelectable); + populate(group, val.toObject(), childPath); + } else { + // Leaf row + QTreeWidgetItem *item = parent ? new QTreeWidgetItem(parent) + : new QTreeWidgetItem(tree); + item->setText(0, displayKey(key)); + item->setData(0, PathRole, childPath); + + const QString strVal = val.toString(); + if (val.isString() && isColorString(strVal)) { + const QColor color(strVal); + item->setIcon(1, colorIcon(color)); + item->setText(1, strVal); + item->setData(0, IsColorRole, true); + item->setToolTip(1, tr("Double-click to edit color")); + } else if (val.isBool()) { + item->setText(1, val.toBool() ? tr("true") : tr("false")); + item->setData(0, IsColorRole, false); + item->setData(0, IsBoolRole, true); + item->setToolTip(1, tr("Double-click to toggle")); + } else if (val.isDouble()) { + item->setText(1, QString::number(val.toDouble())); + item->setData(0, IsNumberRole, true); + item->setToolTip(1, tr("Double-click to edit value")); + } else { + item->setText(1, strVal); + item->setData(0, IsColorRole, false); + } + } + } +} + +void ThemeEditorDialog::editColorItem(QTreeWidgetItem *item) +{ + const QColor current(item->text(1)); + QColorDialog dialog(current, this); + dialog.setOption(QColorDialog::ShowAlphaChannel, true); + dialog.setWindowTitle(tr("Edit: %1").arg(item->text(0))); + + // Live update as user drags the picker + connect(&dialog, &QColorDialog::currentColorChanged, this, [this, item](const QColor &color) { + applyColorToItem(item, color); + emit themeJsonChanged(params); + }); + + if (dialog.exec() == QDialog::Accepted) { + applyColorToItem(item, dialog.selectedColor()); + } else { + // Revert to original if cancelled + applyColorToItem(item, current); + } + emit themeJsonChanged(params); +} + +void ThemeEditorDialog::applyColorToItem(QTreeWidgetItem *item, const QColor &color) +{ + const QString hexStr = color.alpha() < 255 + ? color.name(QColor::HexArgb) + : color.name(QColor::HexRgb); + item->setText(1, hexStr); + item->setIcon(1, colorIcon(color)); + + const QStringList path = item->data(0, PathRole).toStringList(); + setJsonPath(params, path, hexStr); +} + +void ThemeEditorDialog::toggleBoolItem(QTreeWidgetItem *item) +{ + const bool newValue = item->text(1) != tr("true"); + item->setText(1, newValue ? tr("true") : tr("false")); + const QStringList path = item->data(0, PathRole).toStringList(); + setJsonPath(params, path, newValue); + emit themeJsonChanged(params); +} + +void ThemeEditorDialog::editNumberItem(QTreeWidgetItem *item) +{ + const double current = item->text(1).toDouble(); + // Use integer dialog when the stored value has no fractional part + const bool isInt = (current == std::floor(current)); + bool ok = false; + double newValue; + if (isInt) { + const int result = QInputDialog::getInt( + this, tr("Edit: %1").arg(item->text(0)), item->text(0), + static_cast(current), INT_MIN, INT_MAX, 1, &ok); + newValue = result; + } else { + newValue = QInputDialog::getDouble( + this, tr("Edit: %1").arg(item->text(0)), item->text(0), + current, -1e9, 1e9, 4, &ok); + } + if (!ok) + return; + item->setText(1, isInt ? QString::number(static_cast(newValue)) : QString::number(newValue)); + const QStringList path = item->data(0, PathRole).toStringList(); + setJsonPath(params, path, newValue); + emit themeJsonChanged(params); +} + +// Returns true if the item or any of its descendants should be visible. +static bool applyFilter(QTreeWidgetItem *item, const QString &query) +{ + if (query.isEmpty()) { + item->setHidden(false); + for (int i = 0; i < item->childCount(); ++i) + applyFilter(item->child(i), query); + return true; + } + + const bool selfMatch = item->text(0).contains(query, Qt::CaseInsensitive); + + if (item->childCount() == 0) { + // Leaf: match on key name or value text + const bool match = selfMatch || item->text(1).contains(query, Qt::CaseInsensitive); + item->setHidden(!match); + return match; + } + + // Group: if the group name itself matches, show all children + bool anyChildVisible = false; + for (int i = 0; i < item->childCount(); ++i) { + if (selfMatch) { + item->child(i)->setHidden(false); + anyChildVisible = true; + } else { + if (applyFilter(item->child(i), query)) + anyChildVisible = true; + } + } + item->setHidden(!anyChildVisible); + return anyChildVisible; +} + +void ThemeEditorDialog::filterTree(const QString &query) +{ + for (int i = 0; i < tree->topLevelItemCount(); ++i) + applyFilter(tree->topLevelItem(i), query); + + // Keep visible results expanded so they're reachable + if (!query.isEmpty()) + tree->expandAll(); +} + +QIcon ThemeEditorDialog::colorIcon(const QColor &color) +{ + const int size = qApp->style()->pixelMetric(QStyle::PM_SmallIconSize); + QPixmap pix(size, size); + pix.fill(color); + return QIcon(pix); +} + +void ThemeEditorDialog::setJsonPath(QJsonObject &root, const QStringList &path, const QJsonValue &value) +{ + if (path.isEmpty()) + return; + if (path.size() == 1) { + root[path[0]] = value; + return; + } + QJsonObject sub = root[path[0]].toObject(); + setJsonPath(sub, path.mid(1), value); + root[path[0]] = sub; +} + +void ThemeEditorDialog::syncMetaToParams() +{ + auto meta = params["meta"].toObject(); + meta["displayName"] = nameEdit->text(); + meta["variant"] = variantCombo->currentData().toString(); + params["meta"] = meta; +} + +void ThemeEditorDialog::syncMetaFromParams() +{ + const auto meta = params["meta"].toObject(); + idLabel->setText(meta["id"].toString()); + nameEdit->setText(meta["displayName"].toString()); + const QString variant = meta["variant"].toString("dark"); + variantCombo->setCurrentIndex(variant == "light" ? 0 : 1); +} + +void ThemeEditorDialog::saveToFile() +{ + // Assign a user-scoped UUID if the current id is builtin or empty + auto meta = params["meta"].toObject(); + const QString currentId = meta["id"].toString(); + if (currentId.isEmpty() || currentId.startsWith("builtin/")) { + const QString newId = "user/" + QUuid::createUuid().toString(QUuid::WithoutBraces); + meta["id"] = newId; + params["meta"] = meta; + idLabel->setText(newId); + } + + const QString path = QFileDialog::getSaveFileName( + this, tr("Save theme"), QString(), tr("JSON files (*.json);;All files (*)")); + if (path.isEmpty()) + return; + + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::warning(this, tr("Save failed"), tr("Could not open file for writing:\n%1").arg(path)); + return; + } + file.write(QJsonDocument(params).toJson(QJsonDocument::Indented)); +} + +void ThemeEditorDialog::loadFromFile() +{ + const QString path = QFileDialog::getOpenFileName( + this, tr("Load theme"), QString(), tr("JSON files (*.json);;All files (*)")); + if (path.isEmpty()) + return; + + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::warning(this, tr("Load failed"), tr("Could not open file:\n%1").arg(path)); + return; + } + + QJsonParseError err; + const QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err); + if (doc.isNull()) { + QMessageBox::warning(this, tr("Load failed"), tr("Invalid JSON:\n%1").arg(err.errorString())); + return; + } + if (!doc.isObject()) { + QMessageBox::warning(this, tr("Load failed"), tr("Expected a JSON object.")); + return; + } + + params = doc.object(); + syncMetaFromParams(); + tree->clear(); + populate(nullptr, params, { }); + tree->expandAll(); + emit themeJsonChanged(params); +} + +void ThemeEditorDialog::identifyPressed() +{ + QTreeWidgetItem *item = tree->currentItem(); + if (!item) + return; + + const QStringList path = item->data(0, PathRole).toStringList(); + if (path.isEmpty()) + return; // group row, not a leaf + + if (item->data(0, IsColorRole).toBool()) { + identifySnapshot = QJsonValue(item->text(1)); + identifyItem = item; + identifyPath = path; + applyColorToItem(item, QColor(0xFA00FA)); + } else if (item->data(0, IsBoolRole).toBool()) { + const bool current = (item->text(1) == tr("true")); + identifySnapshot = QJsonValue(current); + identifyItem = item; + identifyPath = path; + const bool flipped = !current; + item->setText(1, flipped ? tr("true") : tr("false")); + setJsonPath(params, path, flipped); + } else if (item->data(0, IsNumberRole).toBool()) { + const double current = item->text(1).toDouble(); + identifySnapshot = QJsonValue(current); + identifyItem = item; + identifyPath = path; + const bool isInt = (current == std::floor(current)); + const double highlight = (current > 0.0) ? 0.0 : 10.0; + item->setText(1, isInt ? QString::number(static_cast(highlight)) : QString::number(highlight)); + setJsonPath(params, path, highlight); + } else { + return; // non-editable leaf (plain string), nothing to flash + } + emit themeJsonChanged(params); +} + +void ThemeEditorDialog::identifyReleased() +{ + if (!identifyItem) + return; + + QTreeWidgetItem *item = identifyItem; + const QStringList path = identifyPath; + identifyItem = nullptr; + identifyPath.clear(); + + if (item->data(0, IsColorRole).toBool()) { + applyColorToItem(item, QColor(identifySnapshot.toString())); + } else if (item->data(0, IsBoolRole).toBool()) { + const bool restored = identifySnapshot.toBool(); + item->setText(1, restored ? tr("true") : tr("false")); + setJsonPath(params, path, restored); + } else if (item->data(0, IsNumberRole).toBool()) { + const double restored = identifySnapshot.toDouble(); + const bool isInt = (restored == std::floor(restored)); + item->setText(1, isInt ? QString::number(static_cast(restored)) : QString::number(restored)); + setJsonPath(params, path, restored); + } + identifySnapshot = QJsonValue(); + emit themeJsonChanged(params); +} diff --git a/common/themes/theme_editor_dialog.h b/common/themes/theme_editor_dialog.h new file mode 100644 index 00000000..a2f5a5e2 --- /dev/null +++ b/common/themes/theme_editor_dialog.h @@ -0,0 +1,63 @@ +#ifndef THEME_EDITOR_DIALOG_H +#define THEME_EDITOR_DIALOG_H + +#include +#include + +class QComboBox; +class QLabel; +class QLineEdit; +class QTreeWidget; +class QTreeWidgetItem; + +// Generic theme parameter editor. +// Works entirely on QJsonObject — has no knowledge of app-specific ThemeParams. +// Connect to themeJsonChanged to receive live updates as the user edits colors. +class ThemeEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit ThemeEditorDialog(const QJsonObject ¶ms, QWidget *parent = nullptr); + + QJsonObject currentParams() const { return params; } + +signals: + void themeJsonChanged(const QJsonObject ¶ms); + +private: + void populate(QTreeWidgetItem *parent, const QJsonObject &obj, const QStringList &path); + void editColorItem(QTreeWidgetItem *item); + void applyColorToItem(QTreeWidgetItem *item, const QColor &color); + void toggleBoolItem(QTreeWidgetItem *item); + void editNumberItem(QTreeWidgetItem *item); + void filterTree(const QString &query); + void saveToFile(); + void loadFromFile(); + + // Identify feature: hold the (i) button to temporarily flash the UI element + // that uses the current item's value. The original value is restored on release. + void identifyPressed(); + void identifyReleased(); + + static QIcon colorIcon(const QColor &color); + static void setJsonPath(QJsonObject &root, const QStringList &path, const QJsonValue &value); + + QTreeWidget *tree; + QLineEdit *searchEdit; + QJsonObject params; + + // Meta UI + QLabel *idLabel; + QLineEdit *nameEdit; + QComboBox *variantCombo; + + void syncMetaToParams(); + void syncMetaFromParams(); + + // Identify state (null item = inactive) + QTreeWidgetItem *identifyItem = nullptr; + QStringList identifyPath; + QJsonValue identifySnapshot; // original value saved on press, restored on release +}; + +#endif // THEME_EDITOR_DIALOG_H diff --git a/common/themes/theme_id.h b/common/themes/theme_id.h deleted file mode 100644 index f42c3069..00000000 --- a/common/themes/theme_id.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef THEME_ID_H -#define THEME_ID_H - -enum class ThemeId { - Classic, - Light, - Dark, -}; - -#endif // THEME_ID_H diff --git a/common/themes/theme_manager.cpp b/common/themes/theme_manager.cpp index e39554be..bf003164 100644 --- a/common/themes/theme_manager.cpp +++ b/common/themes/theme_manager.cpp @@ -1,14 +1,13 @@ #include "theme_manager.h" +#include "appearance_configuration.h" #include "theme.h" #include "theme_factory.h" +#include "theme_repository.h" #include -#include #include -// TODO: add API to force color scheme //styleHints->setColorScheme(Qt::ColorScheme::Dark); - ThemeManager::ThemeManager() { } @@ -19,47 +18,106 @@ ThemeManager &ThemeManager::instance() return instance; } -void ThemeManager::initialize() +void ThemeManager::initialize(AppearanceConfiguration *config, ThemeRepository *repository) { - // QStyleHints::colorScheme is only 6.5+ -#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + this->config = config; + this->repository = repository; + auto *styleHints = qGuiApp->styleHints(); - auto colorScheme = styleHints->colorScheme(); + // Re-resolve when OS color scheme changes (relevant for FollowSystem mode) + connect(styleHints, &QStyleHints::colorSchemeChanged, this, &ThemeManager::resolveTheme, Qt::QueuedConnection); - // TODO: settings are needed to decide what theme to use - auto applyColorScheme = [this](Qt::ColorScheme scheme) { - setTheme(scheme == Qt::ColorScheme::Dark ? ThemeId::Dark : ThemeId::Light); - }; + // Re-resolve when the user changes any theme setting + connect(config, &AppearanceConfiguration::selectionChanged, this, &ThemeManager::resolveTheme); - applyColorScheme(colorScheme); - - connect(styleHints, &QStyleHints::colorSchemeChanged, this, applyColorScheme, Qt::QueuedConnection); -#else - auto applyPalette = [this](const QPalette &palette) { - setTheme(palette.color(QPalette::Window).lightness() < 128 ? ThemeId::Dark : ThemeId::Light); - }; - - applyPalette(qGuiApp->palette()); - - connect(qGuiApp, &QGuiApplication::paletteChanged, this, applyPalette, Qt::QueuedConnection); -#endif + resolveTheme(); } -void ThemeManager::setTheme(ThemeId themeId) +void ThemeManager::setTheme(const Theme &theme) { - if (this->themeId == themeId) { - return; - } - - this->themeId = themeId; - - updateCurrentTheme(); - + currentTheme = theme; emit themeChanged(); } -void ThemeManager::updateCurrentTheme() +Theme ThemeManager::themeFromId(const QString &id, ThemeVariant fallbackVariant) { - currentTheme = makeTheme(themeId); + // Try the repository first (handles both builtin and user themes via JSON) + if (repository && repository->contains(id)) { + QJsonObject json = repository->loadThemeJson(id); + if (!json.isEmpty()) + return makeTheme(json); + } + + // Fallback to the builtin that matches the current dark/light intent. + const QString fallbackId = (fallbackVariant == ThemeVariant::Dark) + ? QStringLiteral("builtin/dark") + : QStringLiteral("builtin/light"); + if (repository && repository->contains(fallbackId)) { + QJsonObject json = repository->loadThemeJson(fallbackId); + if (!json.isEmpty()) + return makeTheme(json); + } + + return { }; +} + +void ThemeManager::resolveTheme() +{ + if (!config) + return; + + const auto &sel = config->selection(); + + QString id; + ThemeVariant fallbackVariant; + switch (sel.mode) { + case ThemeMode::FollowSystem: { + const bool isDark = (qGuiApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark); + id = isDark ? sel.darkThemeId : sel.lightThemeId; + fallbackVariant = isDark ? ThemeVariant::Dark : ThemeVariant::Light; + break; + } + case ThemeMode::Light: + id = sel.lightThemeId; + fallbackVariant = ThemeVariant::Light; + break; + case ThemeMode::Dark: + id = sel.darkThemeId; + fallbackVariant = ThemeVariant::Dark; + break; + case ThemeMode::ForcedTheme: + id = sel.fixedThemeId; + fallbackVariant = (qGuiApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark) + ? ThemeVariant::Dark + : ThemeVariant::Light; + break; + } + + const Theme theme = themeFromId(id, fallbackVariant); + + // Sync Qt's application-level color scheme so native widgets (menus, scrollbars, + // standard dialogs) use the correct palette before themeChanged() is emitted. +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + Qt::ColorScheme scheme; + switch (sel.mode) { + case ThemeMode::FollowSystem: + scheme = Qt::ColorScheme::Unknown; // delegate to OS + break; + case ThemeMode::Light: + scheme = Qt::ColorScheme::Light; + break; + case ThemeMode::Dark: + scheme = Qt::ColorScheme::Dark; + break; + case ThemeMode::ForcedTheme: + scheme = (theme.meta.variant == ThemeVariant::Dark) + ? Qt::ColorScheme::Dark + : Qt::ColorScheme::Light; + break; + } + qGuiApp->styleHints()->setColorScheme(scheme); +#endif + + setTheme(theme); } diff --git a/common/themes/theme_manager.h b/common/themes/theme_manager.h index 8bac2dad..7ffbeea7 100644 --- a/common/themes/theme_manager.h +++ b/common/themes/theme_manager.h @@ -1,11 +1,13 @@ #ifndef THEME_MANAGER_H #define THEME_MANAGER_H +#include "appearance_configuration.h" #include "theme.h" -#include "theme_id.h" #include +class ThemeRepository; + class ThemeManager : public QObject { Q_OBJECT @@ -17,21 +19,28 @@ public: ThemeManager(ThemeManager &&) = delete; ThemeManager &operator=(ThemeManager &&) = delete; - void initialize(); - - void setTheme(ThemeId themeId); + void initialize(AppearanceConfiguration *config, ThemeRepository *repository); + void setTheme(const Theme &theme); const Theme &getCurrentTheme() const { return currentTheme; } + AppearanceConfiguration *getAppearanceConfiguration() const { return config; } + ThemeRepository *getRepository() const { return repository; } + signals: void themeChanged(); private: explicit ThemeManager(); - ThemeId themeId = ThemeId::Classic; + + AppearanceConfiguration *config = nullptr; + ThemeRepository *repository = nullptr; Theme currentTheme; - void updateCurrentTheme(); + Theme themeFromId(const QString &id, ThemeVariant fallbackVariant); + +private slots: + void resolveTheme(); }; #endif // THEME_MANAGER_H diff --git a/common/themes/theme_meta.h b/common/themes/theme_meta.h new file mode 100644 index 00000000..e3b9c6d8 --- /dev/null +++ b/common/themes/theme_meta.h @@ -0,0 +1,14 @@ +#ifndef THEME_META_H +#define THEME_META_H + +#include "theme_variant.h" + +#include + +struct ThemeMeta { + QString id; + QString displayName; + ThemeVariant variant; +}; + +#endif // THEME_META_H diff --git a/common/themes/theme_repository.cpp b/common/themes/theme_repository.cpp new file mode 100644 index 00000000..d76695c8 --- /dev/null +++ b/common/themes/theme_repository.cpp @@ -0,0 +1,206 @@ +#include "theme_repository.h" + +#include +#include +#include +#include +#include + +ThemeRepository::ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir) + : qrcPrefix(qrcPrefix), userThemesDir(userThemesDir) +{ + scanBuiltins(); + scanUserThemes(); +} + +QList ThemeRepository::availableThemes() const +{ + QList result; + result.reserve(builtins.size() + userThemes.size()); + + for (const auto &b : builtins) + result.append({ b.meta.id, b.meta.displayName, b.meta.variant, true }); + + for (const auto &u : userThemes) + result.append({ u.meta.id, u.meta.displayName, u.meta.variant, false }); + + return result; +} + +bool ThemeRepository::contains(const QString &themeId) const +{ + for (const auto &b : builtins) + if (b.id == themeId) + return true; + + for (const auto &u : userThemes) + if (u.id == themeId) + return true; + + return false; +} + +QJsonObject ThemeRepository::loadThemeJson(const QString &themeId) const +{ + for (const auto &b : builtins) + if (b.id == themeId) + return readJsonFile(b.resourcePath); + + for (const auto &u : userThemes) + if (u.id == themeId) + return readJsonFile(u.filePath); + + return { }; +} + +QString ThemeRepository::saveUserTheme(QJsonObject themeJson) +{ + QDir().mkpath(userThemesDir); + + auto metaObj = themeJson["meta"].toObject(); + QString id = metaObj["id"].toString(); + + if (id.isEmpty() || id.startsWith("builtin/")) { + const QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + id = "user/" + uuid; + metaObj["id"] = id; + themeJson["meta"] = metaObj; + } + + // Extract uuid from "user/" + const QString uuid = id.mid(5); // skip "user/" + const QString filePath = filePathForUserTheme(uuid); + + QFile file(filePath); + if (file.open(QIODevice::WriteOnly)) { + file.write(QJsonDocument(themeJson).toJson(QJsonDocument::Indented)); + file.close(); + } + + // Update cache + refresh(); + + return id; +} + +bool ThemeRepository::deleteUserTheme(const QString &themeId) +{ + if (themeId.startsWith("builtin/")) + return false; + + for (const auto &u : userThemes) { + if (u.id == themeId) { + const bool removed = QFile::remove(u.filePath); + if (removed) + refresh(); + return removed; + } + } + + return false; +} + +QString ThemeRepository::importThemeFromFile(const QString &filePath) +{ + QJsonObject json = readJsonFile(filePath); + if (json.isEmpty()) + return { }; + + // Force a new user id regardless of what the file contains + auto metaObj = json["meta"].toObject(); + const QString uuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + const QString id = "user/" + uuid; + metaObj["id"] = id; + json["meta"] = metaObj; + + return saveUserTheme(json); +} + +void ThemeRepository::refresh() +{ + scanUserThemes(); +} + +// --- Private helpers --- + +void ThemeRepository::scanBuiltins() +{ + builtins.clear(); + + static const QStringList builtinNames = { "classic", "light", "dark" }; + + for (const auto &name : builtinNames) { + const QString resourcePath = qrcPrefix + "/builtin_" + name + ".json"; + const QJsonObject json = readJsonFile(resourcePath); + if (json.isEmpty()) + continue; + + BuiltinEntry entry; + entry.id = "builtin/" + name; + entry.resourcePath = resourcePath; + entry.meta = extractMeta(json); + // Ensure the id matches the canonical form + entry.meta.id = entry.id; + builtins.append(entry); + } +} + +void ThemeRepository::scanUserThemes() +{ + userThemes.clear(); + + QDir dir(userThemesDir); + if (!dir.exists()) + return; + + const auto entries = dir.entryList({ "*.json" }, QDir::Files); + for (const auto &fileName : entries) { + const QString filePath = dir.absoluteFilePath(fileName); + const QJsonObject json = readJsonFile(filePath); + if (json.isEmpty()) + continue; + + ThemeMeta meta = extractMeta(json); + if (meta.id.isEmpty()) { + // Derive id from filename (strip .json extension) + const QString baseName = fileName.chopped(5); // remove ".json" + meta.id = "user/" + baseName; + } + + UserEntry entry; + entry.id = meta.id; + entry.filePath = filePath; + entry.meta = meta; + userThemes.append(entry); + } +} + +ThemeMeta ThemeRepository::extractMeta(const QJsonObject &json) +{ + const auto meta = json["meta"].toObject(); + return ThemeMeta { + meta["id"].toString(), + meta["displayName"].toString(), + (meta["variant"].toString() == "light") ? ThemeVariant::Light : ThemeVariant::Dark + }; +} + +QJsonObject ThemeRepository::readJsonFile(const QString &path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + return { }; + + const QByteArray data = file.readAll(); + QJsonParseError error; + const QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) + return { }; + + return doc.object(); +} + +QString ThemeRepository::filePathForUserTheme(const QString &uuid) const +{ + return userThemesDir + "/" + uuid + ".json"; +} diff --git a/common/themes/theme_repository.h b/common/themes/theme_repository.h new file mode 100644 index 00000000..e9ec4a85 --- /dev/null +++ b/common/themes/theme_repository.h @@ -0,0 +1,57 @@ +#ifndef THEME_REPOSITORY_H +#define THEME_REPOSITORY_H + +#include "theme_meta.h" + +#include +#include +#include + +struct ThemeListEntry { + QString id; + QString displayName; + ThemeVariant variant; + bool isBuiltin; +}; + +class ThemeRepository +{ +public: + explicit ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir); + + QList availableThemes() const; + bool contains(const QString &themeId) const; + QJsonObject loadThemeJson(const QString &themeId) const; + + QString saveUserTheme(QJsonObject themeJson); + bool deleteUserTheme(const QString &themeId); + QString importThemeFromFile(const QString &filePath); + + void refresh(); + +private: + QString qrcPrefix; + QString userThemesDir; + + struct BuiltinEntry { + QString id; + QString resourcePath; + ThemeMeta meta; + }; + QList builtins; + + struct UserEntry { + QString id; + QString filePath; + ThemeMeta meta; + }; + QList userThemes; + + void scanBuiltins(); + void scanUserThemes(); + static ThemeMeta extractMeta(const QJsonObject &json); + static QJsonObject readJsonFile(const QString &path); + QString filePathForUserTheme(const QString &uuid) const; +}; + +#endif // THEME_REPOSITORY_H diff --git a/common/themes/theme_variant.h b/common/themes/theme_variant.h new file mode 100644 index 00000000..adff0327 --- /dev/null +++ b/common/themes/theme_variant.h @@ -0,0 +1,9 @@ +#ifndef THEME_VARIANT_H +#define THEME_VARIANT_H + +enum class ThemeVariant { + Light, + Dark, +}; + +#endif // THEME_VARIANT_H diff --git a/custom_widgets/yacreader_table_view.cpp b/custom_widgets/yacreader_table_view.cpp index 85c6ccba..1075f9dd 100644 --- a/custom_widgets/yacreader_table_view.cpp +++ b/custom_widgets/yacreader_table_view.cpp @@ -56,7 +56,7 @@ YACReaderTableView::YACReaderTableView(QWidget *parent) void YACReaderTableView::applyTheme(const Theme &theme) { - setStyleSheet(theme.tableView.tableViewQSS); + setStyleSheet(theme.comicsViewTable.tableViewQSS); } void YACReaderTableView::mouseMoveEvent(QMouseEvent *event) diff --git a/custom_widgets/yacreader_table_view.h b/custom_widgets/yacreader_table_view.h index 7c17e34f..f455854a 100644 --- a/custom_widgets/yacreader_table_view.h +++ b/custom_widgets/yacreader_table_view.h @@ -13,8 +13,8 @@ class YACReaderTableView : public QTableView, protected Themable Q_OBJECT public: explicit YACReaderTableView(QWidget *parent = 0); - QColor starRatingColor() const { return theme.tableView.starRatingColor; } - QColor starRatingSelectedColor() const { return theme.tableView.starRatingSelectedColor; } + QColor starRatingColor() const { return theme.comicsViewTable.starRatingColor; } + QColor starRatingSelectedColor() const { return theme.comicsViewTable.starRatingSelectedColor; } protected: void applyTheme(const Theme &theme) override; diff --git a/custom_widgets/yacreader_treeview.cpp b/custom_widgets/yacreader_treeview.cpp index 4a07424e..c2699bd4 100644 --- a/custom_widgets/yacreader_treeview.cpp +++ b/custom_widgets/yacreader_treeview.cpp @@ -21,7 +21,7 @@ YACReaderTreeView::YACReaderTreeView(QWidget *parent) void YACReaderTreeView::applyTheme(const Theme &theme) { - setStyleSheet(theme.treeView.treeViewQSS); + setStyleSheet(theme.navigationTree.navigationTreeQSS); } void YACReaderTreeView::mousePressEvent(QMouseEvent *event) diff --git a/custom_widgets/yacreader_treeview.h b/custom_widgets/yacreader_treeview.h index 78b0154f..bf8e43f1 100644 --- a/custom_widgets/yacreader_treeview.h +++ b/custom_widgets/yacreader_treeview.h @@ -10,7 +10,7 @@ class YACReaderTreeView : public QTreeView, protected Themable Q_OBJECT public: explicit YACReaderTreeView(QWidget *parent = 0); - QColor folderIndicatorColor() const { return theme.treeView.folderIndicatorColor; } + QColor folderIndicatorColor() const { return theme.navigationTree.folderIndicatorColor; } private: void mousePressEvent(QMouseEvent *event) override; diff --git a/images/appearance_config/theme-mode-custom.svg b/images/appearance_config/theme-mode-custom.svg new file mode 100644 index 00000000..227cd5da --- /dev/null +++ b/images/appearance_config/theme-mode-custom.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/appearance_config/theme-mode-dark.svg b/images/appearance_config/theme-mode-dark.svg new file mode 100644 index 00000000..a30277b9 --- /dev/null +++ b/images/appearance_config/theme-mode-dark.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/appearance_config/theme-mode-light.svg b/images/appearance_config/theme-mode-light.svg new file mode 100644 index 00000000..401b54c8 --- /dev/null +++ b/images/appearance_config/theme-mode-light.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/appearance_config/theme-mode-system.svg b/images/appearance_config/theme-mode-system.svg new file mode 100644 index 00000000..62fc4569 --- /dev/null +++ b/images/appearance_config/theme-mode-system.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/f.png b/images/f.png deleted file mode 100644 index 232a785d..00000000 Binary files a/images/f.png and /dev/null differ diff --git a/images/f_overlayed.png b/images/f_overlayed.png deleted file mode 100644 index eb53bca4..00000000 Binary files a/images/f_overlayed.png and /dev/null differ diff --git a/images/f_overlayed_retina.png b/images/f_overlayed_retina.png deleted file mode 100644 index d613de82..00000000 Binary files a/images/f_overlayed_retina.png and /dev/null differ diff --git a/release/server/docroot/css/reset.css b/release/server/docroot/css/reset.css deleted file mode 100644 index b1f6c072..00000000 --- a/release/server/docroot/css/reset.css +++ /dev/null @@ -1,46 +0,0 @@ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td -{ - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-weight: inherit; - font-style: inherit; - font-size: 100%; - font-family: inherit; - vertical-align: baseline; -} -/* remember to define focus styles! */ -:focus -{ - outline: 0; -} -body -{ - line-height: 1; - color: black; -} -ol, ul -{ - list-style: none; -} -/* tables still need 'cellspacing="0"' in the markup */ -table, td, tr -{ - border: 0; - border-collapse: separate; - border-spacing: 0; -} -caption, th, td -{ - text-align: left; - font-weight: normal; -} -blockquote:before, blockquote:after, q:before, q:after -{ - content: ""; -} -blockquote, q -{ - quotes: "" ""; -} diff --git a/release/server/docroot/css/styles_ipad.css b/release/server/docroot/css/styles_ipad.css deleted file mode 100644 index d14b3cfb..00000000 --- a/release/server/docroot/css/styles_ipad.css +++ /dev/null @@ -1,466 +0,0 @@ -body{ - background-color: #F5F5F5; - font-family: Arial, Helvetica, sans-serif; -} - -/* libraries */ -#contentLibraries{ - width: 400px; - border: 1px solid #C6C6C6; - background-color: white; - margin-left: auto; - margin-right: auto; - margin-top: 9px; -} - -#contentLibraries .library-icon -{ - float: left; - background-color: white; - height: 18px; - padding: 11px 19px 10px 19px; - display:block; -} - -#contentLibraries li -{ - border-bottom: 1px solid #e2e2e2; - position: relative; - list-style: none; -} - -#contentLibraries li:last-child -{ - border: none; -} - -#contentLibraries .library-link -{ - width: 311px; - height: 28px; - border: none; - padding: 11px 0 0 0px; - background-color: white; - display: block; - float:left; - font-family: Arial; - font-size: 16px; - text-decoration: none; - color: #525252 ; - overflow: hidden; -} - - #contentLibraries a -{ - position: absolute; - height: 39px; - width: 100%; - z-index: 10; - display: block; - top 0; - text-decoration: none; -} - -#contentLibraries .library-indicator -{ - float: left; - background-color: white; - height: 8px; - padding: 16px 16px 15px 16px; - display:block; -} - - -#content h1 -{ - color: #292929; - text-align: center; - font-size: 21px; -} - -#contentLibraries h1{ - color: #292929; - text-align: center; - border-bottom: 1px solid #C6C6C6; - font-size: 21px; - padding: 15px 0 16px 0; -} - -#folder-header -{ - position: fixed; - width: 100%; - height: 88px; - background-color: rgba(255,255,255,0.9); - border-bottom: 1px solid #C6C6C6; - z-index: 999; -} - -#folder-subheader1 -{ - width: 100%; - height: 40px; - margin-top: 18px; - -} - -#folder-subheader2 -{ - width: 100%; - padding-left: 16px; -} - -#topIndex -{ - position: absolute; - left: 16px; - top: 19px; -} - -#topIndex a -{ - float: left; -} - -.indicator { - margin: 0 9px; -} - -.path { - text-decoration: none; - color: #5C5C5C; - font-family: Arial, Helvetica; - font-size: 15px; - -} - -#header-combos -{ - position: absolute; - right: 15px; - top: 15px; - color: #a3a3a3; - width: 160px; -} - -#topIndex .next{ - width: 25px; - height: 19px; - border: none; - margin: 0 21px 4px 0; - padding: 5px 0 0 0; - display: block; - background: url("/images/next.png") no-repeat scroll 0 0 transparent; - background-size: 25px 19px; - padding: 0; - text-indent: -99999px; -} - -#topIndex .previous{ - width: 25px; - height: 19px; - border: none; - margin: 0 14px 4px 0; - padding: 5px 0 0 0; - display: block; - background: url("/images/prev.png") no-repeat scroll 0 0 transparent; - background-size: 25px 19px; - padding: 0; - text-indent: -99999px; -} - -#topIndex .up{ - width: 15px; - height: 19px; - border: none; - background: url("/images/up.png") no-repeat scroll 0 0 transparent; - background-size: 15px 19px; - color: #FFF; - display: block; - text-indent: -99999px; -} - -#itemContainer li -{ -float: left; -width: 242px; -height: 120px; -border: 1px solid #E2E2E2; -margin: 9px 9px 0px 0; -background-color: white; -overflow: hidden; -position: relative; -} - -.folderContent -{ - padding-top: 90px; - padding-left: 9px; -} -/* hasta aquí */ - -.folder -{ -float: left; - -} - -.cover -{ -float: left; -overflow: hidden; -} - -.mark -{ - position: absolute; - top: 0px; - margin-left: 55px; -} - -.info -{ -padding: 8px 0px 0px 0px; -float: left; -position: relative; -height: 115px; -width: 158px; - -} - -.buttons -{ - position:absolute; - bottom:0px; - left:0px; - border-top: 1px solid #e2e2e2; - padding-top: 3px; - height: 25px; - width: 162px; - font-family: Arial; - color: #6e6e6e; - font-size: 10px; -} - -.elementInfo -{ - position:absolute; - bottom:24px; - padding-top: 3px; - height: 25px; - width: 162px; - font-family: Arial; - color: #adadad; - font-size: 10px; -} - -.numPages -{ - float: left; - padding-left:8px; -} - -.comicSize -{ - float: right; - padding-right: 9px; -} - - - -#itemContainer a -{ - text-decoration: none; - - -} - -.browseButton -{ - width: 60px; - background: url("/images/browse.png") no-repeat scroll 0 0 transparent; - background-position: 1px 6px; - background-size: 7px 7px; - border: none; - text-align:right; - display: block; - float: right; - padding: 4px 10px 0 0; - color: #6e6e6e; -} - -.importButton -{ - width: 60px; - background: url("/images/download.png") no-repeat scroll 0 0 transparent; - background-position: 3px 5px; - background-size: 7px 8px; - border: none; - text-align:left; - display: block; - float: left; - margin: 0 0 0 4px; - padding: 4px 0 0 16px; - color: #6e6e6e; -} - -.readButton -{ - width: 60px; - background: url("/images/read.png") no-repeat scroll 0 0 transparent; - background-position: 18px 5px; - background-size: 7px 9px; - border: none; - text-align:right; - display: block; - float: right; - padding: 4px 10px 0 0; - color: #6e6e6e; -} - -.importedButton -{ - width: 60px; - background: url("/images/imported.png") no-repeat scroll 0 0 transparent; - background-position: 2px 6px; - background-size: 8px 6px; - border: none; - text-align:left; - display: block; - float: left; - margin: 0 0 0 4px; - padding: 4px 0 0 16px; - color: #6e6e6e; -} - - -#indexes{ - border-top: 1px solid #C6C6C6; - background-color: white; - padding: 0px; - margin: 9px 0 0 0; -} - -.index{ - background-color: white; - margin: 9px 0 9px 0; -} - - -#alphaIndex a, #pageIndex a{ - width: 29px; - height: 24px; - border: none; - margin: 0 0 9px 9px; - padding: 5px 0 0 0px; - color: #5C5C5C; - font-size: 20px; - text-align: center; - display: block; - text-decoration: none; - font-family: Arial; - border: 1px solid #E2E2E2; - text-align:center; -} - -#alphaIndex li, #pageIndex li{ - float: left; -} - -#pageIndex .current{ - color: white; - background-color: #A2A2A2; - border: 1px solid #A2A2A2; - -} - - #content h2, #contentLibraries h2{ - color: #000; - font-weight: bold; - font-size: 12px; - margin: 0 0 16px 0; - } - - .inputs_login{ - width: 256px; - height: 64px; - background: url("/images/fnd_inputs.jpg") no-repeat scroll 0 0 #FFF; - margin: 0 0 18px 0; - } - .username{ - width: 200px; - height: 24px; - background: url("/images/fnd_input_username.jpg") no-repeat scroll 0 0 #2b2b2b; - border: none; - padding: 0 0 0 44px; - margin: 5px 0 6px 8px; - font-size: 14px; - color: #6e6e6e; - } - .pass{ - width: 200px; - height: 24px; - background: url("/images/fnd_input_pass.jpg") no-repeat scroll 0 0 #2b2b2b; - border: none; - padding: 0 0 0 44px; - margin: 0 0 0 8px; - font-size: 14px; - color: #6e6e6e; - } - .button_sign{ - width: 86px; - height: 30px; - background: url("/images/bt_login.jpg") no-repeat scroll 0 0 transparent; - border: none; - margin: 0; - padding: 0; - color: #FFF; - font-size: 14px; - float: left; - } - .infor{ - color: #666; - font-size: 8px; - float: left; - width: 112px; - margin: 0 0 0 8px; - line-height: 120%; - } - -.clear{ - height: 2px; - clear: both; -} - -.title{ - font-family: Arial; - font-size: 12px; - margin: 0 0 0 6px; - color: #555555 ; - overflow: hidden; - word-wrap: break-word; - height: 80px; - text-decoration: none; -} - -#indexalpha, #indexnumber{ - - -webkit-appearance: none; - background-color: rgba(255,255,255,0); - border-radius: 0px; - border: none; - color: #a3a3a3; - font-size: 16px; - font-family: Arial, Helvetica; - height: 30px; - margin: 0 0 0 10px; - padding:0; - float: right; -} - -.comboIndicator { - float: right; - padding: 14px 0 0 0; - margin: 0 0 0 4px; - width: 5px; -} \ No newline at end of file diff --git a/release/server/docroot/css/styles_iphone.css b/release/server/docroot/css/styles_iphone.css deleted file mode 100644 index 109b1155..00000000 --- a/release/server/docroot/css/styles_iphone.css +++ /dev/null @@ -1,463 +0,0 @@ -body{ - background-color: #F5F5F5; - font-family: Arial, Helvetica, sans-serif; -} - -/* libraries */ -#contentLibraries{ - border: 1px solid #C6C6C6; - background-color: white; - margin-left: 20px; - margin-right: 20px; - margin-top: 9px; -} - -#contentLibraries .library-icon -{ - float: left; - background-color: white; - height: 18px; - padding: 11px 19px 10px 19px; - display:block; -} - -#contentLibraries li -{ - border-bottom: 1px solid #e2e2e2; - position: relative; - list-style: none; -} - -#contentLibraries li:last-child -{ - border: none; -} - -#contentLibraries .library-link -{ - width: 65%; - height: 28px; - border: none; - padding: 11px 0 0 0px; - background-color: white; - display: block; - float:left; - font-family: Arial; - font-size: 16px; - text-decoration: none; - color: #525252 ; - overflow: hidden; -} - - #contentLibraries a -{ - position: absolute; - height: 39px; - width: 100%; - z-index: 10; - display: block; - top 0; - text-decoration: none; -} - -#contentLibraries .library-indicator -{ - float: right; - background-color: white; - height: 8px; - padding: 16px 16px 15px 16px; - display:block; -} - - -#content h1 -{ - color: #292929; - text-align: center; - font-size: 21px; -} - -#contentLibraries h1{ - color: #292929; - text-align: center; - border-bottom: 1px solid #C6C6C6; - font-size: 21px; - padding: 15px 0 16px 0; -} - -#folder-header -{ - position: fixed; - width: 100%; - height: 88px; - background-color: rgba(255,255,255,0.9); - border-bottom: 1px solid #C6C6C6; - z-index: 999; -} - -#folder-subheader1 -{ - width: 100%; - height: 40px; - margin-top: 18px; - -} - -#folder-subheader2 -{ - width: 100%; - padding-left: 16px; -} - -#topIndex -{ - position: absolute; - left: 16px; - top: 19px; -} - -#topIndex a -{ - float: left; -} - -.indicator { - margin: 0 5px; -} - -.path { - text-decoration: none; - color: #5C5C5C; - font-family: Arial, Helvetica; - font-size: 15px; - -} - -#header-combos -{ - position: absolute; - right: 10px; - top: 15px; - color: #a3a3a3; - width: 160px; -} - -#topIndex .next{ - width: 25px; - height: 19px; - border: none; - margin: 0 21px 4px 0; - padding: 5px 0 0 0; - display: block; - background: url("/images/next.png") no-repeat scroll 0 0 transparent; - background-size: 25px 19px; - padding: 0; - text-indent: -99999px; -} - -#topIndex .previous{ - width: 25px; - height: 19px; - border: none; - margin: 0 14px 4px 0; - padding: 5px 0 0 0; - display: block; - background: url("/images/prev.png") no-repeat scroll 0 0 transparent; - background-size: 25px 19px; - padding: 0; - text-indent: -99999px; -} - -#topIndex .up{ - width: 15px; - height: 19px; - border: none; - background: url("/images/up.png") no-repeat scroll 0 0 transparent; - background-size: 15px 19px; - color: #FFF; - display: block; - text-indent: -99999px; -} - -#itemContainer li -{ - -height: 120px; -border: 1px solid #E2E2E2; -margin: 9px 10px 0px 10px; -background-color: white; -overflow: hidden; -position: relative; -} - -.folderContent -{ - padding-top: 90px; -} -/* hasta aquí */ - -.folder -{ -float: left; - -} - -.cover -{ -float: left; -overflow: hidden; -} - -.mark -{ - position: absolute; - top: 0px; - margin-left: 55px; -} - -.info -{ -padding: 8px 0px 0px 0px; - -position: relative; -height: 115px; - -padding-left: 82px; -} - -.buttons -{ - position:absolute; - bottom:0px; - left:80px; - right: 0px; - border-top: 1px solid #e2e2e2; - padding-top: 3px; - height: 25px; - font-family: Arial; - color: #6e6e6e; - font-size: 10px; -} - -.elementInfo -{ - position:absolute; - bottom:24px; - padding-top: 3px; - height: 25px; - width: 100%; - font-family: Arial; - color: #adadad; - font-size: 10px; -} - -.numPages -{ - float: left; - padding-left:8px; -} - -.comicSize -{ - float: right; - padding-right: 9px; -} - - - -#itemContainer a -{ - text-decoration: none; - - -} - -.browseButton -{ - width: 60px; - background: url("/images/browse.png") no-repeat scroll 0 0 transparent; - background-position: 1px 6px; - background-size: 7px 7px; - border: none; - text-align:right; - display: block; - float: right; - padding: 4px 10px 0 0; - color: #6e6e6e; -} - -.importButton -{ - width: 60px; - background: url("/images/download.png") no-repeat scroll 0 0 transparent; - background-position: 3px 5px; - background-size: 7px 8px; - border: none; - text-align:left; - display: block; - float: left; - margin: 0 0 0 4px; - padding: 4px 0 0 16px; - color: #6e6e6e; -} - -.readButton -{ - width: 60px; - background: url("/images/read.png") no-repeat scroll 0 0 transparent; - background-position: 18px 5px; - background-size: 7px 9px; - border: none; - text-align:right; - display: block; - float: right; - padding: 4px 10px 0 0; - color: #6e6e6e; -} - -.importedButton -{ - width: 60px; - background: url("/images/imported.png") no-repeat scroll 0 0 transparent; - background-position: 2px 6px; - background-size: 8px 6px; - border: none; - text-align:left; - display: block; - float: left; - margin: 0 0 0 4px; - padding: 4px 0 0 16px; - color: #6e6e6e; -} - - -#indexes{ - border-top: 1px solid #C6C6C6; - background-color: white; - padding: 0px; - margin: 9px 0 0 0; -} - -.index{ - background-color: white; - margin: 9px 0 9px 0; -} - - -#alphaIndex a, #pageIndex a{ - width: 29px; - height: 24px; - border: none; - margin: 0 0 9px 9px; - padding: 5px 0 0 0px; - color: #5C5C5C; - font-size: 20px; - text-align: center; - display: block; - text-decoration: none; - font-family: Arial; - border: 1px solid #E2E2E2; - text-align:center; -} - -#alphaIndex li, #pageIndex li{ - float: left; -} - -#pageIndex .current{ - color: white; - background-color: #A2A2A2; - border: 1px solid #A2A2A2; - -} - - #content h2, #contentLibraries h2{ - color: #000; - font-weight: bold; - font-size: 12px; - margin: 0 0 16px 0; - } - - .inputs_login{ - width: 256px; - height: 64px; - background: url("/images/fnd_inputs.jpg") no-repeat scroll 0 0 #FFF; - margin: 0 0 18px 0; - } - .username{ - width: 200px; - height: 24px; - background: url("/images/fnd_input_username.jpg") no-repeat scroll 0 0 #2b2b2b; - border: none; - padding: 0 0 0 44px; - margin: 5px 0 6px 8px; - font-size: 14px; - color: #6e6e6e; - } - .pass{ - width: 200px; - height: 24px; - background: url("/images/fnd_input_pass.jpg") no-repeat scroll 0 0 #2b2b2b; - border: none; - padding: 0 0 0 44px; - margin: 0 0 0 8px; - font-size: 14px; - color: #6e6e6e; - } - .button_sign{ - width: 86px; - height: 30px; - background: url("/images/bt_login.jpg") no-repeat scroll 0 0 transparent; - border: none; - margin: 0; - padding: 0; - color: #FFF; - font-size: 14px; - float: left; - } - .infor{ - color: #666; - font-size: 8px; - float: left; - width: 112px; - margin: 0 0 0 8px; - line-height: 120%; - } - -.clear{ - height: 2px; - clear: both; -} - -.title{ - font-family: Arial; - font-size: 12px; - margin: 0 0 0 6px; - color: #555555 ; - overflow: hidden; - word-wrap: break-word; - height: 65px; - text-decoration: none; -} - -#indexalpha, #indexnumber{ - - -webkit-appearance: none; - background-color: rgba(255,255,255,0); - border-radius: 0px; - border: none; - color: #a3a3a3; - font-size: 16px; - font-family: Arial, Helvetica; - height: 30px; - margin: 0 0 0 10px; - padding:0; - float: right; -} - -.comboIndicator { - float: right; - padding: 14px 0 0 0; - margin: 0 0 0 4px; - width: 5px; -} \ No newline at end of file diff --git a/release/server/docroot/images/browse.png b/release/server/docroot/images/browse.png deleted file mode 100644 index 9043aa9d..00000000 Binary files a/release/server/docroot/images/browse.png and /dev/null differ diff --git a/release/server/docroot/images/browse@2x.png b/release/server/docroot/images/browse@2x.png deleted file mode 100644 index 06d5f058..00000000 Binary files a/release/server/docroot/images/browse@2x.png and /dev/null differ diff --git a/release/server/docroot/images/combo.png b/release/server/docroot/images/combo.png deleted file mode 100644 index 54edc9fa..00000000 Binary files a/release/server/docroot/images/combo.png and /dev/null differ diff --git a/release/server/docroot/images/combo@2x.png b/release/server/docroot/images/combo@2x.png deleted file mode 100644 index 39e5c700..00000000 Binary files a/release/server/docroot/images/combo@2x.png and /dev/null differ diff --git a/release/server/docroot/images/download.png b/release/server/docroot/images/download.png deleted file mode 100644 index 10b43d66..00000000 Binary files a/release/server/docroot/images/download.png and /dev/null differ diff --git a/release/server/docroot/images/download@2x.png b/release/server/docroot/images/download@2x.png deleted file mode 100644 index 98c6ae38..00000000 Binary files a/release/server/docroot/images/download@2x.png and /dev/null differ diff --git a/release/server/docroot/images/f.png b/release/server/docroot/images/f.png deleted file mode 100644 index 32fb3867..00000000 Binary files a/release/server/docroot/images/f.png and /dev/null differ diff --git a/release/server/docroot/images/f@2x.png b/release/server/docroot/images/f@2x.png deleted file mode 100644 index e388d804..00000000 Binary files a/release/server/docroot/images/f@2x.png and /dev/null differ diff --git a/release/server/docroot/images/imported.png b/release/server/docroot/images/imported.png deleted file mode 100644 index 1eaf45d4..00000000 Binary files a/release/server/docroot/images/imported.png and /dev/null differ diff --git a/release/server/docroot/images/imported@2x.png b/release/server/docroot/images/imported@2x.png deleted file mode 100644 index 41b9ad4e..00000000 Binary files a/release/server/docroot/images/imported@2x.png and /dev/null differ diff --git a/release/server/docroot/images/indicator.png b/release/server/docroot/images/indicator.png deleted file mode 100644 index 138c77a8..00000000 Binary files a/release/server/docroot/images/indicator.png and /dev/null differ diff --git a/release/server/docroot/images/indicator@2x.png b/release/server/docroot/images/indicator@2x.png deleted file mode 100644 index 9f30e704..00000000 Binary files a/release/server/docroot/images/indicator@2x.png and /dev/null differ diff --git a/release/server/docroot/images/library.png b/release/server/docroot/images/library.png deleted file mode 100644 index 1a185057..00000000 Binary files a/release/server/docroot/images/library.png and /dev/null differ diff --git a/release/server/docroot/images/library@2x.png b/release/server/docroot/images/library@2x.png deleted file mode 100644 index 131046c7..00000000 Binary files a/release/server/docroot/images/library@2x.png and /dev/null differ diff --git a/release/server/docroot/images/next.png b/release/server/docroot/images/next.png deleted file mode 100644 index 06efccc3..00000000 Binary files a/release/server/docroot/images/next.png and /dev/null differ diff --git a/release/server/docroot/images/next@2x.png b/release/server/docroot/images/next@2x.png deleted file mode 100644 index ec4feb22..00000000 Binary files a/release/server/docroot/images/next@2x.png and /dev/null differ diff --git a/release/server/docroot/images/prev.png b/release/server/docroot/images/prev.png deleted file mode 100644 index 43e4805e..00000000 Binary files a/release/server/docroot/images/prev.png and /dev/null differ diff --git a/release/server/docroot/images/prev@2x.png b/release/server/docroot/images/prev@2x.png deleted file mode 100644 index d505f4f5..00000000 Binary files a/release/server/docroot/images/prev@2x.png and /dev/null differ diff --git a/release/server/docroot/images/read.png b/release/server/docroot/images/read.png deleted file mode 100644 index 9465a7d5..00000000 Binary files a/release/server/docroot/images/read.png and /dev/null differ diff --git a/release/server/docroot/images/read@2x.png b/release/server/docroot/images/read@2x.png deleted file mode 100644 index 68ed8715..00000000 Binary files a/release/server/docroot/images/read@2x.png and /dev/null differ diff --git a/release/server/docroot/images/readMark.png b/release/server/docroot/images/readMark.png deleted file mode 100644 index b672027d..00000000 Binary files a/release/server/docroot/images/readMark.png and /dev/null differ diff --git a/release/server/docroot/images/readMark@2x.png b/release/server/docroot/images/readMark@2x.png deleted file mode 100644 index 1d9d0d3e..00000000 Binary files a/release/server/docroot/images/readMark@2x.png and /dev/null differ diff --git a/release/server/docroot/images/readingMark.png b/release/server/docroot/images/readingMark.png deleted file mode 100644 index e37f3c80..00000000 Binary files a/release/server/docroot/images/readingMark.png and /dev/null differ diff --git a/release/server/docroot/images/readingMark@2x.png b/release/server/docroot/images/readingMark@2x.png deleted file mode 100644 index 589e2e9b..00000000 Binary files a/release/server/docroot/images/readingMark@2x.png and /dev/null differ diff --git a/release/server/docroot/images/up.png b/release/server/docroot/images/up.png deleted file mode 100644 index b039b35f..00000000 Binary files a/release/server/docroot/images/up.png and /dev/null differ diff --git a/release/server/docroot/images/up@2x.png b/release/server/docroot/images/up@2x.png deleted file mode 100644 index d990ce0d..00000000 Binary files a/release/server/docroot/images/up@2x.png and /dev/null differ diff --git a/release/server/docroot/login.html b/release/server/docroot/login.html deleted file mode 100644 index 18810950..00000000 --- a/release/server/docroot/login.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - Login - - -
- -

LOGIN

-

YACREADER LIBRARY

- -
- - -

If you have forgotten your login information, please reset it on the YACReaderLibrary

-
 
-
-
-
 
- - \ No newline at end of file diff --git a/release/server/templates/folder.tpl b/release/server/templates/folder.tpl deleted file mode 100644 index cb13a164..00000000 --- a/release/server/templates/folder.tpl +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - Folder - - -
-
- -
-
{if pageIndex} {end pageIndex} up
- - -
- {if device.ipad} -

{folder.name}

- {end device.ipad} -
- -
- Libraries {library.name} {loop path} {path.name} {end path} -
-
- {if pageIndex} - - - {end pageIndex} - - {if alphaIndex} - - - {end alphaIndex} - -
-
- - -
-
    - {loop element} -
  • -
    - {element.cover.browse} {element.cover.browse.end} -
    -
    -

    {element.name}

    -
    -
    {element.pages} {element.size} -
    -
    {element.download} {element.read} {element.browse} -
    -
    - {element.status} -
  • - {end element} -
-
 
-
-
- - {if index} -
- {if alphaIndex} - -
- -
 
-
- - {end alphaIndex} - - - {if pageIndex} - -
- -
 
-
- {end pageIndex} -
- {end index} - - - - - diff --git a/release/server/templates/libraries.tpl b/release/server/templates/libraries.tpl deleted file mode 100644 index 0de6f4d9..00000000 --- a/release/server/templates/libraries.tpl +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - Libraries - - -
-

Libraries

-

-

    - {loop library} -
  • -
    - -
    -   -
     
    -
  • - {end library} -
-

-
- - diff --git a/shortcuts_management/actions_shortcuts_model.cpp b/shortcuts_management/actions_shortcuts_model.cpp index 7238b2c7..baf50c16 100644 --- a/shortcuts_management/actions_shortcuts_model.cpp +++ b/shortcuts_management/actions_shortcuts_model.cpp @@ -34,7 +34,7 @@ QModelIndex ActionsShortcutsModel::index(int row, int column, const QModelIndex Qt::ItemFlags ActionsShortcutsModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return {}; + return { }; if (index.column() == KEYS) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; diff --git a/tests/concurrent_queue_test/concurrent_queue_test.cpp b/tests/concurrent_queue_test/concurrent_queue_test.cpp index d9c4eb4b..ee187df9 100644 --- a/tests/concurrent_queue_test/concurrent_queue_test.cpp +++ b/tests/concurrent_queue_test/concurrent_queue_test.cpp @@ -461,8 +461,8 @@ void ConcurrentQueueTest::singleUserThread_data() using ms = chrono::milliseconds; - QTest::newRow("-") << 0 << JobDataSet {}; - QTest::newRow("0") << 7 << JobDataSet {}; + QTest::newRow("-") << 0 << JobDataSet { }; + QTest::newRow("0") << 7 << JobDataSet { }; QTest::newRow("A") << 1 << JobDataSet { { 5, ms(0) } }; QTest::newRow("B") << 5 << JobDataSet { { 12, ms(1) } }; QTest::newRow("C") << 1 << JobDataSet { { 1, ms(0) }, { 5, ms(2) }, { 3, ms(1) } }; @@ -559,9 +559,9 @@ void ConcurrentQueueTest::cancelPending1UserThread_data() const auto ms = [](int count) -> Clock::duration { return chrono::milliseconds(count); }; const auto us = [](int count) -> Clock::duration { return chrono::microseconds(count); }; - QTest::newRow("-") << 0 << JobDataSet {} << ms(0); - QTest::newRow("01") << 2 << JobDataSet {} << ms(0); - QTest::newRow("02") << 3 << JobDataSet {} << ms(1); + QTest::newRow("-") << 0 << JobDataSet { } << ms(0); + QTest::newRow("01") << 2 << JobDataSet { } << ms(0); + QTest::newRow("02") << 3 << JobDataSet { } << ms(1); QTest::newRow("A") << 1 << JobDataSet { { 5, ms(3) } } << ms(1); QTest::newRow("B") << 5 << JobDataSet { { 12, ms(1) } } << ms(1);