/* * 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 #include FRAMELESSHELPER_BEGIN_NAMESPACE [[maybe_unused]] static 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 lines = {}; #else QVector lines = {}; #endif static constexpr const auto gap = qreal(0.5); const QPointF leftTop = {gap, gap}; const QPointF rightTop = {qreal(size.width()) - gap, gap}; const QPointF rightBottom = {qreal(size.width()) - gap, qreal(size.height()) - gap}; const QPointF leftBottom = {gap, qreal(size.height()) - gap}; 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::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); 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