diff --git a/CMakeLists.txt b/CMakeLists.txt index 70f5339..e4fc100 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,9 @@ if (UNIX) set(QT ${QT} X11Extras) endif () find_package(Qt5 REQUIRED ${QT}) -find_package(KF5WindowSystem REQUIRED) +if (UNIX) + find_package(KF5WindowSystem REQUIRED) +endif () include(GenerateExportHeader) include(GNUInstallDirs) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 62304b4..0cc7711 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,9 +6,6 @@ target_sources(${TARGET} PRIVATE fishui.cpp iconthemeprovider.cpp thememanager.cpp - shadowhelper/windowshadow.cpp - shadowhelper/boxshadowrenderer.cpp - shadowhelper/tileset.cpp iconitem.cpp newiconitem.cpp @@ -24,12 +21,18 @@ if (MSVC) target_sources(${TARGET} PRIVATE platforms/windows/windowhelper.cpp platforms/windows/windowhelper.h platforms/windows/blurhelper/windowblur.cpp platforms/windows/blurhelper/windowblur.h + platforms/windows/shadowhelper/boxshadowrenderer.cpp platforms/windows/shadowhelper/boxshadowrenderer.h + platforms/windows/shadowhelper/tileset.cpp platforms/windows/shadowhelper/tileset.h + platforms/windows/shadowhelper/windowshadow.cpp platforms/windows/shadowhelper/windowshadow.h ) target_include_directories(${TARGET} PRIVATE platforms/windows) else () target_sources(${TARGET} PRIVATE platforms/linux/windowhelper.cpp platforms/linux/windowhelper.h platforms/linux/blurhelper/windowblur.cpp platforms/linux/blurhelper/windowblur.h + platforms/linux/shadowhelper/boxshadowrenderer.cpp platforms/linux/shadowhelper/boxshadowrenderer.h + platforms/linux/shadowhelper/tileset.cpp platforms/linux/shadowhelper/tileset.h + platforms/linux/shadowhelper/windowshadow.cpp platforms/linux/shadowhelper/windowshadow.h ) target_include_directories(${TARGET} PRIVATE platforms/linux) endif () @@ -43,10 +46,9 @@ target_link_libraries(${TARGET} Qt5::Quick Qt5::QuickControls2 Qt5::GuiPrivate - KF5::WindowSystem ) if (UNIX) - target_link_libraries(${TARGET} PRIVATE Qt5::X11Extras) + target_link_libraries(${TARGET} PRIVATE Qt5::X11Extras KF5::WindowSystem) endif () generate_export_header(${TARGET} BASE_NAME ${TARGET}) diff --git a/src/shadowhelper/boxshadowrenderer.cpp b/src/platforms/linux/shadowhelper/boxshadowrenderer.cpp similarity index 100% rename from src/shadowhelper/boxshadowrenderer.cpp rename to src/platforms/linux/shadowhelper/boxshadowrenderer.cpp diff --git a/src/shadowhelper/boxshadowrenderer.h b/src/platforms/linux/shadowhelper/boxshadowrenderer.h similarity index 100% rename from src/shadowhelper/boxshadowrenderer.h rename to src/platforms/linux/shadowhelper/boxshadowrenderer.h diff --git a/src/shadowhelper/tileset.cpp b/src/platforms/linux/shadowhelper/tileset.cpp similarity index 100% rename from src/shadowhelper/tileset.cpp rename to src/platforms/linux/shadowhelper/tileset.cpp diff --git a/src/shadowhelper/tileset.h b/src/platforms/linux/shadowhelper/tileset.h similarity index 100% rename from src/shadowhelper/tileset.h rename to src/platforms/linux/shadowhelper/tileset.h diff --git a/src/shadowhelper/windowshadow.cpp b/src/platforms/linux/shadowhelper/windowshadow.cpp similarity index 100% rename from src/shadowhelper/windowshadow.cpp rename to src/platforms/linux/shadowhelper/windowshadow.cpp diff --git a/src/shadowhelper/windowshadow.h b/src/platforms/linux/shadowhelper/windowshadow.h similarity index 100% rename from src/shadowhelper/windowshadow.h rename to src/platforms/linux/shadowhelper/windowshadow.h diff --git a/src/platforms/windows/shadowhelper/boxshadowrenderer.cpp b/src/platforms/windows/shadowhelper/boxshadowrenderer.cpp new file mode 100644 index 0000000..fa91853 --- /dev/null +++ b/src/platforms/windows/shadowhelper/boxshadowrenderer.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2018 Vlad Zahorodnii + * + * The box blur implementation is based on AlphaBoxBlur from Firefox. + * + * 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 + */ + +// own +#include "boxshadowrenderer.h" + +// Qt +#include +#include + +static inline int calculateBlurRadius(qreal stdDev) +{ + // See https://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement + const qreal gaussianScaleFactor = (3.0 * qSqrt(2.0 * M_PI) / 4.0) * 1.5; + return qMax(2, qFloor(stdDev * gaussianScaleFactor + 0.5)); +} + +static inline qreal calculateBlurStdDev(int radius) +{ + // See https://www.w3.org/TR/css-backgrounds-3/#shadow-blur + return radius * 0.5; +} + +static inline QSize calculateBlurExtent(int radius) +{ + const int blurRadius = calculateBlurRadius(calculateBlurStdDev(radius)); + return QSize(blurRadius, blurRadius); +} + +struct BoxLobes +{ + int left; ///< how many pixels sample to the left + int right; ///< how many pixels sample to the right +}; + +/** + * Compute box filter parameters. + * + * @param radius The blur radius. + * @returns Parameters for three box filters. + **/ +static QVector computeLobes(int radius) +{ + const int blurRadius = calculateBlurRadius(calculateBlurStdDev(radius)); + const int z = blurRadius / 3; + + int major; + int minor; + int final; + + switch (blurRadius % 3) { + case 0: + major = z; + minor = z; + final = z; + break; + + case 1: + major = z + 1; + minor = z; + final = z; + break; + + case 2: + major = z + 1; + minor = z; + final = z + 1; + break; + + default: + Q_UNREACHABLE(); + } + + Q_ASSERT(major + minor + final == blurRadius); + + return { + {major, minor}, + {minor, major}, + {final, final} + }; +} + +/** + * Process a row with a box filter. + * + * @param src The start of the row. + * @param dst The destination. + * @param width The width of the row, in pixels. + * @param horizontalStride The number of bytes from one alpha value to the + * next alpha value. + * @param verticalStride The number of bytes from one row to the next row. + * @param lobes Params of the box filter. + * @param transposeInput Whether the input is transposed. + * @param transposeOutput Whether the output should be transposed. + **/ +static inline void boxBlurRowAlpha(const uint8_t *src, uint8_t *dst, int width, int horizontalStride, + int verticalStride, const BoxLobes &lobes, bool transposeInput, + bool transposeOutput) +{ + const int inputStep = transposeInput ? verticalStride : horizontalStride; + const int outputStep = transposeOutput ? verticalStride : horizontalStride; + + const int boxSize = lobes.left + 1 + lobes.right; + const int reciprocal = (1 << 24) / boxSize; + + uint32_t alphaSum = (boxSize + 1) / 2; + + const uint8_t *left = src; + const uint8_t *right = src; + uint8_t *out = dst; + + const uint8_t firstValue = src[0]; + const uint8_t lastValue = src[(width - 1) * inputStep]; + + alphaSum += firstValue * lobes.left; + + const uint8_t *initEnd = src + (boxSize - lobes.left) * inputStep; + while (right < initEnd) { + alphaSum += *right; + right += inputStep; + } + + const uint8_t *leftEnd = src + boxSize * inputStep; + while (right < leftEnd) { + *out = (alphaSum * reciprocal) >> 24; + alphaSum += *right - firstValue; + right += inputStep; + out += outputStep; + } + + const uint8_t *centerEnd = src + width * inputStep; + while (right < centerEnd) { + *out = (alphaSum * reciprocal) >> 24; + alphaSum += *right - *left; + left += inputStep; + right += inputStep; + out += outputStep; + } + + const uint8_t *rightEnd = dst + width * outputStep; + while (out < rightEnd) { + *out = (alphaSum * reciprocal) >> 24; + alphaSum += lastValue - *left; + left += inputStep; + out += outputStep; + } +} + +/** + * Blur the alpha channel of a given image. + * + * @param image The input image. + * @param radius The blur radius. + * @param rect Specifies what part of the image to blur. If nothing is provided, then + * the whole alpha channel of the input image will be blurred. + **/ +static inline void boxBlurAlpha(QImage &image, int radius, const QRect &rect = {}) +{ + if (radius < 2) { + return; + } + + const QVector lobes = computeLobes(radius); + + const QRect blurRect = rect.isNull() ? image.rect() : rect; + + const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3; + const int width = blurRect.width(); + const int height = blurRect.height(); + const int rowStride = image.bytesPerLine(); + const int pixelStride = image.depth() >> 3; + + const int bufferStride = qMax(width, height) * pixelStride; + QScopedPointer > buf(new uint8_t[2 * bufferStride]); + uint8_t *buf1 = buf.data(); + uint8_t *buf2 = buf1 + bufferStride; + + // Blur the image in horizontal direction. + for (int i = 0; i < height; ++i) { + uint8_t *row = image.scanLine(blurRect.y() + i) + blurRect.x() * pixelStride + alphaOffset; + boxBlurRowAlpha(row, buf1, width, pixelStride, rowStride, lobes[0], false, false); + boxBlurRowAlpha(buf1, buf2, width, pixelStride, rowStride, lobes[1], false, false); + boxBlurRowAlpha(buf2, row, width, pixelStride, rowStride, lobes[2], false, false); + } + + // Blur the image in vertical direction. + for (int i = 0; i < width; ++i) { + uint8_t *column = image.scanLine(blurRect.y()) + (blurRect.x() + i) * pixelStride + alphaOffset; + boxBlurRowAlpha(column, buf1, height, pixelStride, rowStride, lobes[0], true, false); + boxBlurRowAlpha(buf1, buf2, height, pixelStride, rowStride, lobes[1], false, false); + boxBlurRowAlpha(buf2, column, height, pixelStride, rowStride, lobes[2], false, true); + } +} + +static inline void mirrorTopLeftQuadrant(QImage &image) +{ + const int width = image.width(); + const int height = image.height(); + + const int centerX = qCeil(width * 0.5); + const int centerY = qCeil(height * 0.5); + + const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3; + const int stride = image.depth() >> 3; + + for (int y = 0; y < centerY; ++y) { + uint8_t *in = image.scanLine(y) + alphaOffset; + uint8_t *out = in + (width - 1) * stride; + + for (int x = 0; x < centerX; ++x, in += stride, out -= stride) { + *out = *in; + } + } + + for (int y = 0; y < centerY; ++y) { + const uint8_t *in = image.scanLine(y) + alphaOffset; + uint8_t *out = image.scanLine(width - y - 1) + alphaOffset; + + for (int x = 0; x < width; ++x, in += stride, out += stride) { + *out = *in; + } + } +} + +static void renderShadow(QPainter *painter, const QRect &rect, qreal borderRadius, const QPoint &offset, int radius, const QColor &color) +{ + const QSize inflation = calculateBlurExtent(radius); + const QSize size = rect.size() + 2 * inflation; + + const qreal dpr = painter->device()->devicePixelRatioF(); + + QImage shadow(size * dpr, QImage::Format_ARGB32_Premultiplied); + shadow.setDevicePixelRatio(dpr); + shadow.fill(Qt::transparent); + + QRect boxRect(QPoint(0, 0), rect.size()); + boxRect.moveCenter(QRect(QPoint(0, 0), size).center()); + + const qreal xRadius = 2.0 * borderRadius / boxRect.width(); + const qreal yRadius = 2.0 * borderRadius / boxRect.height(); + + QPainter shadowPainter; + shadowPainter.begin(&shadow); + shadowPainter.setRenderHint(QPainter::Antialiasing); + shadowPainter.setPen(Qt::NoPen); + shadowPainter.setBrush(Qt::black); + shadowPainter.drawRoundedRect(boxRect, xRadius, yRadius); + shadowPainter.end(); + + // Because the shadow texture is symmetrical, that's enough to blur + // only the top-left quadrant and then mirror it. + const QRect blurRect(0, 0, qCeil(shadow.width() * 0.5), qCeil(shadow.height() * 0.5)); + const int scaledRadius = qRound(radius * dpr); + boxBlurAlpha(shadow, scaledRadius, blurRect); + mirrorTopLeftQuadrant(shadow); + + // Give the shadow a tint of the desired color. + shadowPainter.begin(&shadow); + shadowPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + shadowPainter.fillRect(shadow.rect(), color); + shadowPainter.end(); + + // Actually, present the shadow. + QRect shadowRect = shadow.rect(); + shadowRect.setSize(shadowRect.size() / dpr); + shadowRect.moveCenter(rect.center() + offset); + painter->drawImage(shadowRect, shadow); +} + +void BoxShadowRenderer::setBoxSize(const QSize &size) +{ + m_boxSize = size; +} + +void BoxShadowRenderer::setBorderRadius(qreal radius) +{ + m_borderRadius = radius; +} + +void BoxShadowRenderer::setDevicePixelRatio(qreal dpr) +{ + m_dpr = dpr; +} + +void BoxShadowRenderer::addShadow(const QPoint &offset, int radius, const QColor &color) +{ + Shadow shadow = {}; + shadow.offset = offset; + shadow.radius = radius; + shadow.color = color; + m_shadows.append(shadow); +} + +QImage BoxShadowRenderer::render() const +{ + if (m_shadows.isEmpty()) { + return {}; + } + + QSize canvasSize; + for (const Shadow &shadow : qAsConst(m_shadows)) { + canvasSize = canvasSize.expandedTo( + calculateMinimumShadowTextureSize(m_boxSize, shadow.radius, shadow.offset)); + } + + QImage canvas(canvasSize * m_dpr, QImage::Format_ARGB32_Premultiplied); + canvas.setDevicePixelRatio(m_dpr); + canvas.fill(Qt::transparent); + + QRect boxRect(QPoint(0, 0), m_boxSize); + boxRect.moveCenter(QRect(QPoint(0, 0), canvasSize).center()); + + QPainter painter(&canvas); + for (const Shadow &shadow : qAsConst(m_shadows)) { + renderShadow(&painter, boxRect, m_borderRadius, shadow.offset, shadow.radius, shadow.color); + } + painter.end(); + + return canvas; +} + +QSize BoxShadowRenderer::calculateMinimumBoxSize(int radius) +{ + const QSize blurExtent = calculateBlurExtent(radius); + return 2 * blurExtent + QSize(1, 1); +} + +QSize BoxShadowRenderer::calculateMinimumShadowTextureSize(const QSize &boxSize, int radius, const QPoint &offset) +{ + return boxSize + 2 * calculateBlurExtent(radius) + QSize(qAbs(offset.x()), qAbs(offset.y())); +} \ No newline at end of file diff --git a/src/platforms/windows/shadowhelper/boxshadowrenderer.h b/src/platforms/windows/shadowhelper/boxshadowrenderer.h new file mode 100644 index 0000000..6211a73 --- /dev/null +++ b/src/platforms/windows/shadowhelper/boxshadowrenderer.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 Vlad Zahorodnii + * + * 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 + */ + +#pragma once + +// Qt +#include +#include +#include +#include + +class BoxShadowRenderer +{ +public: + // Compiler generated constructors & destructor are fine. + + /** + * Set the size of the box. + * @param size The size of the box. + **/ + void setBoxSize(const QSize &size); + + /** + * Set the radius of box' corners. + * @param radius The border radius, in pixels. + **/ + void setBorderRadius(qreal radius); + + /** + * Set the device pixel ratio of the resulting shadow texture. + * @param dpr The device pixel ratio. + **/ + void setDevicePixelRatio(qreal dpr); + + /** + * Add a shadow. + * @param offset The offset of the shadow. + * @param radius The blur radius. + * @param color The color of the shadow. + **/ + void addShadow(const QPoint &offset, int radius, const QColor &color); + + /** + * Render the shadow. + **/ + QImage render() const; + + /** + * Calculate the minimum size of the box. + * + * This helper computes the minimum size of the box so the shadow behind it has + * full its strength. + * + * @param radius The blur radius of the shadow. + **/ + static QSize calculateMinimumBoxSize(int radius); + + /** + * Calculate the minimum size of the shadow texture. + * + * This helper computes the minimum size of the resulting texture so the shadow + * is not clipped. + * + * @param boxSize The size of the box. + * @param radius The blur radius. + * @param offset The offset of the shadow. + **/ + static QSize calculateMinimumShadowTextureSize(const QSize &boxSize, int radius, const QPoint &offset); + +private: + QSize m_boxSize; + qreal m_borderRadius = 0.0; + qreal m_dpr = 1.0; + + struct Shadow { + QPoint offset; + int radius; + QColor color; + }; + + QVector m_shadows; +}; diff --git a/src/platforms/windows/shadowhelper/tileset.cpp b/src/platforms/windows/shadowhelper/tileset.cpp new file mode 100644 index 0000000..b53f7c8 --- /dev/null +++ b/src/platforms/windows/shadowhelper/tileset.cpp @@ -0,0 +1,183 @@ +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * 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 "tileset.h" + +#include + +//___________________________________________________________ +inline bool bits(TileSet::Tiles flags, TileSet::Tiles testFlags) +{ return (flags & testFlags) == testFlags; } + +//______________________________________________________________________________________ +inline qreal devicePixelRatio( const QPixmap& pixmap ) +{ + return pixmap.devicePixelRatio(); +} + +//______________________________________________________________________________________ +inline void setDevicePixelRatio( QPixmap& pixmap, qreal value ) +{ + return pixmap.setDevicePixelRatio( value ); +} + +//______________________________________________________________ +void TileSet::initPixmap( PixmapList& pixmaps, const QPixmap &source, int width, int height, const QRect &rect) +{ + QSize size( width, height ); + if( !( size.isValid() && rect.isValid() ) ) + { + pixmaps.append( QPixmap() ); + + } else if( size != rect.size() ) { + + const qreal dpiRatio( devicePixelRatio( source ) ); + const QRect scaledRect( rect.topLeft()*dpiRatio, rect.size()*dpiRatio ); + const QSize scaledSize( size*dpiRatio ); + const QPixmap tile( source.copy(scaledRect) ); + QPixmap pixmap( scaledSize ); + + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + painter.drawTiledPixmap(0, 0, scaledSize.width(), scaledSize.height(), tile); + setDevicePixelRatio( pixmap, dpiRatio ); + pixmaps.append( pixmap ); + + } else { + + const qreal dpiRatio( devicePixelRatio( source ) ); + const QRect scaledRect( rect.topLeft()*dpiRatio, rect.size()*dpiRatio ); + QPixmap pixmap( source.copy( scaledRect ) ); + setDevicePixelRatio( pixmap, dpiRatio ); + pixmaps.append( pixmap ); + + } + +} + +//______________________________________________________________ +TileSet::TileSet(): + _w1(0), + _h1(0), + _w3(0), + _h3(0) +{ _pixmaps.reserve(9); } + +//______________________________________________________________ +TileSet::TileSet(const QPixmap &source, int w1, int h1, int w2, int h2 ): + _w1(w1), + _h1(h1), + _w3(0), + _h3(0) +{ + _pixmaps.reserve(9); + if( source.isNull() ) return; + + _w3 = source.width()/devicePixelRatio( source ) - (w1 + w2); + _h3 = source.height()/devicePixelRatio( source ) - (h1 + h2); + int w = w2; + int h = h2; + + // initialise pixmap array + initPixmap( _pixmaps, source, _w1, _h1, QRect(0, 0, _w1, _h1) ); + initPixmap( _pixmaps, source, w, _h1, QRect(_w1, 0, w2, _h1) ); + initPixmap( _pixmaps, source, _w3, _h1, QRect(_w1+w2, 0, _w3, _h1) ); + initPixmap( _pixmaps, source, _w1, h, QRect(0, _h1, _w1, h2) ); + initPixmap( _pixmaps, source, w, h, QRect(_w1, _h1, w2, h2) ); + initPixmap( _pixmaps, source, _w3, h, QRect(_w1+w2, _h1, _w3, h2) ); + initPixmap( _pixmaps, source, _w1, _h3, QRect(0, _h1+h2, _w1, _h3) ); + initPixmap( _pixmaps, source, w, _h3, QRect(_w1, _h1+h2, w2, _h3) ); + initPixmap( _pixmaps, source, _w3, _h3, QRect(_w1+w2, _h1+h2, _w3, _h3) ); +} + +//___________________________________________________________ +void TileSet::render(const QRect &constRect, QPainter *painter, Tiles tiles) const +{ + + const bool oldHint( painter->testRenderHint( QPainter::SmoothPixmapTransform ) ); + painter->setRenderHint( QPainter::SmoothPixmapTransform, true ); + + // check initialization + if( _pixmaps.size() < 9 ) return; + + // copy source rect + QRect rect( constRect ); + + // get rect dimensions + int x0, y0, w, h; + rect.getRect(&x0, &y0, &w, &h); + + // calculate pixmaps widths + int wLeft(0); + int wRight(0); + if( _w1+_w3 > 0 ) + { + qreal wRatio( qreal( _w1 )/qreal( _w1 + _w3 ) ); + wLeft = (tiles&Right) ? qMin( _w1, int(w*wRatio) ):_w1; + wRight = (tiles&Left) ? qMin( _w3, int(w*(1.0-wRatio)) ):_w3; + } + + // calculate pixmap heights + int hTop(0); + int hBottom(0); + if( _h1+_h3 > 0 ) + { + qreal hRatio( qreal( _h1 )/qreal( _h1 + _h3 ) ); + hTop = (tiles&Bottom) ? qMin( _h1, int(h*hRatio) ):_h1; + hBottom = (tiles&Top) ? qMin( _h3, int(h*(1.0-hRatio)) ):_h3; + } + + // calculate corner locations + w -= wLeft + wRight; + h -= hTop + hBottom; + const int x1 = x0 + wLeft; + const int x2 = x1 + w; + const int y1 = y0 + hTop; + const int y2 = y1 + h; + + const int w2 = _pixmaps.at(7).width()/devicePixelRatio( _pixmaps.at(7) ); + const int h2 = _pixmaps.at(5).height()/devicePixelRatio( _pixmaps.at(5) ); + + // corner + if( bits( tiles, Top|Left) ) painter->drawPixmap(x0, y0, _pixmaps.at(0), 0, 0, wLeft*devicePixelRatio( _pixmaps.at(0) ), hTop*devicePixelRatio( _pixmaps.at(0) )); + if( bits( tiles, Top|Right) ) painter->drawPixmap(x2, y0, _pixmaps.at(2), (_w3-wRight)*devicePixelRatio( _pixmaps.at(2) ), 0, wRight*devicePixelRatio( _pixmaps.at(2) ), hTop*devicePixelRatio( _pixmaps.at(2) ) ); + if( bits( tiles, Bottom|Left) ) painter->drawPixmap(x0, y2, _pixmaps.at(6), 0, (_h3-hBottom)*devicePixelRatio( _pixmaps.at(6) ), wLeft*devicePixelRatio( _pixmaps.at(6) ), hBottom*devicePixelRatio( _pixmaps.at(6) )); + if( bits( tiles, Bottom|Right) ) painter->drawPixmap(x2, y2, _pixmaps.at(8), (_w3-wRight)*devicePixelRatio( _pixmaps.at(8) ), (_h3-hBottom)*devicePixelRatio( _pixmaps.at(8) ), wRight*devicePixelRatio( _pixmaps.at(8) ), hBottom*devicePixelRatio( _pixmaps.at(8) ) ); + + // top and bottom + if( w > 0 ) + { + if( tiles&Top ) painter->drawPixmap(x1, y0, w, hTop, _pixmaps.at(1), 0, 0, w2*devicePixelRatio( _pixmaps.at(1) ), hTop*devicePixelRatio( _pixmaps.at(1) ) ); + if( tiles&Bottom ) painter->drawPixmap(x1, y2, w, hBottom, _pixmaps.at(7), 0, (_h3-hBottom)*devicePixelRatio( _pixmaps.at(7) ), w2*devicePixelRatio( _pixmaps.at(7) ), hBottom*devicePixelRatio( _pixmaps.at(7) ) ); + } + + // left and right + if( h > 0 ) + { + if( tiles&Left ) painter->drawPixmap(x0, y1, wLeft, h, _pixmaps.at(3), 0, 0, wLeft*devicePixelRatio( _pixmaps.at(3) ), h2*devicePixelRatio( _pixmaps.at(3) ) ); + if( tiles&Right ) painter->drawPixmap(x2, y1, wRight, h, _pixmaps.at(5), (_w3-wRight)*devicePixelRatio( _pixmaps.at(5) ), 0, wRight*devicePixelRatio( _pixmaps.at(5) ), h2*devicePixelRatio( _pixmaps.at(5) ) ); + } + + // center + if( (tiles&Center) && h > 0 && w > 0 ) painter->drawPixmap(x1, y1, w, h, _pixmaps.at(4)); + + // restore + painter->setRenderHint( QPainter::SmoothPixmapTransform, oldHint ); + +} diff --git a/src/platforms/windows/shadowhelper/tileset.h b/src/platforms/windows/shadowhelper/tileset.h new file mode 100644 index 0000000..4f16f8c --- /dev/null +++ b/src/platforms/windows/shadowhelper/tileset.h @@ -0,0 +1,121 @@ +#ifndef TILESET_H +#define TILESET_H + +/************************************************************************* + * Copyright (C) 2014 by Hugo Pereira Da Costa * + * * + * 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 +#include +#include + +//* handles proper scaling of pixmap to match widget rect. +/** +tilesets are collections of stretchable pixmaps corresponding to a given widget corners, sides, and center. +corner pixmaps are never stretched. center pixmaps are +*/ +class TileSet +{ + public: + /** + Create a TileSet from a pixmap. The size of the bottom/right chunks is + whatever is left over from the other chunks, whose size is specified + in the required parameters. + + @param w1 width of the left chunks + @param h1 height of the top chunks + @param w2 width of the not-left-or-right chunks + @param h2 height of the not-top-or-bottom chunks + */ + TileSet(const QPixmap&, int w1, int h1, int w2, int h2 ); + + //* empty constructor + TileSet(); + + //* destructor + virtual ~TileSet() + {} + + /** + Flags specifying what sides to draw in ::render. Corners are drawn when + the sides forming that corner are drawn, e.g. Top|Left draws the + top-center, center-left, and top-left chunks. The center-center chunk is + only drawn when Center is requested. + */ + enum Tile { + Top = 0x1, + Left = 0x2, + Bottom = 0x4, + Right = 0x8, + Center = 0x10, + TopLeft = Top|Left, + TopRight = Top|Right, + BottomLeft = Bottom|Left, + BottomRight = Bottom|Right, + Ring = Top|Left|Bottom|Right, + Horizontal = Left|Right|Center, + Vertical = Top|Bottom|Center, + Full = Ring|Center + }; + Q_DECLARE_FLAGS(Tiles, Tile) + + /** + Fills the specified rect with tiled chunks. Corners are never tiled, + edges are tiled in one direction, and the center chunk is tiled in both + directions. Partial tiles are used as needed so that the entire rect is + perfectly filled. Filling is performed as if all chunks are being drawn. + */ + void render(const QRect&, QPainter*, Tiles = Ring) const; + + //* return size associated to this tileset + QSize size() const + { return QSize( _w1 + _w3, _h1 + _h3 ); } + + //* is valid + bool isValid() const + { return _pixmaps.size() == 9; } + + //* returns pixmap for given index + QPixmap pixmap( int index ) const + { return _pixmaps[index]; } + + protected: + + //* shortcut to pixmap list + using PixmapList = QVector; + + //* initialize pixmap + void initPixmap( PixmapList&, const QPixmap&, int w, int h, const QRect& ); + + private: + + //* pixmap arry + PixmapList _pixmaps; + + // dimensions + int _w1; + int _h1; + int _w3; + int _h3; + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(TileSet::Tiles) + +#endif //TILESET_H diff --git a/src/platforms/windows/shadowhelper/windowshadow.cpp b/src/platforms/windows/shadowhelper/windowshadow.cpp new file mode 100644 index 0000000..2d8443c --- /dev/null +++ b/src/platforms/windows/shadowhelper/windowshadow.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: revenmartin + * + * 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 3 of the License, or + * 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, see . + */ + +#include "windowshadow.h" +#include "boxshadowrenderer.h" +#include + +enum { + ShadowNone, + ShadowSmall, + ShadowMedium, + ShadowLarge, + ShadowVeryLarge +}; + +const CompositeShadowParams s_shadowParams[] = { + // None + CompositeShadowParams(), + // Small + CompositeShadowParams( + QPoint(0, 3), + ShadowParams(QPoint(0, 0), 16, 0.26), + ShadowParams(QPoint(0, -2), 8, 0.16)), + // Medium + CompositeShadowParams( + QPoint(0, 4), + ShadowParams(QPoint(0, 0), 20, 0.24), + ShadowParams(QPoint(0, -2), 10, 0.14)), + // Large + CompositeShadowParams( + QPoint(0, 5), + ShadowParams(QPoint(0, 0), 24, 0.22), + ShadowParams(QPoint(0, -3), 12, 0.12)), + // Very Large + CompositeShadowParams( + QPoint(0, 6), + ShadowParams(QPoint(0, 0), 32, 0.1), + ShadowParams(QPoint(0, -3), 16, 0.05)) +}; + +WindowShadow::WindowShadow(QObject *parent) noexcept + : QObject(parent) + , m_view(nullptr) +{ + +} + +WindowShadow::~WindowShadow() +{ +} + +CompositeShadowParams WindowShadow::lookupShadowParams(int shadowSizeEnum) +{ + switch (shadowSizeEnum) { + case ShadowNone: + return s_shadowParams[0]; + case ShadowSmall: + return s_shadowParams[1]; + case ShadowMedium: + return s_shadowParams[2]; + case ShadowLarge: + return s_shadowParams[3]; + case ShadowVeryLarge: + return s_shadowParams[4]; + default: + // Fallback to the Large size. + return s_shadowParams[3]; + } +} + +void WindowShadow::classBegin() +{ + m_shadowTiles = this->shadowTiles(); +} + +void WindowShadow::componentComplete() +{ + configureTiles(); +} + +void WindowShadow::setView(QWindow *view) +{ + if (view != m_view) { + m_view = view; + emit viewChanged(); + configureTiles(); + + connect(m_view, &QWindow::visibleChanged, this, &WindowShadow::onViewVisibleChanged); + } +} + +QWindow *WindowShadow::view() const +{ + return m_view; +} + +void WindowShadow::setGeometry(const QRect &rect) +{ + if (rect != m_rect) { + m_rect = rect; + emit geometryChanged(); + configureTiles(); + } +} + +QRect WindowShadow::geometry() const +{ + return m_rect; +} + +void WindowShadow::setRadius(qreal value) +{ + if (m_radius != value) { + m_radius = value; + emit radiusChanged(); + + this->classBegin(); + + configureTiles(); + } +} + +qreal WindowShadow::strength() const +{ + return m_strength; +} + +void WindowShadow::setStrength(qreal strength) +{ + if (m_strength != strength) { + m_strength = strength; + + this->classBegin(); + configureTiles(); + + emit strengthChanged(); + } +} + +void WindowShadow::onViewVisibleChanged(bool visible) +{ + if (visible && m_view) { + configureTiles(); + } +} + +void WindowShadow::configureTiles() +{ +} + +TileSet WindowShadow::shadowTiles() +{ + const qreal frameRadius = m_radius; + const CompositeShadowParams params = lookupShadowParams(ShadowVeryLarge); + + if (params.isNone()) + return TileSet(); + + auto withOpacity = [](const QColor &color, qreal opacity) -> QColor { + QColor c(color); + c.setAlphaF(opacity); + return c; + }; + + const QColor color = Qt::black; + const qreal strength = m_strength; + + const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius) + .expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius)); + + const qreal dpr = qApp->devicePixelRatio(); + + BoxShadowRenderer shadowRenderer; + shadowRenderer.setBorderRadius(frameRadius); + shadowRenderer.setBoxSize(boxSize); + shadowRenderer.setDevicePixelRatio(dpr); + + shadowRenderer.addShadow(params.shadow1.offset, params.shadow1.radius, + withOpacity(color, params.shadow1.opacity * strength)); + shadowRenderer.addShadow(params.shadow2.offset, params.shadow2.radius, + withOpacity(color, params.shadow2.opacity * strength)); + + QImage shadowTexture = shadowRenderer.render(); + + const QRect outerRect(QPoint(0, 0), shadowTexture.size() / dpr); + + QRect boxRect(QPoint(0, 0), boxSize); + boxRect.moveCenter(outerRect.center()); + + // Mask out inner rect. + QPainter painter(&shadowTexture); + painter.setRenderHint(QPainter::Antialiasing); + + int Shadow_Overlap = 3; + const QMargins margins = QMargins( + boxRect.left() - outerRect.left() - Shadow_Overlap - params.offset.x(), + boxRect.top() - outerRect.top() - Shadow_Overlap - params.offset.y(), + outerRect.right() - boxRect.right() - Shadow_Overlap + params.offset.x(), + outerRect.bottom() - boxRect.bottom() - Shadow_Overlap + params.offset.y()); + + painter.setPen(Qt::NoPen); + painter.setBrush(Qt::black); + painter.setCompositionMode(QPainter::CompositionMode_DestinationOut); + painter.drawRoundedRect( + outerRect - margins, + frameRadius, + frameRadius); + + // We're done. + painter.end(); + + const QPoint innerRectTopLeft = outerRect.center(); + TileSet tiles = TileSet( + QPixmap::fromImage(shadowTexture), + innerRectTopLeft.x(), + innerRectTopLeft.y(), + 1, 1); + + return tiles; +} + +QMargins WindowShadow::shadowMargins(TileSet shadowTiles) const +{ + const CompositeShadowParams params = lookupShadowParams(ShadowVeryLarge); + if (params.isNone()) + return QMargins(); + + const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius) + .expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius)); + + const QSize shadowSize = BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow1.radius, params.shadow1.offset) + .expandedTo(BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow2.radius, params.shadow2.offset)); + + const QRect shadowRect(QPoint(0, 0), shadowSize); + + QRect boxRect(QPoint(0, 0), boxSize); + boxRect.moveCenter(shadowRect.center()); + + int Shadow_Overlap = 4; + QMargins margins( + boxRect.left() - shadowRect.left() - Shadow_Overlap - params.offset.x(), + boxRect.top() - shadowRect.top() - Shadow_Overlap - params.offset.y(), + shadowRect.right() - boxRect.right() - Shadow_Overlap + params.offset.x(), + shadowRect.bottom() - boxRect.bottom() - Shadow_Overlap + params.offset.y()); + + margins *= shadowTiles.pixmap(0).devicePixelRatio(); + + return margins; +} diff --git a/src/platforms/windows/shadowhelper/windowshadow.h b/src/platforms/windows/shadowhelper/windowshadow.h new file mode 100644 index 0000000..05dc170 --- /dev/null +++ b/src/platforms/windows/shadowhelper/windowshadow.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: revenmartin + * + * 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 3 of the License, or + * 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, see . + */ + +#ifndef WINDOWSHADOW_H +#define WINDOWSHADOW_H + +#include "tileset.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ShadowParams +{ + ShadowParams() = default; + + ShadowParams(const QPoint &offset, int radius, qreal opacity): + offset(offset), + radius(radius), + opacity(opacity) + {} + + QPoint offset; + int radius = 0; + qreal opacity = 0; +}; + +struct CompositeShadowParams +{ + CompositeShadowParams() = default; + + CompositeShadowParams( + const QPoint &offset, + const ShadowParams &shadow1, + const ShadowParams &shadow2) + : offset(offset) + , shadow1(shadow1) + , shadow2(shadow2) {} + + bool isNone() const + { return qMax(shadow1.radius, shadow2.radius) == 0; } + + QPoint offset; + ShadowParams shadow1; + ShadowParams shadow2; +}; + +class WindowShadow : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QWindow *view READ view WRITE setView NOTIFY viewChanged) + Q_PROPERTY(QRect geometry READ geometry WRITE setGeometry NOTIFY geometryChanged) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) + Q_PROPERTY(qreal strength READ strength WRITE setStrength NOTIFY strengthChanged) + +public: + WindowShadow(QObject *parent = nullptr) noexcept; + ~WindowShadow() override; + + static CompositeShadowParams lookupShadowParams(int shadowSizeEnum); + + void classBegin() override; + void componentComplete() override; + + void setView(QWindow *view); + QWindow *view() const; + + void setGeometry(const QRect &rect); + QRect geometry() const; + + void setRadius(qreal value); + qreal radius() { return m_radius; } + + qreal strength() const; + void setStrength(qreal strength); + +private slots: + void onViewVisibleChanged(bool); + +private: + void configureTiles(); + TileSet shadowTiles(); + + QMargins shadowMargins(TileSet) const; + +signals: + void geometryChanged(); + void enabledChanged(); + void viewChanged(); + void edgesChanged(); + void radiusChanged(); + void strengthChanged(); + +private: + QWindow *m_view; + QRect m_rect; + TileSet m_shadowTiles; + qreal m_radius = 10; + qreal m_strength = 1.2; +}; + +#endif diff --git a/src/platforms/windows/windowhelper.cpp b/src/platforms/windows/windowhelper.cpp index 0a8a871..76b972f 100644 --- a/src/platforms/windows/windowhelper.cpp +++ b/src/platforms/windows/windowhelper.cpp @@ -23,7 +23,6 @@ #include #include -#include WindowHelper::WindowHelper(QObject *parent) : QObject(parent) @@ -42,7 +41,7 @@ void WindowHelper::startSystemResize(QWindow *w, Qt::Edges edges) void WindowHelper::minimizeWindow(QWindow *w) { - KWindowSystem::minimizeWindow(w->winId()); + qWarning() << "not implement"; } void WindowHelper::doStartSystemMoveResize(QWindow *w, int edges)