diff --git a/Styling-Qt-Widgets/CMakeLists.txt b/Styling-Qt-Widgets/CMakeLists.txt new file mode 100644 index 0000000..be81546 --- /dev/null +++ b/Styling-Qt-Widgets/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# This file is part of the Oxygen2 project. +# +# SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +# +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.9) +project(widget-style-oxygen2) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +find_package(QT NAMES Qt5 Qt6 CONFIG REQUIRED COMPONENTS Core Gui Widgets) +find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS Core Gui Widgets) + +set(CMAKE_AUTOMOC TRUE) +set(CMAKE_AUTORCC TRUE) +set(CMAKE_AUTOUIC TRUE) + +add_executable(widget-style-oxygen2 + main.cpp + progressbarstylehelper.cpp + maindialog.cpp + colorrepository.cpp + pushbuttonstylehelper.cpp + resource.qrc + checkboxstylehelper.cpp + maindialog.ui + teststyle.cpp + toggleswitch.cpp +) +target_link_libraries(widget-style-oxygen2 Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets) diff --git a/Styling-Qt-Widgets/CMakeLists.txt.user b/Styling-Qt-Widgets/CMakeLists.txt.user new file mode 100644 index 0000000..63a9d49 --- /dev/null +++ b/Styling-Qt-Widgets/CMakeLists.txt.user @@ -0,0 +1,461 @@ + + + + + + EnvironmentId + {d69a6590-c42d-4943-b436-5ef41e6aebe3} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + false + true + false + 0 + true + true + 1 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Qt 6 (inst debug, ninja) + Qt 6 (inst debug, ninja) + {1773e31a-26ca-4007-83f6-72a56c77f91a} + 0 + 0 + 0 + + Debug + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{IDE:ResourcePath}/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + 0 + /d/kdab/src/David/build-widget-style-oxygen2-Qt_6_inst_debug_ninja-Debug + + + + all + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + Release + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{IDE:ResourcePath}/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + /d/kdab/src/David/build-widget-style-oxygen2-Qt_6_inst_debug_ninja-Release + + + + all + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{IDE:ResourcePath}/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + /d/kdab/src/David/build-widget-style-oxygen2-Qt_6_inst_debug_ninja-RelWithDebInfo + + + + all + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release with Debug Information + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{IDE:ResourcePath}/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + 0 + /d/kdab/src/David/build-widget-style-oxygen2-Qt_6_inst_debug_ninja-Profile + + + + all + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + CMakeProjectManager.CMakeBuildConfiguration + + + MinSizeRel + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=MinSizeRel +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{IDE:ResourcePath}/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + /d/kdab/src/David/build-widget-style-oxygen2-Qt_6_inst_debug_ninja-MinSizeRel + + + + all + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Minimum Size Release + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + + 2 + + QT_PLUGIN_PATH=/d/qt/6/inst/plugins + + widget-style-oxygen2 + CMakeProjectManager.CMakeRunConfiguration.widget-style-oxygen2 + widget-style-oxygen2 + false + true + true + false + true + /d/kdab/src/David/build-widget-style-oxygen2-Qt_6_inst_debug_ninja-Debug + + 1 + + + + ProjectExplorer.Project.Target.1 + + Desktop + My main Qt in /d/qt/5/inst + My main Qt in /d/qt/5/inst + {84fa64da-ff52-4ea9-8293-ae3b54cb3e0f} + 0 + 0 + 0 + + Debug + -DCMAKE_EXTRA_GENERATOR:STRING=CodeBlocks +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{IDE:ResourcePath}/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} +-DCMAKE_GENERATOR:STRING=Unix Makefiles + /d/kdab/src/David/widget-style-oxygen2/build-qt5 + + + + all + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + + 1 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + + 2 + + widget-style-oxygen2 + CMakeProjectManager.CMakeRunConfiguration.widget-style-oxygen2 + widget-style-oxygen2 + false + true + true + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 2 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/Styling-Qt-Widgets/check-dark.png b/Styling-Qt-Widgets/check-dark.png new file mode 100644 index 0000000..4233f46 Binary files /dev/null and b/Styling-Qt-Widgets/check-dark.png differ diff --git a/Styling-Qt-Widgets/check.png b/Styling-Qt-Widgets/check.png new file mode 100644 index 0000000..ad1df95 Binary files /dev/null and b/Styling-Qt-Widgets/check.png differ diff --git a/Styling-Qt-Widgets/checkboxstylehelper.cpp b/Styling-Qt-Widgets/checkboxstylehelper.cpp new file mode 100644 index 0000000..12d8de5 --- /dev/null +++ b/Styling-Qt-Widgets/checkboxstylehelper.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#include "checkboxstylehelper.h" +#include "colorrepository.h" +#include "pushbuttonstylehelper.h" +#include "teststyle.h" + +#include +#include + +void CheckBoxStyleHelper::draw(const TestStyle *style, const QStyleOptionButton *option, QPainter *painter, + const QWidget *widget) const +{ + painter->save(); + painter->setRenderHints(QPainter::Antialiasing); + style->pushButtonStyleHelper()->setupPainterForShape(option, painter, widget); + painter->drawRoundedRect(QRectF(option->rect).adjusted(0.5, 0.5, -0.5, -0.5), 5, 5); + drawIndicator(option, painter); // with the same pen color + painter->restore(); +} + +void CheckBoxStyleHelper::drawIndicator(const QStyleOption *option, QPainter *painter) const +{ + if (option->state & QStyle::State_On) { + const QRect rect = option->rect; + const QVector points{QPointF(rect.x() + 4, rect.y() + 9), QPointF(rect.x() + 8, rect.y() + 12), + QPointF(rect.x() + 14, rect.y() + 5)}; + painter->drawPolyline(points); + } +} + +int CheckBoxStyleHelper::indicatorSize() const +{ + return 18; +} diff --git a/Styling-Qt-Widgets/checkboxstylehelper.h b/Styling-Qt-Widgets/checkboxstylehelper.h new file mode 100644 index 0000000..7012dbc --- /dev/null +++ b/Styling-Qt-Widgets/checkboxstylehelper.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#ifndef CHECKBOXSTYLEHELPER_H +#define CHECKBOXSTYLEHELPER_H + +class TestStyle; +class QPainter; +class QStyleOptionButton; +class QStyleOption; +class QWidget; + +class CheckBoxStyleHelper +{ +public: + void draw(const TestStyle *style, const QStyleOptionButton *option, QPainter *painter, const QWidget *widget) const; + void drawIndicator(const QStyleOption *option, QPainter *painter) const; + int indicatorSize() const; + +}; + +#endif // CHECKBOXSTYLEHELPER_H diff --git a/Styling-Qt-Widgets/colorrepository.cpp b/Styling-Qt-Widgets/colorrepository.cpp new file mode 100644 index 0000000..caba386 --- /dev/null +++ b/Styling-Qt-Widgets/colorrepository.cpp @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#include "colorrepository.h" + +#include +#include +#include + +// To avoid requiring C++17 +template +constexpr const T &clamp(const T &v, const T &lo, const T &hi) +{ + return v < lo ? lo : hi < v ? hi : v; +} + +static bool s_darkMode = true; + +QPalette ColorRepository::standardPalette() +{ + QPalette pal; + // TODO brush with noise.png + pal.setColor(QPalette::Window, windowBackground()); + pal.setColor(QPalette::Base, baseBackground()); + pal.setColor(QPalette::WindowText, text()); + pal.setColor(QPalette::Text, text()); + + // Text color on buttons + pal.setColor(QPalette::ButtonText, text()); + + // pal.setColor(QPalette::ToolTipBase, baseBackground()); + pal.setColor(QPalette::ToolTipText, text()); + + QToolTip::setPalette(pal); + + return pal; +} + +void ColorRepository::setDarkMode(bool dark) +{ + s_darkMode = dark; + qApp->setPalette(standardPalette()); +} + +QColor ColorRepository::windowBackground() +{ + return s_darkMode ? QColor(0x18, 0x21, 0x29) // dark blue + : QColor(0xef, 0xf0, 0xf1); +} + +QColor ColorRepository::baseBackground() +{ + return s_darkMode ? QColor(0x0f, 0x0f, 0x0f) // almost black + : QColor(0xfb, 0xfb, 0xfb); // almost white +} + +QColor ColorRepository::text() +{ + return s_darkMode ? QColor(0xa5, 0xa5, 0xa5) : QColor(0x25, 0x25, 0x25); +} + +QColor ColorRepository::pressedTextColor() +{ + return QColor(0x65, 0x65, 0x65); // medium gray +} + +QColor ColorRepository::hoverTextColor() +{ + return QColor(0xdd, 0xdd, 0xdd); // light gray +} + +QColor ColorRepository::pressedOutlineColor() +{ + return QColor(0x32, 0x2d, 0x35); +} + +QColor ColorRepository::buttonOutlineColor() +{ + return s_darkMode ? QColor(0x59, 0x51, 0x5f) : QColor(0x9f, 0x95, 0xa3); +} + +QBrush ColorRepository::hoverOutlineBrush(const QRect &rect) +{ + // Instructions from the designer: + // "Draw line passing by center of rectangle (+4% to the right) + // and that is perpendicular to the topleft-bottomright diagonal. + // This line intersects the top and bottom in two points, which are the gradient stops" + + const qreal w = rect.width(); + const qreal h = rect.height(); + const qreal xmid = w * 0.54; + const qreal xoffset = (h * h) / (2 * w); // Proportionality: xoffset / (h/2) = h / w + const qreal x0 = xmid - xoffset; + const qreal x1 = xmid + xoffset; + + QLinearGradient gradient(x0, h, x1, 0); + gradient.setColorAt(0.0, QColor(0x53, 0x94, 0x9f)); + gradient.setColorAt(1.0, QColor(0x75, 0x55, 0x79)); + return QBrush(gradient); +} + +QColor ColorRepository::buttonPressedBackground() +{ + return s_darkMode ? QColor(0x17, 0x17, 0x17) : QColor(0xf8, 0xf7, 0xf8); +} + +QColor ColorRepository::buttonHoveredBackground() +{ + QColor color = buttonPressedBackground(); + color.setAlphaF(0.2); + return color; +} + +QColor ColorRepository::buttonBackground() +{ + return s_darkMode ? QColor(0x21, 0x1f, 0x22, 0xa7) : QColor(0xf5, 0xf4, 0xf5) /* TODO with opacity = ? */; +} + +QBrush ColorRepository::progressBarOutlineBrush(const QRect &rect) +{ + QLinearGradient gradient(0, rect.height(), rect.width(), 0); + gradient.setColorAt(0.0, QColor(0x11, 0xc2, 0xe1)); + gradient.setColorAt(1.0, QColor(0x89, 0x3a, 0x94)); + return QBrush(gradient); +} + +QBrush ColorRepository::progressBarOutlineFadingBrush(const QRect &rect) +{ + QLinearGradient gradient(0, rect.height(), rect.width(), 0); + gradient.setColorAt(0.0, QColor(0x11, 0xc2, 0xe1)); + gradient.setColorAt(1.0, QColor(0x89, 0x3a, 0x94)); + return QBrush(gradient); +} + +QBrush ColorRepository::progressBarContentsBrush(const QRect &rect) +{ + // same as outline brush but with 37% opacity + QLinearGradient gradient(0, rect.height(), rect.width(), 0); + gradient.setColorAt(0.0, QColor(0x11, 0xc2, 0xe1, 0x60)); + gradient.setColorAt(1.0, QColor(0x89, 0x3a, 0x94, 0x60)); + return QBrush(gradient); +} + +QColor ColorRepository::progressBarTextColor(bool enabled) +{ + QColor textColor = text(); + if (!enabled) + textColor.setAlphaF(textColor.alphaF() / 2.0); + return textColor; +} diff --git a/Styling-Qt-Widgets/colorrepository.h b/Styling-Qt-Widgets/colorrepository.h new file mode 100644 index 0000000..2f36772 --- /dev/null +++ b/Styling-Qt-Widgets/colorrepository.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#ifndef COLORREPOSITORY_H +#define COLORREPOSITORY_H + +#include +#include + +/** + * Our colors. They are separate from the widget style so that custom widgets can also use them directly. + */ +namespace ColorRepository +{ + QPalette standardPalette(); + void setDarkMode(bool dark); + + QColor windowBackground(); + QColor baseBackground(); + QColor text(); + + QColor pressedTextColor(); + QColor hoverTextColor(); + + QColor pressedOutlineColor(); + QBrush hoverOutlineBrush(const QRect &rect); + QColor buttonOutlineColor(); + + QColor buttonPressedBackground(); + QColor buttonHoveredBackground(); + QColor buttonBackground(); + + QBrush progressBarOutlineBrush(const QRect &rect); + QBrush progressBarOutlineFadingBrush(const QRect &rect); + QBrush progressBarContentsBrush(const QRect &rect); + QColor progressBarTextColor(bool enabled); +} + +#endif // COLORREPOSITORY_H diff --git a/Styling-Qt-Widgets/main.cpp b/Styling-Qt-Widgets/main.cpp new file mode 100644 index 0000000..5618112 --- /dev/null +++ b/Styling-Qt-Widgets/main.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +/************************************************************************* + * + * Copyright (c) 2013-2019, Klaralvdalens Datakonsult AB (KDAB) + * All rights reserved. + * + * See the LICENSE.txt file shipped along with this file for the license. + * + *************************************************************************/ + +#include "colorrepository.h" +#include "maindialog.h" +#include "teststyle.h" + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + QCommandLineParser parser; + QCommandLineOption qss("qss", "Use a Qt stylesheet instead of a widget style"); + parser.addOption(qss); + QCommandLineOption nostyle("nostyle", + "Disable builtin styling; you can still pass --style to select another style like 'windows'"); + parser.addOption(nostyle); + parser.addHelpOption(); + parser.process(app.arguments()); + + const bool useStylesheet = parser.isSet(qss); + QString styleSheetText; + if (useStylesheet) { + QFile styleSheetFile(":/stylesheet/stylesheet.qss"); + if (!styleSheetFile.open(QIODevice::ReadOnly)) { + qWarning() << "Couldn't find" << styleSheetFile.fileName(); + } else { + styleSheetText = QString::fromUtf8(styleSheetFile.readAll()); + } + } else if (!parser.isSet(nostyle)) { + app.setStyle(new TestStyle); + } + app.setPalette(ColorRepository::standardPalette()); + + auto dlg = new MainDialog(styleSheetText, parser.isSet(nostyle)); + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->show(); + + return app.exec(); +} diff --git a/Styling-Qt-Widgets/maindialog.cpp b/Styling-Qt-Widgets/maindialog.cpp new file mode 100644 index 0000000..d5282ec --- /dev/null +++ b/Styling-Qt-Widgets/maindialog.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#include "maindialog.h" +#include "colorrepository.h" +#include "ui_maindialog.h" + +#include +#include +#include +#include + +MainDialog::MainDialog(const QString &styleSheetText, bool noStyle, QWidget *parent) + : QDialog(parent) + , ui(new Ui::MainDialog) + , m_timer(new QTimer(this)) + , m_styleSheetText(styleSheetText) +{ + m_timer->setInterval(50); + + // QSS: Because QDialogButtonBox can't adjust to changes of properties dynamically, this needs to be done + // before creating the child widgets... + if (!styleSheetText.isEmpty()) { + activateStylesheet(true /*dark*/); + } + + ui->setupUi(this); + + if (noStyle) { + ui->labelToggleSwitch->hide(); + ui->toggleSwitch->hide(); + ui->disabledToggleSwitch->hide(); + ui->labelDisabledToggleSwitch->hide(); + } + + connect(ui->darkModeCheckBox, &QCheckBox::clicked, this, [](bool checked) { ColorRepository::setDarkMode(checked); }); + if (!styleSheetText.isEmpty()) { + ui->label->setText(tr("This application uses a QSS stylesheet to style widgets")); + connect(ui->darkModeCheckBox, &QCheckBox::clicked, this, [this](bool checked) { activateStylesheet(checked); }); + } + + connect(ui->startButton, &QPushButton::clicked, this, &MainDialog::start); + connect(ui->stopButton, &QPushButton::clicked, this, &MainDialog::stop); + connect(m_timer, &QTimer::timeout, this, &MainDialog::increaseProgress); + + auto *plusShortcut = new QShortcut(Qt::Key_Plus, this); + connect(plusShortcut, &QShortcut::activated, this, &MainDialog::increaseProgress); + auto *minusShortcut = new QShortcut(Qt::Key_Minus, this); + connect(minusShortcut, &QShortcut::activated, this, &MainDialog::decreaseProgress); +} + +MainDialog::~MainDialog() +{ + delete ui; +} + +void MainDialog::start() +{ + m_timer->start(); + ui->startButton->setEnabled(false); + ui->stopButton->setEnabled(true); +} + +void MainDialog::stop() +{ + m_timer->stop(); + ui->startButton->setEnabled(true); + ui->stopButton->setEnabled(false); +} + +void MainDialog::increaseProgress() +{ + const int value = (ui->progressBar->value() + 1) % 101; + ui->progressBar->setValue(value); +} + +void MainDialog::decreaseProgress() +{ + const int value = (ui->progressBar->value() + 100) % 101; + ui->progressBar->setValue(value); +} + +void MainDialog::activateStylesheet(bool dark) +{ + QStringList lines = m_styleSheetText.split('\n'); + const auto removeLine = [dark](const QString &line) { + if (line.contains("[DARK]")) + return !dark; + else if (line.contains("[LIGHT]")) + return dark; + return false; + }; + lines.erase(std::remove_if(lines.begin(), lines.end(), removeLine), lines.end()); + // qDebug() << lines; + qApp->setStyleSheet(lines.join('\n')); +} diff --git a/Styling-Qt-Widgets/maindialog.h b/Styling-Qt-Widgets/maindialog.h new file mode 100644 index 0000000..f3d8916 --- /dev/null +++ b/Styling-Qt-Widgets/maindialog.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#ifndef MAINDIALOG_H +#define MAINDIALOG_H + +#include + +namespace Ui { +class MainDialog; +} + +class MainDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MainDialog(const QString &styleSheetText, bool noStyle, QWidget *parent = nullptr); + ~MainDialog(); + +private: + void start(); + void stop(); + void increaseProgress(); + void decreaseProgress(); + void activateStylesheet(bool dark); + +private: + Ui::MainDialog *ui; + QTimer *m_timer; + QString m_styleSheetText; +}; + +#endif // MAINDIALOG_H diff --git a/Styling-Qt-Widgets/maindialog.ui b/Styling-Qt-Widgets/maindialog.ui new file mode 100644 index 0000000..5efb4c9 --- /dev/null +++ b/Styling-Qt-Widgets/maindialog.ui @@ -0,0 +1,198 @@ + + + MainDialog + + + + 0 + 0 + 402 + 290 + + + + Test application for widget styling + + + + + + Another checkbox + + + + + + + 0 + + + + + + + false + + + Stop + + + + + + + Start + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Dark mode + + + true + + + + + + + This application uses a QStyle-derived class to style widgets + + + + + + + + + Toggle Switch + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Disabled + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + ToggleSwitch + QCheckBox +
toggleswitch.h
+
+
+ + darkModeCheckBox + checkBox + startButton + stopButton + toggleSwitch + disabledToggleSwitch + + + + + buttonBox + accepted() + MainDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MainDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/Styling-Qt-Widgets/progressbarstylehelper.cpp b/Styling-Qt-Widgets/progressbarstylehelper.cpp new file mode 100644 index 0000000..658d9ed --- /dev/null +++ b/Styling-Qt-Widgets/progressbarstylehelper.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#include "progressbarstylehelper.h" +#include "colorrepository.h" + +#include +#include +#include + +void ProgressBarStyleHelper::drawGroove(const QStyleOptionProgressBar *option, QPainter *painter, const QWidget *widget) +{ + Q_UNUSED(widget); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(ColorRepository::buttonOutlineColor()); + painter->setBrush(ColorRepository::baseBackground()); + const qreal radius = option->rect.height() / 2; + painter->drawRoundedRect(QRectF(option->rect).adjusted(0.5, 0.5, -0.5, -0.5), radius, radius); +} + +void ProgressBarStyleHelper::drawContents(const QStyleOptionProgressBar *option, QPainter *painter, const QWidget *widget) +{ + Q_UNUSED(widget); + const auto value = option->progress - option->minimum; + const auto range = option->maximum - option->minimum; + if (value > 0) { + painter->save(); + const QRect usableRect = option->rect.adjusted(3, 3, -3, -3); + QRect progressRect = usableRect; + progressRect.setWidth(double(value) / double(range) * usableRect.width()); + + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(QPen(ColorRepository::progressBarOutlineBrush(option->rect), 1)); + painter->setBrush(ColorRepository::progressBarContentsBrush(option->rect)); + const qreal radius = option->rect.height() / 2 - 2.5; + painter->drawRoundedRect(QRectF(progressRect).adjusted(0.5, 0.5, -0.5, -0.5), radius, radius); + + // Additional trick: the groove itself gets a gradient until the current X value + QRect repaintedGrooveRect = option->rect; + repaintedGrooveRect.setWidth(double(value) / double(range) * repaintedGrooveRect.width()); + painter->setClipRect(repaintedGrooveRect); + painter->setPen(QPen(ColorRepository::progressBarOutlineFadingBrush(option->rect), 1)); + painter->setBrush(Qt::NoBrush); + painter->setOpacity(0.43); + painter->drawRoundedRect(QRectF(option->rect).adjusted(0.5, 0.5, -0.5, -0.5), radius, radius); + + painter->restore(); + } +} + +void ProgressBarStyleHelper::drawText(const QStyleOptionProgressBar *option, QPainter *painter, const QWidget *widget) +{ + Q_UNUSED(widget) + const QPen oldPen = painter->pen(); + painter->setPen(ColorRepository::progressBarTextColor(option->state & QStyle::State_Enabled)); + painter->drawText(option->rect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextSingleLine, option->text); + painter->setPen(oldPen); +} + +QRect ProgressBarStyleHelper::subElementRect(QStyle::SubElement subElement, const QStyleOptionProgressBar *option, + const QWidget *widget) +{ + Q_UNUSED(widget) + if (subElement == QStyle::SE_ProgressBarLabel) + return option->rect.adjusted(0, 0, -6, 0); // right-align before the round rect + // The area for both groove and content is the whole rect. The drawing will take care of actual contents rect. + return option->rect; +} diff --git a/Styling-Qt-Widgets/progressbarstylehelper.h b/Styling-Qt-Widgets/progressbarstylehelper.h new file mode 100644 index 0000000..9ed13bd --- /dev/null +++ b/Styling-Qt-Widgets/progressbarstylehelper.h @@ -0,0 +1,28 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#ifndef PROGRESSBARSTYLEHELPER_H +#define PROGRESSBARSTYLEHELPER_H + +#include +class QStyleOptionProgressBar; +class QPainter; +class QWidget; + +class ProgressBarStyleHelper +{ +public: + void drawGroove(const QStyleOptionProgressBar *option, QPainter *painter, const QWidget *widget); + void drawContents(const QStyleOptionProgressBar *option, QPainter *painter, const QWidget *widget); + void drawText(const QStyleOptionProgressBar *option, QPainter *painter, const QWidget *widget); + QRect subElementRect(QStyle::SubElement subElement, const QStyleOptionProgressBar *option, const QWidget *widget); +}; + +#endif // PROGRESSBARSTYLEHELPER_H diff --git a/Styling-Qt-Widgets/pushbuttonstylehelper.cpp b/Styling-Qt-Widgets/pushbuttonstylehelper.cpp new file mode 100644 index 0000000..322d3de --- /dev/null +++ b/Styling-Qt-Widgets/pushbuttonstylehelper.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#include "pushbuttonstylehelper.h" + +#include "colorrepository.h" + +#include +#include + +static const int s_radius = 6; + +void PushButtonStyleHelper::setupPainterForShape(const QStyleOptionButton *option, QPainter *painter, const QWidget *widget) +{ + Q_UNUSED(widget) + if (!(option->state & QStyle::State_Enabled)) { + painter->setPen(ColorRepository::buttonOutlineColor()); + painter->setBrush(ColorRepository::buttonBackground()); + painter->setOpacity(0.5); + } else if (option->state & QStyle::State_Sunken) { + painter->setPen(ColorRepository::pressedOutlineColor()); + painter->setBrush(ColorRepository::buttonPressedBackground()); + } else if ((option->state & QStyle::State_MouseOver) || (option->state & QStyle::State_HasFocus)) { + // TODO animation on hover (not on focus) + painter->setPen(QPen(ColorRepository::hoverOutlineBrush(option->rect), 1)); + painter->setBrush(ColorRepository::buttonHoveredBackground()); + } else { + painter->setPen(ColorRepository::buttonOutlineColor()); + painter->setBrush(ColorRepository::buttonBackground()); + } +} + +void PushButtonStyleHelper::drawButtonShape(const QStyleOptionButton *option, QPainter *painter, const QWidget *widget) +{ + painter->save(); + painter->setRenderHints(QPainter::Antialiasing); + setupPainterForShape(option, painter, widget); + painter->drawRoundedRect(QRectF(option->rect).adjusted(0.5, 0.5, -0.5, -0.5), s_radius, s_radius); + painter->restore(); +} + +QSize PushButtonStyleHelper::sizeFromContents(const QStyleOptionButton *option, QSize contentsSize, const QWidget *widget) const +{ + Q_UNUSED(option) + Q_UNUSED(widget) + const int margin = 6; // usually this comes from PM_ButtonMargin + const int frameWidth = 2; // due to pen width 1 in drawButtonBevel, on each side + return QSize(qMax(60, contentsSize.width() + margin + frameWidth), contentsSize.height() + margin + frameWidth); +} + +void PushButtonStyleHelper::adjustTextPalette(QStyleOptionButton *option) const +{ + QColor textColor; + if (!(option->state & QStyle::State_Enabled)) { + textColor = option->palette.color(QPalette::ButtonText); + textColor.setAlphaF(0.5); + } else if (option->state & QStyle::State_Sunken) { + textColor = ColorRepository::pressedTextColor(); + } else if (option->state & QStyle::State_MouseOver) { + textColor = ColorRepository::hoverTextColor(); + } else { + textColor = option->palette.color(QPalette::ButtonText); + } + option->palette.setColor(QPalette::ButtonText, textColor); +} diff --git a/Styling-Qt-Widgets/pushbuttonstylehelper.h b/Styling-Qt-Widgets/pushbuttonstylehelper.h new file mode 100644 index 0000000..873dbe0 --- /dev/null +++ b/Styling-Qt-Widgets/pushbuttonstylehelper.h @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#ifndef PUSHBUTTONSTYLEHELPER_H +#define PUSHBUTTONSTYLEHELPER_H + +#include + +class QPainter; +class QPushButton; +class QStyleOptionButton; +class QWidget; + +class PushButtonStyleHelper +{ +public: + void setupPainterForShape(const QStyleOptionButton *option, QPainter *painter, const QWidget *widget); + void drawButtonShape(const QStyleOptionButton *option, QPainter *painter, const QWidget *widget); + QSize sizeFromContents(const QStyleOptionButton *option, QSize contentsSize, const QWidget *widget) const; + void adjustTextPalette(QStyleOptionButton *option) const; +}; + +#endif // PUSHBUTTONSTYLEHELPER_H diff --git a/Styling-Qt-Widgets/resource.qrc b/Styling-Qt-Widgets/resource.qrc new file mode 100644 index 0000000..17f23ab --- /dev/null +++ b/Styling-Qt-Widgets/resource.qrc @@ -0,0 +1,7 @@ + + + stylesheet.qss + check.png + check-dark.png + + diff --git a/Styling-Qt-Widgets/stylesheet.qss b/Styling-Qt-Widgets/stylesheet.qss new file mode 100644 index 0000000..6dca3d1 --- /dev/null +++ b/Styling-Qt-Widgets/stylesheet.qss @@ -0,0 +1,84 @@ +QPushButton, QCheckBox::indicator { + background-color: #a7211f22; /* [DARK] */ + background-color: #f5f4f5; /* [LIGHT] */ + + border-width: 1px; + border-color: #59515f; /* [DARK] */ + border-color: #9f95a3; /* [LIGHT] */ + border-style: solid; + border-radius: 6px; + padding: 4px +} + +QCheckBox::indicator { + border-radius: 5px; /* override the 6px from above */ + width: 18px; + height: 18px; + padding: 0px; +} + +QCheckBox::indicator:checked { + /* NOT SUPPORTED: drawing lines here, we need a PNG as input. Less flexible for re-colorization. */ + image: url(:/stylesheet/check.png) /* [LIGHT] */ + image: url(:/stylesheet/check-dark.png) /* [DARK] */ +} + +QPushButton:hover, QCheckBox::indicator:hover { + background-color: #33171717; /* [DARK] (20% alpha) */ + background-color: #33f8f7f8; /* [LIGHT] (20% alpha) */ + + /* NOT SUPPORTED: calculations in the gradient definition, we'd have to set a dynamic stylesheet whenever the widget is resized... */ + /* Also: the rendering is actually broken; bug reported as https://bugreports.qt.io/browse/QTBUG-105938 */ + border-color: qlineargradient(x1:0.50, y1:1, x2:0.58, y2:0, stop:0 #53949f, stop:1 #755579); + color: #dddddd; /* light gray */ +} + +QPushButton:pressed, QCheckBox::indicator:pressed { + background-color: #171717; /* [DARK] */ + background-color: #f8f7f8; /* [LIGHT] */ + border-color: #322d35; + color: #656565; /* medium gray */ +} + +QPushButton:!enabled, QCheckBox::indicator:!enabled { + /* I wish I could just say opacity: 0.5 instead of repeating all colors with a halved opacity */ + + background-color: #53211f22; /* [DARK] */ + background-color: #7ff5f4f5; /* [LIGHT] */ + border-color: #7f59515f; /* [DARK] */ + border-color: #7f9f95a3; /* [LIGHT] */ + color: #7fa5a5a5; /* [DARK] */ + color: #7f252525; /* [LIGHT] */ +} + + +QProgressBar { + border-color: #59515f; /* [DARK] - duplicated from QPushButton */ + border-color: #9f95a3; /* [LIGHT] - duplicated from QPushButton */ + border-width: 1px; + border-style: solid; + + background-color: #0f0f0f; /* [DARK] - almost black */ + background-color: #fbfbfb; /* [LIGHT] - almost white */ + + height: 26px; /* hardcoded, could break with large fonts */ + border-radius: 13px; /* half the height, hardcoded */ + + text-align: right; /* NOT SUPPORTED: 6px padding on the right of the text */ + /* NOT SUPPORTED: painting a gradient on the outline, up until the current progress value */ +} + +QProgressBar::chunk { + /* looks buggy somehow */ + border-color: qlineargradient(x1:0, y1:1, x2:1, y2:0, stop:0 #11c2e1, stop:1 #893a94); + border-width: 1px; + border-style: solid; + /* same as border-color but with 37% opacity */ + background-color: qlineargradient(x1:0, y1:1, x2:1, y2:0, stop:0 #6011c2e1, stop:1 #60893a94); + border-radius: 11px; + margin: 2px; +} + +QDialogButtonBox { + dialogbuttonbox-buttons-have-icons: 0; +} diff --git a/Styling-Qt-Widgets/teststyle.cpp b/Styling-Qt-Widgets/teststyle.cpp new file mode 100644 index 0000000..09c3c07 --- /dev/null +++ b/Styling-Qt-Widgets/teststyle.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +/************************************************************************* + * + * Copyright (c) 2013-2019, Klaralvdalens Datakonsult AB (KDAB) + * All rights reserved. + * + * See the LICENSE.txt file shipped along with this file for the license. + * + *************************************************************************/ + +#include "teststyle.h" +#include "checkboxstylehelper.h" +#include "progressbarstylehelper.h" +#include "pushbuttonstylehelper.h" + +#include +#include +#include + +TestStyle::TestStyle() + : super() + , mPushButtonStyleHelper(new PushButtonStyleHelper) + , mCheckBoxStyleHelper(new CheckBoxStyleHelper) + , mProgressBarStyleHelper(new ProgressBarStyleHelper) +{ +} + +void TestStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + switch (pe) { + case PE_IndicatorCheckBox: + if (const auto *optionButton = qstyleoption_cast(option)) { + mCheckBoxStyleHelper->draw(this, optionButton, painter, widget); + } + return; + case PE_FrameFocusRect: + // nothing, we don't want focus rects + break; + default: + super::drawPrimitive(pe, option, painter, widget); + break; + } +} + +int TestStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const +{ + switch (metric) { + case PM_IndicatorHeight: // checkboxes + case PM_IndicatorWidth: // checkboxes + return mCheckBoxStyleHelper->indicatorSize(); + + case PM_ButtonShiftHorizontal: + case PM_ButtonShiftVertical: + return 0; // no shift + + default: + return super::pixelMetric(metric, option, widget); + } +} + +void TestStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const +{ + switch (element) { + case CE_PushButton: + // The default implementation calls CE_PushButtonBevel, CE_PushButtonLabel + // and PE_FrameFocusRect (which we reimplement to nothing). + QCommonStyle::drawControl(element, option, painter, widget); + return; + case CE_PushButtonBevel: + // Draw button shape (background and border). This one we'll fully implement ourselves. + if (const auto *optionButton = qstyleoption_cast(option)) { + mPushButtonStyleHelper->drawButtonShape(optionButton, painter, widget); + } + return; + case CE_PushButtonLabel: + // Draw button text, icon (and menu indicator) + // We just want to call the base class, with an adjusted palette + if (const auto *optionButton = qstyleoption_cast(option)) { + QStyleOptionButton copy = *optionButton; + mPushButtonStyleHelper->adjustTextPalette(©); + QCommonStyle::drawControl(element, ©, painter, widget); + } + return; + + case CE_RadioButton: // simply calls PE_IndicatorRadioButton, CE_RadioButtonLabel (and focus rect) + case CE_RadioButtonLabel: + case CE_CheckBox: // simply calls PE_IndicatorCheckBox, CE_CheckBoxLabel (and focus rect) + case CE_CheckBoxLabel: + QCommonStyle::drawControl(element, option, painter, widget); + return; + + case CE_ProgressBar: // main entry point + // calls CE_ProgressBarGroove, CE_ProgressBarContents and CE_ProgressBarLabel + QCommonStyle::drawControl(element, option, painter, widget); + return; + case CE_ProgressBarGroove: + if (const auto *progressBarOption = qstyleoption_cast(option)) { + mProgressBarStyleHelper->drawGroove(progressBarOption, painter, widget); + } + break; + case CE_ProgressBarContents: + if (const auto *progressBarOption = qstyleoption_cast(option)) { + mProgressBarStyleHelper->drawContents(progressBarOption, painter, widget); + } + break; + case CE_ProgressBarLabel: + if (const auto *progressBarOption = qstyleoption_cast(option)) { + mProgressBarStyleHelper->drawText(progressBarOption, painter, widget); + } + return; + + default: + super::drawControl(element, option, painter, widget); + } +} + +void TestStyle::polish(QWidget *w) +{ + if (qobject_cast(w) || qobject_cast(w)) { + w->setAttribute(Qt::WA_Hover); + } + super::polish(w); +} + +bool TestStyle::eventFilter(QObject *obj, QEvent *event) +{ + return super::eventFilter(obj, event); +} + +PushButtonStyleHelper *TestStyle::pushButtonStyleHelper() const +{ + return mPushButtonStyleHelper.get(); +} + +int TestStyle::styleHint(StyleHint stylehint, const QStyleOption *option, const QWidget *widget, + QStyleHintReturn *returnData) const +{ + switch (stylehint) { + case SH_DialogButtonBox_ButtonsHaveIcons: + return 0; + default: + break; + } + + return super::styleHint(stylehint, option, widget, returnData); +} + +QSize TestStyle::sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, + const QWidget *widget) const +{ + switch (type) { + case CT_PushButton: + if (const auto *buttonOption = qstyleoption_cast(option)) { + return mPushButtonStyleHelper->sizeFromContents(buttonOption, contentsSize, widget); + } + break; + case CT_RadioButton: + case CT_CheckBox: + return QCommonStyle::sizeFromContents(type, option, contentsSize, widget); + + default: + break; + } + return super::sizeFromContents(type, option, contentsSize, widget); +} + +QRect TestStyle::subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget) const +{ + switch (subElement) { + case SE_ProgressBarGroove: + case SE_ProgressBarContents: + case SE_ProgressBarLabel: + if (const auto *progressBarOption = qstyleoption_cast(option)) { + return mProgressBarStyleHelper->subElementRect(subElement, progressBarOption, widget); + } + break; + default: + break; + } + return super::subElementRect(subElement, option, widget); +} + +void TestStyle::drawComplexControl(ComplexControl complexControl, const QStyleOptionComplex *option, QPainter *painter, + const QWidget *widget) const +{ + switch (complexControl) { + default: + super::drawComplexControl(complexControl, option, painter, widget); + } +} diff --git a/Styling-Qt-Widgets/teststyle.h b/Styling-Qt-Widgets/teststyle.h new file mode 100644 index 0000000..85900f8 --- /dev/null +++ b/Styling-Qt-Widgets/teststyle.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +/************************************************************************* + * + * Copyright (c) 2013-2019, Klaralvdalens Datakonsult AB (KDAB) + * All rights reserved. + * + * See the LICENSE.txt file shipped along with this file for the license. + * + *************************************************************************/ + +#include +#include + +class PushButtonStyleHelper; +class CheckBoxStyleHelper; +class ProgressBarStyleHelper; + +// QProxyStyle to see all widgets in an application (even if it means mixing with the default style) +// QCommonStyle to only see what we have styled, no bad interactions from the default style, but the other widgets won't be usable +using super = QCommonStyle; + +class TestStyle : public super +{ +public: + TestStyle(); + + void drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *painter, + const QWidget *widget = nullptr) const override; + + int pixelMetric(PixelMetric pm, const QStyleOption *option = nullptr, + const QWidget *widget = nullptr) const override; + + void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, + const QWidget *widget = nullptr) const override; + + void drawComplexControl(ComplexControl complexControl, const QStyleOptionComplex *opt, QPainter *painter, + const QWidget *widget = nullptr) const override; + + int styleHint(StyleHint stylehint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const override; + + QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const override; + + QRect subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget) const override; + + void polish(QWidget *w) override; + + bool eventFilter(QObject *obj, QEvent *event) override; + + + PushButtonStyleHelper* pushButtonStyleHelper() const; + +private: + std::unique_ptr mPushButtonStyleHelper; + std::unique_ptr mCheckBoxStyleHelper; + std::unique_ptr mProgressBarStyleHelper; + +}; diff --git a/Styling-Qt-Widgets/toggleswitch.cpp b/Styling-Qt-Widgets/toggleswitch.cpp new file mode 100644 index 0000000..f401ce4 --- /dev/null +++ b/Styling-Qt-Widgets/toggleswitch.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#include "toggleswitch.h" +#include "colorrepository.h" + +#include +#include + +static const int s_height = 26; +static const int s_innerMargin = 4; +static const int s_handleSize = s_height - s_innerMargin * 2; +static const int s_width = s_handleSize * 2 + s_innerMargin * 2; + +ToggleSwitch::ToggleSwitch(QWidget *parent) + : QWidget{parent} +{ + setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed}); // sizeHint is the only acceptable size + setFocusPolicy(Qt::TabFocus); // can tab into the widget + setAttribute(Qt::WA_Hover); // repaint on mouse-enter/mouse-exit +} + +void ToggleSwitch::setChecked(bool checked) +{ + if (m_checked == checked) + return; + m_checked = checked; + emit toggled(checked); + update(); +} + +bool ToggleSwitch::isChecked() const +{ + return m_checked; +} + +void ToggleSwitch::toggle() +{ + setChecked(!m_checked); +} + +QSize ToggleSwitch::sizeHint() const +{ + return QSize(s_width, s_height); +} + +void ToggleSwitch::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // Another advantage of QStyle: custom widgets can share code + // with it (here just the color repository, but we could share more code + // if needed). + // With QSS, we can't use QSS to style our custom widget. + + // Similar colors as for pushbuttons (but the shape is different) + if (!isEnabled()) { + painter.setPen(ColorRepository::buttonOutlineColor()); + painter.setOpacity(0.5); + } else if (m_mouseDown) // Sunken + painter.setPen(ColorRepository::pressedOutlineColor()); + else if (underMouse() || hasFocus()) + painter.setPen(QPen(ColorRepository::hoverOutlineBrush(rect()), 1)); + else + painter.setPen(ColorRepository::buttonOutlineColor()); + + if (m_checked) + painter.setBrush(ColorRepository::baseBackground()); + const qreal radius = height() / 2; + painter.drawRoundedRect(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), radius, radius); + + // Now draw the handle + + QRect valueRect = rect().adjusted(s_innerMargin, s_innerMargin, -s_innerMargin, -s_innerMargin); + valueRect.setWidth(valueRect.height()); // must be a square + + if (m_checked) { + valueRect.moveLeft(width() / 2); + painter.setPen(QPen(ColorRepository::progressBarOutlineBrush(valueRect), 1)); + painter.setBrush(Qt::NoBrush); + } else { + painter.setBrush(ColorRepository::baseBackground()); + } + painter.drawEllipse(valueRect); +} + +void ToggleSwitch::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + m_mouseDown = true; + } else { + event->ignore(); + } +} + +void ToggleSwitch::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && m_mouseDown) { + m_mouseDown = false; + toggle(); + emit checked(m_checked); + } else { + event->ignore(); + } +} + +void ToggleSwitch::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Space) { + toggle(); + emit checked(m_checked); + } else { + event->ignore(); // let it propagate to the parent (e.g. so that Return closes dialogs) + } +} diff --git a/Styling-Qt-Widgets/toggleswitch.h b/Styling-Qt-Widgets/toggleswitch.h new file mode 100644 index 0000000..5eed21b --- /dev/null +++ b/Styling-Qt-Widgets/toggleswitch.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** This file is part of the Oxygen2 project. +** +** SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +** +** SPDX-License-Identifier: MIT +** +****************************************************************************/ + +#ifndef TOGGLESWITCH_H +#define TOGGLESWITCH_H + +#include + +class ToggleSwitch : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled) +public: + explicit ToggleSwitch(QWidget *parent = nullptr); + + void setChecked(bool checked); + bool isChecked() const; + + void toggle(); + + QSize sizeHint() const override; + +signals: + void checked(bool checked); // by user + void toggled(bool checked); // by user or by program + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + bool m_checked = false; + bool m_mouseDown = false; +}; + +#endif // TOGGLESWITCH_H