Merge pull request #518 from YACReader/theming-settings
YACReader 10: Theme settings and editor
@ -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
|
||||
|
||||
3
.github/workflows/build.yml
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -12,7 +12,10 @@
|
||||
#include <QLabel>
|
||||
#include <QColorDialog>
|
||||
#include <QCheckBox>
|
||||
#include <QMessageBox>
|
||||
#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);
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
#include "yacreader_options_dialog.h"
|
||||
#include "themable.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
class QDialog;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
|
||||
@ -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 &&
|
||||
|
||||
51
YACReader/themes/builtin_classic.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
51
YACReader/themes/builtin_dark.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
51
YACReader/themes/builtin_light.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,11 @@
|
||||
#define THEME_H
|
||||
|
||||
#include <QtGui>
|
||||
#include <QJsonObject>
|
||||
|
||||
#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;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <QApplication>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
#define THEME_FACTORY_H
|
||||
|
||||
#include "theme.h"
|
||||
#include "theme_id.h"
|
||||
|
||||
Theme makeTheme(ThemeId themeId);
|
||||
#include <QJsonObject>
|
||||
|
||||
Theme makeTheme(const QJsonObject &json);
|
||||
|
||||
#endif // THEME_FACTORY_H
|
||||
|
||||
7
YACReader/themes/themes.qrc
Normal file
@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/themes">
|
||||
<file alias="builtin_classic.json">builtin_classic.json</file>
|
||||
<file alias="builtin_light.json">builtin_light.json</file>
|
||||
<file alias="builtin_dark.json">builtin_dark.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@ -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")
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<QPair<ComicDB, QString>> 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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -36,8 +36,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Type _type {};
|
||||
std::string _lexeme {};
|
||||
Type _type { };
|
||||
std::string _lexeme { };
|
||||
};
|
||||
|
||||
class QueryLexer
|
||||
|
||||
@ -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), { }) });
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<ListItem *>(index.internalPointer());
|
||||
if (typeid(*item) == typeid(ReadingListSeparatorItem))
|
||||
return {};
|
||||
return { };
|
||||
|
||||
if (typeid(*item) == typeid(ReadingListItem) && static_cast<ReadingListItem *>(item)->parent->getId() != 0)
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled; // only sublists are dragable
|
||||
|
||||
@ -263,39 +263,36 @@ void FolderContentView::droppedFiles(const QList<QUrl> &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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -36,8 +36,6 @@
|
||||
<file>../images/empty_container/empty_reading_list.svg</file>
|
||||
<file>../images/library_dialogs/exportComicsInfo.svg</file>
|
||||
<file>../images/library_dialogs/exportLibrary.svg</file>
|
||||
<file>../images/f_overlayed.png</file>
|
||||
<file>../images/f_overlayed_retina.png</file>
|
||||
<file>../images/find_folder.svg</file>
|
||||
<file>../images/flow1.png</file>
|
||||
<file>../images/flow2.png</file>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 <QMessageBox>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -61,6 +61,7 @@ private:
|
||||
QWidget *createLibrariesTab();
|
||||
QWidget *createFlowTab();
|
||||
QWidget *createGridTab();
|
||||
QWidget *createAppearanceTab();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -11,13 +11,9 @@
|
||||
<file>qml/reading.svg</file>
|
||||
<file>qml/star.svg</file>
|
||||
<file>qml/page.svg</file>
|
||||
<file>qml/info-indicator.png</file>
|
||||
<file>qml/info-shadow.png</file>
|
||||
<file>qml/info-indicator-light.png</file>
|
||||
<file>qml/info-shadow-light.png</file>
|
||||
<file>qml/info-indicator-light@2x.png</file>
|
||||
<file>qml/info-shadow-light@2x.png</file>
|
||||
<file>qml/info-top-shadow.png</file>
|
||||
<file>qml/info-indicator.svg</file>
|
||||
<file>qml/info-shadow.svg</file>
|
||||
<file>qml/info-top-shadow.svg</file>
|
||||
<file>qml/ComicInfoView.qml</file>
|
||||
<file>qml/info-favorites.svg</file>
|
||||
<file>qml/info-rating.svg</file>
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ SplitView {
|
||||
source: backgroundImg
|
||||
blurEnabled: true
|
||||
blur: 1.0
|
||||
blurMax: 64
|
||||
blurMax: Math.max(2, backgroundBlurRadius)
|
||||
opacity: backgroundBlurOpacity
|
||||
visible: backgroundBlurVisible
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 526 B |
|
Before Width: | Height: | Size: 652 B |
24
YACReaderLibrary/qml/info-indicator.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 164 17">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f0f;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #0ff;
|
||||
filter: url(#drop-shadow-1);
|
||||
}
|
||||
</style>
|
||||
<filter id="drop-shadow-1" x="-15" y="-4" width="194" height="30" filterUnits="userSpaceOnUse">
|
||||
<feOffset dx="0" dy="0"/>
|
||||
<feGaussianBlur result="blur" stdDeviation="3"/>
|
||||
<feFlood flood-color="#ff0" flood-opacity=".75"/>
|
||||
<feComposite in2="blur" operator="in"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<polygon class="cls-2" points="170 17 -6 17 -6 5 81 5 89 15 97 5 170 5 170 17"/>
|
||||
<polygon class="cls-1" points="170 17 -6 17 -6 6 81 6 89 16 97 6 170 6 170 17"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 860 B |
|
Before Width: | Height: | Size: 120 B |
|
Before Width: | Height: | Size: 125 B |
|
Before Width: | Height: | Size: 135 B |
24
YACReaderLibrary/qml/info-shadow.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1 17">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #0ff;
|
||||
filter: url(#drop-shadow-2);
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #f0f;
|
||||
}
|
||||
</style>
|
||||
<filter id="drop-shadow-2" x="-20" y="-4" width="41" height="30" filterUnits="userSpaceOnUse">
|
||||
<feOffset dx="0" dy="0"/>
|
||||
<feGaussianBlur result="blur" stdDeviation="3"/>
|
||||
<feFlood flood-color="#ff0" flood-opacity=".75"/>
|
||||
<feComposite in2="blur" operator="in"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect class="cls-1" x="-11" y="5" width="23" height="12"/>
|
||||
<rect class="cls-2" x="-11" y="6" width="23" height="11"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 813 B |
|
Before Width: | Height: | Size: 121 B |
19
YACReaderLibrary/qml/info-top-shadow.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1 5">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #f0f;
|
||||
filter: url(#drop-shadow-3);
|
||||
}
|
||||
</style>
|
||||
<filter id="drop-shadow-3" x="-15" y="-19" width="31" height="28" filterUnits="userSpaceOnUse">
|
||||
<feOffset dx="0" dy="0"/>
|
||||
<feGaussianBlur result="blur" stdDeviation="3"/>
|
||||
<feFlood flood-color="#ff0" flood-opacity=".75"/>
|
||||
<feComposite in2="blur" operator="in"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect class="cls-1" x="-6" y="-10" width="13" height="10"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 709 B |
237
YACReaderLibrary/themes/builtin_classic.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
237
YACReaderLibrary/themes/builtin_dark.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
237
YACReaderLibrary/themes/builtin_light.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
@ -2,12 +2,14 @@
|
||||
#define THEME_H
|
||||
|
||||
#include <QtGui>
|
||||
#include <QJsonObject>
|
||||
|
||||
#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<int, QPixmap> 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;
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
#define THEME_FACTORY_H
|
||||
|
||||
#include "theme.h"
|
||||
#include "theme_id.h"
|
||||
|
||||
Theme makeTheme(ThemeId themeId);
|
||||
#include <QJsonObject>
|
||||
|
||||
Theme makeTheme(const QJsonObject &json);
|
||||
|
||||
#endif // THEME_FACTORY_H
|
||||
|
||||
7
YACReaderLibrary/themes/themes.qrc
Normal file
@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/themes">
|
||||
<file alias="builtin_classic.json">builtin_classic.json</file>
|
||||
<file alias="builtin_light.json">builtin_light.json</file>
|
||||
<file alias="builtin_dark.json">builtin_dark.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@ -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<YACReaderTreeView *>(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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
8
common/themes/appearance_config_images.qrc
Normal file
@ -0,0 +1,8 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>../../images/appearance_config/theme-mode-system.svg</file>
|
||||
<file>../../images/appearance_config/theme-mode-light.svg</file>
|
||||
<file>../../images/appearance_config/theme-mode-dark.svg</file>
|
||||
<file>../../images/appearance_config/theme-mode-custom.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
96
common/themes/appearance_configuration.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "appearance_configuration.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
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();
|
||||
}
|
||||
48
common/themes/appearance_configuration.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef APPEARANCE_CONFIGURATION_H
|
||||
#define APPEARANCE_CONFIGURATION_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
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
|
||||
313
common/themes/appearance_tab_widget.cpp
Normal file
@ -0,0 +1,313 @@
|
||||
#include "appearance_tab_widget.h"
|
||||
|
||||
#include "appearance_configuration.h"
|
||||
#include "theme_editor_dialog.h"
|
||||
#include "theme_repository.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QComboBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
// 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<QJsonObject()> currentThemeJson,
|
||||
std::function<void(const QJsonObject &)> 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<int>(ThemeMode::FollowSystem));
|
||||
modeGroup->addButton(lightBtn, static_cast<int>(ThemeMode::Light));
|
||||
modeGroup->addButton(darkBtn, static_cast<int>(ThemeMode::Dark));
|
||||
modeGroup->addButton(customBtn, static_cast<int>(ThemeMode::ForcedTheme));
|
||||
modeGroup->setExclusive(true);
|
||||
|
||||
if (this->config) {
|
||||
const auto mode = this->config->selection().mode;
|
||||
if (auto *btn = modeGroup->button(static_cast<int>(mode)))
|
||||
btn->setChecked(true);
|
||||
}
|
||||
|
||||
connect(modeGroup, &QButtonGroup::idClicked, this, [this](int id) {
|
||||
if (this->config) {
|
||||
this->config->setMode(static_cast<ThemeMode>(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<ThemeVariant> 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);
|
||||
}
|
||||
58
common/themes/appearance_tab_widget.h
Normal file
@ -0,0 +1,58 @@
|
||||
#ifndef APPEARANCE_TAB_WIDGET_H
|
||||
#define APPEARANCE_TAB_WIDGET_H
|
||||
|
||||
#include "theme_variant.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
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<QJsonObject()> currentThemeJson,
|
||||
std::function<void(const QJsonObject &)> applyTheme,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
AppearanceConfiguration *config;
|
||||
ThemeRepository *repository;
|
||||
std::function<QJsonObject()> currentThemeJson;
|
||||
std::function<void(const QJsonObject &)> applyTheme;
|
||||
QPointer<ThemeEditorDialog> 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<ThemeVariant> 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
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
495
common/themes/theme_editor_dialog.cpp
Normal file
@ -0,0 +1,495 @@
|
||||
#include "theme_editor_dialog.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QColorDialog>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QHeaderView>
|
||||
#include <QPixmap>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFile>
|
||||
#include <QInputDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QUuid>
|
||||
|
||||
// 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<int>(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<int>(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<int>(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<int>(restored)) : QString::number(restored));
|
||||
setJsonPath(params, path, restored);
|
||||
}
|
||||
identifySnapshot = QJsonValue();
|
||||
emit themeJsonChanged(params);
|
||||
}
|
||||
63
common/themes/theme_editor_dialog.h
Normal file
@ -0,0 +1,63 @@
|
||||
#ifndef THEME_EDITOR_DIALOG_H
|
||||
#define THEME_EDITOR_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QJsonObject>
|
||||
|
||||
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
|
||||
@ -1,10 +0,0 @@
|
||||
#ifndef THEME_ID_H
|
||||
#define THEME_ID_H
|
||||
|
||||
enum class ThemeId {
|
||||
Classic,
|
||||
Light,
|
||||
Dark,
|
||||
};
|
||||
|
||||
#endif // THEME_ID_H
|
||||
@ -1,14 +1,13 @@
|
||||
#include "theme_manager.h"
|
||||
|
||||
#include "appearance_configuration.h"
|
||||
#include "theme.h"
|
||||
#include "theme_factory.h"
|
||||
#include "theme_repository.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QPalette>
|
||||
#include <QStyleHints>
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
#ifndef THEME_MANAGER_H
|
||||
#define THEME_MANAGER_H
|
||||
|
||||
#include "appearance_configuration.h"
|
||||
#include "theme.h"
|
||||
#include "theme_id.h"
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
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
|
||||
|
||||
14
common/themes/theme_meta.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef THEME_META_H
|
||||
#define THEME_META_H
|
||||
|
||||
#include "theme_variant.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
struct ThemeMeta {
|
||||
QString id;
|
||||
QString displayName;
|
||||
ThemeVariant variant;
|
||||
};
|
||||
|
||||
#endif // THEME_META_H
|
||||
206
common/themes/theme_repository.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
#include "theme_repository.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
|
||||
ThemeRepository::ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir)
|
||||
: qrcPrefix(qrcPrefix), userThemesDir(userThemesDir)
|
||||
{
|
||||
scanBuiltins();
|
||||
scanUserThemes();
|
||||
}
|
||||
|
||||
QList<ThemeListEntry> ThemeRepository::availableThemes() const
|
||||
{
|
||||
QList<ThemeListEntry> 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/<uuid>"
|
||||
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";
|
||||
}
|
||||
57
common/themes/theme_repository.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef THEME_REPOSITORY_H
|
||||
#define THEME_REPOSITORY_H
|
||||
|
||||
#include "theme_meta.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
struct ThemeListEntry {
|
||||
QString id;
|
||||
QString displayName;
|
||||
ThemeVariant variant;
|
||||
bool isBuiltin;
|
||||
};
|
||||
|
||||
class ThemeRepository
|
||||
{
|
||||
public:
|
||||
explicit ThemeRepository(const QString &qrcPrefix, const QString &userThemesDir);
|
||||
|
||||
QList<ThemeListEntry> 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<BuiltinEntry> builtins;
|
||||
|
||||
struct UserEntry {
|
||||
QString id;
|
||||
QString filePath;
|
||||
ThemeMeta meta;
|
||||
};
|
||||
QList<UserEntry> 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
|
||||
9
common/themes/theme_variant.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef THEME_VARIANT_H
|
||||
#define THEME_VARIANT_H
|
||||
|
||||
enum class ThemeVariant {
|
||||
Light,
|
||||
Dark,
|
||||
};
|
||||
|
||||
#endif // THEME_VARIANT_H
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
36
images/appearance_config/theme-mode-custom.svg
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 93 58">
|
||||
<!-- Generator: Adobe Illustrator 29.8.5, SVG Export Plug-In . SVG Version: 2.1.1 Build 2) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #d7d7d9;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #bbb;
|
||||
}
|
||||
|
||||
.st2 {
|
||||
fill: #474747;
|
||||
}
|
||||
|
||||
.st3 {
|
||||
fill: #f7f7f7;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="st3" y="0" width="93" height="58" rx="5" ry="5"/>
|
||||
<rect class="st2" x="4" y="4" width="19" height="50" rx="3.1" ry="3.1"/>
|
||||
<rect class="st1" x="26" y="4" width="63" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="26" y="21" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="39" y="21" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="52" y="21" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="65" y="21" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="78" y="21" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="26" y="38" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="39" y="38" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="52" y="38" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="65" y="38" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
<rect class="st0" x="78" y="38" width="11" height="16" rx="3.1" ry="3.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
29
images/appearance_config/theme-mode-dark.svg
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 93 58">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #404040;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #292929;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" width="93" height="58" rx="5" ry="5"/>
|
||||
<rect class="cls-1" x="4" y="4" width="19" height="50" rx="3.12" ry="3.12"/>
|
||||
<rect class="cls-1" x="26" y="4" width="63" height="16" rx="3.12" ry="3.12"/>
|
||||
<g>
|
||||
<rect class="cls-1" x="26" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="39" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="52" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="65" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="78" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="26" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="39" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="52" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="65" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="78" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
29
images/appearance_config/theme-mode-light.svg
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 93 58">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #d7d7d9;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #f7f7f7;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-2" x="0" width="93" height="58" rx="5" ry="5"/>
|
||||
<rect class="cls-1" x="4" y="4" width="19" height="50" rx="3.12" ry="3.12"/>
|
||||
<rect class="cls-1" x="26" y="4" width="63" height="16" rx="3.12" ry="3.12"/>
|
||||
<g>
|
||||
<rect class="cls-1" x="26" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="39" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="52" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="65" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="78" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="26" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="39" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="52" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="65" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-1" x="78" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
55
images/appearance_config/theme-mode-system.svg
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 93 58">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #d7d7d9;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #404040;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #292929;
|
||||
}
|
||||
|
||||
.cls-5 {
|
||||
fill: #f7f7f7;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<rect class="cls-5" width="93" height="58" rx="5" ry="5"/>
|
||||
<rect class="cls-2" x="4" y="4" width="19" height="50" rx="3.12" ry="3.12"/>
|
||||
<rect class="cls-2" x="26" y="4" width="63" height="16" rx="3.12" ry="3.12"/>
|
||||
<g>
|
||||
<rect class="cls-2" x="26" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="39" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="52" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="65" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="78" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="26" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="39" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="52" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="65" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-2" x="78" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
</g>
|
||||
<polyline class="cls-1" points="93 0 93 58 0 58"/>
|
||||
<path class="cls-4" d="M91.16,1.15L1.84,56.85c.86.71,1.95,1.15,3.16,1.15h83c2.76,0,5-2.24,5-5V5c0-1.56-.73-2.93-1.84-3.85Z"/>
|
||||
<path class="cls-3" d="M85.88,20c1.73,0,3.12-1.4,3.12-3.12V7.12c0-1.52-1.08-2.78-2.51-3.06l-25.56,15.94h24.94Z"/>
|
||||
<path class="cls-3" d="M6.51,53.94c.2.04.4.06.61.06h12.75c1.73,0,3.12-1.4,3.12-3.12v-7.22l-16.49,10.28Z"/>
|
||||
<path class="cls-3" d="M33.67,37h.2c1.3,0,2.41-.8,2.88-1.93l-3.09,1.93Z"/>
|
||||
<path class="cls-3" d="M39,33.68v.2c0,1.73,1.4,3.12,3.12,3.12h4.75c1.73,0,3.12-1.4,3.12-3.12v-7.06l-11,6.86Z"/>
|
||||
<path class="cls-3" d="M52,33.88c0,1.73,1.4,3.12,3.12,3.12h4.75c1.73,0,3.12-1.4,3.12-3.12v-9.75c0-1.73-1.4-3.12-3.12-3.12h-.55l-7.33,4.57v8.31Z"/>
|
||||
<rect class="cls-3" x="65" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-3" x="78" y="21" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<path class="cls-3" d="M26,50.88c0,1.73,1.4,3.12,3.12,3.12h4.75c1.73,0,3.12-1.4,3.12-3.12v-9.75c0-1.73-1.4-3.12-3.12-3.12h-1.81l-6.07,3.78v9.09Z"/>
|
||||
<rect class="cls-3" x="39" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-3" x="52" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-3" x="65" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
<rect class="cls-3" x="78" y="38" width="11" height="16" rx="3.13" ry="3.13"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
BIN
images/f.png
|
Before Width: | Height: | Size: 710 B |
|
Before Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 1.2 KiB |
@ -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: "" "";
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 134 B |
|
Before Width: | Height: | Size: 185 B |
|
Before Width: | Height: | Size: 120 B |
|
Before Width: | Height: | Size: 167 B |