Make the translator themeable

This commit is contained in:
luisangelsm
2026-03-05 18:52:10 +01:00
parent 6ea857fa59
commit 33f18d9d1c
17 changed files with 273 additions and 49 deletions

View File

@ -24,6 +24,17 @@
"shortcutsIcons": {
"iconColor": "#404040"
},
"translator": {
"backgroundColor": "#404040",
"borderColor": "#212121",
"iconColor": "#cccccc",
"inputBackgroundColor": "#2a2a2a",
"inputDarkerBackgroundColor": "#272727",
"primaryTextColor": "#ffffff",
"scrollbarHandleColor": "#dddddd",
"secondaryTextColor": "#e3e3e3",
"selectionBackgroundColor": "#202020"
},
"toolbar": {
"backgroundColor": "#f3f3f3",
"checkedButtonColor": "#cccccc",

View File

@ -24,6 +24,17 @@
"shortcutsIcons": {
"iconColor": "#d0d0d0"
},
"translator": {
"backgroundColor": "#404040",
"borderColor": "#212121",
"iconColor": "#cccccc",
"inputBackgroundColor": "#2a2a2a",
"inputDarkerBackgroundColor": "#272727",
"primaryTextColor": "#ffffff",
"scrollbarHandleColor": "#dddddd",
"secondaryTextColor": "#e3e3e3",
"selectionBackgroundColor": "#202020"
},
"toolbar": {
"backgroundColor": "#202020",
"checkedButtonColor": "#3a3a3a",

View File

@ -24,6 +24,17 @@
"shortcutsIcons": {
"iconColor": "#606060"
},
"translator": {
"backgroundColor": "#e8e8e8",
"borderColor": "#cccccc",
"iconColor": "#404040",
"inputBackgroundColor": "#f5f5f5",
"inputDarkerBackgroundColor": "#ebebeb",
"primaryTextColor": "#1a1a1a",
"scrollbarHandleColor": "#909090",
"secondaryTextColor": "#2a2a2a",
"selectionBackgroundColor": "#dcdcdc"
},
"toolbar": {
"backgroundColor": "#f3f3f3",
"checkedButtonColor": "#cccccc",

View File

@ -21,6 +21,31 @@ struct ViewerThemeTemplates {
QString infoLabelQSS = "QLabel { color : %1; font-size:%2px; }";
};
struct TranslatorThemeTemplates {
// %1 = track color, %2 = handle color
QString scrollBarQSS = "QScrollBar:vertical { border: none; background: %1; width: 7px; margin: 0 3px 0 0; }"
"QScrollBar::handle:vertical { background: %2; width: 7px; min-height: 20px; }"
"QScrollBar::add-line:vertical { border: none; background: %1; height: 10px; subcontrol-position: bottom; subcontrol-origin: margin; margin: 0 3px 0 0;}"
"QScrollBar::sub-line:vertical { border: none; background: %1; height: 10px; subcontrol-position: top; subcontrol-origin: margin; margin: 0 3px 0 0;}"
"QScrollBar::up-arrow:vertical {border:none;width: 9px;height: 6px;background: url(':/images/folders_view/line-up.png') center top no-repeat;}"
"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; }";
// %1 = background, %2 = text color
QString textEditQSS = "QTextEdit{border:none;background:%1;color:%2; font-size:12px; padding:6px;}";
// %1 = background, %2 = text color, %3 = arrow icon path, %4 = list background, %5 = selection background
QString comboBoxQSS = "QComboBox {border:none;background:%1;color:%2;font-size:12px;font-family:Arial;padding-left:8px;}"
"QComboBox::down-arrow {image: url(%3);}"
"QComboBox::drop-down {border:none; padding-right:10px;}"
"QComboBox QAbstractItemView {border: none; background:%4; color:%2; selection-background-color: %5; outline:none;}"
"QComboBox QAbstractItemView::item {padding-left:8px;}";
// %1 = border color, %2 = background, %3 = text color
QString clearButtonQSS = "QPushButton {border:1px solid %1; background:%2; color:%3; font-family:Arial; font-size:12px; padding-top:5px; padding-bottom:5px;}";
// %1 = text color
QString titleQSS = "QLabel {font-size:18px; font-family:Arial; color:%1;}";
QString resultsTitleQSS = "QLabel {font-family:Arial;font-size:14px;color:%1;}";
QString resultTextQSS = "QLabel {color:%1;font-size:12px;}";
};
struct GoToFlowWidgetThemeTemplates {
QString sliderQSS = "QSlider::groove:horizontal {"
" border: 1px solid %1;"
@ -41,6 +66,22 @@ struct GoToFlowWidgetThemeTemplates {
QString labelQSS = "QLabel { color: %1; }";
};
struct TranslatorTheme {
QColor backgroundColor;
QColor inputBackgroundColor;
QString scrollBarQSS;
QString textEditQSS;
QString comboBoxQSS;
QString clearButtonQSS;
QString titleQSS;
QString resultsTitleQSS;
QString resultTextQSS;
QIcon closeIcon;
QIcon speakerIcon;
QIcon searchIcon;
QPixmap fromToPixmap;
};
struct ToolbarTheme {
QString toolbarQSS;
@ -147,6 +188,7 @@ struct Theme {
ThemeMeta meta;
QJsonObject sourceJson;
TranslatorTheme translator;
ToolbarTheme toolbar;
ViewerTheme viewer;
GoToFlowWidgetTheme goToFlowWidget;

View File

@ -56,6 +56,20 @@ struct ShortcutsIconsParams {
QColor iconColor; // Main icon color (replaces #f0f)
};
struct TranslatorParams {
TranslatorThemeTemplates t;
QColor backgroundColor { 0x40, 0x40, 0x40 };
QColor inputBackgroundColor { 0x2a, 0x2a, 0x2a };
QColor inputDarkerBackgroundColor { 0x27, 0x27, 0x27 };
QColor selectionBackgroundColor { 0x20, 0x20, 0x20 };
QColor primaryTextColor { Qt::white };
QColor secondaryTextColor { 0xe3, 0xe3, 0xe3 };
QColor scrollbarHandleColor { 0xdd, 0xdd, 0xdd };
QColor borderColor { 0x21, 0x21, 0x21 };
QColor iconColor { 0xcc, 0xcc, 0xcc };
};
struct ThemeParams {
ThemeMeta meta;
@ -65,6 +79,7 @@ struct ThemeParams {
HelpAboutDialogTheme helpAboutDialogParams;
WhatsNewDialogParams whatsNewDialogParams;
ShortcutsIconsParams shortcutsIconsParams;
TranslatorParams translatorParams;
};
void setToolbarIconPair(QIcon &icon,
@ -211,6 +226,43 @@ Theme makeTheme(const ThemeParams &params)
theme.dialogIcons.findFolderIcon = QIcon(renderSvgToPixmap(path, 13, 13, dpr));
}
// Translator
{
const auto &tr = params.translatorParams;
theme.translator.backgroundColor = tr.backgroundColor;
theme.translator.inputBackgroundColor = tr.inputBackgroundColor;
theme.translator.scrollBarQSS = tr.t.scrollBarQSS.arg(
tr.backgroundColor.name(),
tr.scrollbarHandleColor.name());
theme.translator.textEditQSS = tr.t.textEditQSS.arg(
tr.inputBackgroundColor.name(),
tr.primaryTextColor.name());
const QString dropDownArrowPath = recoloredSvgToThemeFile(
":/images/translator/dropDownArrow.svg", tr.iconColor, params.meta.id);
theme.translator.comboBoxQSS = tr.t.comboBoxQSS.arg(
tr.inputBackgroundColor.name(),
tr.primaryTextColor.name(),
dropDownArrowPath,
tr.inputDarkerBackgroundColor.name(),
tr.selectionBackgroundColor.name());
theme.translator.clearButtonQSS = tr.t.clearButtonQSS.arg(
tr.borderColor.name(),
tr.inputBackgroundColor.name(),
tr.primaryTextColor.name());
theme.translator.titleQSS = tr.t.titleQSS.arg(tr.primaryTextColor.name());
theme.translator.resultsTitleQSS = tr.t.resultsTitleQSS.arg(tr.secondaryTextColor.name());
theme.translator.resultTextQSS = tr.t.resultTextQSS.arg(tr.primaryTextColor.name());
theme.translator.closeIcon = QIcon(recoloredSvgToThemeFile(
":/images/translator/close.svg", tr.iconColor, params.meta.id));
theme.translator.speakerIcon = QIcon(recoloredSvgToThemeFile(
":/images/translator/speaker.svg", tr.iconColor, params.meta.id));
theme.translator.searchIcon = QIcon(recoloredSvgToThemeFile(
":/images/translator/translatorSearch.svg", tr.iconColor, params.meta.id));
theme.translator.fromToPixmap = QPixmap(recoloredSvgToThemeFile(
":/images/translator/fromTo.svg", tr.iconColor, params.meta.id));
}
// end Translator
return theme;
}
@ -288,6 +340,20 @@ Theme makeTheme(const QJsonObject &json)
p.shortcutsIconsParams.iconColor = colorFromJson(s, "iconColor", p.shortcutsIconsParams.iconColor);
}
if (json.contains("translator")) {
const auto t = json["translator"].toObject();
auto &tp = p.translatorParams;
tp.backgroundColor = colorFromJson(t, "backgroundColor", tp.backgroundColor);
tp.inputBackgroundColor = colorFromJson(t, "inputBackgroundColor", tp.inputBackgroundColor);
tp.inputDarkerBackgroundColor = colorFromJson(t, "inputDarkerBackgroundColor", tp.inputDarkerBackgroundColor);
tp.selectionBackgroundColor = colorFromJson(t, "selectionBackgroundColor", tp.selectionBackgroundColor);
tp.primaryTextColor = colorFromJson(t, "primaryTextColor", tp.primaryTextColor);
tp.secondaryTextColor = colorFromJson(t, "secondaryTextColor", tp.secondaryTextColor);
tp.scrollbarHandleColor = colorFromJson(t, "scrollbarHandleColor", tp.scrollbarHandleColor);
tp.borderColor = colorFromJson(t, "borderColor", tp.borderColor);
tp.iconColor = colorFromJson(t, "iconColor", tp.iconColor);
}
if (json.contains("meta")) {
const auto o = json["meta"].toObject();
p.meta.id = o["id"].toString(p.meta.id);

View File

@ -29,39 +29,25 @@
YACReaderTranslator::YACReaderTranslator(Viewer *parent)
: QWidget(parent), drag(false)
{
QString scrollBarStyle = "QScrollBar:vertical { border: none; background: #404040; width: 7px; margin: 0 3px 0 0; }"
"QScrollBar::handle:vertical { background: #DDDDDD; width: 7px; min-height: 20px; }"
"QScrollBar::add-line:vertical { border: none; background: #404040; height: 10px; subcontrol-position: bottom; subcontrol-origin: margin; margin: 0 3px 0 0;}"
"QScrollBar::sub-line:vertical { border: none; background: #404040; height: 10px; subcontrol-position: top; subcontrol-origin: margin; margin: 0 3px 0 0;}"
"QScrollBar::up-arrow:vertical {border:none;width: 9px;height: 6px;background: url(':/images/folders_view/line-up.png') center top no-repeat;}"
"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; }";
this->setCursor(QCursor(Qt::ArrowCursor));
this->setAutoFillBackground(true);
this->setBackgroundRole(QPalette::Window);
QPalette p(this->palette());
p.setColor(QPalette::Window, QColor(0x404040));
this->setPalette(p);
auto layout = new QVBoxLayout(this);
// TITLE BAR
auto titleBar = new QHBoxLayout();
auto close = new QPushButton(QIcon(":/images/close.svg"), "");
close->setFlat(true);
auto title = new QLabel(tr("YACReader translator"));
title->setStyleSheet("QLabel {font-size:18px; font-family:Arial; color:white;}");
titleBar->addWidget(title);
closeButton = new QPushButton(this);
closeButton->setFlat(true);
titleLabel = new QLabel(tr("YACReader translator"));
titleBar->addWidget(titleLabel);
titleBar->addStretch();
close->resize(14, 14);
close->setStyleSheet("QPushButton {margin:0;padding:0;border:none;}");
titleBar->addWidget(close);
closeButton->resize(14, 14);
closeButton->setStyleSheet("QPushButton {margin:0;padding:0;border:none;}");
titleBar->addWidget(closeButton);
titleBar->setContentsMargins(0, 0, 0, 0);
titleBar->setSpacing(0);
connect(close, &QAbstractButton::clicked, parent, &Viewer::animateHideTranslator);
connect(closeButton, &QAbstractButton::clicked, parent, &Viewer::animateHideTranslator);
layout->addLayout(titleBar);
@ -71,32 +57,19 @@ YACReaderTranslator::YACReaderTranslator(Viewer *parent)
text->setMaximumHeight(110);
layout->addSpacing(12);
layout->addWidget(text);
text->setStyleSheet("QTextEdit{border:none;background:#2a2a2a;color:white; font-size:12px; padding:6px;}" + scrollBarStyle);
// COMBOBOXES
auto combos = new QHBoxLayout();
from = new QComboBox(this);
to = new QComboBox(this);
QString comboBoxStyle = "QComboBox {border:none;background:#2a2a2a;color:white;font-size:12px;font-family:Arial;padding-left:8px;}"
"QComboBox::down-arrow {image: url(:/images/dropDownArrow.png);}"
"QComboBox::drop-down {border:none; padding-right:10px;}"
"QComboBox QAbstractItemView {border: none; background:#272727; color:white; selection-background-color: #202020; outline:none;}"
"QComboBox QAbstractItemView::item {padding-left:8px;}" +
scrollBarStyle;
from->setStyleSheet(comboBoxStyle);
to->setStyleSheet(comboBoxStyle);
from->setFixedHeight(22);
to->setFixedHeight(22);
QLabel *arrow = new QLabel(this);
QPixmap arrowPixmap(":/images/fromTo.png");
arrow->setPixmap(arrowPixmap);
auto searchButton = new QPushButton(this);
searchButton->setIcon(QIcon(":/images/translatorSearch.png"));
searchButton->setStyleSheet("QPushButton {border:none; background:#2a2a2a;}");
arrowLabel = new QLabel(this);
searchButton = new QPushButton(this);
searchButton->setFixedSize(22, 22);
combos->addWidget(from, 1);
combos->addSpacing(9);
combos->addWidget(arrow, 0);
combos->addWidget(arrowLabel, 0);
combos->addSpacing(9);
combos->addWidget(to, 1);
combos->addSpacing(9);
@ -107,10 +80,8 @@ YACReaderTranslator::YACReaderTranslator(Viewer *parent)
// RESULTS
auto resultsTitleLayout = new QHBoxLayout();
resultsTitle = new QLabel(tr("Translation"));
resultsTitle->setStyleSheet("QLabel {font-family:Arial;font-size:14px;color:#e3e3e3;}");
speakButton = new QPushButton(this);
speakButton->setStyleSheet("QPushButton {border:none;}");
speakButton->setIcon(QIcon(":/images/speaker.png"));
resultsTitleLayout->addWidget(resultsTitle, 0, Qt::AlignVCenter);
resultsTitleLayout->addSpacing(10);
resultsTitleLayout->addWidget(speakButton, 0, Qt::AlignVCenter);
@ -122,7 +93,6 @@ YACReaderTranslator::YACReaderTranslator(Viewer *parent)
resultText = new QLabel();
resultText->setWordWrap(true);
resultText->setStyleSheet("QLabel {color:white;font-size:12px;}");
resultText->setText("");
layout->addWidget(resultText);
@ -132,7 +102,6 @@ YACReaderTranslator::YACReaderTranslator(Viewer *parent)
clearButton = new QPushButton(tr("clear"));
layout->addWidget(clearButton, 0, Qt::AlignRight);
clearButton->setMinimumWidth(95);
clearButton->setStyleSheet("QPushButton {border:1px solid #212121; background:#2a2a2a; color:white; font-family:Arial; font-size:12px; padding-top:5px; padding-bottom:5px;}");
resize(400, 479);
@ -147,13 +116,41 @@ YACReaderTranslator::YACReaderTranslator(Viewer *parent)
busyIndicator->move((this->width() - busyIndicator->width()) / 2, (this->height() - busyIndicator->height()) * 2 / 3);
busyIndicator->hide();
show();
connect(searchButton, &QAbstractButton::pressed, this, &YACReaderTranslator::translate);
connect(speakButton, &QAbstractButton::pressed, this, &YACReaderTranslator::play);
connect(clearButton, &QAbstractButton::pressed, this, &YACReaderTranslator::clear);
tts = new QTextToSpeech(this);
initTheme(this);
show();
}
void YACReaderTranslator::applyTheme(const Theme &theme)
{
const auto &tr = theme.translator;
QPalette p(this->palette());
p.setColor(QPalette::Window, tr.backgroundColor);
this->setPalette(p);
text->setStyleSheet(tr.textEditQSS + tr.scrollBarQSS);
from->setStyleSheet(tr.comboBoxQSS + tr.scrollBarQSS);
to->setStyleSheet(tr.comboBoxQSS + tr.scrollBarQSS);
titleLabel->setStyleSheet(tr.titleQSS);
resultsTitle->setStyleSheet(tr.resultsTitleQSS);
resultText->setStyleSheet(tr.resultTextQSS);
clearButton->setStyleSheet(tr.clearButtonQSS);
searchButton->setStyleSheet(
QString("QPushButton {border:none; background:%1;}").arg(tr.inputBackgroundColor.name()));
closeButton->setIcon(tr.closeIcon);
speakButton->setIcon(tr.speakerIcon);
searchButton->setIcon(tr.searchIcon);
arrowLabel->setPixmap(tr.fromToPixmap);
}
void YACReaderTranslator::hideResults()

View File

@ -14,8 +14,9 @@ class QTextToSpeech;
#include <QThread>
#include <QUrl>
#include "viewer.h"
#include "themable.h"
class YACReaderTranslator : public QWidget
class YACReaderTranslator : public QWidget, protected Themable
{
Q_OBJECT
public:
@ -31,6 +32,7 @@ protected slots:
void clear();
protected:
void applyTheme(const Theme &theme) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
@ -48,8 +50,12 @@ private:
QTextEdit *text;
QComboBox *from;
QComboBox *to;
QLabel *titleLabel;
QLabel *resultsTitle;
QLabel *arrowLabel;
QPushButton *speakButton;
QPushButton *closeButton;
QPushButton *searchButton;
QLabel *resultText;
YACReaderBusyWidget *busyIndicator;
QPushButton *clearButton;

View File

@ -13,10 +13,12 @@
<file>../images/centerFlow.svg</file>
<file>../images/gotoFlow.svg</file>
<file>../images/defaultCover.png</file>
<file>../images/fromTo.png</file>
<file>../images/dropDownArrow.png</file>
<file>../images/translatorSearch.png</file>
<file>../images/speaker.png</file>
<file>../images/translator/close.svg</file>
<file>../images/translator/dropDownArrow.svg</file>
<file>../images/translator/fromTo.svg</file>
<file>../images/translator/speaker.svg</file>
<file>../images/translator/translatorSearch.svg</file>
<file>../images/shortcuts/clear_shortcut.svg</file>
<file>../images/shortcuts/accept_shortcut.svg</file>
<file>../images/shortcuts/shortcuts_group_comics.svg</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

View File

@ -0,0 +1,15 @@
<?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 14 14">
<defs>
<style>
.cls-1 {
fill: none;
stroke: #f0f;
stroke-miterlimit: 10;
stroke-width: 2px;
}
</style>
</defs>
<line class="cls-1" x1="2" y1="2" x2="12" y2="12"/>
<line class="cls-1" x1="12" y1="2" x2="2" y2="12"/>
</svg>

After

Width:  |  Height:  |  Size: 414 B

View File

@ -0,0 +1,11 @@
<?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 10 5">
<defs>
<style>
.cls-1 {
fill: #f0f;
}
</style>
</defs>
<polygon class="cls-1" points="5 5 5 5 1 0 9 0 5 5"/>
</svg>

After

Width:  |  Height:  |  Size: 281 B

View File

@ -0,0 +1,22 @@
<?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 14 14">
<defs>
<style>
.cls-1 {
stroke-width: 2px;
}
.cls-1, .cls-2 {
fill: none;
stroke: #f0f;
stroke-miterlimit: 10;
}
.cls-2 {
stroke-width: 1.8px;
}
</style>
</defs>
<polyline class="cls-2" points="6 13 12 7 6 1"/>
<line class="cls-1" x1="11.5" y1="7" x2="1.5" y2="7"/>
</svg>

After

Width:  |  Height:  |  Size: 499 B

View 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" viewBox="0 0 14 14">
<defs>
<style>
.cls-1 {
fill: none;
stroke: #f0f;
stroke-miterlimit: 10;
}
.cls-2 {
fill: #f0f;
}
</style>
</defs>
<polygon class="cls-2" points="4 4 0 4 0 10 4 10 8 13 8 1 4 4"/>
<path class="cls-1" d="M9.65,4.75c.23.54.35,1.13.35,1.75s-.13,1.21-.35,1.75"/>
<path class="cls-1" d="M10.45,1.55c.63.63,1.15,1.39,1.5,2.23s.55,1.76.55,2.72-.2,1.89-.55,2.72-.87,1.59-1.5,2.23"/>
</svg>

After

Width:  |  Height:  |  Size: 589 B

View File

@ -0,0 +1,11 @@
<?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 14 14">
<defs>
<style>
.cls-1 {
fill: #f0f;
}
</style>
</defs>
<path class="cls-1" d="M13.5,11.64l-2.03-2.03c.58-.92.92-2,.92-3.17,0-3.28-2.66-5.94-5.94-5.94S.5,3.16.5,6.44s2.67,5.94,5.94,5.94c1.16,0,2.25-.34,3.17-.92l2.03,2.03s1.86-1.86,1.86-1.86ZM6.44,10.65c-2.31,0-4.19-1.88-4.19-4.19s1.88-4.2,4.19-4.2,4.19,1.88,4.19,4.19-1.88,4.19-4.19,4.19h0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 B