framelesshelper/src/core/windowborderpainter.cpp

295 lines
8.4 KiB
C++

/*
* MIT License
*
* Copyright (C) 2021-2023 by wangwenx190 (Yuhang Zhao)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "windowborderpainter.h"
#include "windowborderpainter_p.h"
#include "utils.h"
#include "framelessmanager.h"
#ifdef Q_OS_WINDOWS
# include "winverhelper_p.h"
#endif
#include <QtGui/qpainter.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcWindowBorderPainter, "wangwenx190.framelesshelper.core.windowborderpainter")
#ifdef FRAMELESSHELPER_CORE_NO_DEBUG_OUTPUT
# define INFO QT_NO_QDEBUG_MACRO()
# define DEBUG QT_NO_QDEBUG_MACRO()
# define WARNING QT_NO_QDEBUG_MACRO()
# define CRITICAL QT_NO_QDEBUG_MACRO()
#else
# define INFO qCInfo(lcWindowBorderPainter)
# define DEBUG qCDebug(lcWindowBorderPainter)
# define WARNING qCWarning(lcWindowBorderPainter)
# define CRITICAL qCCritical(lcWindowBorderPainter)
#endif
using namespace Global;
static constexpr const int kMaximumBorderThickness = 100;
WindowBorderPainterPrivate::WindowBorderPainterPrivate(WindowBorderPainter *q) : QObject(q)
{
Q_ASSERT(q);
if (!q) {
return;
}
q_ptr = q;
initialize();
}
WindowBorderPainterPrivate::~WindowBorderPainterPrivate() = default;
WindowBorderPainterPrivate *WindowBorderPainterPrivate::get(WindowBorderPainter *q)
{
Q_ASSERT(q);
if (!q) {
return nullptr;
}
return q->d_func();
}
const WindowBorderPainterPrivate *WindowBorderPainterPrivate::get(const WindowBorderPainter *q)
{
Q_ASSERT(q);
if (!q) {
return nullptr;
}
return q->d_func();
}
int WindowBorderPainterPrivate::getNativeBorderThickness()
{
// Qt will scale it to the appropriate value for us automatically,
// based on the current system DPI and scale factor rounding policy.
return kDefaultWindowFrameBorderThickness;
}
QColor WindowBorderPainterPrivate::getNativeBorderColor(const bool active)
{
return Utils::getFrameBorderColor(active);
}
WindowEdges WindowBorderPainterPrivate::getNativeBorderEdges()
{
#ifdef Q_OS_WINDOWS
if (Utils::isWindowFrameBorderVisible() && !WindowsVersionHelper::isWin11OrGreater()) {
return {WindowEdge::Top};
}
#endif
return {};
}
void WindowBorderPainterPrivate::paint(QPainter *painter, const QSize &size, const bool active) const
{
Q_ASSERT(painter);
Q_ASSERT(!size.isEmpty());
if (!painter || size.isEmpty()) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QList<QLine> lines = {};
#else
QVector<QLine> lines = {};
#endif
const QPoint leftTop = {0, 0};
// In fact, we should use "size.width() - 1" here in theory but we can't
// because Qt's drawing system has some rounding errors internally and if
// we minus one here we'll get a one pixel gap, so sad. But drawing a line
// with a little extra pixels won't hurt anyway.
const QPoint rightTop = {size.width(), 0};
// Same here as above: we should use "size.height() - 1" ideally but we
// can't, sadly.
const QPoint rightBottom = {size.width(), size.height()};
const QPoint leftBottom = {0, size.height()};
const WindowEdges edges = m_edges.value_or(getNativeBorderEdges());
if (edges & WindowEdge::Left) {
lines.append({leftBottom, leftTop});
}
if (edges & WindowEdge::Top) {
lines.append({leftTop, rightTop});
}
if (edges & WindowEdge::Right) {
lines.append({rightTop, rightBottom});
}
if (edges & WindowEdge::Bottom) {
lines.append({rightBottom, leftBottom});
}
if (lines.isEmpty()) {
return;
}
painter->save();
painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
// We can't enable antialiasing here, because the border is too thin and antialiasing
// will break it's painting.
painter->setRenderHint(QPainter::Antialiasing, false);
QPen pen = {};
pen.setColor([active, this]() -> QColor {
QColor color = {};
if (active) {
color = m_activeColor.value_or(getNativeBorderColor(true));
} else {
color = m_inactiveColor.value_or(getNativeBorderColor(false));
}
if (color.isValid()) {
return color;
}
return (active ? kDefaultBlackColor : kDefaultDarkGrayColor);
}());
pen.setWidth(m_thickness.value_or(getNativeBorderThickness()));
painter->setPen(pen);
painter->drawLines(lines);
painter->restore();
}
void WindowBorderPainterPrivate::initialize()
{
Q_Q(WindowBorderPainter);
connect(FramelessManager::instance(), &FramelessManager::systemThemeChanged,
q, &WindowBorderPainter::nativeBorderChanged);
connect(q, &WindowBorderPainter::nativeBorderChanged, q, &WindowBorderPainter::shouldRepaint);
}
WindowBorderPainter::WindowBorderPainter(QObject *parent)
: QObject(parent), d_ptr(new WindowBorderPainterPrivate(this))
{
}
WindowBorderPainter::~WindowBorderPainter() = default;
int WindowBorderPainter::thickness() const
{
Q_D(const WindowBorderPainter);
return d->m_thickness.value_or(d->getNativeBorderThickness());
}
WindowEdges WindowBorderPainter::edges() const
{
Q_D(const WindowBorderPainter);
return d->m_edges.value_or(d->getNativeBorderEdges());
}
QColor WindowBorderPainter::activeColor() const
{
Q_D(const WindowBorderPainter);
return d->m_activeColor.value_or(d->getNativeBorderColor(true));
}
QColor WindowBorderPainter::inactiveColor() const
{
Q_D(const WindowBorderPainter);
return d->m_inactiveColor.value_or(d->getNativeBorderColor(false));
}
int WindowBorderPainter::nativeThickness() const
{
Q_D(const WindowBorderPainter);
return d->getNativeBorderThickness();
}
WindowEdges WindowBorderPainter::nativeEdges() const
{
Q_D(const WindowBorderPainter);
return d->getNativeBorderEdges();
}
QColor WindowBorderPainter::nativeActiveColor() const
{
Q_D(const WindowBorderPainter);
return d->getNativeBorderColor(true);
}
QColor WindowBorderPainter::nativeInactiveColor() const
{
Q_D(const WindowBorderPainter);
return d->getNativeBorderColor(false);
}
void WindowBorderPainter::paint(QPainter *painter, const QSize &size, const bool active) const
{
Q_D(const WindowBorderPainter);
d->paint(painter, size, active);
}
void WindowBorderPainter::setThickness(const int value)
{
Q_ASSERT(value >= 0);
Q_ASSERT(value < kMaximumBorderThickness);
if ((value < 0) || (value >= kMaximumBorderThickness)) {
return;
}
if (thickness() == value) {
return;
}
Q_D(WindowBorderPainter);
d->m_thickness = value;
Q_EMIT thicknessChanged();
Q_EMIT shouldRepaint();
}
void WindowBorderPainter::setEdges(const Global::WindowEdges value)
{
if (edges() == value) {
return;
}
Q_D(WindowBorderPainter);
d->m_edges = value;
Q_EMIT edgesChanged();
Q_EMIT shouldRepaint();
}
void WindowBorderPainter::setActiveColor(const QColor &value)
{
Q_ASSERT(value.isValid());
if (!value.isValid()) {
return;
}
if (activeColor() == value) {
return;
}
Q_D(WindowBorderPainter);
d->m_activeColor = value;
Q_EMIT activeColorChanged();
Q_EMIT shouldRepaint();
}
void WindowBorderPainter::setInactiveColor(const QColor &value)
{
Q_ASSERT(value.isValid());
if (!value.isValid()) {
return;
}
if (inactiveColor() == value) {
return;
}
Q_D(WindowBorderPainter);
d->m_inactiveColor = value;
Q_EMIT inactiveColorChanged();
Q_EMIT shouldRepaint();
}
FRAMELESSHELPER_END_NAMESPACE