Skip to content

Commit

Permalink
Make Fusion checkbox outlines more visible
Browse files Browse the repository at this point in the history
When using a dark palette. The Fusion style has a really stupid value
hard-coded that makes this outline just meld entirely into most of the
background colors used in dark themes, so now we lighten that up harder
on dark themes.

This requires some really stupid hackery because the Fusion style is so
non-extensible, but it's necessary to make checkboxes in menus and tree
views visible at all.
  • Loading branch information
askmeaboutlo0m committed Aug 19, 2023
1 parent 3c86668 commit f730ee9
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 4 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Unreleased Version 2.2.0-pre
* Fix: Show the current color properly on program startup.
* Feature: Make the color wheel innards go from least to most saturated, putting it in line with how most other software presents it. This can be toggled in the preferences.
* Feature: Holding Shift while moving a selection now keeps it along the closest axis. Thanks Kvothen for suggesting.
* Fix: Make checkbox outlines in the Fusion style not totally invisible in dark themes.

2023-07-31 Version 2.2.0-beta.6
* Fix: Don't forget account password when entering a wrong session password.
Expand Down
9 changes: 9 additions & 0 deletions src/desktop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ target_sources(drawpile PRIVATE
ui/textsettings.ui
ui/userinfodialog.ui
utils/actionbuilder.h
utils/fusionui.cpp
utils/fusionui.h
utils/globalkeyeventfilter.cpp
utils/globalkeyeventfilter.h
utils/listserverdelegate.cpp
Expand Down Expand Up @@ -387,6 +389,13 @@ if(WIN32 AND QT_VERSION_MAJOR VERSION_EQUAL 6)
target_include_directories(drawpile PRIVATE "${Qt6Gui_PRIVATE_INCLUDE_DIRS}")
endif()

# To make checkbox outlines not look completely invisible on dark themes, we
# have to rope in a bunch of nonsense from the implementation of the Fusion
# style because it's just got that color hard-coded. That implementation
# requires some stuff out of the private headers, so we need to pull those in.
target_include_directories(drawpile PRIVATE
"${Qt${QT_VERSION_MAJOR}Widgets_PRIVATE_INCLUDE_DIRS}")

# Some special handling for Android:
# * It can't deal with multiple main windows properly, trying to open a second
# one hides the first with no way to switch between them.
Expand Down
16 changes: 12 additions & 4 deletions src/desktop/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "libclient/drawdance/global.h"
#include "libshared/util/qtcompat.h"
#include "cmake-config/config.h"
#include "desktop/utils/fusionui.h"
#include "desktop/utils/qtguicompat.h"

#ifdef Q_OS_MACOS
Expand Down Expand Up @@ -132,12 +133,19 @@ void DrawpileApp::setThemeStyle(const QString &themeStyle)
if (themeStyle.isEmpty() || themeStyle.startsWith(QStringLiteral("mac"))) {
foundStyle = true;
setStyle(new macui::MacProxyStyle);
} else {
foundStyle = setStyle(themeStyle);
}
#else
foundStyle = setStyle(themeStyle.isEmpty() ? m_originalSystemStyle : themeStyle);
#endif
if(!foundStyle && themeStyle.contains(QStringLiteral("fusion"), Qt::CaseInsensitive)) {
QStyle *fusionStyle = QStyleFactory::create(themeStyle);
if(fusionStyle) {
foundStyle = true;
setStyle(new fusionui::FusionProxyStyle{fusionStyle});
}
}

if(!foundStyle) {
foundStyle = setStyle(themeStyle.isEmpty() ? m_originalSystemStyle : themeStyle);
}

if (!foundStyle) {
qWarning() << "Could not find style" << themeStyle;
Expand Down
195 changes: 195 additions & 0 deletions src/desktop/utils/fusionui.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "desktop/utils/fusionui.h"
#include <QPainter>
#include <QPainterPath>
#include <QStyleOption>
#include <QtWidgets/private/qstylehelper_p.h>

#ifdef Q_OS_MACOS
QT_WARNING_PUSH
QT_WARNING_DISABLE_CLANG("-Wc++98-compat-extra-semi")
QT_WARNING_DISABLE_CLANG("-Wsuggest-destructor-override")
QT_WARNING_DISABLE_CLANG("-Wunused-template")
QT_WARNING_DISABLE_CLANG("-Wzero-as-null-pointer-constant")
# include <QtGui/private/qguiapplication_p.h>
# include <qpa/qplatformtheme.h>
QT_WARNING_POP
#endif

namespace fusionui {

FusionProxyStyle::FusionProxyStyle(QStyle *style)
: QProxyStyle{style}
{
}

void FusionProxyStyle::drawPrimitive(
PrimitiveElement element, const QStyleOption *option, QPainter *painter,
const QWidget *widget) const
{
if(element == PrimitiveElement::PE_IndicatorCheckBox) {
// SPDX-SnippetBegin
// SPDX-License-Identifier: GPL-3.0-or-later
// SDPX—SnippetName: checkbox painting from qfusionstyle.cpp
QRect rect = option->rect;
int state = option->state;
painter->save();
if(const QStyleOptionButton *checkbox =
qstyleoption_cast<const QStyleOptionButton *>(option)) {
painter->setRenderHint(QPainter::Antialiasing, true);
painter->translate(0.5, 0.5);
rect = rect.adjusted(0, 0, -1, -1);

QColor pressedColor = mergedColors(
option->palette.base().color(),
option->palette.windowText().color(), 85);
painter->setBrush(Qt::NoBrush);

// Gradient fill
QLinearGradient gradient(rect.topLeft(), rect.bottomLeft());
gradient.setColorAt(
0, (state & State_Sunken)
? pressedColor
: option->palette.base().color().darker(115));
gradient.setColorAt(
0.15, (state & State_Sunken) ? pressedColor
: option->palette.base().color());
gradient.setColorAt(
1, (state & State_Sunken) ? pressedColor
: option->palette.base().color());

painter->setBrush(
(state & State_Sunken) ? QBrush(pressedColor) : gradient);

// Drawpile patch. This is the only (!) thing we want to change, but
// we have to pull in all this other garbage to actually do it.
// Lightening the outline by only 110 ends up being completely
// invisible in dark themes, so we increase that some more.
bool dark =
option->palette.color(QPalette::Window).lightness() < 128;
painter->setPen(
QPen(outline(option->palette).lighter(dark ? 170 : 110)));
// End of Drawpile patch.

if(option->state & State_HasFocus &&
option->state & State_KeyboardFocusChange)
painter->setPen(QPen(highlightedOutline(option->palette)));
painter->drawRect(rect);

QColor checkMarkColor = option->palette.text().color().darker(120);
const qreal checkMarkPadding =
1 + rect.width() * 0.13; // at least one pixel padding

if(checkbox->state & State_NoChange) {
gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft());
checkMarkColor.setAlpha(80);
gradient.setColorAt(0, checkMarkColor);
checkMarkColor.setAlpha(140);
gradient.setColorAt(1, checkMarkColor);
checkMarkColor.setAlpha(180);
painter->setPen(QPen(checkMarkColor, 1));
painter->setBrush(gradient);
painter->drawRect(rect.adjusted(
checkMarkPadding, checkMarkPadding, -checkMarkPadding,
-checkMarkPadding));

} else if(checkbox->state & State_On) {
const qreal dpi = QStyleHelper::dpi(option);
qreal penWidth = QStyleHelper::dpiScaled(1.5, dpi);
penWidth = qMax<qreal>(penWidth, 0.13 * rect.height());
penWidth = qMin<qreal>(penWidth, 0.20 * rect.height());
QPen checkPen = QPen(checkMarkColor, penWidth);
checkMarkColor.setAlpha(210);
painter->translate(
QStyleHelper::dpiScaled(-0.8, dpi),
QStyleHelper::dpiScaled(0.5, dpi));
painter->setPen(checkPen);
painter->setBrush(Qt::NoBrush);

// Draw checkmark
QPainterPath path;
const qreal rectHeight =
rect.height(); // assuming height equals width
path.moveTo(
checkMarkPadding + rectHeight * 0.11, rectHeight * 0.47);
path.lineTo(rectHeight * 0.5, rectHeight - checkMarkPadding);
path.lineTo(rectHeight - checkMarkPadding, checkMarkPadding);
painter->drawPath(path.translated(rect.topLeft()));
}
}
painter->restore();
// SPDX-SnippetEnd
} else {
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}

// SPDX-SnippetBegin
// SPDX-License-Identifier: GPL-3.0-or-later
// SDPX—SnippetName: ancillary qfusionstyle.cpp not available externally

QColor FusionProxyStyle::mergedColors(
const QColor &colorA, const QColor &colorB, int factor)
{
const int maxFactor = 100;
QColor tmp = colorA;
tmp.setRed(
(tmp.red() * factor) / maxFactor +
(colorB.red() * (maxFactor - factor)) / maxFactor);
tmp.setGreen(
(tmp.green() * factor) / maxFactor +
(colorB.green() * (maxFactor - factor)) / maxFactor);
tmp.setBlue(
(tmp.blue() * factor) / maxFactor +
(colorB.blue() * (maxFactor - factor)) / maxFactor);
return tmp;
}

// SPDX-SnippetEnd

// SPDX-SnippetBegin
// SPDX-License-Identifier: GPL-3.0-or-later
// SDPX—SnippetName: ancillary qfusionstyle_p_p.h stuff not available outside

bool FusionProxyStyle::isMacSystemPalette(const QPalette &pal)
{
Q_UNUSED(pal);
#if defined(Q_OS_MACOS)
const QPalette *themePalette =
QGuiApplicationPrivate::platformTheme()->palette();
if(themePalette &&
themePalette->color(QPalette::Normal, QPalette::Highlight) ==
pal.color(QPalette::Normal, QPalette::Highlight) &&
themePalette->color(QPalette::Normal, QPalette::HighlightedText) ==
pal.color(QPalette::Normal, QPalette::HighlightedText))
return true;
#endif
return false;
}

QColor FusionProxyStyle::highlight(const QPalette &pal)
{
if(isMacSystemPalette(pal))
return QColor(60, 140, 230);
return pal.color(QPalette::Highlight);
}

QColor FusionProxyStyle::outline(const QPalette &pal)
{
if(pal.window().style() == Qt::TexturePattern)
return QColor(0, 0, 0, 160);
return pal.window().color().darker(140);
}

QColor FusionProxyStyle::highlightedOutline(const QPalette &pal)
{
QColor highlightedOutline = highlight(pal).darker(125);
if(highlightedOutline.value() > 160)
highlightedOutline.setHsl(
highlightedOutline.hue(), highlightedOutline.saturation(), 160);
return highlightedOutline;
}

// SPDX-SnippetEnd

}
29 changes: 29 additions & 0 deletions src/desktop/utils/fusionui.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef DESKTOP_UTILS_FUSIONUI_H
#define DESKTOP_UTILS_FUSIONUI_H
#include <QColor>
#include <QProxyStyle>

namespace fusionui {

class FusionProxyStyle : public QProxyStyle {
public:
explicit FusionProxyStyle(QStyle *style);

void drawPrimitive(
PrimitiveElement element, const QStyleOption *option, QPainter *painter,
const QWidget *widget = nullptr) const override;

private:
static QColor
mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50);

static bool isMacSystemPalette(const QPalette &pal);
static QColor highlight(const QPalette &pal);
static QColor outline(const QPalette &pal);
static QColor highlightedOutline(const QPalette &pal);
};

}

#endif

0 comments on commit f730ee9

Please sign in to comment.