/******************************************************************************
Skulpture - Classical Three-Dimensional Artwork for Qt 4
Copyright (c) 2007-2009 Christoph Feck <christoph@maxiom.de>
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 <cmath>
#include <functional>
#include <QStyleOptionSlider>
#include <QPixmapCache>
#include <QPainter>
#include <QPointF>
#include <QRectF>
#include "qdialskulpturestyle.hpp"
#ifndef M_PI
#define M_PI 3.141592653589793f
#endif
static const bool UsePixmapCache = true;
static void
paintIndicatorCached(QPainter *painter, const QStyleOption *option, std::function<void(QPainter*,const QStyleOption*)> paintFunc, bool useCache, const QString &pixmapName)
{
QRect rect = option->rect;
QPixmap internalPixmapCache;
QImage imageCache;
QPainter *p = painter;
int txType = painter->deviceTransform().type() | painter->worldTransform().type();
bool doPixmapCache = useCache && (!option->rect.isEmpty())
&& ((txType <= QTransform::TxTranslate) || (painter->deviceTransform().type() == QTransform::TxScale));
if (doPixmapCache && QPixmapCache::find(pixmapName, &internalPixmapCache)) {
painter->drawPixmap(option->rect.topLeft(), internalPixmapCache);
} else {
if (doPixmapCache) {
rect.setRect(0, 0, option->rect.width(), option->rect.height());
qreal pixelRatio=painter->device()->devicePixelRatioF();
imageCache = QImage(option->rect.size() * pixelRatio, QImage::Format_ARGB32_Premultiplied);
imageCache.setDevicePixelRatio(pixelRatio);
imageCache.fill(0);
p = new QPainter(&imageCache);
}
paintFunc(p,option);
if (doPixmapCache) {
p->end();
delete p;
internalPixmapCache = QPixmap::fromImage(imageCache);
painter->drawPixmap(option->rect.topLeft(), internalPixmapCache);
QPixmapCache::insert(pixmapName, internalPixmapCache);
}
}
}
void
paintDialBase(QPainter *painter, const QStyleOption *option)
{
// painter->fillRect(option->rect, Qt::red);
// painter->save();
// painter->setRenderHint(QPainter::Antialiasing, true);
int d = qMin(option->rect.width(), option->rect.height());
/* if (d > 20 && option->notchTarget > 0) {
d += -1;
}
*/ QRectF r((option->rect.width() - d) / 2.0, (option->rect.height() - d) / 2.0, d, d);
const qreal angle = option->direction == Qt::LeftToRight ? 135.0 : 45.0;
// const qreal angle = 90;
painter->setPen(Qt::NoPen);
painter->setRenderHint(QPainter::Antialiasing);
QColor border_color = option->palette.color(QPalette::Window);
#if 0
{
QRadialGradient depth_gradient(r.center(), d / 2);
// depth_gradient.setColorAt(0.0, QColor(0, 0, 0, 255));
depth_gradient.setColorAt(0.5, QColor(0, 0, 0, 255));
depth_gradient.setColorAt(1.0, QColor(0, 0, 0, 0));
painter->setBrush(depth_gradient);
painter->drawEllipse(r);
}
#endif
#if 1
if (option->state & QStyle::State_HasFocus && option->state & QStyle::State_KeyboardFocusChange) {
painter->setBrush(option->palette.color(QPalette::Highlight).darker(180));
r.adjust(1, 1, -1, -1);
painter->drawEllipse(r);
painter->setBrush(border_color);
r.adjust(1, 1, -1, -1);
painter->drawEllipse(r);
r.adjust(1, 1, -1, -1);
} else {
painter->setBrush(border_color);
r.adjust(1, 1, -1, -1);
painter->drawEllipse(r);
r.adjust(1, 1, -1, -1);
QConicalGradient border_gradient(r.center(), angle);
if (!(option->state & QStyle::State_Enabled)) {
border_color = border_color.lighter(120);
}
border_gradient.setColorAt(0.0, border_color.darker(180));
border_gradient.setColorAt(0.3, border_color.darker(130));
border_gradient.setColorAt(0.5, border_color.darker(170));
border_gradient.setColorAt(0.7, border_color.darker(130));
border_gradient.setColorAt(1.0, border_color.darker(180));
painter->setBrush(border_gradient);
// painter->setBrush(Qt::blue);
painter->drawEllipse(r);
r.adjust(1, 1, -1, -1);
}
d -= 6;
QColor dial_color;
if (option->state & QStyle::State_Enabled) {
dial_color = option->palette.color(QPalette::Button).lighter(101);
if (option->state & QStyle::State_MouseOver) {
dial_color = dial_color.lighter(103);
}
} else {
dial_color = option->palette.color(QPalette::Window);
}
qreal t = option->state & QStyle::State_Enabled ? 2.0 : 1.5;
// ###: work around Qt 4.3.0 bug? (this works for 4.3.1)
QConicalGradient border_gradient(r.center(), angle);
border_gradient.setColorAt(0.0, dial_color.lighter(120));
border_gradient.setColorAt(0.2, dial_color);
border_gradient.setColorAt(0.5, dial_color.darker(130));
border_gradient.setColorAt(0.8, dial_color);
border_gradient.setColorAt(1.0, dial_color.lighter(120));
painter->setPen(QPen(border_gradient, t));
#if 0
QLinearGradient dial_gradient(r.topLeft(), r.bottomLeft());
dial_gradient.setColorAt(0.0, dial_color.darker(105));
dial_gradient.setColorAt(0.5, dial_color.lighter(102));
dial_gradient.setColorAt(1.0, dial_color.lighter(105));
#elif 1
QLinearGradient dial_gradient(option->direction == Qt::LeftToRight ? r.topLeft() : r.topRight(), option->direction == Qt::LeftToRight ? r.bottomRight() : r.bottomLeft());
// QLinearGradient dial_gradient(r.topLeft(), r.bottomLeft());
if (option->state & QStyle::State_Enabled) {
#if 1
dial_gradient.setColorAt(0.0, dial_color.darker(106));
dial_gradient.setColorAt(1.0, dial_color.lighter(104));
#else
dial_gradient.setColorAt(0.0, dial_color.lighter(101));
dial_gradient.setColorAt(0.5, dial_color.darker(103));
dial_gradient.setColorAt(1.0, dial_color.lighter(104));
#endif
} else {
dial_gradient.setColorAt(0.0, dial_color);
dial_gradient.setColorAt(1.0, dial_color);
}
#elif 0
QConicalGradient dial_gradient(r.center(), angle);
dial_gradient.setColorAt(0.0, dial_color.lighter(102));
dial_gradient.setColorAt(0.5, dial_color.darker(103));
dial_gradient.setColorAt(1.0, dial_color.lighter(102));
#else
QBrush dial_gradient(dial_color);
#endif
painter->setBrush(dial_gradient);
t = t / 2;
painter->drawEllipse(r.adjusted(t, t, -t, -t));
// painter->setPen(Qt::NoPen);
// painter->setBrush(dial_color);
// painter->drawEllipse(r.adjusted(d / 4, d / 4, - d / 4, - d / 4));
#if 0
QLinearGradient border2_gradient(r.topLeft(), r.bottomRight());
border2_gradient.setColorAt(1.0, dial_color.darker(425));
border2_gradient.setColorAt(0.9, dial_color);
border2_gradient.setColorAt(0.0, dial_color.darker(400));
painter->setPen(QPen(border2_gradient, 1.3));
painter->setBrush(Qt::NoBrush);
painter->drawEllipse(r.adjusted(0.3, 0.3, -0.3, -0.3));
#endif
// painter->restore();
#endif
}
void
paintCachedDialBase(QPainter *painter, const QStyleOptionSlider *option)
{
bool useCache = UsePixmapCache;
QString pixmapName;
QRect r = option->rect;
int d = qMin(r.width(), r.height());
if (/* option->state & (QStyle::State_HasFocus | QStyle::State_MouseOver) ||*/ d > 128) {
useCache = false;
}
if (useCache) {
uint state = uint(option->state) & (QStyle::State_Enabled | QStyle::State_On | QStyle::State_MouseOver | QStyle::State_KeyboardFocusChange | QStyle::State_HasFocus);
if (!(state & QStyle::State_Enabled)) {
state &= ~(QStyle::State_MouseOver | QStyle::State_HasFocus | QStyle::State_KeyboardFocusChange);
}
// state &= ~(QStyle::State_HasFocus);
pixmapName = QString("scp-qdb-%1-%2-%3-%4")
.arg(state, 0, 16)
.arg(option->direction, 0, 16)
.arg(option->palette.cacheKey(), 0, 16)
.arg(d, 0, 16);
}
paintIndicatorCached(painter, option, paintDialBase, useCache, pixmapName);
}
void
paintIndicatorDial(QPainter *painter, const QStyleOptionSlider *option)
{
int d = qMin(option->rect.width(), option->rect.height());
QRect rect(option->rect.center() - QPoint((d - 1) / 2, (d - 1) / 2), QSize(d, d));
QStyleOptionSlider opt;
opt.QStyleOption::operator=(*option);
opt.rect = rect;
paintCachedDialBase(painter, &opt);
}
QColor
shaded_color(const QColor &color, int shade)
{
#if 1
const qreal contrast = 1.0;
int r, g, b;
color.getRgb(&r, &g, &b);
int gray = qGray(r, g, b);
gray = qMax(r, qMax(g, b));
gray = (r + b + g + 3 * gray) / 6;
if (shade < 0) {
qreal k = 220.0 / 255.0 * shade;
k *= contrast;
int a = 255;
if (gray > 0) {
a = int(k * 255 / (0 - gray));
if (a < 0) a = 0;
if (a > 255) a = 255;
}
return QColor(0, 0, 0, a);
} else {
qreal k = (255 - 220.0) / (255.0) * shade;
k *= contrast;
int a = 255;
if (gray < 255) {
a = int(k * 255 / (255 - gray));
if (a < 0) a = 0;
if (a > 255) a = 255;
}
return QColor(255, 255, 255, a);
}
#else
if (shade < 0) {
return QColor(0, 0, 0, -shade);
} else {
return QColor(255, 255, 255, shade);
}
#endif
}
static void
paintGrip(QPainter *painter, const QStyleOption *option)
{
//painter->fillRect(option->rect, Qt::red);
int d = qMin(option->rect.width(), option->rect.height());
// good values are 3 (very small), 4 (small), 5 (good), 7 (large), 9 (huge)
// int d = 5;
QRectF rect(QRectF(option->rect).center() - QPointF(d / 2.0, d / 2.0), QSizeF(d, d));
const qreal angle = option->direction == Qt::LeftToRight ? 135.0 : 45.0;
// const qreal angle = 90;
QColor color;
qreal opacity = 0.9;
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(Qt::NoPen);
if (option->state & QStyle::State_Enabled) {
if (option->state & QStyle::State_Sunken) {
color = option->palette.color(QPalette::Highlight).darker(110);
} else {
color = option->palette.color(QPalette::Button);
}
} else {
color = option->palette.color(QPalette::Button);
opacity = 0.5;
}
QConicalGradient gradient1(rect.center(), angle);
gradient1.setColorAt(0.0, shaded_color(color, -110));
gradient1.setColorAt(0.25, shaded_color(color, -30));
gradient1.setColorAt(0.5, shaded_color(color, 180));
gradient1.setColorAt(0.75, shaded_color(color, -30));
gradient1.setColorAt(1.0, shaded_color(color, -110));
painter->setBrush(color);
painter->drawEllipse(rect);
painter->setBrush(gradient1);
#if (QT_VERSION >= QT_VERSION_CHECK(4, 2, 0))
// ### merge opacity into color
painter->setOpacity(opacity);
#endif
painter->drawEllipse(rect);
#if (QT_VERSION >= QT_VERSION_CHECK(4, 2, 0))
painter->setOpacity(1.0);
if (d > 2) {
QConicalGradient gradient2(rect.center(), angle);
gradient2.setColorAt(0.0, shaded_color(color, -40));
gradient2.setColorAt(0.25, shaded_color(color, 0));
gradient2.setColorAt(0.5, shaded_color(color, 210));
gradient2.setColorAt(0.75, shaded_color(color, 0));
gradient2.setColorAt(1.0, shaded_color(color, -40));
rect.adjust(1, 1, -1, -1);
painter->setBrush(color);
painter->drawEllipse(rect);
painter->setBrush(gradient2);
painter->setOpacity(opacity);
painter->drawEllipse(rect);
painter->setOpacity(1.0);
if (d > 8) {
QConicalGradient gradient3(rect.center(), angle);
gradient3.setColorAt(0.0, shaded_color(color, -10));
gradient3.setColorAt(0.25, shaded_color(color, 0));
gradient3.setColorAt(0.5, shaded_color(color, 180));
gradient3.setColorAt(0.75, shaded_color(color, 0));
gradient3.setColorAt(1.0, shaded_color(color, -10));
rect.adjust(2, 2, -2, -2);
painter->setBrush(color);
painter->drawEllipse(rect);
painter->setBrush(gradient3);
painter->setOpacity(opacity);
painter->drawEllipse(rect);
painter->setOpacity(1.0);
}
}
#endif
painter->restore();
}
void
paintCachedGrip(QPainter *painter, const QStyleOption *option)
{
bool useCache = UsePixmapCache;
QString pixmapName;
if (/* option->state & (QStyle::State_HasFocus | QStyle::State_MouseOver) ||*/ option->rect.width() * option->rect.height() > 4096) {
useCache = false;
}
if (useCache) {
QStyle::State state = option->state & (QStyle::State_Enabled | QStyle::State_On | QStyle::State_MouseOver | QStyle::State_Sunken | QStyle::State_HasFocus);
if (!(state & QStyle::State_Enabled)) {
state &= ~(QStyle::State_MouseOver | QStyle::State_HasFocus);
}
state &= ~(QStyle::State_HasFocus);
QByteArray colorName = option->palette.color(QPalette::Button).name().toLatin1();
pixmapName = QString("scp-isg-%1-%2-%3-%4-%5")
.arg(state, 0, 16)
.arg(option->direction, 0, 16)
.arg(QString(colorName))
.arg(option->rect.width(), 0, 16)
.arg(option->rect.height(), 0, 16);
}
paintIndicatorCached(painter,option,
[=](QPainter *painter,const QStyleOption *option){
QStyleOption opt(*option);
opt.rect.moveTo(0, 0);
paintGrip(painter,&opt);
},
useCache,pixmapName);
}
void
QDialSkulptureStyle::drawComplexControl( ComplexControl cc,
const QStyleOptionComplex *optc,
QPainter *painter,
const QWidget *widget) const
{
if (cc != QStyle::CC_Dial) {
QCommonStyle::drawComplexControl(cc, optc, painter, widget);
return;
}
const QStyleOptionSlider *option = qstyleoption_cast<const QStyleOptionSlider *>(optc);
if (option == nullptr)
return;
int d = qMin(option->rect.width() & ~1, option->rect.height() & ~1);
QStyleOptionSlider opt = *option;
const QAbstractSlider *slider = nullptr;
// always highlight knob if pressed (even if mouse is not over knob)
if ((option->state & QStyle::State_HasFocus) && (slider = qobject_cast<const QAbstractSlider *>(widget))) {
if (slider->isSliderDown()) {
opt.state |= QStyle::State_MouseOver;
}
}
// tickmarks
opt.palette.setColor(QPalette::Inactive, QPalette::WindowText, QColor(80, 80, 80, 255));
opt.palette.setColor(QPalette::Active, QPalette::WindowText, QColor(80, 80, 80, 255));
opt.state &= ~QStyle::State_HasFocus;
opt.rect.setWidth(opt.rect.width() & ~1);
opt.rect.setHeight(opt.rect.height() & ~1);
opt.rect.moveCenter(option->rect.center());
QCommonStyle::drawComplexControl(QStyle::CC_Dial, &opt, painter, widget);
// focus rectangle
if (option->state & QStyle::State_HasFocus) {
QStyleOptionFocusRect focus;
opt.state |= QStyle::State_HasFocus;
focus.QStyleOption::operator=(opt);
focus.rect.adjust(-1, -1, 1, 1);
//drawPrimitive(QStyle::PE_FrameFocusRect, &focus, painter, widget);
}
opt.palette = option->palette;
// dial base
if (d <= 256) {
QStyleOptionSlider topt(opt);
topt.rect.adjust(2,2,-2,-2);
topt.rect.moveCenter(option->rect.center());
paintIndicatorDial(painter, &topt);
} else {
// large dials are slow to render, do not render them
}
// dial knob
d -= 6;
int gripSize = (option->fontMetrics.height() / 4) * 2 - 1;
opt.rect.setSize(QSize(gripSize, gripSize));
opt.rect.moveCenter(option->rect.center());
// angle calculation from qcommonstyle.cpp (c) Trolltech 1992-2007, ASA.
qreal angle;
int sliderPosition = option->upsideDown ? option->sliderPosition : (option->maximum - option->sliderPosition);
int range = option->maximum - option->minimum;
if (!range) {
angle = M_PI / 2;
} else if (option->dialWrapping) {
angle = M_PI * 1.5 - (sliderPosition - option->minimum) * 2 * M_PI / range;
} else {
angle = (M_PI * 8 - (sliderPosition - option->minimum) * 10 * M_PI / range) / 6;
}
qreal rr = d / 2.0 - gripSize - 2;
opt.rect.translate(int(0.5 + rr * cos(angle)), int(0.5 - rr * sin(angle)));
paintCachedGrip(painter, &opt);
}