Add system theme

The system theme uses system colors and styles except for the scintilla editor.
This commit is contained in:
Stefan Knotzer 2021-12-29 12:46:26 +01:00
parent 2bcb2a1976
commit affdd15b82
11 changed files with 283 additions and 196 deletions

4
conf/themes/Dark.lua Normal file → Executable file
View File

@ -221,7 +221,7 @@ theme.property['style.controlchar'] = '$(style.nothing)'
theme.property['style.default'] = 'fore:#AAB2BE,back:#212228'
theme.property['style.definition'] = 'fore:#F6E9D0'
theme.property['style.embedded'] = '$(style.tag),back:#333333'
theme.property['style.error'] = 'fore:#994D4D,italics'
theme.property['style.error'] = 'fore:#994D4D'
theme.property['style.function'] = 'fore:#4D99E6'
theme.property['style.identifier'] = '$(style.nothing)'
theme.property['style.indentguide'] = 'fore:#333333,back:#333333'
@ -237,4 +237,4 @@ theme.property['style.string'] = 'fore:#93C37E'
theme.property['style.tag'] = 'fore:#CCCCCC'
theme.property['style.type'] = 'fore:#CC77DA'
theme.property['style.variable'] = 'fore:#80CCFF'
theme.property['style.whitespace'] = ''
theme.property['style.whitespace'] = '$(style.nothing)'

View File

@ -215,11 +215,11 @@ theme.property['style.bracebad'] = '$(style.nothing)'
theme.property['style.bracelight'] = '$(style.nothing)'
theme.property['style.calltip'] = '$(style.nothing)'
theme.property['style.class'] = '$(style.nothing)'
theme.property['style.comment'] = 'fore:#0000FF,italics'
theme.property['style.comment'] = 'fore:#0000FF'
theme.property['style.constant'] = '$(style.keyword)'
theme.property['style.controlchar'] = '$(style.nothing)'
theme.property['style.default'] = 'fore:$(color.black),back:$(color.white)'
theme.property['style.definition'] = '$(style.nothing)'
theme.property['style.embedded'] = '$(style.nothing)'
theme.property['style.error'] = 'fore:#FF0000'
theme.property['style.function'] = '$(style.label)'
@ -234,7 +234,7 @@ theme.property['style.operator'] = 'fore:$(color.black),bold'
theme.property['style.preprocessor'] = 'fore:$(color.green),bold'
theme.property['style.regex'] = '$(style.nothing)'
theme.property['style.string'] = 'fore:#CC0000'
theme.property['style.tag'] = '$(style.nothing)'
theme.property['style.type'] = 'fore:$(color.blue)'
theme.property['style.variable'] = '$(style.label)'
theme.property['style.whitespace'] = '$(style.nothing)'

126
conf/themes/System.lua Normal file
View File

@ -0,0 +1,126 @@
--
-- Many colors support 'active', 'inactive', and 'disabled' states.
-- They can all be set to the same color with the syntax:
--
-- key = '<color>'
--
-- Or set individually with syntax like:
--
-- key = { active = '<color>', inactive = '<color>', disabled = '<color>' }
--
-- Use the 'default' key set one state individually and the remainder
-- to a default value:
--
-- key = { default = '<color>', disabled = '<color>' }
--
-- graph edge colors
theme['graph'] = {
edge1 = '#53AFEC',
edge2 = '#82DA2A',
edge3 = '#DA2ADA',
edge4 = '#DA822A',
edge5 = '#2ADADA',
edge6 = '#DA2A82',
edge7 = '#84A896',
edge8 = '#2ADA82',
edge9 = '#822ADA',
edge10 = '#66D1E0',
edge11 = '#D3C27E',
edge12 = '#95CB80',
edge13 = '#50D4BE',
edge14 = '#2ADA82',
edge15 = '#DA822A'
}
-- editor styles
-- Styles are composed of a string like:
-- fore:<color>,back:<color>,bold,italics,underline
-- Symbolic style names are allowed:
-- $(style.name)
-- http://www.scintilla.org/MyScintillaDoc.html#Styling
if theme.dark then
-- colors
theme.property['color.red'] = '#994D4D'
theme.property['color.yellow'] = '#99994D'
theme.property['color.green'] = '#4D994D'
theme.property['color.teal'] = '#4D9999'
theme.property['color.purple'] = '#994D99'
theme.property['color.orange'] = '#E6994D'
theme.property['color.blue'] = '#4D99E6'
theme.property['color.black'] = '#1A1A1A'
theme.property['color.grey'] = '#808080'
theme.property['color.white'] = '#E6E6E6'
-- styles
theme.property['style.bracebad'] = 'fore:#CC8080'
theme.property['style.bracelight'] = 'fore:#80CCFF'
theme.property['style.calltip'] = 'fore:#AAB2BE,back:#333333'
theme.property['style.class'] = 'fore:#F6E9D0'
theme.property['style.comment'] = 'fore:#E2D9C9'
theme.property['style.constant'] = 'fore:#E8C080'
theme.property['style.controlchar'] = '$(style.nothing)'
-- theme.property['style.default'] = 'fore:#AAB2BE,back:#212228'
theme.property['style.definition'] = 'fore:#F6E9D0'
theme.property['style.embedded'] = '$(style.tag),back:#333333'
theme.property['style.error'] = 'fore:#994D4D'
theme.property['style.function'] = 'fore:#4D99E6'
theme.property['style.identifier'] = '$(style.nothing)'
theme.property['style.indentguide'] = 'fore:#333333,back:#333333'
theme.property['style.keyword'] = 'fore:#53AFEC,bold'
theme.property['style.label'] = 'fore:#E8C080'
-- theme.property['style.linenumber'] = 'fore:#5F6672,back:#2A2B30,bold'
theme.property['style.nothing'] = ''
theme.property['style.number'] = 'fore:#4D99E6'
theme.property['style.operator'] = 'fore:#CCCCCC,bold'
theme.property['style.preprocessor'] = 'fore:#CC77DA,bold'
theme.property['style.regex'] = 'fore:#80CC80'
theme.property['style.string'] = 'fore:#93C37E'
theme.property['style.tag'] = 'fore:#CCCCCC'
theme.property['style.type'] = 'fore:#CC77DA'
theme.property['style.variable'] = 'fore:#80CCFF'
theme.property['style.whitespace'] = '$(style.nothing)'
else
-- colors
theme.property['color.red'] = '#800000'
theme.property['color.yellow'] = '#808000'
theme.property['color.green'] = '#008000'
theme.property['color.teal'] = '#008080'
theme.property['color.purple'] = '#800080'
theme.property['color.orange'] = '#B08000'
theme.property['color.blue'] = '#000080'
theme.property['color.black'] = '#000000'
theme.property['color.grey'] = '#808080'
theme.property['color.white'] = '#FFFFFF'
-- styles
theme.property['style.bracebad'] = '$(style.nothing)'
theme.property['style.bracelight'] = '$(style.nothing)'
theme.property['style.calltip'] = '$(style.nothing)'
theme.property['style.class'] = '$(style.nothing)'
theme.property['style.comment'] = 'fore:#0000FF'
theme.property['style.constant'] = '$(style.keyword)'
theme.property['style.controlchar'] = '$(style.nothing)'
-- theme.property['style.default'] = 'fore:$(color.black),back:$(color.white)'
theme.property['style.definition'] = '$(style.nothing)'
theme.property['style.embedded'] = '$(style.nothing)'
theme.property['style.error'] = 'fore:#FF0000'
theme.property['style.function'] = '$(style.label)'
theme.property['style.identifier'] = '$(style.nothing)'
theme.property['style.indentguide'] = '$(style.nothing)'
theme.property['style.keyword'] = 'fore:$(color.blue),bold'
theme.property['style.label'] = 'fore:$(color.red)'
-- theme.property['style.linenumber'] = 'fore:$(color.black),back:#DCDCDC'
theme.property['style.nothing'] = ''
theme.property['style.number'] = 'fore:$(color.teal)'
theme.property['style.operator'] = 'fore:$(color.black),bold'
theme.property['style.preprocessor'] = 'fore:$(color.green),bold'
theme.property['style.regex'] = '$(style.nothing)'
theme.property['style.string'] = 'fore:#CC0000'
theme.property['style.tag'] = '$(style.nothing)'
theme.property['style.type'] = 'fore:$(color.blue)'
theme.property['style.variable'] = '$(style.label)'
theme.property['style.whitespace'] = '$(style.nothing)'
end

BIN
rsrc/dark.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 101 KiB

1
rsrc/resources.qrc Normal file → Executable file
View File

@ -52,6 +52,7 @@
<file>submodules.png</file>
<file>submodules@2x.png</file>
<file>sunken.png</file>
<file>system.png</file>
<file>terminal.png</file>
<file>terminal@2x.png</file>
<file>tools.png</file>

BIN
rsrc/system.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

35
src/app/CustomTheme.cpp Normal file → Executable file
View File

@ -11,6 +11,7 @@
#include "conf/ConfFile.h"
#include "conf/Settings.h"
#include <QDir>
#include <QPainter>
#include <QProxyStyle>
#include <QStandardPaths>
#include <QStyleOptionButton>
@ -75,7 +76,7 @@ public:
}
case PE_IndicatorTabClose:
Theme::drawCloseButton(option, painter);
CustomTheme::drawCloseButton(option, painter);
break;
default:
@ -495,6 +496,38 @@ void CustomTheme::polishWindow(QWindow *window) const
}
#endif
void CustomTheme::drawCloseButton(
const QStyleOption *option,
QPainter *painter)
{
qreal in = 3.5;
qreal out = 8.0;
QRect rect = option->rect;
qreal x = rect.x() + (rect.width() / 2);
qreal y = rect.y() + (rect.height() / 2);
painter->save();
painter->setRenderHints(QPainter::Antialiasing);
// Draw background.
if (option->state & QStyle::State_MouseOver) {
painter->save();
painter->setPen(Qt::NoPen);
bool selected = (option->state & QStyle::State_Selected);
painter->setBrush(QColor(selected ? QPalette().color(QPalette::Highlight) :
QPalette().color(QPalette::Base)));
QRectF background(x - out, y - out, 2 * out, 2 * out);
painter->drawRoundedRect(background, 2.0, 2.0);
painter->restore();
}
// Draw x.
painter->setPen(QPen(QPalette().color(QPalette::WindowText), 1.5));
painter->drawLine(QPointF(x - in, y - in), QPointF(x + in, y + in));
painter->drawLine(QPointF(x - in, y + in), QPointF(x + in, y - in));
painter->restore();
}
QDir CustomTheme::userDir(bool create, bool *exists)
{
QDir dir = Settings::userDir();

4
src/app/CustomTheme.h Normal file → Executable file
View File

@ -40,6 +40,10 @@ public:
QVariantMap checkbox() const;
void polishWindow(QWindow *window) const;
static void drawCloseButton(
const QStyleOption *option,
QPainter *painter);
static QDir userDir(bool create = false, bool *exists = nullptr);
static bool isValid(const QString &name);

View File

@ -9,9 +9,9 @@
#include "Theme.h"
#include "CustomTheme.h"
#include "conf/ConfFile.h"
#include "conf/Settings.h"
#include "dialogs/ThemeDialog.h"
#include <QPainter>
#include <QProxyStyle>
#include <QStyleOption>
#include <QWidget>
@ -22,26 +22,9 @@ class Style : public QProxyStyle
{
public:
Style(const Theme *theme)
: QProxyStyle("fusion"), mTheme(theme)
: mTheme(theme)
{}
void drawPrimitive(
PrimitiveElement elem,
const QStyleOption *option,
QPainter *painter,
const QWidget *widget) const override
{
switch (elem) {
case PE_IndicatorTabClose:
Theme::drawCloseButton(option, painter);
break;
default:
baseStyle()->drawPrimitive(elem, option, painter, widget);
break;
}
}
void polish(QPalette &palette) override
{
baseStyle()->polish(palette);
@ -56,6 +39,34 @@ private:
Theme::Theme()
{
mDir = Settings::themesDir();
mName = QString("System");
// Create Qt theme.
QFile themeFile(mDir.filePath(QString("%1.lua").arg(mName)).toUtf8());
if (themeFile.open(QIODevice::ReadOnly)) {
QDir tempDir = QDir::temp();
QFile tempFile(tempDir.filePath(QString("%1.lua").arg(mName)).toUtf8());
if (tempFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
mDir = tempDir;
// Copy template.
tempFile.write(themeFile.readAll());
// Add theme colors for scintilla editor.
tempFile.write(QString("theme.property['style.default'] = 'fore:%1,back:%2'\n")
.arg(QPalette().color(QPalette::Text).name(QColor::HexRgb),
QPalette().color(QPalette::Base).name(QColor::HexRgb))
.toUtf8());
tempFile.close();
}
themeFile.close();
}
// Load Qt theme.
QByteArray file = mDir.filePath(QString("%1.lua").arg(mName)).toUtf8();
mMap = ConfFile(file).parse("theme");
QPalette palette;
QColor base = palette.color(QPalette::Base);
QColor text = palette.color(QPalette::Text);
@ -64,14 +75,12 @@ Theme::Theme()
QDir Theme::dir() const
{
QDir dir = Settings::confDir();
dir.cd("themes");
return dir;
return mDir;
}
QString Theme::name() const
{
return "Default";
return mName;
}
QStyle *Theme::style() const
@ -86,126 +95,78 @@ QString Theme::styleSheet() const
void Theme::polish(QPalette &palette) const
{
palette.setColor(QPalette::BrightText, mDark ? "#9C9C9C" : "#646464");
palette.setColor(QPalette::Light, mDark ? "#121212" : "#E6E6E6");
palette.setColor(QPalette::BrightText, palette.color(QPalette::Text));
palette.setColor(QPalette::Light, palette.color(QPalette::Dark));
palette.setColor(QPalette::Shadow, palette.color(QPalette::Mid));
if (mDark)
palette.setColor(QPalette::Link, "#2A82DA");
}
QColor Theme::badge(BadgeRole role, BadgeState state)
{
if (mDark) {
switch (role) {
case BadgeRole::Foreground:
switch (state) {
case BadgeState::Selected:
return "#2A82DA";
default:
return "#E1E5F2";
}
case BadgeRole::Background:
switch (state) {
case BadgeState::Normal:
return "#2A82DA";
case BadgeState::Selected:
return "#E1E5F2";
case BadgeState::Conflicted:
return "#DA2ADA";
case BadgeState::Head:
return "#52A500";
case BadgeState::Notification:
return "#8C2026";
}
}
}
switch (role) {
case BadgeRole::Foreground:
switch (state) {
case BadgeState::Selected:
return "#6C6C6C";
return QPalette().color(QPalette::Text);
case BadgeState::Head:
return QPalette().color(QPalette::HighlightedText);
default:
return Qt::white;
return QPalette().color(QPalette::WindowText);
}
case BadgeRole::Background:
switch (state) {
case BadgeState::Normal:
return "#A6ACB6";
return mDark ? QPalette().color(QPalette::Inactive, QPalette::Highlight) :
QPalette().color(QPalette::Mid);
case BadgeState::Selected:
return Qt::white;
return QPalette().color(QPalette::Base);
case BadgeState::Conflicted:
return "#D22222";
return mDark ? "#DA2ADA" :
"#D22222";
case BadgeState::Head:
return "#6F7379";
return QPalette().color(QPalette::Highlight);
case BadgeState::Notification:
return Qt::red;
return mDark ? "#8C2026" :
"#FF0000";
}
}
}
QList<QColor> Theme::branchTopologyEdges()
{
return {
"steelblue",
"crimson",
"forestgreen",
"goldenrod",
"darkviolet",
"darkcyan",
"orange",
"cornflowerblue",
"tomato",
"darkturquoise",
"palevioletred",
"sandybrown"
};
QVariantMap edge = mMap.value("graph").toMap();
QList<QColor> colors;
for (int i = 0; i < 15; i++) {
QString name = QString("edge%1").arg(i + 1);
colors.append(edge.value(name).toString());
}
return colors;
}
QColor Theme::buttonChecked()
{
return mDark ? "#19B4FB" : "#0086F3";
return QPalette().color(QPalette::Highlight);
}
QPalette Theme::commitList()
{
QPalette palette;
QColor bright = palette.color(QPalette::BrightText);
QColor inactive = mDark ? Qt::white : Qt::black;
palette.setColor(QPalette::Active, QPalette::HighlightedText, Qt::white);
palette.setColor(QPalette::Inactive, QPalette::HighlightedText, inactive);
palette.setColor(QPalette::Active, QPalette::WindowText, "#C0C0C0");
palette.setColor(QPalette::Inactive, QPalette::WindowText, bright);
return palette;
return QPalette();
}
QColor Theme::commitEditor(CommitEditor color)
{
if (mDark) {
switch (color) {
case CommitEditor::SpellError: return "#BC0009";
case CommitEditor::SpellIgnore: return "#E1E5F2";
case CommitEditor::LengthWarning: return "#464614";
}
}
switch (color) {
case CommitEditor::SpellError: return Qt::red;
case CommitEditor::SpellIgnore: return Qt::gray;
case CommitEditor::LengthWarning: return "#EFF0F1";
case CommitEditor::LengthWarning: return Qt::yellow;
}
}
@ -236,9 +197,9 @@ QColor Theme::diff(Diff color)
case Diff::WordDeletion: return "#F2B0B0";
case Diff::Plus: return "#45CC45";
case Diff::Minus: return "#F28080";
case Diff::Note: return Qt::black;
case Diff::Warning: return Qt::yellow;
case Diff::Error: return Qt::red;
case Diff::Note: return "#000000";
case Diff::Warning: return "#FFFF00";
case Diff::Error: return "#FF0000";
}
}
@ -249,16 +210,10 @@ QPalette Theme::fileList()
QColor Theme::heatMap(HeatMap color)
{
if (mDark) {
switch (color) {
case HeatMap::Hot: return "#5E3638";
case HeatMap::Cold: return "#282940";
}
}
switch (color) {
case HeatMap::Hot: return "#FFC0C0";
case HeatMap::Cold: return "#C0C0FF";
case HeatMap::Hot: return QPalette().color(QPalette::Highlight);
case HeatMap::Cold: return mDark ? QPalette().color(QPalette::Inactive, QPalette::Highlight) :
QPalette().color(QPalette::Mid);
}
}
@ -266,47 +221,15 @@ QColor Theme::remoteComment(Comment color)
{
switch (color) {
case Comment::Background: return QPalette().color(QPalette::Base);
case Comment::Body: return "#383838";
case Comment::Author: return "#1A76F4";
case Comment::Timestamp: return "#6C6C6C";
case Comment::Body: return QPalette().color(QPalette::Window);
case Comment::Author: return QPalette().color(QPalette::WindowText);
case Comment::Timestamp: return QPalette().color(QPalette::WindowText);
}
}
QColor Theme::star()
{
return "#FFCE6D";
}
void Theme::drawCloseButton(
const QStyleOption *option,
QPainter *painter)
{
qreal in = 3.5;
qreal out = 8.0;
QRect rect = option->rect;
qreal x = rect.x() + (rect.width() / 2);
qreal y = rect.y() + (rect.height() / 2);
painter->save();
painter->setRenderHints(QPainter::Antialiasing);
// Draw background.
if (option->state & QStyle::State_MouseOver) {
painter->save();
painter->setPen(Qt::NoPen);
bool selected = (option->state & QStyle::State_Selected);
painter->setBrush(QColor(selected ? QPalette().color(QPalette::Highlight) :
QPalette().color(QPalette::Base)));
QRectF background(x - out, y - out, 2 * out, 2 * out);
painter->drawRoundedRect(background, 2.0, 2.0);
painter->restore();
}
// Draw x.
painter->setPen(QPen(QPalette().color(QPalette::WindowText), 1.5));
painter->drawLine(QPointF(x - in, y - in), QPointF(x + in, y + in));
painter->drawLine(QPointF(x - in, y + in), QPointF(x + in, y - in));
painter->restore();
return QPalette().color(QPalette::Highlight);
}
Theme *Theme::create(const QString &defaultName)
@ -314,7 +237,7 @@ Theme *Theme::create(const QString &defaultName)
// Upgrade theme key to capital case.
Settings *settings = Settings::instance();
QString key = settings->value("window/theme").toString();
if (key == "default" || key == "dark") {
if (key == "default" || key == "dark" || key == "system") {
key[0] = key.at(0).toUpper();
settings->setValue("window/theme", key);
}
@ -327,9 +250,9 @@ Theme *Theme::create(const QString &defaultName)
}
// Load custom theme.
if (CustomTheme::isValid(name))
if (CustomTheme::isValid(name) && !name.contains("System"))
return new CustomTheme(name);
// Use Qt theme.
return new Theme;
return new Theme();
}

View File

@ -13,6 +13,7 @@
#include <QDir>
#include <QPalette>
#include <QString>
#include <QMap>
class QStyle;
class QStyleOption;
@ -93,14 +94,13 @@ public:
virtual QColor remoteComment(Comment color);
virtual QColor star();
static void drawCloseButton(
const QStyleOption *option,
QPainter *painter);
static Theme *create(const QString &name = QString());
private:
bool mDark;
QString mName;
QDir mDir;
QVariantMap mMap;
};
#endif

View File

@ -8,16 +8,10 @@
//
#include "ThemeDialog.h"
#include "app/Theme.h"
#include "conf/Settings.h"
#include <QIcon>
#include <QLabel>
#include <QPalette>
#include <QPointer>
#include <QProxyStyle>
#include <QPushButton>
#include <QStyle>
#include <QToolButton>
#include <QVBoxLayout>
namespace {
@ -27,48 +21,36 @@ class ThemeButton : public QPushButton
Q_OBJECT
public:
enum class Theme
{
Default,
Dark,
System
};
ThemeButton(
const QString &title,
const QIcon &icon,
const QString &description,
const Theme &theme,
QWidget *parent = nullptr)
: QPushButton(parent)
: QPushButton(parent), mTheme(theme)
{
setStyleSheet(
"ThemeButton {"
" background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, "
" stop: 0 #E2E2E2, stop: 1 #ECECEC);"
" border: 1px solid #D3D3D3;"
" border-radius: 4px"
"}"
"ThemeButton:default {"
" background: #DCEBFB;"
" border: 2px solid #AFD1F5"
"}"
"ThemeButton:pressed {"
" background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, "
" stop: 0 #D8D8D8, stop: 1 #E2E2E2);"
" border: 1px solid #D3D3D3"
"}"
"ThemeButton #description {"
" color: #9A9A9A;"
" font-size: 12px;"
" font-style: italics;"
" margin: 0px 0px 16px 0px"
"}"
"ThemeButton #title {"
" color: #9A9A9A;"
" font-weight: bold;"
" font-size: 16px;"
" margin: 13px 0px 20px 8px"
"}"
);
QSize iconSize = QSize(350, 280);
QSize iconSize = QSize(245, 196);
setFixedHeight(iconSize.height() + 80);
setFixedWidth(iconSize.width() + 35);
setFocusPolicy(Qt::StrongFocus);
@ -85,10 +67,16 @@ public:
mDescription->setObjectName("description");
connect(this, &QPushButton::clicked, [this] {
if (mTitle->text().startsWith("dark", Qt::CaseInsensitive)) {
Settings::instance()->setValue("window/theme", "Dark");
} else {
Settings::instance()->setValue("window/theme", "Default");
switch (mTheme) {
case Theme::System:
Settings::instance()->setValue("window/theme", "System");
break;
case Theme::Dark:
Settings::instance()->setValue("window/theme", "Dark");
break;
default:
Settings::instance()->setValue("window/theme", "Default");
break;
}
window()->close();
@ -105,6 +93,7 @@ public:
private:
QLabel *mTitle;
QLabel *mDescription;
Theme mTheme;
};
} // anon. namespace
@ -117,19 +106,30 @@ ThemeDialog::ThemeDialog(QWidget *parent)
ThemeButton *native = new ThemeButton(
tr("Default Theme"),
QIcon(":/native.png"),
tr("A flexible look matching system colors")
tr("A consistent bright theme"),
ThemeButton::Theme::Default
);
ThemeButton *dark = new ThemeButton(
tr("Dark Theme"),
QIcon(":/dark.png"),
tr("A consistent look optimal for reducing eye strain")
tr("A consistent look optimal for reducing eye strain"),
ThemeButton::Theme::Dark
);
ThemeButton *system = new ThemeButton(
tr("System Theme"),
QIcon(":/system.png"),
tr("A flexible look matching system colors"),
ThemeButton::Theme::System
);
QHBoxLayout *themeButtons = new QHBoxLayout;
themeButtons->addWidget(native);
themeButtons->addWidget(dark);
themeButtons->addSpacing(20);
themeButtons->addWidget(system);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addLayout(themeButtons);
}