/*************************************************************************
* Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr> *
* Copyright (C) 2014-2018 Martin Bříza <m@rtinbriza.cz> *
* Copyright (C) 2019-2020 Jan Grulich <jgrulich@redhat.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
*************************************************************************/
#include "adwaita.h"
#include "adwaitacolors.h"
#include "adwaitahelper.h"
#include <QApplication>
#include <QPainter>
#include <QLibrary>
#if ADWAITA_HAVE_X11
#include <X11/Xlib-xcb.h>
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace Adwaita
{
//* contrast for arrow and treeline rendering
static const qreal arrowShade = 0.15;
//____________________________________________________________________
Helper::Helper()
{
init();
}
// static
bool Helper::isWindowActive(const QWidget *widget)
{
const QWindow *win = widget ? widget->window()->windowHandle() : nullptr;
if (win) {
return win->isActive();
}
return false;
}
//______________________________________________________________________________
void Helper::renderDebugFrame(QPainter *painter, const QRect &rect) const
{
painter->save();
painter->setRenderHints(QPainter::Antialiasing);
painter->setBrush(Qt::NoBrush);
painter->setPen(Qt::red);
painter->drawRect(QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5));
painter->restore();
}
//______________________________________________________________________________
void Helper::renderFocusRect(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, Sides sides) const
{
if (!color.isValid())
return;
painter->save();
painter->setRenderHints(QPainter::Antialiasing);
painter->setBrush(color);
if (!(outline.isValid() && sides)) {
painter->setPen(Qt::NoPen);
painter->drawRect(rect);
} else {
painter->setClipRect(rect);
QRectF copy(rect);
copy.adjust(0.5, 0.5, -0.5, -0.5);
qreal radius(frameRadius(-1.0));
if (!(sides & SideTop))
copy.adjust(0, -radius, 0, 0);
if (!(sides & SideBottom))
copy.adjust(0, 0, 0, radius);
if (!(sides & SideLeft))
copy.adjust(-radius, 0, 0, 0);
if (!(sides & SideRight))
copy.adjust(0, 0, radius, 0);
painter->setPen(outline);
// painter->setBrush( Qt::NoBrush );
painter->drawRoundedRect(copy, radius, radius);
}
painter->restore();
return;
}
//______________________________________________________________________________
void Helper::renderFocusLine(QPainter *painter, const QRect &rect, const QColor &color) const
{
if (!color.isValid())
return;
painter->save();
QPen pen(color, 1);
pen.setStyle(Qt::DotLine);
painter->setRenderHint(QPainter::Antialiasing, false);
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawRoundedRect(rect, 1, 1);
painter->restore();
}
//______________________________________________________________________________
void Helper::renderFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool hasFocus) const
{
painter->setRenderHint(QPainter::Antialiasing);
QRectF frameRect(rect.adjusted(1, 1, -1, -1));
qreal radius(frameRadius());
// set pen
if (outline.isValid()) {
if (hasFocus) {
painter->setPen(QPen(outline, 2));
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
} else {
painter->setPen(outline);
}
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
radius = qMax(radius - 1, qreal(0.0));
} else {
painter->setPen(Qt::NoPen);
}
// set brush
if (color.isValid())
painter->setBrush(color);
else
painter->setBrush(Qt::NoBrush);
// render
painter->drawRoundedRect(frameRect, radius, radius);
}
//______________________________________________________________________________
void Helper::renderSquareFrame(QPainter *painter, const QRect &rect, QColor color, bool hasFocus) const
{
painter->setPen(color);
painter->drawRect(rect.adjusted(1, 1, -2, -2));
if (hasFocus) {
color.setAlphaF(0.5);
painter->setPen(color);
painter->drawRect(rect.adjusted(0, 0, -1, -1));
}
}
//______________________________________________________________________________
void Helper::renderFlatFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool hasFocus) const
{
painter->setRenderHint(QPainter::Antialiasing);
QRectF frameRect(rect.adjusted(1, 1, -1, -1));
qreal radius(frameRadius());
// set pen
if (outline.isValid()) {
if (hasFocus) {
painter->setPen(QPen(outline, 2));
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
} else {
painter->setPen(outline);
}
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
radius = qMax(radius - 1, qreal(0.0));
} else {
painter->setPen(Qt::NoPen);
}
// set brush
if (color.isValid())
painter->setBrush(color);
else
painter->setBrush(Qt::NoBrush);
QPainterPath path;
path.setFillRule(Qt::WindingFill);
path.addRect(frameRect.adjusted(2 * radius, 0, 0, 0));
path.addRoundedRect(frameRect.adjusted(0, 0, - 2 * radius, 0), radius, radius);
painter->drawPath(path.simplified());
// render
//painter->drawRoundedRect( frameRect, radius, radius );
}
//______________________________________________________________________________
void Helper::renderSidePanelFrame(QPainter *painter, const QRect &rect, const QColor &outline, Side side) const
{
// check color
if (!outline.isValid())
return;
// adjust rect
QRectF frameRect(rect.adjusted(1, 1, -1, -1));
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
// setup painter
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(outline);
// render
switch (side) {
case SideLeft:
frameRect.adjust(0, 1, 0, -1);
painter->drawLine(frameRect.topRight(), frameRect.bottomRight());
break;
case SideTop:
frameRect.adjust(1, 0, -1, 0);
painter->drawLine(frameRect.topLeft(), frameRect.topRight());
break;
case SideRight:
frameRect.adjust(0, 1, 0, -1);
painter->drawLine(frameRect.topLeft(), frameRect.bottomLeft());
break;
case SideBottom:
frameRect.adjust(1, 0, -1, 0);
painter->drawLine(frameRect.bottomLeft(), frameRect.bottomRight());
break;
case AllSides: {
qreal radius(frameRadius(-1.0));
painter->drawRoundedRect(frameRect, radius, radius);
break;
}
default:
break;
}
}
//______________________________________________________________________________
void Helper::renderMenuFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool roundCorners) const
{
// set brush
if (color.isValid())
painter->setBrush(color);
else
painter->setBrush(Qt::NoBrush);
painter->setRenderHint(QPainter::Antialiasing, false);
QRectF frameRect(rect);
if (outline.isValid()) {
painter->setPen(outline);
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
} else
painter->setPen(Qt::NoPen);
painter->drawRect(frameRect);
}
//______________________________________________________________________________
void Helper::renderButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow,
bool hasFocus, bool sunken, bool mouseOver, bool active, bool darkMode) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect
QRectF frameRect(rect);
frameRect.adjust(1, 1, -1, -1);
qreal radius(frameRadius());
if (outline.isValid()) {
painter->setPen(QPen(outline, 1.0));
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
radius = qMax(radius - 1, qreal(0.0));
} else
painter->setPen(Qt::NoPen);
// content
if (color.isValid() && active) {
QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft());
if (sunken) {
// Pressed button in normal and dark mode is not a gradient, just an image consting from same $color
gradient.setColorAt(0, color);
gradient.setColorAt(1, color);
} else if (mouseOver) {
if (darkMode) {
QColor baseColor = Colors::lighten(color, 0.01);
// Hovered button in dark mode is a gradient from $color to Colors::lighten(bg_color, 0.01)
gradient.setColorAt(0, Colors::lighten(baseColor, 0.01)); // FIXME not correct according to adwaita's _drawing.scss file, but looks more close than before
gradient.setColorAt(1, Colors::lighten(baseColor, 0.01));
} else {
QColor baseColor = color;
// Hovered button in normal mode is a gradient from $color to Colors::lighten(bg_color, 0.01)
gradient.setColorAt(0, color);
gradient.setColorAt(1, Colors::lighten(baseColor, 0.01));
}
} else {
if (darkMode) {
QColor baseColor = Colors::lighten(color, 0.01);
// Normal button in dark mode is a gradient from $color to bg_color
gradient.setColorAt(0, color);
gradient.setColorAt(1, baseColor);
} else {
QColor baseColor = Colors::lighten(color, 0.04);
// Normal button in normal mode is a gradient from $color to bg_color
gradient.setColorAt(0, color);
gradient.setColorAt(1, baseColor);
}
}
painter->setBrush(gradient);
} else if (!active) {
painter->setBrush(color);
} else {
painter->setBrush(Qt::NoBrush);
}
// render
painter->drawRoundedRect(frameRect, radius, radius);
if (!sunken && active && color.isValid()) {
painter->setPen(color.lighter(140));
painter->drawLine(frameRect.topLeft() + QPoint(3, 1), frameRect.topRight() + QPoint(-3, 1));
painter->setPen(outline.darker(114));
painter->drawLine(frameRect.bottomLeft() + QPointF(2.7, 0), frameRect.bottomRight() + QPointF(-2.7, 0));
}
}
//______________________________________________________________________________
void Helper::renderCheckBoxFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow,
bool hasFocus, bool sunken, bool mouseOver, bool active, CheckBoxState state, bool darkMode, bool inMenu) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect
QRectF frameRect(rect);
frameRect.adjust(1, 1, -1, -1);
qreal radius(frameRadius());
if (outline.isValid()) {
painter->setPen(QPen(outline, 1.0));
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
radius = qMax(radius - 1, qreal(0.0));
} else
painter->setPen(Qt::NoPen);
if (inMenu || state == CheckOff) {
// content
if (color.isValid() && active) {
QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft());
if (sunken) {
// Pressed-alt button in dark mode is not a gradient, just an image consting from same $color
if (darkMode) {
gradient.setColorAt(0, color);
gradient.setColorAt(1, color);
} else {
// Pressed-alt button in normal mode is not a gradient, just an image consting from same $color
gradient.setColorAt(0, color);
gradient.setColorAt(1, color);
}
} else if (mouseOver) {
if (darkMode) {
QColor baseColor = color;
// Hovered-alt button in dark mode is a gradient from $color to Colors::darken(bg_color, 0.04)
gradient.setColorAt(0, Colors::darken(baseColor, 0.04));
gradient.setColorAt(1, color);
} else {
QColor baseColor = Colors::darken(color, 0.09);
// Hovered-alt button in normal mode is a gradient from $color to Colors::lighten(bg_color, 0.04)
gradient.setColorAt(0, color); // FIXME:
gradient.setColorAt(1, Colors::lighten(baseColor, 0.04)); // should be vice-versa, but this way it seems to be more accurate
}
} else {
if (darkMode) {
QColor baseColor = Colors::lighten(color, 0.03);
// Normal-alt button in dark mode is a gradient from $color to Colors::darken(bg_color, 0.06)
gradient.setColorAt(0, Colors::darken(baseColor, 0.06));
gradient.setColorAt(1, color);
} else {
QColor baseColor = Colors::darken(color, 0.05);
// Normal-alt button in normal mode is a gradient from $color to bg_color
gradient.setColorAt(0, baseColor);
gradient.setColorAt(1, color);
}
}
painter->setBrush(gradient);
} else if (!active) {
painter->setBrush(color);
} else {
painter->setBrush(Qt::NoBrush);
}
} else {
if (color.isValid()) {
QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft());
gradient.setColorAt(0, color);
gradient.setColorAt(1, Colors::lighten(color, 0.04));
painter->setBrush(gradient);
} else {
painter->setBrush(Qt::NoBrush);
}
}
// render
painter->drawRoundedRect(frameRect, radius, radius);
}
//______________________________________________________________________________
void Helper::renderFlatButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow,
bool hasFocus, bool sunken, bool mouseOver, bool active) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect
QRectF frameRect(rect);
frameRect.adjust(1, 1, -1, -1);
qreal radius(frameRadius());
if (outline.isValid()) {
painter->setPen(QPen(outline, 1.0));
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
radius = qMax(radius - 1, qreal(0.0));
} else
painter->setPen(Qt::NoPen);
// content
if (color.isValid()) {
QLinearGradient gradient(frameRect.topLeft(), frameRect.bottomLeft());
//gradient.setColorAt( 0, color.darker( sunken ? 110 : (hasFocus|mouseOver) ? 85 : 100 ) );
//gradient.setColorAt( 1, color.darker( sunken ? 130 : (hasFocus|mouseOver) ? 95 : 110 ) );
if (!active) {
gradient.setColorAt(0, color);
} else if (sunken) {
gradient.setColorAt(0, color);
} else {
gradient.setColorAt(0, Colors::mix(color, Qt::white, 0.07));
gradient.setColorAt(1, Colors::mix(color, Qt::black, 0.1));
}
painter->setBrush(gradient);
} else
painter->setBrush(Qt::NoBrush);
QPainterPath path;
path.setFillRule(Qt::WindingFill);
path.addRoundedRect(frameRect.adjusted(2 * radius, 0, 0, 0), radius, radius);
path.addRect(frameRect.adjusted(0, 0, -2 * radius, 0));
painter->drawPath(path.simplified());
if (!sunken && active) {
painter->setPen(color.lighter(140));
painter->drawLine(frameRect.topLeft() + QPoint(1, 1), frameRect.topRight() + QPoint(-3, 1));
painter->setPen(outline.darker(114));
painter->drawLine(frameRect.bottomLeft() + QPointF(0.7, 0), frameRect.bottomRight() + QPointF(-2.7, 0));
}
// render
//painter->drawRoundedRect( frameRect, radius, radius );
}
//______________________________________________________________________________
void Helper::renderToolButtonFrame(QPainter *painter, const QRect &rect, const QColor &color, bool sunken) const
{
// do nothing for invalid color
if (!color.isValid())
return;
// setup painter
painter->setRenderHints(QPainter::Antialiasing);
QRectF baseRect(rect);
if (sunken) {
qreal radius(frameRadius());
painter->setPen(Qt::NoPen);
painter->setBrush(color);
QRectF contentRect(baseRect.adjusted(1, 1, -1, -1));
painter->drawRoundedRect(contentRect, radius, radius);
} else {
qreal radius(frameRadius(-0.5));
painter->setPen(color);
painter->setBrush(Qt::NoBrush);
QRectF outlineRect(baseRect.adjusted(1.5, 1.5, -1.5, -1.5));
painter->drawRoundedRect(outlineRect, radius, radius);
}
}
//______________________________________________________________________________
void Helper::renderToolBoxFrame(QPainter *painter, const QRect &rect, int tabWidth, const QColor &outline) const
{
if (!outline.isValid())
return;
// round radius
qreal radius(frameRadius());
QSizeF cornerSize(2 * radius, 2 * radius);
// if rect - tabwidth is even, need to increase tabWidth by 1 unit
// for anti aliasing
if (!((rect.width() - tabWidth) % 2))
++tabWidth;
// adjust rect for antialiasing
QRectF baseRect(rect);
baseRect.adjust(0.5, 0.5, -0.5, -0.5);
// create path
QPainterPath path;
path.moveTo(0, baseRect.height() - 1);
path.lineTo((baseRect.width() - tabWidth) / 2 - radius, baseRect.height() - 1);
path.arcTo(QRectF(QPointF((baseRect.width() - tabWidth) / 2 - 2 * radius, baseRect.height() - 1 - 2 * radius), cornerSize), 270, 90);
path.lineTo((baseRect.width() - tabWidth) / 2, radius);
path.arcTo(QRectF(QPointF((baseRect.width() - tabWidth) / 2, 0), cornerSize), 180, -90);
path.lineTo((baseRect.width() + tabWidth) / 2 - 1 - radius, 0);
path.arcTo(QRectF(QPointF((baseRect.width() + tabWidth) / 2 - 1 - 2 * radius, 0), cornerSize), 90, -90);
path.lineTo((baseRect.width() + tabWidth) / 2 - 1, baseRect.height() - 1 - radius);
path.arcTo(QRectF(QPointF((baseRect.width() + tabWidth) / 2 - 1, baseRect.height() - 1 - 2 * radius), cornerSize), 180, 90);
path.lineTo(baseRect.width() - 1, baseRect.height() - 1);
// render
painter->save();
painter->setRenderHints(QPainter::Antialiasing);
painter->setBrush(Qt::NoBrush);
painter->setPen(outline);
painter->translate(baseRect.topLeft());
painter->drawPath(path);
painter->restore();
return;
}
//______________________________________________________________________________
void Helper::renderTabWidgetFrame(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, Corners corners) const
{
painter->setRenderHint(QPainter::Antialiasing);
QRectF frameRect(rect.adjusted(1, 1, -1, -1));
qreal radius(frameRadius());
// set pen
if (outline.isValid()) {
painter->setPen(outline);
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
radius = qMax(radius - 1, qreal(0.0));
} else
painter->setPen(Qt::NoPen);
// set brush
if (color.isValid())
painter->setBrush(color);
else
painter->setBrush(Qt::NoBrush);
// render
QPainterPath path(roundedPath(frameRect, corners, radius));
painter->drawPath(path);
}
//______________________________________________________________________________
void Helper::renderSelection(QPainter *painter, const QRect &rect, const QColor &color) const
{
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(Qt::NoPen);
painter->setBrush(color);
painter->drawRect(rect);
}
//______________________________________________________________________________
void Helper::renderSeparator(QPainter *painter, const QRect &rect, const QColor &color, bool vertical) const
{
painter->save();
painter->setRenderHint(QPainter::Antialiasing, false);
painter->setBrush(Qt::NoBrush);
painter->setPen(color);
if (vertical) {
painter->translate(rect.width() / 2, 0);
painter->drawLine(rect.topLeft(), rect.bottomLeft());
} else {
painter->translate(0, rect.height() / 2);
painter->drawLine(rect.topLeft(), rect.topRight());
}
painter->restore();
return;
}
//______________________________________________________________________________
void Helper::renderCheckBoxBackground(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool sunken) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect and radius
QRectF frameRect(rect);
frameRect.adjust(3, 3, -3, -3);
painter->setPen(outline);
painter->setBrush(color);
painter->drawRect(frameRect);
}
//______________________________________________________________________________
void Helper::renderCheckBox(QPainter *painter, const QRect &rect, const QColor &background, const QColor &outline, const QColor &tickColor,
bool sunken, CheckBoxState state, bool mouseOver, qreal animation, bool active, bool darkMode, bool inMenu) const
{
// setup painter
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect and radius
QRectF frameRect(rect);
frameRect.adjust(2, 2, -2, -2);
qreal radius(frameRadius());
// content
{
renderCheckBoxFrame(painter, rect, background, outline, Qt::transparent, false, sunken, mouseOver, active, state, darkMode, inMenu);
}
// mark
if (state == CheckOn) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->setBrush(Qt::NoBrush);
QPen pen(tickColor, 3);
pen.setJoinStyle(Qt::MiterJoin);
painter->setPen(pen);
QRectF markerRect(frameRect);
QPainterPath path;
path.moveTo(markerRect.right() - markerRect.width() / 4, markerRect.top() + markerRect.height() / 3);
path.lineTo(markerRect.center().x(), markerRect.bottom() - markerRect.height() / 3.0);
path.lineTo(markerRect.left() + markerRect.width() / 4, markerRect.center().y());
painter->setClipRect(markerRect);
painter->drawPath(path);
painter->restore();
} else if (state == CheckPartial) {
QPen pen(tickColor, 4);
pen.setCapStyle(Qt::RoundCap);
painter->setPen(pen);
QRectF markerRect(frameRect.adjusted(4, 4, -4, -4));
painter->drawLine(markerRect.center() - QPoint(3, 0), markerRect.center() + QPoint(3, 0));
} else if (state == CheckAnimated) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->setBrush(Qt::NoBrush);
QPen pen(tickColor, 3);
pen.setJoinStyle(Qt::MiterJoin);
painter->setPen(pen);
QRectF markerRect(frameRect);
QPainterPath path;
path.moveTo(markerRect.right() - markerRect.width() / 4, markerRect.top() + markerRect.height() / 3);
path.lineTo(markerRect.center().x(), markerRect.bottom() - markerRect.height() / 3.0);
path.lineTo(markerRect.left() + markerRect.width() / 4, markerRect.center().y());
path.translate(-markerRect.right(), -markerRect.top());
painter->setClipRect(markerRect.adjusted(1, 1, -1, -1));
painter->translate(markerRect.right(), markerRect.top());
painter->scale(animation, 0.5 + 0.5 * animation);
painter->drawPath(path);
painter->restore();
}
painter->restore();
}
//______________________________________________________________________________
void Helper::renderRadioButtonBackground(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, bool sunken) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect
QRectF frameRect(rect);
frameRect.adjust(3, 3, -3, -3);
if (sunken)
frameRect.translate(1, 1);
painter->setPen(outline);
painter->setBrush(color);
painter->drawEllipse(frameRect);
}
//______________________________________________________________________________
void Helper::renderRadioButton(QPainter *painter, const QRect &rect, const QColor &background, const QColor &outline, const QColor &tickColor,
bool sunken, bool enabled, RadioButtonState state, qreal animation, bool mouseOver, bool darkMode, bool inMenu) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect
QRectF frameRect(rect);
frameRect.adjust(2, 2, -2, -2);
if (inMenu || state == RadioOff) {
if (background.isValid() && enabled) {
QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft());
if (sunken) {
// Pressed-alt button in normal and dark mode is not a gradient, just an image consting from same $background
gradient.setColorAt(0, background);
gradient.setColorAt(1, background);
} else if (mouseOver) {
if (darkMode) {
QColor baseColor = background;
// Hovered-alt button in dark mode is a gradient from $background to Colors::darken(bg_background, 0.04)
gradient.setColorAt(0, Colors::darken(baseColor, 0.04));
gradient.setColorAt(1, background);
} else {
QColor baseColor = Colors::darken(background, 0.09);
// Hovered-alt button in normal mode is a gradient from $background to Colors::lighten(bg_background, 0.04)
gradient.setColorAt(0, background); // FIXME:
gradient.setColorAt(1, Colors::lighten(baseColor, 0.04)); // should be vice-versa, but this way it seems to be more accurate
}
} else {
if (darkMode) {
QColor baseColor = Colors::lighten(background, 0.03);
// Normal-alt button in dark mode is a gradient from $background to Colors::darken(bg_background, 0.06)
gradient.setColorAt(0, Colors::darken(baseColor, 0.06));
gradient.setColorAt(1, background);
} else {
QColor baseColor = Colors::darken(background, 0.05);
// Normal-alt button in normal mode is a gradient from $background to bg_background
gradient.setColorAt(0, baseColor);
gradient.setColorAt(1, background);
}
}
painter->setBrush(gradient);
} else if (!enabled) {
painter->setBrush(background);
} else {
painter->setBrush(Qt::NoBrush);
}
painter->setPen(QPen(outline, 1));
QRectF contentRect(frameRect.adjusted(0.5, 0.5, -0.5, -0.5));
painter->drawEllipse(contentRect);
} else {
if (background.isValid()) {
QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft());
gradient.setColorAt(0, background);
gradient.setColorAt(1, Colors::lighten(background, 0.04));
painter->setBrush(gradient);
} else {
painter->setBrush(Qt::NoBrush);
}
painter->setPen(QPen(outline, 1));
QRectF contentRect(frameRect.adjusted(0.5, 0.5, -0.5, -0.5));
painter->drawEllipse(contentRect);
}
// mark
if (state == RadioOn) {
painter->setBrush(tickColor);
painter->setPen(Qt::NoPen);
QRectF markerRect(frameRect.adjusted(5, 5, -5, -5));
painter->drawEllipse(markerRect);
} else if (state == RadioAnimated) {
painter->setBrush(tickColor);
painter->setPen(Qt::NoPen);
QRectF markerRect(frameRect.adjusted(5, 5, -5, -5));
qreal remaining = markerRect.width() / 2.0 * (1.0 - animation);
markerRect.adjust(remaining, remaining, -remaining, -remaining);
painter->drawEllipse(markerRect);
}
}
//______________________________________________________________________________
void Helper::renderSliderGroove(QPainter *painter, const QRect &rect, const QColor &color) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
QRectF baseRect(rect);
qreal radius(0.5 * Metrics::Slider_GrooveThickness);
// content
if (color.isValid()) {
painter->setPen(Qt::NoPen);
painter->setBrush(color);
painter->drawRoundedRect(baseRect, radius, radius);
}
return;
}
//______________________________________________________________________________
void Helper::renderDialGroove(QPainter *painter, const QRect &rect, const QColor &color) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
QRectF baseRect(rect);
// content
if (color.isValid()) {
qreal penWidth(Metrics::Slider_GrooveThickness);
QRectF grooveRect(rect.adjusted(penWidth / 2, penWidth / 2, -penWidth / 2, -penWidth / 2));
painter->setPen(QPen(color, penWidth));
painter->setBrush(Qt::NoBrush);
painter->drawEllipse(grooveRect);
}
return;
}
//______________________________________________________________________________
void Helper::renderDialContents(QPainter *painter, const QRect &rect, const QColor &color, qreal first, qreal second) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
QRectF baseRect(rect);
// content
if (color.isValid()) {
// setup groove rect
qreal penWidth(Metrics::Slider_GrooveThickness);
QRectF grooveRect(rect.adjusted(penWidth / 2, penWidth / 2, -penWidth / 2, -penWidth / 2));
// setup angles
int angleStart(first * 180 * 16 / M_PI);
int angleSpan((second - first) * 180 * 16 / M_PI);
// setup pen
if (angleSpan != 0) {
QPen pen(color, penWidth);
pen.setCapStyle(Qt::RoundCap);
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawArc(grooveRect, angleStart, angleSpan);
}
}
return;
}
//______________________________________________________________________________
void Helper::renderSliderHandle(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline, const QColor &shadow,
bool sunken, bool enabled, Side ticks, qreal angle, bool darkMode) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
// copy rect
QRectF frameRect(rect);
frameRect.adjust(1, 1, -1, -1);
// set pen
if (outline.isValid()) {
QPen pen(outline);
pen.setCapStyle(Qt::FlatCap);
pen.setJoinStyle(Qt::MiterJoin);
painter->setPen(pen);
frameRect.adjust(0.5, 0.5, -0.5, -0.5);
} else
painter->setPen(Qt::NoPen);
// set brush
if (color.isValid() && enabled) {
QLinearGradient gradient(frameRect.bottomLeft(), frameRect.topLeft());
if (sunken) {
// Pressed-alt button in normal and dark mode is not a gradient, just an image consting from same $background
gradient.setColorAt(0, color);
gradient.setColorAt(1, color);
} else {
if (darkMode) {
QColor baseColor = Colors::lighten(color, 0.03);
// Normal-alt button in dark mode is a gradient from $color to Colors::darken(bg_background, 0.06)
gradient.setColorAt(0, Colors::darken(baseColor, 0.06));
gradient.setColorAt(1, color);
} else {
QColor baseColor = Colors::darken(color, 0.05);
// Normal-alt button in normal mode is a gradient from $color to bg_background
gradient.setColorAt(0, baseColor);
gradient.setColorAt(1, color);
}
}
painter->setBrush(gradient);
} else if (!enabled) {
painter->setBrush(color);
} else {
painter->setBrush(Qt::NoBrush);
}
QRect r(rect.right() - rect.height(), rect.top(), rect.height(), rect.height());
r.adjust(4.5, 3.5, -2.5, -3.5);
QPainterPath circle;
circle.addEllipse(r);
circle.closeSubpath();
if (ticks & SideBottom) {
QPainterPath triangle(r.center());
triangle.moveTo(r.left() + 1.5, r.center().y() + 5.5);
triangle.lineTo(r.center().x() + 1, r.bottom() + 4.5);
triangle.lineTo(r.right() - 0.5, r.center().y() + 5.5);
triangle.closeSubpath();
circle = circle.united(triangle);
} else if (ticks & SideTop) {
QPainterPath triangle(r.center());
triangle.moveTo(r.left() + 1.5, r.center().y() - 3.5);
triangle.lineTo(r.center().x() + 1, r.top() - 2.5);
triangle.lineTo(r.right() - 0.5, r.center().y() - 3.5);
triangle.closeSubpath();
circle = circle.united(triangle);
} else if (ticks & SideLeft) {
QPainterPath triangle(r.center());
triangle.moveTo(r.center().x() - 3.5, r.top() + 1.5);
triangle.lineTo(r.left() - 2.5, r.center().y() + 1);
triangle.lineTo(r.center().x() - 3.5, r.bottom() - 0.5);
triangle.closeSubpath();
circle = circle.united(triangle);
} else if (ticks & SideRight) {
QPainterPath triangle(r.center());
triangle.moveTo(r.center().x() + 3.5, r.top() + 1.5);
triangle.lineTo(r.right() + 2.5, r.center().y() + 1);
triangle.lineTo(r.center().x() + 3.5, r.bottom() - 0.5);
triangle.closeSubpath();
circle = circle.united(triangle);
}
QTransform rotate;
rotate.translate(frameRect.center().x(), frameRect.center().y());
rotate.rotate(angle);
rotate.translate(-frameRect.center().x(), -frameRect.center().y());
painter->drawPolygon(circle.toFillPolygon(rotate));
}
//______________________________________________________________________________
void Helper::renderProgressBarGroove(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
QRectF baseRect(rect);
qreal radius(0.5);
// content
if (color.isValid()) {
painter->setPen(outline);
painter->setBrush(color);
painter->drawRoundedRect(baseRect.translated(0.5, 0.5), radius, radius);
}
return;
}
//______________________________________________________________________________
void Helper::renderProgressBarBusyContents(QPainter *painter, const QRect &rect, const QColor &color, const QColor &outline,
bool horizontal, bool reverse, int progress) const
{
Q_UNUSED(reverse);
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
QRectF baseRect(rect);
qreal radius(0.25 * Metrics::ProgressBar_Thickness);
QRectF contentRect;
if (horizontal) {
contentRect = QRect(baseRect.left(), baseRect.top(), Metrics::ProgressBar_BusyIndicatorSize, baseRect.height());
contentRect.translate(fabs(progress - 50) / 50.0 * (baseRect.width() - contentRect.width()), 0);
} else {
contentRect = QRect(baseRect.left(), baseRect.top(), baseRect.width(), Metrics::ProgressBar_BusyIndicatorSize);
contentRect.translate(0, fabs(progress - 50) / 50.0 * (baseRect.height() - contentRect.height()));
}
painter->setBrush(color);
painter->setPen(outline);
painter->drawRoundedRect(contentRect.translated(0.5, 0.5), radius, radius);
return;
}
//______________________________________________________________________________
void Helper::renderScrollBarHandle(QPainter *painter, const QRect &rect, const QColor &color) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, true);
QRectF baseRect(rect);
qreal metric(rect.width() < rect.height() ? rect.width() : rect.height());
qreal radius(0.5 * metric);
// content
if (color.isValid()) {
painter->setPen(Qt::NoPen);
painter->setBrush(color);
painter->drawRoundedRect(baseRect, radius, radius);
}
return;
}
//______________________________________________________________________________
void Helper::renderTabBarTab(QPainter *painter, const QRect &rect, const QColor &background, const QColor &color, const QColor &outline, Corners corners, bool renderFrame) const
{
// setup painter
painter->setRenderHint(QPainter::Antialiasing, false);
QRectF frameRect(rect);
qreal adjustment;
// pen
if (outline.isValid()) {
painter->setPen(outline);
frameRect.adjust(1.0, 1.0, -1.0, -1.0);
adjustment = 0;
painter->setBrush(background);
// render
painter->drawRect(frameRect);
} else if (!renderFrame) {
adjustment = 9;
}
painter->setPen(QPen(color, 6));
switch (corners) {
case CornersTop:
painter->drawLine(frameRect.left() + adjustment, frameRect.bottom(), frameRect.right() - adjustment, frameRect.bottom());
break;
case CornersBottom:
painter->drawLine(frameRect.left() + adjustment, frameRect.top(), frameRect.right() - adjustment, frameRect.top());
break;
case CornersLeft:
painter->drawLine(frameRect.right(), frameRect.top() + adjustment, frameRect.right(), frameRect.bottom() - adjustment);
break;
case CornersRight:
painter->drawLine(frameRect.left(), frameRect.top() + adjustment, frameRect.left(), frameRect.bottom() - adjustment);
break;
}
}
//______________________________________________________________________________
// TODO blurry edges
void Helper::renderArrow(QPainter *painter, const QRect &rect, const QColor &color, ArrowOrientation orientation) const
{
// define polygon
QPolygonF arrow;
switch (orientation) {
case ArrowUp:
arrow << QPointF(-4, 2) << QPointF(0, -2) << QPointF(4, 2);
break;
case ArrowDown:
arrow << QPointF(-4, -2) << QPointF(0, 2) << QPointF(4, -2);
break;
case ArrowLeft:
arrow << QPointF(2, -4) << QPointF(-2, 0) << QPointF(2, 4);
break;
case ArrowRight:
arrow << QPointF(-2, -4) << QPointF(2, 0) << QPointF(-2, 4);
break;
default:
break;
}
QPen pen(color, 1.2);
pen.setCapStyle(Qt::FlatCap);
pen.setJoinStyle(Qt::MiterJoin);
painter->save();
painter->setRenderHints(QPainter::Antialiasing);
painter->translate(QRectF(rect).center());
painter->setBrush(color);
painter->setPen(pen);
painter->drawPolygon(arrow);
painter->restore();
return;
}
void Helper::renderSign(QPainter *painter, const QRect &rect, const QColor &color, bool orientation) const
{
QPen pen(color, 2);
pen.setCapStyle(Qt::FlatCap);
QRect r = rect.adjusted(1, 2, 0, 0);
painter->setPen(pen);
painter->drawLine(r.center() - QPointF(5, 0), r.center() + QPointF(5, 0));
if (orientation)
painter->drawLine(r.center() - QPointF(0, 5), r.center() + QPointF(0, 5));
}
//______________________________________________________________________________
void Helper::renderDecorationButton(QPainter *painter, const QRect &rect, const QColor &color, ButtonType buttonType, bool inverted) const
{
painter->save();
painter->setViewport(rect);
painter->setWindow(0, 0, 18, 18);
painter->setRenderHints(QPainter::Antialiasing, false);
// initialize pen
QPen pen;
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::MiterJoin);
painter->setBrush(Qt::NoBrush);
pen.setColor(color);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::MiterJoin);
pen.setWidthF(2.0 * qMax(1.0, 18.0 / rect.width()));
painter->setPen(pen);
switch (buttonType) {
case ButtonClose: {
painter->setRenderHints(QPainter::Antialiasing, true);
painter->drawLine(QPointF(5, 5), QPointF(13, 13));
painter->drawLine(13, 5, 5, 13);
break;
}
case ButtonMaximize: {
painter->drawPolyline(QPolygonF()
<< QPointF(4, 4)
<< QPointF(4, 14)
<< QPointF(14, 14)
<< QPointF(14, 4));
break;
}
case ButtonMinimize: {
painter->drawPolyline(QPolygonF()
<< QPointF(4, 14)
<< QPointF(14, 14));
break;
}
case ButtonRestore: {
painter->setPen(pen);
QPolygonF rect = QPolygonF() << QPointF(0, 0) << QPointF(8, 0) << QPointF(8, 8) << QPointF(0, 8);
painter->drawPolygon(rect.translated(7, 3));
painter->drawPolygon(rect.translated(3, 7));
break;
}
default:
break;
}
painter->restore();
return;
}
//______________________________________________________________________________
bool Helper::isX11(void)
{
static const bool s_isX11 = qApp->platformName() == QLatin1String("xcb");
return s_isX11;
}
bool Helper::isWayland(void)
{
static const bool s_isWayland = qApp->platformName().startsWith(QLatin1String("wayland"));
return s_isWayland;
}
//______________________________________________________________________________
QRectF Helper::shadowRect(const QRectF &rect) const
{
return rect;
}
//______________________________________________________________________________
QPainterPath Helper::roundedPath(const QRectF &rect, Corners corners, qreal radius) const
{
QPainterPath path;
// simple cases
if (corners == 0) {
path.addRect(rect);
return path;
}
if (corners == AllCorners) {
path.addRoundedRect(rect, radius, radius);
return path;
}
QSizeF cornerSize(2 * radius, 2 * radius);
// rotate counterclockwise
// top left corner
if (corners & CornerTopLeft) {
path.moveTo(rect.topLeft() + QPointF(radius, 0));
path.arcTo(QRectF(rect.topLeft(), cornerSize), 90, 90);
} else
path.moveTo(rect.topLeft());
// bottom left corner
if (corners & CornerBottomLeft) {
path.lineTo(rect.bottomLeft() - QPointF(0, radius));
path.arcTo(QRectF(rect.bottomLeft() - QPointF(0, 2 * radius), cornerSize), 180, 90);
} else
path.lineTo(rect.bottomLeft());
// bottom right corner
if (corners & CornerBottomRight) {
path.lineTo(rect.bottomRight() - QPointF(radius, 0));
path.arcTo(QRectF(rect.bottomRight() - QPointF(2 * radius, 2 * radius), cornerSize), 270, 90);
} else
path.lineTo(rect.bottomRight());
// top right corner
if (corners & CornerTopRight) {
path.lineTo(rect.topRight() + QPointF(0, radius));
path.arcTo(QRectF(rect.topRight() - QPointF(2 * radius, 0), cornerSize), 0, 90);
} else
path.lineTo(rect.topRight());
path.closeSubpath();
return path;
}
//________________________________________________________________________________________________________
bool Helper::compositingActive(void) const
{
#if ADWAITA_HAVE_X11
if (isX11()) {
// direct call to X
xcb_get_selection_owner_cookie_t cookie(xcb_get_selection_owner(connection(), _compositingManagerAtom));
ScopedPointer<xcb_get_selection_owner_reply_t> reply(xcb_get_selection_owner_reply(connection(), cookie, nullptr));
return reply && reply->owner;
}
#endif
// use KWindowSystem
//return KWindowSystem::compositingActive();
return false;
}
//____________________________________________________________________
bool Helper::hasAlphaChannel(const QWidget *widget) const
{
return compositingActive() && widget && widget->testAttribute(Qt::WA_TranslucentBackground);
}
//______________________________________________________________________________________
QPixmap Helper::highDpiPixmap(int width, int height) const
{
qreal dpiRatio(qApp->devicePixelRatio());
QPixmap pixmap(width * dpiRatio, height * dpiRatio);
pixmap.setDevicePixelRatio(dpiRatio);
return pixmap;
}
//______________________________________________________________________________________
qreal Helper::devicePixelRatio(const QPixmap &pixmap) const
{
Q_UNUSED(pixmap);
return 1;
}
#if ADWAITA_HAVE_X11
//____________________________________________________________________
xcb_connection_t *Helper::connection(void)
{
return QX11Info::connection();
}
//____________________________________________________________________
xcb_atom_t Helper::createAtom(const QString &name) const
{
if (isX11()) {
xcb_connection_t *connection(Helper::connection());
xcb_intern_atom_cookie_t cookie(xcb_intern_atom(connection, false, name.size(), qPrintable(name)));
ScopedPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(connection, cookie, nullptr));
return reply ? reply->atom : 0;
} else
return 0;
}
#endif
//____________________________________________________________________
void Helper::init(void)
{
#if ADWAITA_HAVE_X11
if (isX11()) {
// create compositing screen
QString atomName(QStringLiteral("_NET_WM_CM_S%1").arg(QX11Info::appScreen()));
_compositingManagerAtom = createAtom(atomName);
}
#endif
}
//____________________________________________________________________
void Helper::setVariant(QWidget *widget, const QByteArray &variant)
{
#if ADWAITA_HAVE_X11
if (isX11() && widget) { // && !widget->testAttribute(Qt::WA_)) {
static const char *_GTK_THEME_VARIANT = "_GTK_THEME_VARIANT";
// Check if already set
QVariant var = widget->property("_GTK_THEME_VARIANT");
if (var.isValid() && var.toByteArray() == variant) {
return;
}
// Typedef's from xcb/xcb.h - copied so that there is no
// direct xcb dependency
typedef quint32 XcbAtom;
struct XcbInternAtomCookie {
unsigned int sequence;
};
struct XcbInternAtomReply {
quint8 response_type;
quint8 pad0;
quint16 sequence;
quint32 length;
XcbAtom atom;
};
struct XcbVoidCookie {
unsigned int sequence;
};
typedef void *(*XcbConnectFn)(const char *, int *);
typedef XcbInternAtomCookie(*XcbInternAtomFn)(void *, quint8, quint16, const char *);
typedef XcbInternAtomReply * (*XcbInternAtomReplyFn)(void *, XcbInternAtomCookie, void *);
typedef XcbVoidCookie(*XcbChangePropertyFn)(void *, quint8, quint32, XcbAtom, XcbAtom, quint8, quint32, const void *);
typedef int (*XcbFlushFn)(void *);
static QLibrary *lib = 0;
static XcbAtom variantAtom = 0;
static XcbAtom utf8TypeAtom = 0;
static void *xcbConn = 0;
static XcbChangePropertyFn XcbChangePropertyFnPtr = 0;
static XcbFlushFn XcbFlushFnPtr = 0;
if (!lib) {
lib = new QLibrary("libxcb", qApp);
if (lib->load()) {
XcbConnectFn XcbConnectFnPtr = (XcbConnectFn)lib->resolve("xcb_connect");
XcbInternAtomFn XcbInternAtomFnPtr = (XcbInternAtomFn)lib->resolve("xcb_intern_atom");
XcbInternAtomReplyFn XcbInternAtomReplyFnPtr = (XcbInternAtomReplyFn)lib->resolve("xcb_intern_atom_reply");
XcbChangePropertyFnPtr = (XcbChangePropertyFn)lib->resolve("xcb_change_property");
XcbFlushFnPtr = (XcbFlushFn)lib->resolve("xcb_flush");
if (XcbConnectFnPtr && XcbInternAtomFnPtr && XcbInternAtomReplyFnPtr && XcbChangePropertyFnPtr && XcbFlushFnPtr) {
xcbConn = (*XcbConnectFnPtr)(0, 0);
if (xcbConn) {
XcbInternAtomReply *typeReply = (*XcbInternAtomReplyFnPtr)(xcbConn, (*XcbInternAtomFnPtr)(xcbConn, 0, 11, "UTF8_STRING"), 0);
if (typeReply) {
XcbInternAtomReply *gtkVarReply = (*XcbInternAtomReplyFnPtr)(xcbConn,
(*XcbInternAtomFnPtr)(xcbConn, 0, strlen(_GTK_THEME_VARIANT),
_GTK_THEME_VARIANT), 0);
if (gtkVarReply) {
utf8TypeAtom = typeReply->atom;
variantAtom = gtkVarReply->atom;
free(gtkVarReply);
}
free(typeReply);
}
}
}
}
}
if (0 != variantAtom) {
(*XcbChangePropertyFnPtr)(xcbConn, 0, widget->effectiveWinId(), variantAtom, utf8TypeAtom, 8,
variant.length(), (const void *)variant.constData());
(*XcbFlushFnPtr)(xcbConn);
widget->setProperty(_GTK_THEME_VARIANT, variant);
}
}
#endif
}
}