From 05d6761baafae94efd02d6f4ec9eccea6b3f9b66 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 3 May 2021 03:35:43 +0300 Subject: [PATCH] a bit of refactor, fix the time to request next portion of messages in ui, fancy shadows are back! --- ui/CMakeLists.txt | 4 +- ui/utils/dropshadoweffect.cpp | 558 +------------------------------- ui/utils/dropshadoweffect.h | 2 + ui/utils/eb.cpp | 579 ++++++++++++++++++++++++++++++++++ ui/utils/eb.h | 34 ++ ui/utils/feedview.cpp | 10 + ui/utils/feedview.h | 3 + ui/utils/shadowoverlay.cpp | 91 ++++++ ui/utils/shadowoverlay.h | 58 ++++ ui/widgets/CMakeLists.txt | 1 + ui/widgets/conversation.cpp | 74 +++-- ui/widgets/conversation.h | 12 +- 12 files changed, 826 insertions(+), 600 deletions(-) create mode 100644 ui/utils/eb.cpp create mode 100644 ui/utils/eb.h create mode 100644 ui/utils/shadowoverlay.cpp create mode 100644 ui/utils/shadowoverlay.h diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index d6e29d3..4b53439 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -32,7 +32,7 @@ set(squawkUI_SRC models/messagefeed.cpp models/element.cpp utils/messageline.cpp - utils//message.cpp + utils/message.cpp utils/resizer.cpp utils/image.cpp utils/flowlayout.cpp @@ -42,6 +42,8 @@ set(squawkUI_SRC utils/dropshadoweffect.cpp utils/feedview.cpp utils/messagedelegate.cpp + utils/eb.cpp + utils/shadowoverlay.cpp ) # Tell CMake to create the helloworld executable diff --git a/ui/utils/dropshadoweffect.cpp b/ui/utils/dropshadoweffect.cpp index 91a0258..1090fcd 100644 --- a/ui/utils/dropshadoweffect.cpp +++ b/ui/utils/dropshadoweffect.cpp @@ -17,562 +17,6 @@ */ #include "dropshadoweffect.h" -#include "QtMath" - -static const int tileSize = 32; -template -static -inline void qt_memrotate90_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride) -{ - sstride /= sizeof(T); - dstride /= sizeof(T); - const int pack = sizeof(quint32) / sizeof(T); - const int unaligned = - qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h)); - const int restX = w % tileSize; - const int restY = (h - unaligned) % tileSize; - const int unoptimizedY = restY % pack; - const int numTilesX = w / tileSize + (restX > 0); - const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); - for (int tx = 0; tx < numTilesX; ++tx) { - const int startx = w - tx * tileSize - 1; - const int stopx = qMax(startx - tileSize, 0); - if (unaligned) { - for (int x = startx; x >= stopx; --x) { - T *d = dest + (w - x - 1) * dstride; - for (int y = 0; y < unaligned; ++y) { - *d++ = src[y * sstride + x]; - } - } - } - for (int ty = 0; ty < numTilesY; ++ty) { - const int starty = ty * tileSize + unaligned; - const int stopy = qMin(starty + tileSize, h - unoptimizedY); - for (int x = startx; x >= stopx; --x) { - quint32 *d = reinterpret_cast(dest + (w - x - 1) * dstride + starty); - for (int y = starty; y < stopy; y += pack) { - quint32 c = src[y * sstride + x]; - for (int i = 1; i < pack; ++i) { - const int shift = (sizeof(T) * 8 * i); - const T color = src[(y + i) * sstride + x]; - c |= color << shift; - } - *d++ = c; - } - } - } - if (unoptimizedY) { - const int starty = h - unoptimizedY; - for (int x = startx; x >= stopx; --x) { - T *d = dest + (w - x - 1) * dstride + starty; - for (int y = starty; y < h; ++y) { - *d++ = src[y * sstride + x]; - } - } - } - } -} -template -static -inline void qt_memrotate90_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest, - int dstride) -{ - const int numTilesX = (w + tileSize - 1) / tileSize; - const int numTilesY = (h + tileSize - 1) / tileSize; - for (int tx = 0; tx < numTilesX; ++tx) { - const int startx = w - tx * tileSize - 1; - const int stopx = qMax(startx - tileSize, 0); - for (int ty = 0; ty < numTilesY; ++ty) { - const int starty = ty * tileSize; - const int stopy = qMin(starty + tileSize, h); - for (int x = startx; x >= stopx; --x) { - T *d = (T *)((char*)dest + (w - x - 1) * dstride) + starty; - const char *s = (const char*)(src + x) + starty * sstride; - for (int y = starty; y < stopy; ++y) { - *d++ = *(const T *)(s); - s += sstride; - } - } - } - } -} -template -static -inline void qt_memrotate270_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride) -{ - sstride /= sizeof(T); - dstride /= sizeof(T); - const int pack = sizeof(quint32) / sizeof(T); - const int unaligned = - qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h)); - const int restX = w % tileSize; - const int restY = (h - unaligned) % tileSize; - const int unoptimizedY = restY % pack; - const int numTilesX = w / tileSize + (restX > 0); - const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); - for (int tx = 0; tx < numTilesX; ++tx) { - const int startx = tx * tileSize; - const int stopx = qMin(startx + tileSize, w); - if (unaligned) { - for (int x = startx; x < stopx; ++x) { - T *d = dest + x * dstride; - for (int y = h - 1; y >= h - unaligned; --y) { - *d++ = src[y * sstride + x]; - } - } - } - for (int ty = 0; ty < numTilesY; ++ty) { - const int starty = h - 1 - unaligned - ty * tileSize; - const int stopy = qMax(starty - tileSize, unoptimizedY); - for (int x = startx; x < stopx; ++x) { - quint32 *d = reinterpret_cast(dest + x * dstride - + h - 1 - starty); - for (int y = starty; y >= stopy; y -= pack) { - quint32 c = src[y * sstride + x]; - for (int i = 1; i < pack; ++i) { - const int shift = (sizeof(T) * 8 * i); - const T color = src[(y - i) * sstride + x]; - c |= color << shift; - } - *d++ = c; - } - } - } - if (unoptimizedY) { - const int starty = unoptimizedY - 1; - for (int x = startx; x < stopx; ++x) { - T *d = dest + x * dstride + h - 1 - starty; - for (int y = starty; y >= 0; --y) { - *d++ = src[y * sstride + x]; - } - } - } - } -} -template -static -inline void qt_memrotate270_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest, - int dstride) -{ - const int numTilesX = (w + tileSize - 1) / tileSize; - const int numTilesY = (h + tileSize - 1) / tileSize; - for (int tx = 0; tx < numTilesX; ++tx) { - const int startx = tx * tileSize; - const int stopx = qMin(startx + tileSize, w); - for (int ty = 0; ty < numTilesY; ++ty) { - const int starty = h - 1 - ty * tileSize; - const int stopy = qMax(starty - tileSize, 0); - for (int x = startx; x < stopx; ++x) { - T *d = (T*)((char*)dest + x * dstride) + h - 1 - starty; - const char *s = (const char*)(src + x) + starty * sstride; - for (int y = starty; y >= stopy; --y) { - *d++ = *(const T*)s; - s -= sstride; - } - } - } - } -} -template -static -inline void qt_memrotate90_template(const T *src, int srcWidth, int srcHeight, int srcStride, - T *dest, int dstStride) -{ - #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer - if (sizeof(quint32) % sizeof(T) == 0) - qt_memrotate90_tiled(src, srcWidth, srcHeight, srcStride, dest, dstStride); - else - #endif - qt_memrotate90_tiled_unpacked(src, srcWidth, srcHeight, srcStride, dest, dstStride); -} -template <> -inline void qt_memrotate90_template(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride) -{ - // packed algorithm doesn't have any benefit for quint32 - qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); -} -template <> -inline void qt_memrotate90_template(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride) -{ - qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); -} -template -static -inline void qt_memrotate180_template(const T *src, int w, int h, int sstride, T *dest, int dstride) -{ - const char *s = (const char*)(src) + (h - 1) * sstride; - for (int dy = 0; dy < h; ++dy) { - T *d = reinterpret_cast((char *)(dest) + dy * dstride); - src = reinterpret_cast(s); - for (int dx = 0; dx < w; ++dx) { - d[dx] = src[w - 1 - dx]; - } - s -= sstride; - } -} -template -static -inline void qt_memrotate270_template(const T *src, int srcWidth, int srcHeight, int srcStride, - T *dest, int dstStride) -{ - #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer - if (sizeof(quint32) % sizeof(T) == 0) - qt_memrotate270_tiled(src, srcWidth, srcHeight, srcStride, dest, dstStride); - else - #endif - qt_memrotate270_tiled_unpacked(src, srcWidth, srcHeight, srcStride, dest, dstStride); -} -template <> -inline void qt_memrotate270_template(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride) -{ - // packed algorithm doesn't have any benefit for quint32 - qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); -} -template <> -inline void qt_memrotate270_template(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride) -{ - qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); -} -#define QT_IMPL_MEMROTATE(type) \ -void qt_memrotate90(const type *src, int w, int h, int sstride, \ -type *dest, int dstride) \ -{ \ - qt_memrotate90_template(src, w, h, sstride, dest, dstride); \ -} \ -void qt_memrotate180(const type *src, int w, int h, int sstride, \ -type *dest, int dstride) \ -{ \ - qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ -} \ -void qt_memrotate270(const type *src, int w, int h, int sstride, \ -type *dest, int dstride) \ -{ \ - qt_memrotate270_template(src, w, h, sstride, dest, dstride); \ -} -#define QT_IMPL_SIMPLE_MEMROTATE(type) \ -void qt_memrotate90(const type *src, int w, int h, int sstride, \ -type *dest, int dstride) \ -{ \ - qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); \ -} \ -void qt_memrotate180(const type *src, int w, int h, int sstride, \ -type *dest, int dstride) \ -{ \ - qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ -} \ -void qt_memrotate270(const type *src, int w, int h, int sstride, \ -type *dest, int dstride) \ -{ \ - qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); \ -} -QT_IMPL_MEMROTATE(quint64) -QT_IMPL_MEMROTATE(quint32) -QT_IMPL_MEMROTATE(quint16) -QT_IMPL_MEMROTATE(quint8) -void qt_memrotate90_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate90(srcPixels, w, h, sbpl, destPixels, dbpl); -} -void qt_memrotate180_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate180(srcPixels, w, h, sbpl, destPixels, dbpl); -} -void qt_memrotate270_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate270(srcPixels, w, h, sbpl, destPixels, dbpl); -} -void qt_memrotate90_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate90((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); -} -void qt_memrotate180_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate180((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); -} -void qt_memrotate270_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate270((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); -} -void qt_memrotate90_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate90((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); -} -void qt_memrotate180_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate180((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); -} -void qt_memrotate270_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate270((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); -} -void qt_memrotate90_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate90((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); -} -void qt_memrotate180_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate180((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); -} -void qt_memrotate270_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) -{ - qt_memrotate270((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); -} - -#define AVG(a,b) ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) ) -#define AVG16(a,b) ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) ) -const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); - -QImage qt_halfScaled(const QImage &source) -{ - if (source.width() < 2 || source.height() < 2) - return QImage(); - - QImage srcImage = source; - - if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) { - // assumes grayscale - QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); - dest.setDevicePixelRatio(source.devicePixelRatioF()); - - const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); - qsizetype sx = srcImage.bytesPerLine(); - qsizetype sx2 = sx << 1; - - uchar *dst = reinterpret_cast(dest.bits()); - qsizetype dx = dest.bytesPerLine(); - int ww = dest.width(); - int hh = dest.height(); - - for (int y = hh; y; --y, dst += dx, src += sx2) { - const uchar *p1 = src; - const uchar *p2 = src + sx; - uchar *q = dst; - for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2) - *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2; - } - - return dest; - } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) { - QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); - dest.setDevicePixelRatio(source.devicePixelRatioF()); - - const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); - qsizetype sx = srcImage.bytesPerLine(); - qsizetype sx2 = sx << 1; - - uchar *dst = reinterpret_cast(dest.bits()); - qsizetype dx = dest.bytesPerLine(); - int ww = dest.width(); - int hh = dest.height(); - - for (int y = hh; y; --y, dst += dx, src += sx2) { - const uchar *p1 = src; - const uchar *p2 = src + sx; - uchar *q = dst; - for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) { - // alpha - q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3])); - // rgb - const quint16 p16_1 = (p1[2] << 8) | p1[1]; - const quint16 p16_2 = (p1[5] << 8) | p1[4]; - const quint16 p16_3 = (p2[2] << 8) | p2[1]; - const quint16 p16_4 = (p2[5] << 8) | p2[4]; - const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4)); - q[1] = result & 0xff; - q[2] = result >> 8; - } - } - - return dest; - } else if (source.format() != QImage::Format_ARGB32_Premultiplied - && source.format() != QImage::Format_RGB32) - { - srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied); - } - - QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); - dest.setDevicePixelRatio(source.devicePixelRatioF()); - - const quint32 *src = reinterpret_cast(const_cast(srcImage).bits()); - qsizetype sx = srcImage.bytesPerLine() >> 2; - qsizetype sx2 = sx << 1; - - quint32 *dst = reinterpret_cast(dest.bits()); - qsizetype dx = dest.bytesPerLine() >> 2; - int ww = dest.width(); - int hh = dest.height(); - - for (int y = hh; y; --y, dst += dx, src += sx2) { - const quint32 *p1 = src; - const quint32 *p2 = src + sx; - quint32 *q = dst; - for (int x = ww; x; --x, q++, p1 += 2, p2 += 2) - *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1])); - } - - return dest; -} - -template -inline int qt_static_shift(int value) -{ - if (shift == 0) - return value; - else if (shift > 0) - return value << (uint(shift) & 0x1f); - else - return value >> (uint(-shift) & 0x1f); -} - -template -inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) -{ - QRgb *pixel = (QRgb *)bptr; - - #define Z_MASK (0xff << zprec) - const int A_zprec = qt_static_shift(*pixel) & Z_MASK; - const int R_zprec = qt_static_shift(*pixel) & Z_MASK; - const int G_zprec = qt_static_shift(*pixel) & Z_MASK; - const int B_zprec = qt_static_shift(*pixel) & Z_MASK; - #undef Z_MASK - - const int zR_zprec = zR >> aprec; - const int zG_zprec = zG >> aprec; - const int zB_zprec = zB >> aprec; - const int zA_zprec = zA >> aprec; - - zR += alpha * (R_zprec - zR_zprec); - zG += alpha * (G_zprec - zG_zprec); - zB += alpha * (B_zprec - zB_zprec); - zA += alpha * (A_zprec - zA_zprec); - - #define ZA_MASK (0xff << (zprec + aprec)) - *pixel = - qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK) - | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK) - | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK) - | qt_static_shift<-zprec - aprec>(zB & ZA_MASK); - #undef ZA_MASK -} - -template -inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha) -{ - const int A_zprec = int(*(bptr)) << zprec; - const int z_zprec = z >> aprec; - z += alpha * (A_zprec - z_zprec); - *(bptr) = z >> (zprec + aprec); -} - -template -inline void qt_blurrow(QImage & im, int line, int alpha) -{ - uchar *bptr = im.scanLine(line); - - int zR = 0, zG = 0, zB = 0, zA = 0; - - if (alphaOnly && im.format() != QImage::Format_Indexed8) - bptr += alphaIndex; - - const int stride = im.depth() >> 3; - const int im_width = im.width(); - for (int index = 0; index < im_width; ++index) { - if (alphaOnly) - qt_blurinner_alphaOnly(bptr, zA, alpha); - else - qt_blurinner(bptr, zR, zG, zB, zA, alpha); - bptr += stride; - } - - bptr -= stride; - - for (int index = im_width - 2; index >= 0; --index) { - bptr -= stride; - if (alphaOnly) - qt_blurinner_alphaOnly(bptr, zA, alpha); - else - qt_blurinner(bptr, zR, zG, zB, zA, alpha); - } -} - -template -void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0) -{ - // halve the radius if we're using two passes - if (improvedQuality) - radius *= qreal(0.5); - - Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied - || img.format() == QImage::Format_RGB32 - || img.format() == QImage::Format_Indexed8 - || img.format() == QImage::Format_Grayscale8); - - // choose the alpha such that pixels at radius distance from a fully - // saturated pixel will have an alpha component of no greater than - // the cutOffIntensity - const qreal cutOffIntensity = 2; - int alpha = radius <= qreal(1e-5) - ? ((1 << aprec)-1) - : qRound((1<(img, row, alpha); - } - - QImage temp(img.height(), img.width(), img.format()); - temp.setDevicePixelRatio(img.devicePixelRatioF()); - if (transposed >= 0) { - if (img.depth() == 8) { - qt_memrotate270(reinterpret_cast(img.bits()), - img.width(), img.height(), img.bytesPerLine(), - reinterpret_cast(temp.bits()), - temp.bytesPerLine()); - } else { - qt_memrotate270(reinterpret_cast(img.bits()), - img.width(), img.height(), img.bytesPerLine(), - reinterpret_cast(temp.bits()), - temp.bytesPerLine()); - } - } else { - if (img.depth() == 8) { - qt_memrotate90(reinterpret_cast(img.bits()), - img.width(), img.height(), img.bytesPerLine(), - reinterpret_cast(temp.bits()), - temp.bytesPerLine()); - } else { - qt_memrotate90(reinterpret_cast(img.bits()), - img.width(), img.height(), img.bytesPerLine(), - reinterpret_cast(temp.bits()), - temp.bytesPerLine()); - } - } - - img_height = temp.height(); - for (int row = 0; row < img_height; ++row) { - for (int i = 0; i <= int(improvedQuality); ++i) - qt_blurrow(temp, row, alpha); - } - - if (transposed == 0) { - if (img.depth() == 8) { - qt_memrotate90(reinterpret_cast(temp.bits()), - temp.width(), temp.height(), temp.bytesPerLine(), - reinterpret_cast(img.bits()), - img.bytesPerLine()); - } else { - qt_memrotate90(reinterpret_cast(temp.bits()), - temp.width(), temp.height(), temp.bytesPerLine(), - reinterpret_cast(img.bits()), - img.bytesPerLine()); - } - } else { - img = temp; - } -} PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {} PixmapFilter::~PixmapFilter(){} @@ -640,7 +84,7 @@ void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap tmpPainter.fillRect(shadow, mColor); } - expblur<12, 10, false>(tmp, mRadius, false, 0); + Utils::exponentialblur(tmp, mRadius, false, 0); tmpPainter.end(); // Draw the actual pixmap... diff --git a/ui/utils/dropshadoweffect.h b/ui/utils/dropshadoweffect.h index b2768b7..f374ce3 100644 --- a/ui/utils/dropshadoweffect.h +++ b/ui/utils/dropshadoweffect.h @@ -24,6 +24,8 @@ #include #include +#include "eb.h" + class PixmapFilter : public QObject { Q_OBJECT diff --git a/ui/utils/eb.cpp b/ui/utils/eb.cpp new file mode 100644 index 0000000..f44e53b --- /dev/null +++ b/ui/utils/eb.cpp @@ -0,0 +1,579 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 + * (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, see . + */ + +#include "eb.h" + +static const int tileSize = 32; +template +static +inline void qt_memrotate90_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride) +{ + sstride /= sizeof(T); + dstride /= sizeof(T); + const int pack = sizeof(quint32) / sizeof(T); + const int unaligned = + qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = w - tx * tileSize - 1; + const int stopx = qMax(startx - tileSize, 0); + if (unaligned) { + for (int x = startx; x >= stopx; --x) { + T *d = dest + (w - x - 1) * dstride; + for (int y = 0; y < unaligned; ++y) { + *d++ = src[y * sstride + x]; + } + } + } + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = ty * tileSize + unaligned; + const int stopy = qMin(starty + tileSize, h - unoptimizedY); + for (int x = startx; x >= stopx; --x) { + quint32 *d = reinterpret_cast(dest + (w - x - 1) * dstride + starty); + for (int y = starty; y < stopy; y += pack) { + quint32 c = src[y * sstride + x]; + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(T) * 8 * i); + const T color = src[(y + i) * sstride + x]; + c |= color << shift; + } + *d++ = c; + } + } + } + if (unoptimizedY) { + const int starty = h - unoptimizedY; + for (int x = startx; x >= stopx; --x) { + T *d = dest + (w - x - 1) * dstride + starty; + for (int y = starty; y < h; ++y) { + *d++ = src[y * sstride + x]; + } + } + } + } +} +template +static +inline void qt_memrotate90_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest, + int dstride) +{ + const int numTilesX = (w + tileSize - 1) / tileSize; + const int numTilesY = (h + tileSize - 1) / tileSize; + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = w - tx * tileSize - 1; + const int stopx = qMax(startx - tileSize, 0); + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = ty * tileSize; + const int stopy = qMin(starty + tileSize, h); + for (int x = startx; x >= stopx; --x) { + T *d = (T *)((char*)dest + (w - x - 1) * dstride) + starty; + const char *s = (const char*)(src + x) + starty * sstride; + for (int y = starty; y < stopy; ++y) { + *d++ = *(const T *)(s); + s += sstride; + } + } + } + } +} +template +static +inline void qt_memrotate270_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride) +{ + sstride /= sizeof(T); + dstride /= sizeof(T); + const int pack = sizeof(quint32) / sizeof(T); + const int unaligned = + qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = tx * tileSize; + const int stopx = qMin(startx + tileSize, w); + if (unaligned) { + for (int x = startx; x < stopx; ++x) { + T *d = dest + x * dstride; + for (int y = h - 1; y >= h - unaligned; --y) { + *d++ = src[y * sstride + x]; + } + } + } + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = h - 1 - unaligned - ty * tileSize; + const int stopy = qMax(starty - tileSize, unoptimizedY); + for (int x = startx; x < stopx; ++x) { + quint32 *d = reinterpret_cast(dest + x * dstride + + h - 1 - starty); + for (int y = starty; y >= stopy; y -= pack) { + quint32 c = src[y * sstride + x]; + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(T) * 8 * i); + const T color = src[(y - i) * sstride + x]; + c |= color << shift; + } + *d++ = c; + } + } + } + if (unoptimizedY) { + const int starty = unoptimizedY - 1; + for (int x = startx; x < stopx; ++x) { + T *d = dest + x * dstride + h - 1 - starty; + for (int y = starty; y >= 0; --y) { + *d++ = src[y * sstride + x]; + } + } + } + } +} +template +static +inline void qt_memrotate270_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest, + int dstride) +{ + const int numTilesX = (w + tileSize - 1) / tileSize; + const int numTilesY = (h + tileSize - 1) / tileSize; + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = tx * tileSize; + const int stopx = qMin(startx + tileSize, w); + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = h - 1 - ty * tileSize; + const int stopy = qMax(starty - tileSize, 0); + for (int x = startx; x < stopx; ++x) { + T *d = (T*)((char*)dest + x * dstride) + h - 1 - starty; + const char *s = (const char*)(src + x) + starty * sstride; + for (int y = starty; y >= stopy; --y) { + *d++ = *(const T*)s; + s -= sstride; + } + } + } + } +} +template +static +inline void qt_memrotate90_template(const T *src, int srcWidth, int srcHeight, int srcStride, + T *dest, int dstStride) +{ + #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer + if (sizeof(quint32) % sizeof(T) == 0) + qt_memrotate90_tiled(src, srcWidth, srcHeight, srcStride, dest, dstStride); + else + #endif + qt_memrotate90_tiled_unpacked(src, srcWidth, srcHeight, srcStride, dest, dstStride); +} +template <> +inline void qt_memrotate90_template(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride) +{ + // packed algorithm doesn't have any benefit for quint32 + qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +template <> +inline void qt_memrotate90_template(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride) +{ + qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +template +static +inline void qt_memrotate180_template(const T *src, int w, int h, int sstride, T *dest, int dstride) +{ + const char *s = (const char*)(src) + (h - 1) * sstride; + for (int dy = 0; dy < h; ++dy) { + T *d = reinterpret_cast((char *)(dest) + dy * dstride); + src = reinterpret_cast(s); + for (int dx = 0; dx < w; ++dx) { + d[dx] = src[w - 1 - dx]; + } + s -= sstride; + } +} +template +static +inline void qt_memrotate270_template(const T *src, int srcWidth, int srcHeight, int srcStride, + T *dest, int dstStride) +{ + #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer + if (sizeof(quint32) % sizeof(T) == 0) + qt_memrotate270_tiled(src, srcWidth, srcHeight, srcStride, dest, dstStride); + else + #endif + qt_memrotate270_tiled_unpacked(src, srcWidth, srcHeight, srcStride, dest, dstStride); +} +template <> +inline void qt_memrotate270_template(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride) +{ + // packed algorithm doesn't have any benefit for quint32 + qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +template <> +inline void qt_memrotate270_template(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride) +{ + qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +#define QT_IMPL_MEMROTATE(type) \ +void qt_memrotate90(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate90_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate180(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate270(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate270_template(src, w, h, sstride, dest, dstride); \ +} +#define QT_IMPL_SIMPLE_MEMROTATE(type) \ +void qt_memrotate90(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate180(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate270(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); \ +} +QT_IMPL_MEMROTATE(quint64) +QT_IMPL_MEMROTATE(quint32) +QT_IMPL_MEMROTATE(quint16) +QT_IMPL_MEMROTATE(quint8) +void qt_memrotate90_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90(srcPixels, w, h, sbpl, destPixels, dbpl); +} +void qt_memrotate180_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180(srcPixels, w, h, sbpl, destPixels, dbpl); +} +void qt_memrotate270_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270(srcPixels, w, h, sbpl, destPixels, dbpl); +} +void qt_memrotate90_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} +void qt_memrotate180_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} +void qt_memrotate270_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} +void qt_memrotate90_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} +void qt_memrotate180_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} +void qt_memrotate270_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} +void qt_memrotate90_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); +} +void qt_memrotate180_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); +} +void qt_memrotate270_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); +} + +#define AVG(a,b) ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) ) +#define AVG16(a,b) ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) ) +const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); + +QImage qt_halfScaled(const QImage &source) +{ + if (source.width() < 2 || source.height() < 2) + return QImage(); + + QImage srcImage = source; + + if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) { + // assumes grayscale + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + dest.setDevicePixelRatio(source.devicePixelRatioF()); + + const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); + qsizetype sx = srcImage.bytesPerLine(); + qsizetype sx2 = sx << 1; + + uchar *dst = reinterpret_cast(dest.bits()); + qsizetype dx = dest.bytesPerLine(); + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const uchar *p1 = src; + const uchar *p2 = src + sx; + uchar *q = dst; + for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2) + *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2; + } + + return dest; + } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) { + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + dest.setDevicePixelRatio(source.devicePixelRatioF()); + + const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); + qsizetype sx = srcImage.bytesPerLine(); + qsizetype sx2 = sx << 1; + + uchar *dst = reinterpret_cast(dest.bits()); + qsizetype dx = dest.bytesPerLine(); + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const uchar *p1 = src; + const uchar *p2 = src + sx; + uchar *q = dst; + for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) { + // alpha + q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3])); + // rgb + const quint16 p16_1 = (p1[2] << 8) | p1[1]; + const quint16 p16_2 = (p1[5] << 8) | p1[4]; + const quint16 p16_3 = (p2[2] << 8) | p2[1]; + const quint16 p16_4 = (p2[5] << 8) | p2[4]; + const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4)); + q[1] = result & 0xff; + q[2] = result >> 8; + } + } + + return dest; + } else if (source.format() != QImage::Format_ARGB32_Premultiplied + && source.format() != QImage::Format_RGB32) + { + srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + dest.setDevicePixelRatio(source.devicePixelRatioF()); + + const quint32 *src = reinterpret_cast(const_cast(srcImage).bits()); + qsizetype sx = srcImage.bytesPerLine() >> 2; + qsizetype sx2 = sx << 1; + + quint32 *dst = reinterpret_cast(dest.bits()); + qsizetype dx = dest.bytesPerLine() >> 2; + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const quint32 *p1 = src; + const quint32 *p2 = src + sx; + quint32 *q = dst; + for (int x = ww; x; --x, q++, p1 += 2, p2 += 2) + *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1])); + } + + return dest; +} + +template +inline int qt_static_shift(int value) +{ + if (shift == 0) + return value; + else if (shift > 0) + return value << (uint(shift) & 0x1f); + else + return value >> (uint(-shift) & 0x1f); +} + +template +inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) +{ + QRgb *pixel = (QRgb *)bptr; + + #define Z_MASK (0xff << zprec) + const int A_zprec = qt_static_shift(*pixel) & Z_MASK; + const int R_zprec = qt_static_shift(*pixel) & Z_MASK; + const int G_zprec = qt_static_shift(*pixel) & Z_MASK; + const int B_zprec = qt_static_shift(*pixel) & Z_MASK; + #undef Z_MASK + + const int zR_zprec = zR >> aprec; + const int zG_zprec = zG >> aprec; + const int zB_zprec = zB >> aprec; + const int zA_zprec = zA >> aprec; + + zR += alpha * (R_zprec - zR_zprec); + zG += alpha * (G_zprec - zG_zprec); + zB += alpha * (B_zprec - zB_zprec); + zA += alpha * (A_zprec - zA_zprec); + + #define ZA_MASK (0xff << (zprec + aprec)) + *pixel = + qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK) + | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK) + | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK) + | qt_static_shift<-zprec - aprec>(zB & ZA_MASK); + #undef ZA_MASK +} + +template +inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha) +{ + const int A_zprec = int(*(bptr)) << zprec; + const int z_zprec = z >> aprec; + z += alpha * (A_zprec - z_zprec); + *(bptr) = z >> (zprec + aprec); +} + +template +inline void qt_blurrow(QImage & im, int line, int alpha) +{ + uchar *bptr = im.scanLine(line); + + int zR = 0, zG = 0, zB = 0, zA = 0; + + if (alphaOnly && im.format() != QImage::Format_Indexed8) + bptr += alphaIndex; + + const int stride = im.depth() >> 3; + const int im_width = im.width(); + for (int index = 0; index < im_width; ++index) { + if (alphaOnly) + qt_blurinner_alphaOnly(bptr, zA, alpha); + else + qt_blurinner(bptr, zR, zG, zB, zA, alpha); + bptr += stride; + } + + bptr -= stride; + + for (int index = im_width - 2; index >= 0; --index) { + bptr -= stride; + if (alphaOnly) + qt_blurinner_alphaOnly(bptr, zA, alpha); + else + qt_blurinner(bptr, zR, zG, zB, zA, alpha); + } +} + +template +void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0) +{ + // halve the radius if we're using two passes + if (improvedQuality) + radius *= qreal(0.5); + + Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied + || img.format() == QImage::Format_RGB32 + || img.format() == QImage::Format_Indexed8 + || img.format() == QImage::Format_Grayscale8); + + // choose the alpha such that pixels at radius distance from a fully + // saturated pixel will have an alpha component of no greater than + // the cutOffIntensity + const qreal cutOffIntensity = 2; + int alpha = radius <= qreal(1e-5) + ? ((1 << aprec)-1) + : qRound((1<(img, row, alpha); + } + + QImage temp(img.height(), img.width(), img.format()); + temp.setDevicePixelRatio(img.devicePixelRatioF()); + if (transposed >= 0) { + if (img.depth() == 8) { + qt_memrotate270(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } else { + qt_memrotate270(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } + } else { + if (img.depth() == 8) { + qt_memrotate90(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } else { + qt_memrotate90(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } + } + + img_height = temp.height(); + for (int row = 0; row < img_height; ++row) { + for (int i = 0; i <= int(improvedQuality); ++i) + qt_blurrow(temp, row, alpha); + } + + if (transposed == 0) { + if (img.depth() == 8) { + qt_memrotate90(reinterpret_cast(temp.bits()), + temp.width(), temp.height(), temp.bytesPerLine(), + reinterpret_cast(img.bits()), + img.bytesPerLine()); + } else { + qt_memrotate90(reinterpret_cast(temp.bits()), + temp.width(), temp.height(), temp.bytesPerLine(), + reinterpret_cast(img.bits()), + img.bytesPerLine()); + } + } else { + img = temp; + } +} + +void Utils::exponentialblur(QImage& img, qreal radius, bool improvedQuality, int transposed) +{ + expblur<12, 10, false>(img, radius, improvedQuality, transposed); +} diff --git a/ui/utils/eb.h b/ui/utils/eb.h new file mode 100644 index 0000000..665a9ee --- /dev/null +++ b/ui/utils/eb.h @@ -0,0 +1,34 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 + * (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, see . + */ + +#ifndef EB_H +#define EB_H + +#include +#include +#include + +/** + * @todo write docs + */ + +namespace Utils { + void exponentialblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0); +}; + +#endif // EB_H diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp index 45cb723..58bcc45 100644 --- a/ui/utils/feedview.cpp +++ b/ui/utils/feedview.cpp @@ -25,6 +25,7 @@ #include "messagedelegate.h" #include "ui/models/messagefeed.h" +#include "eb.h" constexpr int maxMessageHeight = 10000; constexpr int approximateSingleMessageHeight = 20; @@ -288,6 +289,10 @@ void FeedView::paintEvent(QPaintEvent* event) del->endClearWidgets(); clearWidgetsMode = false; } + + if (event->rect().height() == vp->height()) { + // draw the blurred drop shadow... + } } void FeedView::verticalScrollbarValueChanged(int value) @@ -300,6 +305,10 @@ void FeedView::verticalScrollbarValueChanged(int value) clearWidgetsMode = true; } + if (modelState == Models::MessageFeed::incomplete && value < progressSize) { + model()->fetchMore(rootIndex()); + } + QAbstractItemView::verticalScrollbarValueChanged(vo); } @@ -317,6 +326,7 @@ void FeedView::resizeEvent(QResizeEvent* event) QAbstractItemView::resizeEvent(event); positionProgress(); + emit resized(); } void FeedView::positionProgress() diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h index 2789464..0b7e7d9 100644 --- a/ui/utils/feedview.h +++ b/ui/utils/feedview.h @@ -49,6 +49,9 @@ public: QFont getFont() const; +signals: + void resized(); + public slots: protected slots: diff --git a/ui/utils/shadowoverlay.cpp b/ui/utils/shadowoverlay.cpp new file mode 100644 index 0000000..3c28a15 --- /dev/null +++ b/ui/utils/shadowoverlay.cpp @@ -0,0 +1,91 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 + * (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, see . + */ + +#include "shadowoverlay.h" + +ShadowOverlay::ShadowOverlay(unsigned int r, unsigned int t, const QColor& c, QWidget* parent): + QWidget(parent), + top(false), + right(false), + bottom(false), + left(false), + thickness(t), + radius(r), + color(c), + shadow(1, 1, QImage::Format_ARGB32_Premultiplied) +{ + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void ShadowOverlay::paintEvent(QPaintEvent* event) +{ + QWidget::paintEvent(event); + + QPainter painter(this); + + painter.drawImage(0, 0, shadow); +} + +void ShadowOverlay::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + + updateImage(); +} + +void ShadowOverlay::updateImage() +{ + int w = width(); + int h = height(); + shadow = QImage({w, h + int(thickness)}, QImage::Format_ARGB32_Premultiplied); + shadow.fill(0); + + QPainter tmpPainter(&shadow); + tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); + if (top) { + QRectF shadow(0, 0, w, thickness); + tmpPainter.fillRect(shadow, color); + } + if (right) { + QRectF shadow(w - thickness, 0, thickness, h); + tmpPainter.fillRect(shadow, color); + } + if (bottom) { + QRectF shadow(0, h - thickness, w, thickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space + tmpPainter.fillRect(shadow, color); + } + if (left) { + QRectF shadow(0, 0, thickness, h); + tmpPainter.fillRect(shadow, color); + } + + Utils::exponentialblur(shadow, radius, false, 0); + tmpPainter.end(); +} + +void ShadowOverlay::setFrames(bool t, bool r, bool b, bool l) +{ + top = t; + right = r; + bottom = b; + left = l; + + updateImage(); + update(); +} diff --git a/ui/utils/shadowoverlay.h b/ui/utils/shadowoverlay.h new file mode 100644 index 0000000..36aa5d5 --- /dev/null +++ b/ui/utils/shadowoverlay.h @@ -0,0 +1,58 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 + * (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, see . + */ + +#ifndef SHADOWOVERLAY_H +#define SHADOWOVERLAY_H + +#include +#include +#include +#include +#include +#include + +#include + +/** + * @todo write docs + */ +class ShadowOverlay : public QWidget { + +public: + ShadowOverlay(unsigned int radius = 10, unsigned int thickness = 1, const QColor& color = Qt::black, QWidget* parent = nullptr); + + void setFrames(bool top, bool right, bool bottom, bool left); + +protected: + void updateImage(); + + void paintEvent(QPaintEvent * event) override; + void resizeEvent(QResizeEvent * event) override; + +private: + bool top; + bool right; + bool bottom; + bool left; + unsigned int thickness; + unsigned int radius; + QColor color; + QImage shadow; +}; + +#endif // SHADOWOVERLAY_H diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index 0a21f04..830fee6 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -26,5 +26,6 @@ add_library(squawkWidgets ${squawkWidgets_SRC}) # Use the Widgets module from Qt 5. target_link_libraries(squawkWidgets vCardUI) target_link_libraries(squawkWidgets Qt5::Widgets) +target_link_libraries(squawkWidgets squawkUI) qt5_use_modules(squawkWidgets Core Widgets) diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index b8141c5..39f6837 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -46,19 +46,23 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, filesToAttach(), feed(new FeedView()), delegate(new MessageDelegate(this)), - scroll(down), manualSliderChange(false), - tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1) + tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1), + shadow(10, 1, Qt::black, this) { m_ui->setupUi(this); + shadow.setFrames(true, false, true, false); + feed->setItemDelegate(delegate); + feed->setFrameShape(QFrame::NoFrame); delegate->initializeFonts(feed->getFont()); feed->setModel(el->feed); el->feed->incrementObservers(); m_ui->widget->layout()->addWidget(feed); connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage); + connect(feed, &FeedView::resized, this, &Conversation::positionShadow); connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged); @@ -77,9 +81,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, m_ui->messageEditor->installEventFilter(&ker); - //QScrollBar* vs = m_ui->scrollArea->verticalScrollBar(); - //m_ui->scrollArea->setWidget(line); - //vs->installEventFilter(&vis); //line->setAutoFillBackground(false); //if (testAttribute(Qt::WA_TranslucentBackground)) { @@ -93,28 +94,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, //line->setMyAvatarPath(acc->getAvatarPath()); //line->setMyName(acc->getName()); - QGridLayout* gr = static_cast(layout()); - QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message")); - gr->addWidget(overlay, 0, 0, 2, 1); - QVBoxLayout* nl = new QVBoxLayout(); - QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect(); - opacity->setOpacity(0.8); - overlay->setLayout(nl); - overlay->setBackgroundRole(QPalette::Base); - overlay->setAutoFillBackground(true); - overlay->setGraphicsEffect(opacity); - progressLabel->setAlignment(Qt::AlignCenter); - QFont pf = progressLabel->font(); - pf.setBold(true); - pf.setPointSize(26); - progressLabel->setWordWrap(true); - progressLabel->setFont(pf); - nl->addStretch(); - nl->addWidget(progressLabel); - nl->addStretch(); - overlay->hide(); - - applyVisualEffects(); + initializeOverlay(); } Conversation::~Conversation() @@ -136,14 +116,28 @@ void Conversation::onAccountChanged(Models::Item* item, int row, int col) } } -void Conversation::applyVisualEffects() +void Conversation::initializeOverlay() { -// DropShadowEffect *e1 = new DropShadowEffect; -// e1->setBlurRadius(10); -// e1->setColor(Qt::black); -// e1->setThickness(1); -// e1->setFrame(true, false, true, false); -// m_ui->scrollArea->setGraphicsEffect(e1); + QGridLayout* gr = static_cast(layout()); + QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message")); + gr->addWidget(overlay, 0, 0, 2, 1); + QVBoxLayout* nl = new QVBoxLayout(); + QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect(); + opacity->setOpacity(0.8); + overlay->setLayout(nl); + overlay->setBackgroundRole(QPalette::Base); + overlay->setAutoFillBackground(true); + overlay->setGraphicsEffect(opacity); + progressLabel->setAlignment(Qt::AlignCenter); + QFont pf = progressLabel->font(); + pf.setBold(true); + pf.setPointSize(26); + progressLabel->setWordWrap(true); + progressLabel->setFont(pf); + nl->addStretch(); + nl->addWidget(progressLabel); + nl->addStretch(); + overlay->hide(); } void Conversation::setName(const QString& name) @@ -325,7 +319,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size) void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left) { - //static_cast(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left); + shadow.setFrames(top, right, bottom, left); } void Conversation::dragEnterEvent(QDragEnterEvent* event) @@ -399,3 +393,13 @@ void Conversation::onMessage(const Shared::Message& msg) } } } + +void Conversation::positionShadow() +{ + int w = width(); + int h = feed->height(); + + shadow.resize(w, h); + shadow.move(feed->pos()); + shadow.raise(); +} diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 1c81d31..5f98d8a 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -34,6 +34,7 @@ #include "ui/utils/badge.h" #include "ui/utils/feedview.h" #include "ui/utils/messagedelegate.h" +#include "ui/utils/shadowoverlay.h" #include "shared/icons.h" #include "shared/utils.h" @@ -81,7 +82,6 @@ signals: protected: virtual void setName(const QString& name); - void applyVisualEffects(); virtual Shared::Message createMessage() const; void setStatus(const QString& status); void addAttachedFile(const QString& path); @@ -90,6 +90,7 @@ protected: void dragEnterEvent(QDragEnterEvent* event) override; void dragLeaveEvent(QDragLeaveEvent* event) override; void dropEvent(QDropEvent* event) override; + void initializeOverlay(); virtual void onMessage(const Shared::Message& msg); protected slots: @@ -101,16 +102,12 @@ protected slots: void onTextEditDocSizeChanged(const QSizeF& size); void onAccountChanged(Models::Item* item, int row, int col); void onFeedMessage(const Shared::Message& msg); + void positionShadow(); public: const bool isMuc; protected: - enum Scroll { - nothing, - keep, - down - }; Models::Account* account; Models::Element* element; QString palJid; @@ -125,9 +122,10 @@ protected: W::Order filesToAttach; FeedView* feed; MessageDelegate* delegate; - Scroll scroll; bool manualSliderChange; bool tsb; //transient scroll bars + + ShadowOverlay shadow; }; #endif // CONVERSATION_H