Updated lib_ui sources to TDesktop version 3.5.2+95e806c

This commit is contained in:
Eric Kotato 2022-02-15 02:35:19 +03:00
commit bbd3af01c9
72 changed files with 1945 additions and 1219 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 B

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 B

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 B

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 B

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

After

Width:  |  Height:  |  Size: 813 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -26,7 +26,9 @@ AbstractButton::AbstractButton(QWidget *parent) : RpWidget(parent) {
}
void AbstractButton::leaveEventHook(QEvent *e) {
if (_state & StateFlag::Down) return;
if (_state & StateFlag::Down) {
return;
}
setOver(false, StateChangeSource::ByHover);
return TWidget::leaveEventHook(e);
@ -48,12 +50,13 @@ void AbstractButton::checkIfOver(QPoint localPos) {
void AbstractButton::mousePressEvent(QMouseEvent *e) {
checkIfOver(e->pos());
if (_acceptBoth || (e->buttons() & Qt::LeftButton)) {
if ((_state & StateFlag::Over) && !(_state & StateFlag::Down)) {
auto was = _state;
_state |= StateFlag::Down;
onStateChanged(was, StateChangeSource::ByPress);
if (_state & StateFlag::Over) {
const auto set = setDown(
true,
StateChangeSource::ByPress,
e->modifiers(),
e->button());
if (set) {
e->accept();
}
}
@ -68,21 +71,13 @@ void AbstractButton::mouseMoveEvent(QMouseEvent *e) {
}
void AbstractButton::mouseReleaseEvent(QMouseEvent *e) {
if (_state & StateFlag::Down) {
const auto was = _state;
_state &= ~State(StateFlag::Down);
const auto weak = MakeWeak(this);
onStateChanged(was, StateChangeSource::ByPress);
if (!weak) {
return;
}
if (was & StateFlag::Over) {
clicked(e->modifiers(), e->button());
} else {
setOver(false, StateChangeSource::ByHover);
}
const auto set = setDown(
false,
StateChangeSource::ByPress,
e->modifiers(),
e->button());
if (set) {
e->accept();
}
}
@ -123,6 +118,36 @@ void AbstractButton::setOver(bool over, StateChangeSource source) {
updateCursor();
}
bool AbstractButton::setDown(
bool down,
StateChangeSource source,
Qt::KeyboardModifiers modifiers,
Qt::MouseButton button) {
if (down
&& !(_state & StateFlag::Down)
&& (_acceptBoth || button == Qt::LeftButton)) {
auto was = _state;
_state |= StateFlag::Down;
onStateChanged(was, source);
return true;
} else if (!down && (_state & StateFlag::Down)) {
const auto was = _state;
_state &= ~State(StateFlag::Down);
const auto weak = MakeWeak(this);
onStateChanged(was, source);
if (weak) {
if (was & StateFlag::Over) {
clicked(modifiers, button);
} else {
setOver(false, source);
}
}
return true;
}
return false;
}
void AbstractButton::updateCursor() {
auto pointerCursor = _enablePointerCursor && (_state & StateFlag::Over);
setCursor(pointerCursor ? style::cur_pointer : style::cur_default);

View file

@ -80,6 +80,11 @@ protected:
ByHover = 0x02,
};
void setOver(bool over, StateChangeSource source = StateChangeSource::ByUser);
bool setDown(
bool down,
StateChangeSource source,
Qt::KeyboardModifiers modifiers,
Qt::MouseButton button);
virtual void onStateChanged(State was, StateChangeSource source) {
}

View file

@ -10,7 +10,7 @@
#include "ui/text/text_entity.h"
#include "ui/integration.h"
#include "base/qthelp_url.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_string_view.h"
#include <QtCore/QUrl>
#include <QtCore/QRegularExpression>

View file

@ -14,6 +14,7 @@ namespace anim {
namespace {
rpl::variable<bool> AnimationsDisabled = false;
int SlowMultiplierMinusOne/* = 0*/;
} // namespace
@ -76,6 +77,16 @@ void SetDisabled(bool disabled) {
AnimationsDisabled = disabled;
}
int SlowMultiplier() {
return (SlowMultiplierMinusOne + 1);
}
void SetSlowMultiplier(int multiplier) {
Expects(multiplier > 0);
SlowMultiplierMinusOne = multiplier - 1;
}
void DrawStaticLoading(
QPainter &p,
QRectF rect,

View file

@ -351,6 +351,8 @@ QPainterPath path(QPointF (&from)[N]) {
rpl::producer<bool> Disables();
bool Disabled();
void SetDisabled(bool disabled);
int SlowMultiplier();
void SetSlowMultiplier(int multiplier); // 1 - default, 10 - slow x10.
void DrawStaticLoading(
QPainter &p,

View file

@ -410,7 +410,7 @@ inline void Simple::startPrepared(
anim::transition transition) {
_data->from = _data->value;
_data->delta = to - _data->from;
_data->duration = duration;
_data->duration = duration * anim::SlowMultiplier();
_data->transition = transition;
_data->animation.start();
}

View file

@ -8,7 +8,7 @@
#include "base/basic_types.h"
#include "base/binary_guard.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_string_view.h"
#include "emoji.h"
#include <QtGui/QPainter>

View file

@ -9,6 +9,7 @@
#include "ui/gl/gl_shader.h"
#include "ui/integration.h"
#include "base/debug_log.h"
#include "base/options.h"
#include <QtCore/QSet>
#include <QtCore/QFile>
@ -34,6 +35,14 @@ bool ForceDisabled/* = false*/;
ANGLE ResolvedANGLE = ANGLE::Auto;
#endif // Q_OS_WIN
base::options::toggle AllowLinuxNvidiaOpenGL({
.id = kOptionAllowLinuxNvidiaOpenGL,
.name = "Allow OpenGL on the NVIDIA drivers (Linux)",
.description = "Qt+OpenGL have problems on Linux with NVIDIA drivers.",
.scope = base::options::linux,
.restartRequired = true,
});
void CrashCheckStart() {
auto f = QFile(Integration::Instance().openglCheckFilePath());
if (f.open(QIODevice::WriteOnly)) {
@ -44,6 +53,8 @@ void CrashCheckStart() {
} // namespace
const char kOptionAllowLinuxNvidiaOpenGL[] = "allow-linux-nvidia-opengl";
Capabilities CheckCapabilities(QWidget *widget) {
if (ForceDisabled) {
LOG_ONCE(("OpenGL: Force-disabled."));
@ -169,8 +180,12 @@ Capabilities CheckCapabilities(QWidget *widget) {
#ifdef Q_OS_LINUX
if (version && QByteArray(version).contains("NVIDIA")) {
// https://github.com/telegramdesktop/tdesktop/issues/16830
LOG_ONCE(("OpenGL: Disable on NVIDIA driver on Linux."));
return false;
if (AllowLinuxNvidiaOpenGL.value()) {
LOG_ONCE(("OpenGL: Allow on NVIDIA driver (experimental)."));
} else {
LOG_ONCE(("OpenGL: Disable on NVIDIA driver on Linux."));
return false;
}
}
#endif // Q_OS_LINUX

View file

@ -12,6 +12,8 @@ class QOpenGLContext;
namespace Ui::GL {
extern const char kOptionAllowLinuxNvidiaOpenGL[];
enum class Backend {
Raster,
OpenGL,

View file

@ -18,6 +18,7 @@
#include "zlib.h"
#include <QtCore/QFile>
#include <QtCore/QBuffer>
#include <QtCore/QMutex>
#include <QtGui/QImageReader>
#include <QtSvg/QSvgRenderer>
@ -27,19 +28,26 @@ namespace {
// They should be smaller.
constexpr auto kMaxGzipFileSize = 5 * 1024 * 1024;
TG_FORCE_INLINE uint64 blurGetColors(const uchar *p) {
return (uint64)p[0] + ((uint64)p[1] << 16) + ((uint64)p[2] << 32) + ((uint64)p[3] << 48);
TG_FORCE_INLINE uint64 BlurGetColors(const uchar *p) {
return (uint64)p[0]
+ ((uint64)p[1] << 16)
+ ((uint64)p[2] << 32)
+ ((uint64)p[3] << 48);
}
const QImage &circleMask(QSize size) {
uint64 key = (uint64(uint32(size.width())) << 32)
const QImage &CircleMask(QSize size) {
const auto key = (uint64(uint32(size.width())) << 32)
| uint64(uint32(size.height()));
static auto masks = base::flat_map<uint64, QImage>();
const auto i = masks.find(key);
if (i != end(masks)) {
static auto Masks = base::flat_map<uint64, QImage>();
static auto Mutex = QMutex();
auto lock = QMutexLocker(&Mutex);
const auto i = Masks.find(key);
if (i != end(Masks)) {
return i->second;
}
lock.unlock();
auto mask = QImage(
size,
QImage::Format_ARGB32_Premultiplied);
@ -51,7 +59,9 @@ const QImage &circleMask(QSize size) {
p.setPen(Qt::NoPen);
p.drawEllipse(QRect(QPoint(), size));
}
return masks.emplace(key, std::move(mask)).first->second;
lock.relock();
return Masks.emplace(key, std::move(mask)).first->second;
}
std::array<QImage, 4> PrepareCornersMask(int radius) {
@ -280,7 +290,10 @@ template <int kBits> // 4 means 16x16, 3 means 8x8
auto exact = GenerateSmallComplexGradient(colors, rotation, progress);
return (exact.size() == size)
? exact
: exact.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
: exact.scaled(
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
} // namespace
@ -449,101 +462,135 @@ ReadResult Read(ReadArgs &&args) {
if (args.forceOpaque
&& result.format != qstr("jpg")
&& result.format != qstr("jpeg")) {
result.image = prepareOpaque(std::move(result.image));
result.image = Opaque(std::move(result.image));
}
return result;
}
QImage prepareBlur(QImage img) {
if (img.isNull()) {
return img;
[[nodiscard]] Options RoundOptions(
ImageRoundRadius radius,
RectParts corners) {
const auto withCorners = [&](Option rounding) {
if (rounding == Option::None) {
return Options();
}
const auto corner = [&](RectPart part, Option skip) {
return !(corners & part) ? skip : Option();
};
return rounding
| corner(RectPart::TopLeft, Option::RoundSkipTopLeft)
| corner(RectPart::TopRight, Option::RoundSkipTopRight)
| corner(RectPart::BottomLeft, Option::RoundSkipBottomLeft)
| corner(RectPart::BottomRight, Option::RoundSkipBottomRight);
};
return withCorners((radius == ImageRoundRadius::Large)
? Option::RoundLarge
: (radius == ImageRoundRadius::Small)
? Option::RoundSmall
: (radius == ImageRoundRadius::Ellipse)
? Option::RoundCircle
: Option::None);
}
QImage Blur(QImage &&image) {
if (image.isNull()) {
return std::move(image);
}
const auto ratio = img.devicePixelRatio();
const auto fmt = img.format();
if (fmt != QImage::Format_RGB32 && fmt != QImage::Format_ARGB32_Premultiplied) {
img = std::move(img).convertToFormat(QImage::Format_ARGB32_Premultiplied);
img.setDevicePixelRatio(ratio);
const auto ratio = image.devicePixelRatio();
const auto format = image.format();
if (format != QImage::Format_RGB32
&& format != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(ratio);
}
uchar *pix = img.bits();
if (pix) {
int w = img.width(), h = img.height();
const int radius = 3;
const int r1 = radius + 1;
const int div = radius * 2 + 1;
const int stride = w * 4;
if (radius < 16 && div < w && div < h && stride <= w * 4) {
bool withalpha = img.hasAlphaChannel();
if (withalpha) {
QImage imgsmall(w, h, img.format());
{
QPainter p(&imgsmall);
PainterHighQualityEnabler hq(p);
auto pix = image.bits();
if (!pix) {
return std::move(image);
}
const auto w = image.width();
const auto h = image.height();
const auto radius = 3;
const auto r1 = radius + 1;
const auto div = radius * 2 + 1;
const auto stride = w * 4;
if (radius >= 16 || div >= w || div >= h || stride > w * 4) {
return std::move(image);
}
const auto withalpha = image.hasAlphaChannel();
if (withalpha) {
auto smaller = QImage(image.size(), image.format());
{
QPainter p(&smaller);
PainterHighQualityEnabler hq(p);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, w, h, Qt::transparent);
p.drawImage(QRect(radius, radius, w - 2 * radius, h - 2 * radius), img, QRect(0, 0, w, h));
}
imgsmall.setDevicePixelRatio(ratio);
auto was = img;
img = std::move(imgsmall);
imgsmall = QImage();
Assert(!img.isNull());
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(0, 0, w, h, Qt::transparent);
p.drawImage(
QRect(radius, radius, w - 2 * radius, h - 2 * radius),
image,
QRect(0, 0, w, h));
}
smaller.setDevicePixelRatio(ratio);
auto was = std::exchange(image, base::take(smaller));
Assert(!image.isNull());
pix = img.bits();
if (!pix) return was;
}
uint64 *rgb = new uint64[w * h];
pix = image.bits();
if (!pix) return was;
}
const auto buffer = std::make_unique<uint64[]>(w * h);
const auto rgb = buffer.get();
int x, y, i;
int x, y, i;
int yw = 0;
const int we = w - r1;
for (y = 0; y < h; y++) {
uint64 cur = blurGetColors(&pix[yw]);
uint64 rgballsum = -radius * cur;
uint64 rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
int yw = 0;
const int we = w - r1;
for (y = 0; y < h; y++) {
uint64 cur = BlurGetColors(&pix[yw]);
uint64 rgballsum = -radius * cur;
uint64 rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
for (i = 1; i <= radius; i++) {
uint64 cur = blurGetColors(&pix[yw + i * 4]);
rgbsum += cur * (r1 - i);
rgballsum += cur;
}
for (i = 1; i <= radius; i++) {
uint64 cur = BlurGetColors(&pix[yw + i * 4]);
rgbsum += cur * (r1 - i);
rgballsum += cur;
}
x = 0;
x = 0;
#define update(start, middle, end) \
rgb[y * w + x] = (rgbsum >> 4) & 0x00FF00FF00FF00FFLL; \
rgballsum += blurGetColors(&pix[yw + (start) * 4]) - 2 * blurGetColors(&pix[yw + (middle) * 4]) + blurGetColors(&pix[yw + (end) * 4]); \
rgballsum += BlurGetColors(&pix[yw + (start) * 4]) - 2 * BlurGetColors(&pix[yw + (middle) * 4]) + BlurGetColors(&pix[yw + (end) * 4]); \
rgbsum += rgballsum; \
x++;
while (x < r1) {
update(0, x, x + r1);
}
while (x < we) {
update(x - r1, x, x + r1);
}
while (x < w) {
update(x - r1, x, w - 1);
}
while (x < r1) {
update(0, x, x + r1);
}
while (x < we) {
update(x - r1, x, x + r1);
}
while (x < w) {
update(x - r1, x, w - 1);
}
#undef update
yw += stride;
}
yw += stride;
}
const int he = h - r1;
for (x = 0; x < w; x++) {
uint64 rgballsum = -radius * rgb[x];
uint64 rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
for (i = 1; i <= radius; i++) {
rgbsum += rgb[i * w + x] * (r1 - i);
rgballsum += rgb[i * w + x];
}
const int he = h - r1;
for (x = 0; x < w; x++) {
uint64 rgballsum = -radius * rgb[x];
uint64 rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
for (i = 1; i <= radius; i++) {
rgbsum += rgb[i * w + x] * (r1 - i);
rgballsum += rgb[i * w + x];
}
y = 0;
int yi = x * 4;
y = 0;
int yi = x * 4;
#define update(start, middle, end) \
uint64 res = rgbsum >> 4; \
@ -556,30 +603,27 @@ rgbsum += rgballsum; \
y++; \
yi += stride;
while (y < r1) {
update(0, y, y + r1);
}
while (y < he) {
update(y - r1, y, y + r1);
}
while (y < h) {
update(y - r1, y, h - 1);
}
while (y < r1) {
update(0, y, y + r1);
}
while (y < he) {
update(y - r1, y, y + r1);
}
while (y < h) {
update(y - r1, y, h - 1);
}
#undef update
}
delete[] rgb;
}
}
return img;
return std::move(image);
}
QImage BlurLargeImage(QImage image, int radius) {
[[nodiscard]] QImage BlurLargeImage(QImage &&image, int radius) {
const auto width = image.width();
const auto height = image.height();
if (width <= radius || height <= radius || radius < 1) {
return image;
return std::move(image);
}
if (image.format() != QImage::Format_RGB32
@ -787,10 +831,10 @@ QImage BlurLargeImage(QImage image, int radius) {
}
}
}
return image;
return std::move(image);
}
[[nodiscard]] QImage DitherImage(QImage image) {
[[nodiscard]] QImage DitherImage(const QImage &image) {
Expects(image.bytesPerLine() == image.width() * 4);
const auto width = image.width();
@ -925,21 +969,32 @@ QImage GenerateShadow(
return result;
}
void prepareCircle(QImage &img) {
Assert(!img.isNull());
QImage Circle(QImage &&image, QRect target) {
Expects(!image.isNull());
img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
Assert(!img.isNull());
if (target.isNull()) {
target = QRect(QPoint(), image.size());
} else {
Assert(QRect(QPoint(), image.size()).contains(target));
}
QPainter p(&img);
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
Assert(!image.isNull());
const auto ratio = image.devicePixelRatio();
auto p = QPainter(&image);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.drawImage(
QRect(QPoint(), img.size() / img.devicePixelRatio()),
circleMask(img.size()));
QRectF(target.topLeft() / ratio, target.size() / ratio),
CircleMask(target.size()));
p.end();
return std::move(image);
}
void prepareRound(
QImage &image,
QImage Round(
QImage &&image,
gsl::span<const QImage, 4> cornerMasks,
RectParts corners,
QRect target) {
@ -953,7 +1008,7 @@ void prepareRound(
auto targetWidth = target.width();
auto targetHeight = target.height();
if (targetWidth < cornerWidth || targetHeight < cornerHeight) {
return;
return std::move(image);
}
// We need to detach image first (if it is shared), before we
@ -996,20 +1051,20 @@ void prepareRound(
if (corners & RectPart::TopRight) maskCorner(intsTopRight, cornerMasks[1]);
if (corners & RectPart::BottomLeft) maskCorner(intsBottomLeft, cornerMasks[2]);
if (corners & RectPart::BottomRight) maskCorner(intsBottomRight, cornerMasks[3]);
return std::move(image);
}
void prepareRound(
QImage &image,
QImage Round(
QImage &&image,
ImageRoundRadius radius,
RectParts corners,
QRect target) {
if (!static_cast<int>(corners)) {
return;
return std::move(image);
} else if (radius == ImageRoundRadius::Ellipse) {
Assert((corners & RectPart::AllCorners) == RectPart::AllCorners);
Assert(target.isNull());
prepareCircle(image);
return;
return Circle(std::move(image), target);
}
Assert(!image.isNull());
@ -1018,17 +1073,40 @@ void prepareRound(
Assert(!image.isNull());
const auto masks = CornersMask(radius);
prepareRound(image, masks, corners, target);
return Round(std::move(image), masks, corners, target);
}
QImage prepareColored(style::color add, QImage image) {
return prepareColored(add->c, std::move(image));
QImage Round(QImage &&image, Options options, QRect target) {
if (options & Option::RoundCircle) {
return Circle(std::move(image), target);
} else if (!(options & (Option::RoundLarge | Option::RoundSmall))) {
return std::move(image);
}
const auto corner = [&](Option skip, RectPart part) {
return !(options & skip) ? part : RectPart::None;
};
return Round(
std::move(image),
((options & Option::RoundLarge)
? ImageRoundRadius::Large
: ImageRoundRadius::Small),
(corner(Option::RoundSkipTopLeft, RectPart::TopLeft)
| corner(Option::RoundSkipTopRight, RectPart::TopRight)
| corner(Option::RoundSkipBottomLeft, RectPart::BottomLeft)
| corner(Option::RoundSkipBottomRight, RectPart::BottomRight)),
target);
}
QImage prepareColored(QColor add, QImage image) {
QImage Colored(QImage &&image, style::color add) {
return Colored(std::move(image), add->c);
}
QImage Colored(QImage &&image, QColor add) {
const auto format = image.format();
if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
if (format != QImage::Format_RGB32
&& format != QImage::Format_ARGB32_Premultiplied) {
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
}
if (const auto pix = image.bits()) {
@ -1040,95 +1118,107 @@ QImage prepareColored(QColor add, QImage image) {
const auto h = image.height();
const auto size = w * h * 4;
for (auto i = index_type(); i != size; i += 4) {
int b = pix[i], g = pix[i + 1], r = pix[i + 2], a = pix[i + 3], aca = a * ca;
const auto b = pix[i];
const auto g = pix[i + 1];
const auto r = pix[i + 2];
const auto a = pix[i + 3];
const auto aca = a * ca;
pix[i + 0] = uchar(b + ((aca * (cb - b)) >> 16));
pix[i + 1] = uchar(g + ((aca * (cg - g)) >> 16));
pix[i + 2] = uchar(r + ((aca * (cr - r)) >> 16));
pix[i + 3] = uchar(a + ((aca * (0xFF - a)) >> 16));
}
}
return image;
return std::move(image);
}
QImage prepareOpaque(QImage image) {
QImage Opaque(QImage &&image) {
if (image.hasAlphaChannel()) {
image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
image = std::move(image).convertToFormat(
QImage::Format_ARGB32_Premultiplied);
auto ints = reinterpret_cast<uint32*>(image.bits());
auto bg = anim::shifted(st::imageBgTransparent->c);
auto width = image.width();
auto height = image.height();
auto addPerLine = (image.bytesPerLine() / sizeof(uint32)) - width;
const auto bg = anim::shifted(QColor(Qt::white));
const auto width = image.width();
const auto height = image.height();
const auto addPerLine = (image.bytesPerLine() / sizeof(uint32)) - width;
for (auto y = 0; y != height; ++y) {
for (auto x = 0; x != width; ++x) {
auto components = anim::shifted(*ints);
*ints++ = anim::unshifted(components * 256 + bg * (256 - anim::getAlpha(components)));
const auto components = anim::shifted(*ints);
*ints++ = anim::unshifted(components * 256
+ bg * (256 - anim::getAlpha(components)));
}
ints += addPerLine;
}
}
return image;
return std::move(image);
}
QImage prepare(QImage img, int w, int h, Images::Options options, int outerw, int outerh, const style::color *colored) {
Assert(!img.isNull());
if (options & Images::Option::Blurred) {
img = prepareBlur(std::move(img));
Assert(!img.isNull());
QImage Prepare(QImage image, int w, int h, const PrepareArgs &args) {
Expects(!image.isNull());
if (args.options & Option::Blur) {
image = Blur(std::move(image));
Assert(!image.isNull());
}
if (w <= 0 || (w == img.width() && (h <= 0 || h == img.height()))) {
if (w <= 0
|| (w == image.width() && (h <= 0 || h == image.height()))) {
} else if (h <= 0) {
img = img.scaledToWidth(w, (options & Images::Option::Smooth) ? Qt::SmoothTransformation : Qt::FastTransformation);
Assert(!img.isNull());
image = image.scaledToWidth(
w,
((args.options & Images::Option::FastTransform)
? Qt::FastTransformation
: Qt::SmoothTransformation));
Assert(!image.isNull());
} else {
img = img.scaled(w, h, Qt::IgnoreAspectRatio, (options & Images::Option::Smooth) ? Qt::SmoothTransformation : Qt::FastTransformation);
Assert(!img.isNull());
image = image.scaled(
w,
h,
Qt::IgnoreAspectRatio,
((args.options & Images::Option::FastTransform)
? Qt::FastTransformation
: Qt::SmoothTransformation));
Assert(!image.isNull());
}
if (outerw > 0 && outerh > 0) {
const auto pixelRatio = style::DevicePixelRatio();
outerw *= pixelRatio;
outerh *= pixelRatio;
if (outerw != w || outerh != h) {
img.setDevicePixelRatio(pixelRatio);
auto result = QImage(outerw, outerh, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(pixelRatio);
if (options & Images::Option::TransparentBackground) {
auto outer = args.outer;
if (!outer.isEmpty()) {
const auto ratio = style::DevicePixelRatio();
outer *= ratio;
if (outer != QSize(w, h)) {
image.setDevicePixelRatio(ratio);
auto result = QImage(outer, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(ratio);
if (args.options & Images::Option::TransparentBackground) {
result.fill(Qt::transparent);
}
{
QPainter p(&result);
if (!(options & Images::Option::TransparentBackground)) {
if (w < outerw || h < outerh) {
p.fillRect(0, 0, result.width(), result.height(), st::imageBg);
if (!(args.options & Images::Option::TransparentBackground)) {
if (w < outer.width() || h < outer.height()) {
p.fillRect(
QRect({}, result.size() / ratio),
Qt::black);
}
}
p.drawImage((result.width() - img.width()) / (2 * pixelRatio), (result.height() - img.height()) / (2 * pixelRatio), img);
p.drawImage(
(result.width() - image.width()) / (2 * ratio),
(result.height() - image.height()) / (2 * ratio),
image);
}
img = result;
Assert(!img.isNull());
image = std::move(result);
Assert(!image.isNull());
}
}
auto corners = [](Images::Options options) {
return ((options & Images::Option::RoundedTopLeft) ? RectPart::TopLeft : RectPart::None)
| ((options & Images::Option::RoundedTopRight) ? RectPart::TopRight : RectPart::None)
| ((options & Images::Option::RoundedBottomLeft) ? RectPart::BottomLeft : RectPart::None)
| ((options & Images::Option::RoundedBottomRight) ? RectPart::BottomRight : RectPart::None);
};
if (options & Images::Option::Circled) {
prepareCircle(img);
Assert(!img.isNull());
} else if (options & Images::Option::RoundedLarge) {
prepareRound(img, ImageRoundRadius::Large, corners(options));
Assert(!img.isNull());
} else if (options & Images::Option::RoundedSmall) {
prepareRound(img, ImageRoundRadius::Small, corners(options));
Assert(!img.isNull());
if (args.options
& (Option::RoundCircle | Option::RoundLarge | Option::RoundSmall)) {
image = Round(std::move(image), args.options);
Assert(!image.isNull());
}
if (options & Images::Option::Colored) {
Assert(colored != nullptr);
img = prepareColored(*colored, std::move(img));
if (args.colored) {
image = Colored(std::move(image), *args.colored);
}
img.setDevicePixelRatio(style::DevicePixelRatio());
return img;
image.setDevicePixelRatio(style::DevicePixelRatio());
return image;
}
} // namespace Images

View file

@ -26,8 +26,8 @@ enum class ImageRoundRadius {
namespace Images {
[[nodiscard]] QPixmap PixmapFast(QImage &&image);
[[nodiscard]] QImage BlurLargeImage(QImage image, int radius);
[[nodiscard]] QImage DitherImage(QImage image);
[[nodiscard]] QImage BlurLargeImage(QImage &&image, int radius);
[[nodiscard]] QImage DitherImage(const QImage &image);
[[nodiscard]] QImage GenerateGradient(
QSize size,
@ -79,44 +79,78 @@ struct ReadResult {
};
[[nodiscard]] ReadResult Read(ReadArgs &&args);
QImage prepareBlur(QImage image);
void prepareRound(
QImage &image,
ImageRoundRadius radius,
RectParts corners = RectPart::AllCorners,
QRect target = QRect());
void prepareRound(
QImage &image,
gsl::span<const QImage, 4> cornerMasks,
RectParts corners = RectPart::AllCorners,
QRect target = QRect());
void prepareCircle(QImage &image);
QImage prepareColored(style::color add, QImage image);
QImage prepareColored(QColor add, QImage image);
QImage prepareOpaque(QImage image);
enum class Option {
None = 0,
Smooth = (1 << 0),
Blurred = (1 << 1),
Circled = (1 << 2),
RoundedLarge = (1 << 3),
RoundedSmall = (1 << 4),
RoundedTopLeft = (1 << 5),
RoundedTopRight = (1 << 6),
RoundedBottomLeft = (1 << 7),
RoundedBottomRight = (1 << 8),
RoundedAll = (None
| RoundedTopLeft
| RoundedTopRight
| RoundedBottomLeft
| RoundedBottomRight),
Colored = (1 << 9),
FastTransform = (1 << 0),
Blur = (1 << 1),
RoundCircle = (1 << 2),
RoundLarge = (1 << 3),
RoundSmall = (1 << 4),
RoundSkipTopLeft = (1 << 5),
RoundSkipTopRight = (1 << 6),
RoundSkipBottomLeft = (1 << 7),
RoundSkipBottomRight = (1 << 8),
Colorize = (1 << 9),
TransparentBackground = (1 << 10),
};
using Options = base::flags<Option>;
inline constexpr auto is_flag_type(Option) { return true; };
QImage prepare(QImage img, int w, int h, Options options, int outerw, int outerh, const style::color *colored = nullptr);
[[nodiscard]] Options RoundOptions(
ImageRoundRadius radius,
RectParts corners = RectPart::AllCorners);
[[nodiscard]] QImage Blur(QImage &&image);
[[nodiscard]] QImage Round(
QImage &&image,
ImageRoundRadius radius,
RectParts corners = RectPart::AllCorners,
QRect target = QRect());
[[nodiscard]] QImage Round(
QImage &&image,
gsl::span<const QImage, 4> cornerMasks,
RectParts corners = RectPart::AllCorners,
QRect target = QRect());
[[nodiscard]] QImage Round(
QImage &&image,
Options options,
QRect target = QRect());
[[nodiscard]] QImage Circle(QImage &&image, QRect target = QRect());
[[nodiscard]] QImage Colored(QImage &&image, style::color add);
[[nodiscard]] QImage Colored(QImage &&image, QColor add);
[[nodiscard]] QImage Opaque(QImage &&image);
struct PrepareArgs {
const style::color *colored = nullptr;
Options options;
QSize outer;
[[nodiscard]] PrepareArgs blurred() const {
auto result = *this;
result.options |= Option::Blur;
return result;
}
};
[[nodiscard]] QImage Prepare(
QImage image,
int w,
int h,
const PrepareArgs &args);
[[nodiscard]] inline QImage Prepare(
QImage image,
int w,
const PrepareArgs &args) {
return Prepare(std::move(image), w, 0, args);
}
[[nodiscard]] inline QImage Prepare(
QImage image,
QSize size,
const PrepareArgs &args) {
return Prepare(std::move(image), size.width(), size.height(), args);
}
} // namespace Images

View file

@ -8,7 +8,7 @@
#include "ui/platform/mac/ui_window_title_mac.h"
#include "ui/widgets/rp_window.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_common_adapters.h"
#include "base/platform/base_platform_info.h"
#include "styles/palette.h"

View file

@ -9,6 +9,7 @@
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/ui_utility.h"
#include "base/debug_log.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
@ -59,20 +60,29 @@ void TitleWidget::init(int height) {
}, lifetime());
const auto families = QStringList{
QString(".SF NS Text"),
QString("Helvetica Neue")
u".AppleSystemUIFont"_q,
u".SF NS Text"_q,
u"Helvetica Neue"_q,
};
for (auto family : families) {
_font.setFamily(family);
if (QFontInfo(_font).family() == _font.family()) {
static const auto logged = [&] {
LOG(("Title Font: %1").arg(family));
return true;
}();
break;
}
}
if (QFontInfo(_font).family() == _font.family()) {
_font.setPixelSize((height * 15) / 24);
if (QFontInfo(_font).family() != _font.family()) {
_font = st::semiboldFont;
_font.setPixelSize(13);
} else if (_font.family() == u".AppleSystemUIFont"_q) {
_font.setBold(true);
_font.setPixelSize(13);
} else {
_font = st::normalFont;
_font.setPixelSize((height * 15) / 24);
}
}

View file

@ -33,6 +33,9 @@ BasicWindowHelper::BasicWindowHelper(not_null<RpWidget*> window)
_window->setWindowFlag(Qt::Window);
}
void BasicWindowHelper::initInWindow(not_null<RpWindow*> window) {
}
not_null<RpWidget*> BasicWindowHelper::body() {
return _window;
}
@ -41,6 +44,27 @@ QMargins BasicWindowHelper::frameMargins() {
return nativeFrameMargins();
}
int BasicWindowHelper::additionalContentPadding() const {
return 0;
}
rpl::producer<int> BasicWindowHelper::additionalContentPaddingValue() const {
return rpl::single(0);
}
auto BasicWindowHelper::hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>> {
return rpl::never<not_null<HitTestRequest*>>();
}
rpl::producer<HitTestResult> BasicWindowHelper::systemButtonOver() const {
return rpl::never<HitTestResult>();
}
rpl::producer<HitTestResult> BasicWindowHelper::systemButtonDown() const {
return rpl::never<HitTestResult>();
}
void BasicWindowHelper::setTitle(const QString &title) {
_window->setWindowTitle(title);
}

View file

@ -15,11 +15,14 @@ struct WindowTitle;
namespace Ui {
class RpWidget;
class RpWindow;
enum class WindowTitleHitTestFlag;
using WindowTitleHitTestFlags = base::flags<WindowTitleHitTestFlag>;
namespace Platform {
struct HitTestRequest;
enum class HitTestResult;
class DefaultTitleWidget;
class BasicWindowHelper {
@ -27,8 +30,22 @@ public:
explicit BasicWindowHelper(not_null<RpWidget*> window);
virtual ~BasicWindowHelper() = default;
[[nodiscard]] not_null<RpWidget*> window() const {
return _window;
}
virtual void initInWindow(not_null<RpWindow*> window);
[[nodiscard]] virtual not_null<RpWidget*> body();
[[nodiscard]] virtual QMargins frameMargins();
[[nodiscard]] virtual int additionalContentPadding() const;
[[nodiscard]] virtual auto additionalContentPaddingValue() const
-> rpl::producer<int>;
[[nodiscard]] virtual auto hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>>;
[[nodiscard]] virtual auto systemButtonOver() const
-> rpl::producer<HitTestResult>;
[[nodiscard]] virtual auto systemButtonDown() const
-> rpl::producer<HitTestResult>;
virtual void setTitle(const QString &title);
virtual void setTitleStyle(const style::WindowTitle &st);
virtual void setNativeFrame(bool enabled);
@ -43,9 +60,6 @@ public:
void setBodyTitleArea(Fn<WindowTitleHitTestFlags(QPoint)> testMethod);
protected:
[[nodiscard]] not_null<RpWidget*> window() const {
return _window;
}
[[nodiscard]] WindowTitleHitTestFlags bodyTitleAreaHit(
QPoint point) const {
return _bodyTitleAreaTestMethod
@ -108,7 +122,7 @@ private:
return std::make_unique<DefaultWindowHelper>(window);
}
bool NativeWindowFrameSupported();
[[nodiscard]] bool NativeWindowFrameSupported();
} // namespace Platform
} // namespace Ui

View file

@ -10,10 +10,12 @@
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/ui_utility.h"
#include "ui/widgets/rp_window.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
#include "base/algorithm.h"
#include "base/event_filter.h"
#include "base/platform/base_platform_info.h"
#include <QtGui/QPainter>
#include <QtGui/QtEvents>
@ -35,6 +37,50 @@ void RemoveDuplicates(std::vector<T> &v) {
} // namespace
bool SemiNativeSystemButtonProcessing() {
return ::Platform::IsWindows11OrGreater();
}
void SetupSemiNativeSystemButtons(
not_null<TitleControls*> controls,
not_null<RpWindow*> window,
rpl::lifetime &lifetime,
Fn<bool()> filter) {
if (!SemiNativeSystemButtonProcessing()) {
return;
}
window->systemButtonOver(
) | rpl::filter([=](HitTestResult button) {
return !filter || filter() || (button == HitTestResult::None);
}) | rpl::start_with_next([=](HitTestResult button) {
controls->buttonOver(button);
}, lifetime);
window->systemButtonDown(
) | rpl::filter([=](HitTestResult button) {
return !filter || filter() || (button == HitTestResult::None);
}) | rpl::start_with_next([=](HitTestResult button) {
controls->buttonDown(button);
}, lifetime);
}
class TitleControls::Button final : public IconButton {
public:
using IconButton::IconButton;
void setOver(bool over) {
IconButton::setOver(over, StateChangeSource::ByPress);
}
void setDown(bool down) {
IconButton::setDown(
down,
StateChangeSource::ByPress,
{},
Qt::LeftButton);
}
};
TitleControls::TitleControls(
not_null<RpWidget*> parent,
const style::WindowTitle &st,
@ -172,7 +218,47 @@ void TitleControls::raise() {
_close->raise();
}
Ui::IconButton *TitleControls::controlWidget(Control control) const {
HitTestResult TitleControls::hitTest(QPoint point) const {
const auto test = [&](const object_ptr<Button> &button) {
return button && button->geometry().contains(point);
};
if (test(_minimize)) {
return HitTestResult::Minimize;
} else if (test(_maximizeRestore)) {
return HitTestResult::MaximizeRestore;
} else if (test(_close)) {
return HitTestResult::Close;
}
return HitTestResult::None;
}
void TitleControls::buttonOver(HitTestResult testResult) {
const auto update = [&](
const object_ptr<Button> &button,
HitTestResult buttonTestResult) {
if (const auto raw = button.data()) {
raw->setOver(testResult == buttonTestResult);
}
};
update(_minimize, HitTestResult::Minimize);
update(_maximizeRestore, HitTestResult::MaximizeRestore);
update(_close, HitTestResult::Close);
}
void TitleControls::buttonDown(HitTestResult testResult) {
const auto update = [&](
const object_ptr<Button> &button,
HitTestResult buttonTestResult) {
if (const auto raw = button.data()) {
raw->setDown(testResult == buttonTestResult);
}
};
update(_minimize, HitTestResult::Minimize);
update(_maximizeRestore, HitTestResult::MaximizeRestore);
update(_close, HitTestResult::Close);
}
TitleControls::Button *TitleControls::controlWidget(Control control) const {
switch (control) {
case Control::Minimize: return _minimize;
case Control::Maximize: return _maximizeRestore;
@ -382,5 +468,50 @@ void DefaultTitleWidget::mouseDoubleClickEvent(QMouseEvent *e) {
}
}
SeparateTitleControls::SeparateTitleControls(
QWidget *parent,
const style::WindowTitle &st,
Fn<void(bool maximized)> maximize)
: wrap(parent)
, controls(&wrap, st, std::move(maximize)) {
}
std::unique_ptr<SeparateTitleControls> SetupSeparateTitleControls(
not_null<RpWindow*> window,
const style::WindowTitle &st,
Fn<void(bool maximized)> maximize) {
auto result = std::make_unique<SeparateTitleControls>(
window->body(),
st,
std::move(maximize));
const auto raw = result.get();
auto &lifetime = raw->wrap.lifetime();
rpl::combine(
window->body()->widthValue(),
window->additionalContentPaddingValue()
) | rpl::start_with_next([=](int width, int padding) {
raw->wrap.setGeometry(
padding,
0,
width - 2 * padding,
raw->controls.geometry().height());
}, lifetime);
window->hitTestRequests(
) | rpl::start_with_next([=](not_null<HitTestRequest*> request) {
const auto origin = raw->wrap.pos();
const auto relative = request->point - origin;
const auto controlsResult = raw->controls.hitTest(relative);
if (controlsResult != HitTestResult::None) {
request->result = controlsResult;
}
}, lifetime);
SetupSemiNativeSystemButtons(&raw->controls, window, lifetime);
return result;
}
} // namespace Platform
} // namespace Ui

View file

@ -20,9 +20,41 @@ namespace Ui {
class IconButton;
class PlainShadow;
class RpWindow;
namespace Platform {
class TitleControls;
enum class HitTestResult {
None = 0,
Client,
Minimize,
MaximizeRestore,
Close,
Caption,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
TopLeft,
};
struct HitTestRequest {
QPoint point;
HitTestResult result = HitTestResult::Client;
};
[[nodiscard]] bool SemiNativeSystemButtonProcessing();
void SetupSemiNativeSystemButtons(
not_null<TitleControls*> controls,
not_null<RpWindow*> window,
rpl::lifetime &lifetime,
Fn<bool()> filter = nullptr);
class TitleControls final {
public:
TitleControls(
@ -36,6 +68,11 @@ public:
void setResizeEnabled(bool enabled);
void raise();
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
void buttonOver(HitTestResult testResult);
void buttonDown(HitTestResult testResult);
enum class Control {
Unknown,
Minimize,
@ -49,9 +86,11 @@ public:
};
private:
class Button;
[[nodiscard]] not_null<RpWidget*> parent() const;
[[nodiscard]] not_null<QWidget*> window() const;
[[nodiscard]] Ui::IconButton *controlWidget(Control control) const;
[[nodiscard]] Button *controlWidget(Control control) const;
void init(Fn<void(bool maximized)> maximize);
void subscribeToStateChanges();
@ -64,9 +103,9 @@ private:
not_null<const style::WindowTitle*> _st;
object_ptr<Ui::IconButton> _minimize;
object_ptr<Ui::IconButton> _maximizeRestore;
object_ptr<Ui::IconButton> _close;
object_ptr<Button> _minimize;
object_ptr<Button> _maximizeRestore;
object_ptr<Button> _close;
bool _maximizedState = false;
bool _activeState = false;
@ -98,5 +137,21 @@ private:
};
struct SeparateTitleControls {
SeparateTitleControls(
QWidget *parent,
const style::WindowTitle &st,
Fn<void(bool maximized)> maximize);
RpWidget wrap;
TitleControls controls;
};
[[nodiscard]] auto SetupSeparateTitleControls(
not_null<RpWindow*> window,
const style::WindowTitle &st,
Fn<void(bool maximized)> maximize = nullptr)
-> std::unique_ptr<SeparateTitleControls>;
} // namespace Platform
} // namespace Ui

View file

@ -6,9 +6,14 @@
//
#include "ui/platform/win/ui_window_title_win.h"
#include "ui/platform/win/ui_window_win.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/rp_window.h"
#include "ui/ui_utility.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/win/base_windows_safe_library.h"
#include "base/debug_log.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
@ -16,28 +21,88 @@
#include <QtGui/QtEvents>
#include <QtGui/QWindow>
#include <windows.h>
#include <shellscalingapi.h>
namespace Ui {
namespace Platform {
namespace {
HRESULT(__stdcall *GetScaleFactorForMonitor)(
_In_ HMONITOR hMon,
_Out_ DEVICE_SCALE_FACTOR *pScale);
[[nodiscard]] bool ScaleQuerySupported() {
static const auto Result = [&] {
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
const auto shcore = base::Platform::SafeLoadLibrary(L"Shcore.dll");
return LOAD_SYMBOL(shcore, GetScaleFactorForMonitor);
#undef LOAD_SYMBOL
}();
return Result;
}
} // namespace
struct TitleWidget::PaddingHelper {
explicit PaddingHelper(QWidget *parent) : controlsParent(parent) {
}
RpWidget controlsParent;
rpl::variable<int> padding = 0;
};
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
: RpWidget(parent)
, _controls(this, st::defaultWindowTitle)
, _paddingHelper(CheckTitlePaddingRequired()
? std::make_unique<PaddingHelper>(this)
: nullptr)
, _controls(
_paddingHelper ? &_paddingHelper->controlsParent : this,
st::defaultWindowTitle)
, _shadow(this, st::titleShadow) {
setAttribute(Qt::WA_OpaquePaintEvent);
parent->widthValue(
) | rpl::start_with_next([=](int width) {
setGeometry(0, 0, width, _controls.st()->height);
refreshGeometryWithWidth(width);
}, lifetime());
}
void TitleWidget::initInWindow(not_null<RpWindow*> window) {
window->hitTestRequests(
) | rpl::filter([=](not_null<HitTestRequest*> request) {
return !isHidden() && geometry().contains(request->point);
}) | rpl::start_with_next([=](not_null<HitTestRequest*> request) {
request->result = hitTest(request->point);
}, lifetime());
SetupSemiNativeSystemButtons(&_controls, window, lifetime(), [=] {
return !isHidden() && (_controls.st()->height > 0);
});
}
TitleWidget::~TitleWidget() = default;
void TitleWidget::setText(const QString &text) {
window()->setWindowTitle(text);
}
void TitleWidget::setStyle(const style::WindowTitle &st) {
_controls.setStyle(st);
setGeometry(0, 0, window()->width(), _controls.st()->height);
refreshGeometryWithWidth(window()->width());
}
void TitleWidget::refreshGeometryWithWidth(int width) {
const auto add = additionalPadding();
setGeometry(0, 0, width, _controls.st()->height + add);
if (_paddingHelper) {
_paddingHelper->controlsParent.setGeometry(
add,
add,
width - 2 * add,
_controls.st()->height);
}
update();
}
@ -57,16 +122,123 @@ void TitleWidget::paintEvent(QPaintEvent *e) {
}
void TitleWidget::resizeEvent(QResizeEvent *e) {
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
const auto thickness = st::lineWidth;
_shadow->setGeometry(0, height() - thickness, width(), thickness);
}
HitTestResult TitleWidget::hitTest(QPoint point) const {
if (_controls.geometry().contains(point)) {
return HitTestResult::SysButton;
} else if (rect().contains(point)) {
return HitTestResult::Caption;
const auto origin = _paddingHelper
? _paddingHelper->controlsParent.pos()
: QPoint();
const auto controlsResult = _controls.hitTest(point - origin);
return (controlsResult != HitTestResult::None)
? controlsResult
: HitTestResult::Caption;
}
bool TitleWidget::additionalPaddingRequired() const {
return _paddingHelper && !isHidden();
}
void TitleWidget::refreshAdditionalPaddings() {
if (!additionalPaddingRequired()) {
return;
}
return HitTestResult::None;
const auto handle = GetWindowHandle(this);
if (!handle) {
LOG(("System Error: GetWindowHandle failed."));
return;
}
refreshAdditionalPaddings(handle);
}
void TitleWidget::refreshAdditionalPaddings(HWND handle) {
if (!additionalPaddingRequired()) {
return;
}
auto placement = WINDOWPLACEMENT{
.length = sizeof(WINDOWPLACEMENT),
};
if (!GetWindowPlacement(handle, &placement)) {
LOG(("System Error: GetWindowPlacement failed."));
return;
}
refreshAdditionalPaddings(handle, placement);
}
void TitleWidget::refreshAdditionalPaddings(
HWND handle,
const WINDOWPLACEMENT &placement) {
if (!additionalPaddingRequired()) {
return;
}
auto geometry = RECT();
if (!GetWindowRect(handle, &geometry)) {
LOG(("System Error: GetWindowRect failed."));
return;
}
const auto normal = placement.rcNormalPosition;
const auto rounded = (normal.left == geometry.left)
&& (normal.right == geometry.right)
&& (normal.top == geometry.top)
&& (normal.bottom == geometry.bottom);
const auto padding = [&] {
if (!rounded) {
return 0;
}
const auto monitor = MonitorFromWindow(
handle,
MONITOR_DEFAULTTONEAREST);
if (!monitor) {
LOG(("System Error: MonitorFromWindow failed."));
return -1;
}
auto factor = DEVICE_SCALE_FACTOR();
if (!SUCCEEDED(GetScaleFactorForMonitor(monitor, &factor))) {
LOG(("System Error: GetScaleFactorForMonitor failed."));
return -1;
} else if (factor < 100 || factor > 500) {
LOG(("System Error: Bad scale factor %1.").arg(int(factor)));
return -1;
}
const auto pixels = (factor + 50) / 100;
return int(base::SafeRound(pixels / window()->devicePixelRatioF()));
}();
if (padding < 0) {
return;
}
setAdditionalPadding(padding);
}
int TitleWidget::additionalPadding() const {
return _paddingHelper ? _paddingHelper->padding.current() : 0;
}
rpl::producer<int> TitleWidget::additionalPaddingValue() const {
return _paddingHelper ? _paddingHelper->padding.value() : rpl::single(0);
}
void TitleWidget::setAdditionalPadding(int padding) {
Expects(_paddingHelper != nullptr);
if (_paddingHelper->padding.current() == padding) {
return;
}
_paddingHelper->padding = padding;
refreshGeometryWithWidth(window()->width());
}
void TitleWidget::setVisibleHook(bool visible) {
RpWidget::setVisibleHook(visible);
if (additionalPaddingRequired()) {
PostponeCall(this, [=] {
refreshAdditionalPaddings();
});
}
}
bool CheckTitlePaddingRequired() {
return ::Platform::IsWindows11OrGreater() && ScaleQuerySupported();
}
} // namespace Platform

View file

@ -13,51 +13,60 @@
#include <QtCore/QRect>
#include <QtCore/QPoint>
#include <Windows.h> // HWND, WINDOWPLACEMENT
namespace style {
struct WindowTitle;
} // namespace style
namespace Ui {
class RpWindow;
class IconButton;
class PlainShadow;
namespace Platform {
enum class HitTestResult {
None = 0,
Client,
SysButton,
Caption,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
TopLeft,
};
class TitleWidget : public RpWidget {
public:
explicit TitleWidget(not_null<RpWidget*> parent);
~TitleWidget();
void initInWindow(not_null<RpWindow*> window);
void setText(const QString &text);
void setStyle(const style::WindowTitle &st);
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
void setResizeEnabled(bool enabled);
void refreshAdditionalPaddings();
void refreshAdditionalPaddings(HWND handle);
void refreshAdditionalPaddings(
HWND handle,
const WINDOWPLACEMENT &placement);
[[nodiscard]] int additionalPadding() const;
[[nodiscard]] rpl::producer<int> additionalPaddingValue() const;
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void setVisibleHook(bool visible) override;
private:
struct PaddingHelper;
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
[[nodiscard]] bool additionalPaddingRequired() const;
void refreshGeometryWithWidth(int width);
void setAdditionalPadding(int padding);
std::unique_ptr<PaddingHelper> _paddingHelper;
TitleControls _controls;
object_ptr<Ui::PlainShadow> _shadow;
};
[[nodiscard]] bool CheckTitlePaddingRequired();
} // namespace Platform
} // namespace Ui

View file

@ -8,8 +8,8 @@
#include "ui/inactive_press.h"
#include "ui/platform/win/ui_window_title_win.h"
#include "ui/widgets/rp_window.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/win/base_windows_safe_library.h"
#include "base/integration.h"
#include "base/debug_log.h"
#include "styles/palette.h"
@ -31,6 +31,7 @@ namespace Platform {
namespace {
constexpr auto kDWMWCP_ROUND = DWORD(2);
constexpr auto kDWMWCP_DONOTROUND = DWORD(1);
constexpr auto kDWMWA_WINDOW_CORNER_PREFERENCE = DWORD(33);
constexpr auto kDWMWA_CAPTION_COLOR = DWORD(35);
constexpr auto kDWMWA_TEXT_COLOR = DWORD(36);
@ -170,6 +171,10 @@ WindowHelper::~WindowHelper() {
GetNativeFilter()->unregisterWindow(_handle);
}
void WindowHelper::initInWindow(not_null<RpWindow*> window) {
_title->initInWindow(window);
}
not_null<RpWidget*> WindowHelper::body() {
return _body;
}
@ -180,6 +185,19 @@ QMargins WindowHelper::frameMargins() {
: QMargins{ 0, _title->height(), 0, 0 };
}
int WindowHelper::additionalContentPadding() const {
return _title->isHidden() ? 0 : _title->additionalPadding();
}
rpl::producer<int> WindowHelper::additionalContentPaddingValue() const {
return rpl::combine(
_title->shownValue(),
_title->additionalPaddingValue()
) | rpl::map([](bool shown, int padding) {
return shown ? padding : 0;
}) | rpl::distinct_until_changed();
}
void WindowHelper::setTitle(const QString &title) {
_title->setText(title);
window()->setWindowTitle(title);
@ -218,15 +236,19 @@ void WindowHelper::initialShadowUpdate() {
} else {
_shadow->update(Change::Moved | Change::Resized | Change::Shown);
}
updateCornersRounding();
}
if (::Platform::IsWindows11OrGreater()) {
auto preference = kDWMWCP_ROUND;
DwmSetWindowAttribute(
_handle,
kDWMWA_WINDOW_CORNER_PREFERENCE,
&preference,
sizeof(preference));
void WindowHelper::updateCornersRounding() {
if (!::Platform::IsWindows11OrGreater()) {
return;
}
auto preference = _isFullScreen ? kDWMWCP_DONOTROUND : kDWMWCP_ROUND;
DwmSetWindowAttribute(
_handle,
kDWMWA_WINDOW_CORNER_PREFERENCE,
&preference,
sizeof(preference));
}
void WindowHelper::setMinimumSize(QSize size) {
@ -249,6 +271,7 @@ void WindowHelper::showFullScreen() {
if (!_isFullScreen) {
_isFullScreen = true;
updateMargins();
updateCornersRounding();
}
window()->showFullScreen();
}
@ -258,9 +281,23 @@ void WindowHelper::showNormal() {
if (_isFullScreen) {
_isFullScreen = false;
updateMargins();
updateCornersRounding();
}
}
auto WindowHelper::hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>> {
return _hitTestRequests.events();
}
rpl::producer<HitTestResult> WindowHelper::systemButtonOver() const {
return _systemButtonOver.events();
}
rpl::producer<HitTestResult> WindowHelper::systemButtonDown() const {
return _systemButtonDown.events();
}
void WindowHelper::init() {
_title->show();
GetNativeFilter()->registerWindow(_handle, this);
@ -322,6 +359,10 @@ bool WindowHelper::handleNativeEvent(
WPARAM wParam,
LPARAM lParam,
LRESULT *result) {
if (handleSystemButtonEvent(msg, wParam, lParam, result)) {
return true;
}
switch (msg) {
case WM_ACTIVATE: {
@ -383,13 +424,11 @@ bool WindowHelper::handleNativeEvent(
return true;
}
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
case WM_NCXBUTTONDBLCLK: {
if (!fixedSize()) {
case WM_NCRBUTTONUP: {
if (_title->isHidden()) {
return false;
}
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
if (result) *result = 0;
} return true;
@ -409,11 +448,16 @@ bool WindowHelper::handleNativeEvent(
case WM_WINDOWPOSCHANGING:
case WM_WINDOWPOSCHANGED: {
if (_shadow) {
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
if (GetWindowPlacement(_handle, &wp)
&& (wp.showCmd == SW_SHOWMAXIMIZED
|| wp.showCmd == SW_SHOWMINIMIZED)) {
auto placement = WINDOWPLACEMENT{
.length = sizeof(WINDOWPLACEMENT),
};
if (!GetWindowPlacement(_handle, &placement)) {
LOG(("System Error: GetWindowPlacement failed."));
return false;
}
_title->refreshAdditionalPaddings(_handle, placement);
if (placement.showCmd == SW_SHOWMAXIMIZED
|| placement.showCmd == SW_SHOWMINIMIZED) {
_shadow->update(WindowShadow::Change::Hidden);
} else {
_shadow->update(
@ -439,6 +483,7 @@ bool WindowHelper::handleNativeEvent(
}
updateMargins();
if (_shadow) {
_title->refreshAdditionalPaddings(_handle);
const auto changes = (wParam == SIZE_MINIMIZED
|| wParam == SIZE_MAXIMIZED)
? WindowShadow::Change::Hidden
@ -462,6 +507,7 @@ bool WindowHelper::handleNativeEvent(
case WM_MOVE: {
if (_shadow) {
_title->refreshAdditionalPaddings(_handle);
_shadow->update(WindowShadow::Change::Moved);
}
} return false;
@ -477,33 +523,35 @@ bool WindowHelper::handleNativeEvent(
const auto mapped = QPoint(
p.x - r.left + _marginsDelta.left(),
p.y - r.top + _marginsDelta.top());
if (!window()->rect().contains(mapped)) {
*result = HTTRANSPARENT;
} else if (_title->isHidden()
|| !_title->geometry().contains(mapped)) {
*result = HTCLIENT;
} else switch (_title->hitTest(_title->pos() + mapped)) {
case HitTestResult::Client:
case HitTestResult::SysButton: *result = HTCLIENT; break;
case HitTestResult::Caption: *result = HTCAPTION; break;
case HitTestResult::Top: *result = HTTOP; break;
case HitTestResult::TopRight: *result = HTTOPRIGHT; break;
case HitTestResult::Right: *result = HTRIGHT; break;
case HitTestResult::BottomRight: *result = HTBOTTOMRIGHT; break;
case HitTestResult::Bottom: *result = HTBOTTOM; break;
case HitTestResult::BottomLeft: *result = HTBOTTOMLEFT; break;
case HitTestResult::Left: *result = HTLEFT; break;
case HitTestResult::TopLeft: *result = HTTOPLEFT; break;
case HitTestResult::None:
default: *result = HTTRANSPARENT; break;
};
} return true;
*result = [&] {
if (!window()->rect().contains(mapped)) {
return HTTRANSPARENT;
}
auto request = HitTestRequest{
.point = mapped,
};
_hitTestRequests.fire(&request);
switch (const auto result = request.result) {
case HitTestResult::Client: return HTCLIENT;
case HitTestResult::Caption: return HTCAPTION;
case HitTestResult::Top: return HTTOP;
case HitTestResult::TopRight: return HTTOPRIGHT;
case HitTestResult::Right: return HTRIGHT;
case HitTestResult::BottomRight: return HTBOTTOMRIGHT;
case HitTestResult::Bottom: return HTBOTTOM;
case HitTestResult::BottomLeft: return HTBOTTOMLEFT;
case HitTestResult::Left: return HTLEFT;
case HitTestResult::TopLeft: return HTTOPLEFT;
case WM_NCRBUTTONUP: {
if (_title->isHidden()) {
return false;
}
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
case HitTestResult::Minimize:
case HitTestResult::MaximizeRestore:
case HitTestResult::Close: return systemButtonHitTest(result);
case HitTestResult::None:
default: return HTTRANSPARENT;
};
}();
_systemButtonOver.fire(systemButtonHitTest(*result));
} return true;
case WM_SYSCOMMAND: {
@ -553,6 +601,87 @@ bool WindowHelper::fixedSize() const {
return window()->minimumSize() == window()->maximumSize();
}
bool WindowHelper::handleSystemButtonEvent(
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result) {
if (_title->isHidden()) {
return false;
}
const auto testResult = LOWORD(wParam);
const auto sysButtons = { HTMINBUTTON, HTMAXBUTTON, HTCLOSE };
const auto overSysButton = ranges::contains(sysButtons, testResult);
switch (msg) {
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
case WM_NCXBUTTONDBLCLK: {
if (!overSysButton || fixedSize()) {
return false;
}
// Ignore double clicks on system buttons.
if (result) *result = 0;
} return true;
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP:
_systemButtonDown.fire((msg == WM_NCLBUTTONDOWN)
? systemButtonHitTest(testResult)
: HitTestResult::None);
if (overSysButton) {
if (result) *result = 0;
}
return overSysButton;
case WM_NCMBUTTONDOWN:
case WM_NCMBUTTONUP:
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONUP:
case WM_NCXBUTTONDOWN:
case WM_NCXBUTTONUP:
if (!overSysButton) {
return false;
}
if (result) *result = 0;
return true;
case WM_NCMOUSEHOVER:
case WM_NCMOUSEMOVE:
_systemButtonOver.fire(systemButtonHitTest(testResult));
if (overSysButton) {
if (result) *result = 0;
}
return overSysButton;
case WM_NCMOUSELEAVE:
_systemButtonOver.fire(HitTestResult::None);
return false;
}
return false;
}
int WindowHelper::systemButtonHitTest(HitTestResult result) const {
if (!SemiNativeSystemButtonProcessing()) {
return HTCLIENT;
}
switch (result) {
case HitTestResult::Minimize: return HTMINBUTTON;
case HitTestResult::MaximizeRestore: return HTMAXBUTTON;
case HitTestResult::Close: return HTCLOSE;
}
return HTTRANSPARENT;
}
HitTestResult WindowHelper::systemButtonHitTest(int result) const {
if (!SemiNativeSystemButtonProcessing()) {
return HitTestResult::None;
}
switch (result) {
case HTMINBUTTON: return HitTestResult::Minimize;
case HTMAXBUTTON: return HitTestResult::MaximizeRestore;
case HTCLOSE: return HitTestResult::Close;
}
return HitTestResult::None;
}
int WindowHelper::titleHeight() const {
return _title->isHidden() ? 0 : _title->height();
}

View file

@ -15,14 +15,19 @@ namespace Ui {
namespace Platform {
class TitleWidget;
struct HitTestRequest;
enum class HitTestResult;
class WindowHelper final : public BasicWindowHelper {
public:
explicit WindowHelper(not_null<RpWidget*> window);
~WindowHelper();
void initInWindow(not_null<RpWindow*> window) override;
not_null<RpWidget*> body() override;
QMargins frameMargins() override;
int additionalContentPadding() const override;
rpl::producer<int> additionalContentPaddingValue() const override;
void setTitle(const QString &title) override;
void setTitleStyle(const style::WindowTitle &st) override;
void setNativeFrame(bool enabled) override;
@ -32,6 +37,13 @@ public:
void showFullScreen() override;
void showNormal() override;
[[nodiscard]] auto hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>> override;
[[nodiscard]] auto systemButtonOver() const
-> rpl::producer<HitTestResult> override;
[[nodiscard]] auto systemButtonDown() const
-> rpl::producer<HitTestResult> override;
private:
class NativeFilter;
friend class NativeFilter;
@ -43,13 +55,21 @@ private:
void updateSystemMenu();
void updateSystemMenu(Qt::WindowState state);
void initialShadowUpdate();
void updateCornersRounding();
void fixMaximizedWindow();
[[nodiscard]] bool handleNativeEvent(
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result);
[[nodiscard]] bool handleSystemButtonEvent(
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result);
[[nodiscard]] bool fixedSize() const;
[[nodiscard]] int systemButtonHitTest(HitTestResult result) const;
[[nodiscard]] HitTestResult systemButtonHitTest(int result) const;
[[nodiscard]] int titleHeight() const;
static not_null<NativeFilter*> GetNativeFilter();
@ -57,10 +77,13 @@ private:
const HWND _handle = nullptr;
const not_null<TitleWidget*> _title;
const not_null<RpWidget*> _body;
rpl::event_stream<not_null<HitTestRequest*>> _hitTestRequests;
rpl::event_stream<HitTestResult> _systemButtonOver;
rpl::event_stream<HitTestResult> _systemButtonDown;
std::optional<WindowShadow> _shadow;
bool _updatingMargins = false;
QMargins _marginsDelta;
HMENU _menu = nullptr;
bool _updatingMargins = false;
bool _isFullScreen = false;
};

View file

@ -25,12 +25,22 @@ void DrawRoundedRect(
const auto h = rect.height();
const auto cornerWidth = corners[0].width() / pixelRatio;
const auto cornerHeight = corners[0].height() / pixelRatio;
if (w < 2 * cornerWidth || h < 2 * cornerHeight) return;
const auto hasLeft = (parts & RectPart::Left) != 0;
const auto hasRight = (parts & RectPart::Right) != 0;
const auto hasTop = (parts & RectPart::Top) != 0;
const auto hasBottom = (parts & RectPart::Bottom) != 0;
if (w < ((hasLeft ? cornerWidth : 0) + (hasRight ? cornerWidth : 0))) {
return;
}
if (h < ((hasTop ? cornerHeight : 0) + (hasBottom ? cornerHeight : 0))) {
return;
}
if (w > 2 * cornerWidth) {
if (parts & RectPart::Top) {
if (hasTop) {
p.fillRect(x + cornerWidth, y, w - 2 * cornerWidth, cornerHeight, brush);
}
if (parts & RectPart::Bottom) {
if (hasBottom) {
p.fillRect(x + cornerWidth, y + h - cornerHeight, w - 2 * cornerWidth, cornerHeight, brush);
}
}
@ -38,13 +48,13 @@ void DrawRoundedRect(
if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) {
p.fillRect(x, y + cornerHeight, w, h - 2 * cornerHeight, brush);
} else {
if (parts & RectPart::Left) {
if (hasLeft) {
p.fillRect(x, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush);
}
if ((parts & RectPart::Center) && w > 2 * cornerWidth) {
p.fillRect(x + cornerWidth, y + cornerHeight, w - 2 * cornerWidth, h - 2 * cornerHeight, brush);
}
if (parts & RectPart::Right) {
if (hasRight) {
p.fillRect(x + w - cornerWidth, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush);
}
}

File diff suppressed because it is too large Load diff

View file

@ -22,25 +22,6 @@ static const auto kQEllipsis = QStringLiteral("...");
} // namespace Ui
static const QChar TextCommand(0x0010);
enum TextCommands {
TextCommandBold = 0x01,
TextCommandNoBold = 0x02,
TextCommandItalic = 0x03,
TextCommandNoItalic = 0x04,
TextCommandUnderline = 0x05,
TextCommandNoUnderline = 0x06,
TextCommandStrikeOut = 0x07,
TextCommandNoStrikeOut = 0x08,
TextCommandSemibold = 0x09,
TextCommandNoSemibold = 0x0A,
TextCommandLinkIndex = 0x0B, // 0 - NoLink
TextCommandLinkText = 0x0C,
TextCommandSkipBlock = 0x0D,
TextCommandSpoiler = 0x0E,
TextCommandNoSpoiler = 0x0F,
TextCommandLangTag = 0x20,
};
struct TextParseOptions {
int32 flags;
@ -122,8 +103,7 @@ public:
const style::TextStyle &st,
const QString &text,
const TextParseOptions &options = _defaultOptions,
int32 minResizeWidth = QFIXED_MAX,
bool richText = false);
int32 minResizeWidth = QFIXED_MAX);
String(const String &other) = default;
String(String &&other) = default;
String &operator=(const String &other) = default;
@ -134,7 +114,6 @@ public:
int countHeight(int width, bool breakEverywhere = false) const;
void countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere = false) const;
void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions);
void setRichText(const style::TextStyle &st, const QString &text, TextParseOptions options = _defaultOptions);
void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions, const std::any &context = {});
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
@ -253,10 +232,10 @@ private:
[[nodiscard]] bool IsAlmostLinkEnd(QChar ch);
[[nodiscard]] bool IsLinkEnd(QChar ch);
[[nodiscard]] bool IsNewline(QChar ch);
[[nodiscard]] bool IsSpace(QChar ch, bool rich = false);
[[nodiscard]] bool IsSpace(QChar ch);
[[nodiscard]] bool IsDiac(QChar ch);
[[nodiscard]] bool IsReplacedBySpace(QChar ch);
[[nodiscard]] bool IsTrimmed(QChar ch, bool rich = false);
[[nodiscard]] bool IsTrimmed(QChar ch);
} // namespace Text
} // namespace Ui
@ -276,18 +255,3 @@ inline TextSelection shiftSelection(TextSelection selection, const Ui::Text::Str
inline TextSelection unshiftSelection(TextSelection selection, const Ui::Text::String &byText) {
return unshiftSelection(selection, byText.length());
}
// textcmd
QString textcmdSkipBlock(ushort w, ushort h);
QString textcmdStartLink(ushort lnkIndex);
QString textcmdStartLink(const QString &url);
QString textcmdStopLink();
QString textcmdLink(ushort lnkIndex, const QString &text);
QString textcmdLink(const QString &url, const QString &text);
QString textcmdStartSemibold();
QString textcmdStopSemibold();
QString textcmdStartSpoiler();
QString textcmdStopSpoiler();
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);

View file

@ -345,11 +345,11 @@ AbstractBlock::AbstractBlock(
const QString &str,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex)
: _from(from)
, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12))
, _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14))
, _spoilerIndex(spoilerIndex) {
}
@ -374,11 +374,11 @@ QFixed AbstractBlock::f_rpadding() const {
}
uint16 AbstractBlock::lnkIndex() const {
return (_flags >> 12) & 0xFFFF;
return (_flags >> 14) & 0xFFFF;
}
void AbstractBlock::setLnkIndex(uint16 lnkIndex) {
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
_flags = (_flags & ~(0xFFFF << 14)) | (lnkIndex << 14);
}
uint16 AbstractBlock::spoilerIndex() const {
@ -390,11 +390,11 @@ void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) {
}
TextBlockType AbstractBlock::type() const {
return TextBlockType((_flags >> 8) & 0x0F);
return TextBlockType((_flags >> 10) & 0x0F);
}
int32 AbstractBlock::flags() const {
return (_flags & 0xFFF);
return (_flags & 0b1111111111);
}
QFixed AbstractBlock::f_rbearing() const {
@ -409,11 +409,11 @@ TextBlock::TextBlock(
QFixed minResizeWidth,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
_flags |= ((TextBlockTText & 0x0F) << 8);
_flags |= ((TextBlockTText & 0x0F) << 10);
if (length) {
style::font blockFont = font;
if (!flags && lnkIndex) {
@ -446,18 +446,22 @@ TextBlock::TextBlock(
}
}
QFixed TextBlock::real_f_rbearing() const {
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
}
EmojiBlock::EmojiBlock(
const style::font &font,
const QString &str,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex,
EmojiPtr emoji)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex)
, _emoji(emoji) {
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
_flags |= ((TextBlockTEmoji & 0x0F) << 10);
_width = int(st::emojiSize + 2 * st::emojiPadding);
_rpadding = 0;
for (auto i = length; i != 0;) {
@ -470,5 +474,279 @@ EmojiBlock::EmojiBlock(
}
}
NewlineBlock::NewlineBlock(
const style::font &font,
const QString &str,
uint16 from,
uint16 length,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
_flags |= ((TextBlockTNewline & 0x0F) << 10);
}
Qt::LayoutDirection NewlineBlock::nextDirection() const {
return _nextDir;
}
SkipBlock::SkipBlock(
const style::font &font,
const QString &str,
uint16 from,
int32 w,
int32 h,
uint16 lnkIndex,
uint16 spoilerIndex)
: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex)
, _height(h) {
_flags |= ((TextBlockTSkip & 0x0F) << 10);
_width = w;
}
int SkipBlock::height() const {
return _height;
}
TextWord::TextWord(
uint16 from,
QFixed width,
QFixed rbearing,
QFixed rpadding)
: _from(from)
, _rbearing((rbearing.value() > 0x7FFF)
? 0x7FFF
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
, _width(width)
, _rpadding(rpadding) {
}
uint16 TextWord::from() const {
return _from;
}
QFixed TextWord::f_rbearing() const {
return QFixed::fromFixed(_rbearing);
}
QFixed TextWord::f_width() const {
return _width;
}
QFixed TextWord::f_rpadding() const {
return _rpadding;
}
void TextWord::add_rpadding(QFixed padding) {
_rpadding += padding;
}
Block::Block() {
Unexpected("Should not be called.");
}
Block::Block(const Block &other) {
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
break;
case TextBlockTText:
emplace<TextBlock>(other.unsafe<TextBlock>());
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
break;
case TextBlockTSkip:
emplace<SkipBlock>(other.unsafe<SkipBlock>());
break;
default:
Unexpected("Bad text block type in Block(const Block&).");
}
}
Block::Block(Block &&other) {
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
break;
case TextBlockTText:
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
break;
case TextBlockTSkip:
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
break;
default:
Unexpected("Bad text block type in Block(Block&&).");
}
}
Block &Block::operator=(const Block &other) {
if (&other == this) {
return *this;
}
destroy();
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
break;
case TextBlockTText:
emplace<TextBlock>(other.unsafe<TextBlock>());
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
break;
case TextBlockTSkip:
emplace<SkipBlock>(other.unsafe<SkipBlock>());
break;
default:
Unexpected("Bad text block type in operator=(const Block&).");
}
return *this;
}
Block &Block::operator=(Block &&other) {
if (&other == this) {
return *this;
}
destroy();
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
break;
case TextBlockTText:
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
break;
case TextBlockTSkip:
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
break;
default:
Unexpected("Bad text block type in operator=(Block&&).");
}
return *this;
}
Block::~Block() {
destroy();
}
Block Block::Newline(
const style::font &font,
const QString &str,
uint16 from,
uint16 length,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex) {
return New<NewlineBlock>(
font,
str,
from,
length,
flags,
lnkIndex,
spoilerIndex);
}
Block Block::Text(
const style::font &font,
const QString &str,
QFixed minResizeWidth,
uint16 from,
uint16 length,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex) {
return New<TextBlock>(
font,
str,
minResizeWidth,
from,
length,
flags,
lnkIndex,
spoilerIndex);
}
Block Block::Emoji(
const style::font &font,
const QString &str,
uint16 from,
uint16 length,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex,
EmojiPtr emoji) {
return New<EmojiBlock>(
font,
str,
from,
length,
flags,
lnkIndex,
spoilerIndex,
emoji);
}
Block Block::Skip(
const style::font &font,
const QString &str,
uint16 from,
int32 w,
int32 h,
uint16 lnkIndex,
uint16 spoilerIndex) {
return New<SkipBlock>(font, str, from, w, h, lnkIndex, spoilerIndex);
}
AbstractBlock *Block::get() {
return &unsafe<AbstractBlock>();
}
const AbstractBlock *Block::get() const {
return &unsafe<AbstractBlock>();
}
AbstractBlock *Block::operator->() {
return get();
}
const AbstractBlock *Block::operator->() const {
return get();
}
AbstractBlock &Block::operator*() {
return *get();
}
const AbstractBlock &Block::operator*() const {
return *get();
}
void Block::destroy() {
switch (get()->type()) {
case TextBlockTNewline:
unsafe<NewlineBlock>().~NewlineBlock();
break;
case TextBlockTText:
unsafe<TextBlock>().~TextBlock();
break;
case TextBlockTEmoji:
unsafe<EmojiBlock>().~EmojiBlock();
break;
case TextBlockTSkip:
unsafe<SkipBlock>().~SkipBlock();
break;
default:
Unexpected("Bad text block type in Block(Block&&).");
}
}
} // namespace Text
} // namespace Ui

View file

@ -30,6 +30,7 @@ enum TextBlockFlags {
TextBlockFSemibold = 0x20,
TextBlockFCode = 0x40,
TextBlockFPre = 0x80,
TextBlockFPlainLink = 0x100,
};
class AbstractBlock {
@ -58,13 +59,13 @@ protected:
const QString &str,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex);
uint16 _from = 0;
uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
uint32 _flags = 0; // 2 bits empty, 16 bits lnkIndex, 4 bits type, 10 bits flags
uint16 _spoilerIndex = 0;
@ -86,16 +87,11 @@ public:
const QString &str,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
_flags |= ((TextBlockTNewline & 0x0F) << 8);
}
uint16 spoilerIndex);
Qt::LayoutDirection nextDirection() const {
return _nextDir;
}
Qt::LayoutDirection nextDirection() const;
private:
Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto;
@ -109,29 +105,12 @@ private:
class TextWord final {
public:
TextWord() = default;
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0)
: _from(from)
, _rbearing((rbearing.value() > 0x7FFF)
? 0x7FFF
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
, _width(width)
, _rpadding(rpadding) {
}
uint16 from() const {
return _from;
}
QFixed f_rbearing() const {
return QFixed::fromFixed(_rbearing);
}
QFixed f_width() const {
return _width;
}
QFixed f_rpadding() const {
return _rpadding;
}
void add_rpadding(QFixed padding) {
_rpadding += padding;
}
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0);
uint16 from() const;
QFixed f_rbearing() const;
QFixed f_width() const;
QFixed f_rpadding() const;
void add_rpadding(QFixed padding);
private:
uint16 _from = 0;
@ -148,14 +127,12 @@ public:
QFixed minResizeWidth,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex);
private:
QFixed real_f_rbearing() const {
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
}
QFixed real_f_rbearing() const;
QVector<TextWord> _words;
@ -174,7 +151,7 @@ public:
const QString &str,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex,
EmojiPtr emoji);
@ -197,16 +174,9 @@ public:
int32 w,
int32 h,
uint16 lnkIndex,
uint16 spoilerIndex)
: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex)
, _height(h) {
_flags |= ((TextBlockTSkip & 0x0F) << 8);
_width = w;
}
uint16 spoilerIndex);
int height() const {
return _height;
}
int height() const;
private:
int _height = 0;
@ -219,105 +189,21 @@ private:
class Block final {
public:
Block() {
Unexpected("Should not be called.");
}
Block(const Block &other) {
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
break;
case TextBlockTText:
emplace<TextBlock>(other.unsafe<TextBlock>());
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
break;
case TextBlockTSkip:
emplace<SkipBlock>(other.unsafe<SkipBlock>());
break;
default:
Unexpected("Bad text block type in Block(const Block&).");
}
}
Block(Block &&other) {
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
break;
case TextBlockTText:
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
break;
case TextBlockTSkip:
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
break;
default:
Unexpected("Bad text block type in Block(Block&&).");
}
}
Block &operator=(const Block &other) {
if (&other == this) {
return *this;
}
destroy();
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
break;
case TextBlockTText:
emplace<TextBlock>(other.unsafe<TextBlock>());
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
break;
case TextBlockTSkip:
emplace<SkipBlock>(other.unsafe<SkipBlock>());
break;
default:
Unexpected("Bad text block type in operator=(const Block&).");
}
return *this;
}
Block &operator=(Block &&other) {
if (&other == this) {
return *this;
}
destroy();
switch (other->type()) {
case TextBlockTNewline:
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
break;
case TextBlockTText:
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
break;
case TextBlockTEmoji:
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
break;
case TextBlockTSkip:
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
break;
default:
Unexpected("Bad text block type in operator=(Block&&).");
}
return *this;
}
~Block() {
destroy();
}
Block();
Block(const Block &other);
Block(Block &&other);
Block &operator=(const Block &other);
Block &operator=(Block &&other);
~Block();
[[nodiscard]] static Block Newline(
const style::font &font,
const QString &str,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex) {
return New<NewlineBlock>(font, str, from, length, flags, lnkIndex, spoilerIndex);
}
uint16 spoilerIndex);
[[nodiscard]] static Block Text(
const style::font &font,
@ -325,39 +211,19 @@ public:
QFixed minResizeWidth,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex) {
return New<TextBlock>(
font,
str,
minResizeWidth,
from,
length,
flags,
lnkIndex,
spoilerIndex);
}
uint16 spoilerIndex);
[[nodiscard]] static Block Emoji(
const style::font &font,
const QString &str,
uint16 from,
uint16 length,
uchar flags,
uint16 flags,
uint16 lnkIndex,
uint16 spoilerIndex,
EmojiPtr emoji) {
return New<EmojiBlock>(
font,
str,
from,
length,
flags,
lnkIndex,
spoilerIndex,
emoji);
}
EmojiPtr emoji);
[[nodiscard]] static Block Skip(
const style::font &font,
@ -366,9 +232,7 @@ public:
int32 w,
int32 h,
uint16 lnkIndex,
uint16 spoilerIndex) {
return New<SkipBlock>(font, str, from, w, h, lnkIndex, spoilerIndex);
}
uint16 spoilerIndex);
template <typename FinalBlock>
[[nodiscard]] FinalBlock &unsafe() {
@ -380,29 +244,14 @@ public:
return *reinterpret_cast<const FinalBlock*>(&_data);
}
[[nodiscard]] AbstractBlock *get() {
return &unsafe<AbstractBlock>();
}
[[nodiscard]] AbstractBlock *get();
[[nodiscard]] const AbstractBlock *get() const;
[[nodiscard]] const AbstractBlock *get() const {
return &unsafe<AbstractBlock>();
}
[[nodiscard]] AbstractBlock *operator->();
[[nodiscard]] const AbstractBlock *operator->() const;
[[nodiscard]] AbstractBlock *operator->() {
return get();
}
[[nodiscard]] const AbstractBlock *operator->() const {
return get();
}
[[nodiscard]] AbstractBlock &operator*() {
return *get();
}
[[nodiscard]] const AbstractBlock &operator*() const {
return *get();
}
[[nodiscard]] AbstractBlock &operator*();
[[nodiscard]] const AbstractBlock &operator*() const;
private:
struct Tag {
@ -423,24 +272,7 @@ private:
new (&_data) FinalType(std::forward<Args>(args)...);
}
void destroy() {
switch (get()->type()) {
case TextBlockTNewline:
unsafe<NewlineBlock>().~NewlineBlock();
break;
case TextBlockTText:
unsafe<TextBlock>().~TextBlock();
break;
case TextBlockTEmoji:
unsafe<EmojiBlock>().~EmojiBlock();
break;
case TextBlockTSkip:
unsafe<SkipBlock>().~SkipBlock();
break;
default:
Unexpected("Bad text block type in Block(Block&&).");
}
}
void destroy();
static_assert(sizeof(NewlineBlock) <= sizeof(TextBlock));
static_assert(alignof(NewlineBlock) <= alignof(void*));

View file

@ -12,7 +12,7 @@
#include "ui/text/text.h"
#include "ui/widgets/input_fields.h"
#include "ui/emoji_config.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_common_adapters.h"
#include <QtCore/QStack>
#include <QtCore/QMimeData>
@ -22,7 +22,7 @@
namespace TextUtilities {
namespace {
constexpr auto kTagSeparator = '$';
constexpr auto kTagSeparator = '\\';
using namespace Ui::Text;
@ -1293,35 +1293,17 @@ bool IsValidTopDomain(const QString &protocol) {
return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
}
QString Clean(const QString &text, bool keepSpoilers) {
auto result = text;
for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch) {
if (keepSpoilers && (*ch == TextCommand)) {
if ((*(ch + 1) == TextCommandSpoiler)
|| (*(ch - 1) == TextCommandSpoiler)
|| (*(ch + 1) == TextCommandNoSpoiler)
|| (*(ch - 1) == TextCommandNoSpoiler)) {
continue;
}
}
if (*ch == TextCommand) {
result[int(ch - s)] = QChar::Space;
}
}
return result;
}
QString EscapeForRichParsing(const QString &text) {
QString result;
result.reserve(text.size());
auto s = text.constData(), ch = s;
for (const QChar *e = s + text.size(); ch != e; ++ch) {
if (*ch == TextCommand) {
if (ch > s) result.append(s, ch - s);
result.append(QChar::Space);
s = ch + 1;
continue;
}
// if (*ch == TextCommand) {
// if (ch > s) result.append(s, ch - s);
// result.append(QChar::Space);
// s = ch + 1;
// continue;
// }
if (ch->unicode() == '\\' || ch->unicode() == '[') {
if (ch > s) result.append(s, ch - s);
result.append('\\');
@ -1349,7 +1331,7 @@ QString SingleLine(const QString &text) {
}
for (auto ch = s; ch != e; ++ch) {
if (IsNewline(*ch) || *ch == TextCommand) {
if (IsNewline(*ch)/* || *ch == TextCommand*/) {
result[int(ch - s)] = QChar::Space;
}
}
@ -1545,56 +1527,14 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
return true;
}
bool textcmdStartsLink(const QChar *start, int32 len, int32 commandOffset) {
if (commandOffset + 2 < len) {
if (*(start + commandOffset + 1) == TextCommandLinkIndex) {
return (*(start + commandOffset + 2) != 0);
}
return (*(start + commandOffset + 1) != TextCommandLinkText);
}
return false;
}
bool checkTagStartInCommand(const QChar *start, int32 len, int32 tagStart, int32 &commandOffset, bool &commandIsLink, bool &inLink) {
bool inCommand = false;
const QChar *commandEnd = start + commandOffset;
while (commandOffset < len && tagStart > commandOffset) { // skip commands, evaluating are we in link or not
commandEnd = textSkipCommand(start + commandOffset, start + len);
if (commandEnd > start + commandOffset) {
if (tagStart < (commandEnd - start)) {
inCommand = true;
break;
}
for (commandOffset = commandEnd - start; commandOffset < len; ++commandOffset) {
if (*(start + commandOffset) == TextCommand) {
inLink = commandIsLink;
commandIsLink = textcmdStartsLink(start, len, commandOffset);
break;
}
}
if (commandOffset >= len) {
inLink = commandIsLink;
commandIsLink = false;
}
} else {
break;
}
}
if (inCommand) {
commandOffset = commandEnd - start;
}
return inCommand;
}
TextWithEntities ParseEntities(const QString &text, int32 flags) {
const auto rich = ((flags & TextParseRichText) != 0);
auto result = TextWithEntities{ text, EntitiesInText() };
ParseEntities(result, flags, rich);
ParseEntities(result, flags);
return result;
}
// Some code is duplicated in message_field.cpp!
void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
void ParseEntities(TextWithEntities &result, int32 flags) {
constexpr auto kNotFound = std::numeric_limits<int>::max();
auto newEntities = EntitiesInText();
@ -1605,20 +1545,10 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
int existingEntityIndex = 0, existingEntitiesCount = result.entities.size();
int existingEntityEnd = 0;
int32 len = result.text.size(), commandOffset = rich ? 0 : len;
bool inLink = false, commandIsLink = false;
int32 len = result.text.size();
const auto start = result.text.constData();
const auto end = start + result.text.size();
for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) {
if (commandOffset <= offset) {
for (commandOffset = offset; commandOffset < len; ++commandOffset) {
if (*(start + commandOffset) == TextCommand) {
inLink = commandIsLink;
commandIsLink = textcmdStartsLink(start, len, commandOffset);
break;
}
}
}
auto mDomain = qthelp::RegExpDomain().match(result.text, matchOffset);
auto mExplicitDomain = qthelp::RegExpDomainExplicit().match(result.text, matchOffset);
auto mHashtag = withHashtags ? RegExpHashtag().match(result.text, matchOffset) : QRegularExpressionMatch();
@ -1706,17 +1636,6 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
offset = matchOffset = mentionEnd;
continue;
}
const auto inCommand = checkTagStartInCommand(
start,
len,
mentionStart,
commandOffset,
commandIsLink,
inLink);
if (inCommand || inLink) {
offset = matchOffset = commandOffset;
continue;
}
lnkType = EntityType::Mention;
lnkStart = mentionStart;
@ -1727,50 +1646,15 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
offset = matchOffset = hashtagEnd;
continue;
}
const auto inCommand = checkTagStartInCommand(
start,
len,
hashtagStart,
commandOffset,
commandIsLink,
inLink);
if (inCommand || inLink) {
offset = matchOffset = commandOffset;
continue;
}
lnkType = EntityType::Hashtag;
lnkStart = hashtagStart;
lnkLength = hashtagEnd - hashtagStart;
} else if (botCommandStart < domainStart) {
const auto inCommand = checkTagStartInCommand(
start,
len,
botCommandStart,
commandOffset,
commandIsLink,
inLink);
if (inCommand || inLink) {
offset = matchOffset = commandOffset;
continue;
}
lnkType = EntityType::BotCommand;
lnkStart = botCommandStart;
lnkLength = botCommandEnd - botCommandStart;
} else {
const auto inCommand = checkTagStartInCommand(
start,
len,
domainStart,
commandOffset,
commandIsLink,
inLink);
if (inCommand || inLink) {
offset = matchOffset = commandOffset;
continue;
}
auto protocol = mDomain.captured(1).toLower();
auto topDomain = mDomain.captured(3).toLower();
auto isProtocolValid = protocol.isEmpty() || IsValidProtocol(protocol);
@ -2332,36 +2216,6 @@ void SetClipboardText(
}
}
QString TextWithSpoilerCommands(const TextWithEntities &textWithEntities) {
auto text = textWithEntities.text;
auto offset = 0;
const auto start = textcmdStartSpoiler();
const auto stop = textcmdStopSpoiler();
for (const auto &e : textWithEntities.entities) {
if (e.type() == EntityType::Spoiler) {
text.insert(e.offset() + offset, start);
offset += start.size();
text.insert(e.offset() + e.length() + offset, stop);
offset += stop.size();
}
}
return text;
}
QString CutTextWithCommands(
QString text,
int length,
const QString &start,
const QString &stop) {
text = text.mid(0, length);
const auto lastStart = text.lastIndexOf(start);
const auto lastStop = text.lastIndexOf(stop);
const auto additional = ((lastStart == -1) || (lastStart < lastStop))
? QString()
: stop;
return text + additional + Ui::kQEllipsis;
}
} // namespace TextUtilities
EntityInText::EntityInText(

View file

@ -25,6 +25,7 @@ enum class EntityType : uchar {
MentionName,
BotCommand,
MediaTimestamp,
PlainLink, // Senders in chat list, attachements in chat list, etc.
Bold,
Semibold,
@ -239,11 +240,11 @@ struct TextForMimeData {
enum {
TextParseMultiline = 0x001,
TextParseLinks = 0x002,
TextParseRichText = 0x004,
TextParseMentions = 0x008,
TextParseHashtags = 0x010,
TextParseBotCommands = 0x020,
TextParseMarkdown = 0x040,
TextParseMentions = 0x004,
TextParseHashtags = 0x008,
TextParseBotCommands = 0x010,
TextParseMarkdown = 0x020,
TextParsePlainLinks = 0x040,
};
struct TextWithTags {
@ -298,7 +299,6 @@ QString MarkdownSpoilerGoodBefore();
QString MarkdownSpoilerBadAfter();
// Text preprocess.
QString Clean(const QString &text, bool keepSpoilers = false);
QString EscapeForRichParsing(const QString &text);
QString SingleLine(const QString &text);
TextWithEntities SingleLine(const TextWithEntities &text);
@ -337,7 +337,7 @@ inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
// New entities are added to the ones that are already in result.
// Changes text if (flags & TextParseMarkdown).
TextWithEntities ParseEntities(const QString &text, int32 flags);
void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
void ParseEntities(TextWithEntities &result, int32 flags);
void PrepareForSending(TextWithEntities &result, int32 flags);
void Trim(TextWithEntities &result);
@ -384,12 +384,4 @@ void SetClipboardText(
const TextForMimeData &text,
QClipboard::Mode mode = QClipboard::Clipboard);
[[nodiscard]] QString TextWithSpoilerCommands(
const TextWithEntities &textWithEntities);
[[nodiscard]] QString CutTextWithCommands(
QString text,
int length,
const QString &start,
const QString &stop);
} // namespace TextUtilities

View file

@ -7,7 +7,7 @@
#include "ui/text/text_utilities.h"
#include "base/algorithm.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_string_view.h"
#include <QtCore/QRegularExpression>
@ -42,6 +42,25 @@ TextWithEntities Link(const QString &text, const QString &url) {
return WithSingleEntity(text, EntityType::CustomUrl, url);
}
TextWithEntities Link(const QString &text, int index) {
Expects(index > 0 && index < 10);
return Link(text, QString("internal:index") + QChar('0' + index));
}
TextWithEntities PlainLink(const QString &text) {
return WithSingleEntity(text, EntityType::PlainLink);
}
TextWithEntities Wrapped(
TextWithEntities text,
EntityType type,
const QString &data) {
text.entities.insert(
text.entities.begin(),
{ type, 0, int(text.text.size()), data });
return text;
}
TextWithEntities RichLangValue(const QString &text) {
static const auto kStart = QRegularExpression("(\\*\\*|__)");
@ -74,5 +93,48 @@ TextWithEntities RichLangValue(const QString &text) {
return result;
}
TextWithEntities Mid(const TextWithEntities &text, int position, int n) {
if (n == -1) {
n = int(text.text.size()) - position;
}
const auto midEnd = (position + n);
auto entities = ranges::views::all(
text.entities
) | ranges::views::filter([&](const EntityInText &entity) {
// Intersects of ranges.
const auto l1 = entity.offset();
const auto r1 = entity.offset() + entity.length() - 1;
const auto l2 = position;
const auto r2 = midEnd - 1;
return !(l1 > r2 || l2 > r1);
}) | ranges::views::transform([&](const EntityInText &entity) {
if ((entity.offset() == position) && (entity.length() == n)) {
return entity;
}
const auto start = std::max(entity.offset(), position);
const auto end = std::min(entity.offset() + entity.length(), midEnd);
return EntityInText(
entity.type(),
start - position,
end - start,
entity.data());
}) | ranges::to<EntitiesInText>();
return {
.text = text.text.mid(position, n),
.entities = std::move(entities),
};
}
TextWithEntities Filtered(
const TextWithEntities &text,
const std::vector<EntityType> &types) {
auto result = ranges::views::all(
text.entities
) | ranges::views::filter([&](const EntityInText &entity) {
return ranges::contains(types, entity.type());
}) | ranges::to<EntitiesInText>();
return { .text = text.text, .entities = std::move(result) };
}
} // namespace Text
} // namespace Ui

View file

@ -30,6 +30,12 @@ inline constexpr auto Upper = details::ToUpperType{};
[[nodiscard]] TextWithEntities Link(
const QString &text,
const QString &url = "internal:action");
[[nodiscard]] TextWithEntities Link(const QString &text, int index);
[[nodiscard]] TextWithEntities PlainLink(const QString &text);
[[nodiscard]] TextWithEntities Wrapped(
TextWithEntities text,
EntityType type,
const QString &data = QString());
[[nodiscard]] TextWithEntities RichLangValue(const QString &text);
[[nodiscard]] inline TextWithEntities WithEntities(const QString &text) {
return { text };
@ -65,5 +71,13 @@ inline constexpr auto Upper = details::ToUpperType{};
return rpl::map(WithEntities);
}
[[nodiscard]] TextWithEntities Mid(
const TextWithEntities &text,
int position,
int n = -1);
[[nodiscard]] TextWithEntities Filtered(
const TextWithEntities &result,
const std::vector<EntityType> &types);
} // namespace Text
} // namespace Ui

View file

@ -741,19 +741,35 @@ void SettingsButton::setColorOverride(std::optional<QColor> textColorOverride) {
update();
}
const style::SettingsButton &SettingsButton::st() const {
return _st;
}
void SettingsButton::paintEvent(QPaintEvent *e) {
Painter p(this);
auto paintOver = (isOver() || isDown()) && !isDisabled();
p.fillRect(e->rect(), paintOver ? _st.textBgOver : _st.textBg);
const auto paintOver = (isOver() || isDown()) && !isDisabled();
paintBg(p, e->rect(), paintOver);
paintRipple(p, 0, 0);
auto outerw = width();
const auto outerw = width();
paintText(p, paintOver, outerw);
if (_toggle) {
paintToggle(p, outerw);
}
}
void SettingsButton::paintBg(Painter &p, const QRect &rect, bool over) const {
p.fillRect(rect, over ? _st.textBgOver : _st.textBg);
}
void SettingsButton::paintText(Painter &p, bool over, int outerw) const {
p.setFont(_st.font);
p.setPen(_textColorOverride
? QPen(*_textColorOverride)
: paintOver
: over
? _st.textFgOver
: _st.textFg);
p.drawTextLeft(
@ -762,7 +778,9 @@ void SettingsButton::paintEvent(QPaintEvent *e) {
outerw,
_text,
_textWidth);
}
void SettingsButton::paintToggle(Painter &p, int outerw) const {
if (_toggle) {
auto rect = toggleRect();
_toggle->paint(p, rect.left(), rect.top(), outerw);

View file

@ -13,6 +13,8 @@
#include <memory>
class Painter;
namespace Ui {
class RippleAnimation;
@ -273,6 +275,11 @@ protected:
void paintEvent(QPaintEvent *e) override;
const style::SettingsButton &st() const;
void paintBg(Painter &p, const QRect &rect, bool over) const;
void paintText(Painter &p, bool over, int outerw) const;
void paintToggle(Painter &p, int outerw) const;
private:
void setText(QString &&text);
QRect toggleRect() const;

View file

@ -22,7 +22,7 @@ TextParseOptions _checkboxOptions = {
};
TextParseOptions _checkboxRichOptions = {
TextParseMultiline | TextParseRichText, // flags
TextParseMultiline, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir

View file

@ -122,6 +122,9 @@ public:
QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override;
void setLocked(bool locked);
bool isLocked() {
return _locked;
}
private:
void paintXV(Painter &p, int left, int top, int outerWidth, float64 toggled, const QBrush &brush);
@ -203,6 +206,10 @@ public:
}
QRect checkRect() const;
not_null<AbstractCheckView*> checkView() const {
return _check.get();
}
protected:
void paintEvent(QPaintEvent *e) override;

View file

@ -16,7 +16,7 @@
#include "base/platform/base_platform_info.h"
#include "emoji_suggestions_helper.h"
#include "styles/palette.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_common_adapters.h"
#include <QtWidgets/QCommonStyle>
#include <QtWidgets/QScrollBar>
@ -706,6 +706,7 @@ void RemoveDocumentTags(
format.setProperty(kTagProperty, QString());
format.setProperty(kReplaceTagId, QString());
format.setForeground(st.textFg);
format.setBackground(QBrush());
format.setFont(st.font);
cursor.mergeCharFormat(format);
}
@ -752,6 +753,7 @@ void ApplyTagFormat(QTextCharFormat &to, const QTextCharFormat &from) {
to.setProperty(kReplaceTagId, from.property(kReplaceTagId));
to.setFont(from.font());
to.setForeground(from.foreground());
to.setBackground(from.background());
}
// Returns the position of the first inserted tag or "changedEnd" value if none found.
@ -1467,7 +1469,9 @@ void InputField::updatePalette() {
auto format = fragment.charFormat();
const auto tag = format.property(kTagProperty).toString();
format.setForeground(PrepareTagFormat(_st, tag).foreground());
const auto updatedFormat = PrepareTagFormat(_st, tag);
format.setForeground(updatedFormat.foreground());
format.setBackground(updatedFormat.background());
cursor.setPosition(fragment.position());
cursor.setPosition(till, QTextCursor::KeepAnchor);
cursor.mergeCharFormat(format);

View file

@ -13,7 +13,7 @@
#include "ui/widgets/box_content_divider.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler
#include "ui/inactive_press.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_common_adapters.h"
#include <QtWidgets/QApplication>
#include <QtGui/QClipboard>
@ -32,7 +32,7 @@ TextParseOptions _labelOptions = {
};
TextParseOptions _labelMarkedOptions = {
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
TextParseMultiline | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
@ -257,11 +257,6 @@ void FlatLabel::setText(const QString &text) {
textUpdated();
}
void FlatLabel::setRichText(const QString &text) {
_text.setRichText(_st.style, text, _labelOptions);
textUpdated();
}
void FlatLabel::setMarkedText(const TextWithEntities &textWithEntities) {
_text.setMarkedText(_st.style, textWithEntities, _labelMarkedOptions);
textUpdated();

View file

@ -116,7 +116,6 @@ public:
void setTextColorOverride(std::optional<QColor> color);
void setText(const QString &text);
void setRichText(const QString &text);
void setMarkedText(const TextWithEntities &textWithEntities);
void setSelectable(bool selectable);
void setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph);

View file

@ -40,7 +40,7 @@ namespace {
}
TextParseOptions MenuTextOptions = {
TextParseLinks | TextParseRichText, // flags
TextParseLinks, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir

View file

@ -63,7 +63,7 @@ rpl::producer<CallbackData> ItemBase::clicks() const {
AbstractButton::clicks() | rpl::to_empty,
_clicks.events()
) | rpl::filter([=] {
return isEnabled();
return isEnabled() && !AbstractButton::isDisabled();
}) | rpl::map([=]() -> CallbackData {
return { action(), y(), _lastTriggeredSource, _index, true };
});

View file

@ -15,6 +15,7 @@ RpWindow::RpWindow(QWidget *parent)
, _helper(Platform::CreateWindowHelper(this)) {
Expects(_helper != nullptr);
_helper->initInWindow(this);
hide();
}
@ -32,6 +33,27 @@ QMargins RpWindow::frameMargins() const {
return _helper->frameMargins();
}
int RpWindow::additionalContentPadding() const {
return _helper->additionalContentPadding();
}
rpl::producer<int> RpWindow::additionalContentPaddingValue() const {
return _helper->additionalContentPaddingValue();
}
auto RpWindow::hitTestRequests() const
-> rpl::producer<not_null<Platform::HitTestRequest*>> {
return _helper->hitTestRequests();
}
rpl::producer<Platform::HitTestResult> RpWindow::systemButtonOver() const {
return _helper->systemButtonOver();
}
rpl::producer<Platform::HitTestResult> RpWindow::systemButtonDown() const {
return _helper->systemButtonDown();
}
void RpWindow::setTitle(const QString &title) {
_helper->setTitle(title);
}

View file

@ -16,6 +16,8 @@ struct WindowTitle;
namespace Ui {
namespace Platform {
class BasicWindowHelper;
struct HitTestRequest;
enum class HitTestResult;
} // namespace Platform
enum class WindowTitleHitTestFlag {
@ -38,6 +40,21 @@ public:
[[nodiscard]] not_null<const RpWidget*> body() const;
[[nodiscard]] QMargins frameMargins() const;
// In Windows 11 the window rounding shadow takes about
// round(1px * system_scale) from the window geometry on each side.
//
// Top shift is made by the TitleWidget height, but the rest of the
// side shifts are left for the RpWindow client to consider.
[[nodiscard]] int additionalContentPadding() const;
[[nodiscard]] rpl::producer<int> additionalContentPaddingValue() const;
[[nodiscard]] auto hitTestRequests() const
-> rpl::producer<not_null<Platform::HitTestRequest*>>;
[[nodiscard]] auto systemButtonOver() const
-> rpl::producer<Platform::HitTestResult>;
[[nodiscard]] auto systemButtonDown() const
-> rpl::producer<Platform::HitTestResult>;
void setTitle(const QString &title);
void setTitleStyle(const style::WindowTitle &st);
void setNativeFrame(bool enabled);

View file

@ -8,7 +8,7 @@
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_common_adapters.h"
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QApplication>
@ -284,6 +284,10 @@ void ScrollBar::resizeEvent(QResizeEvent *e) {
updateBar();
}
void ScrollBar::wheelEvent(QWheelEvent *e) {
static_cast<ScrollArea*>(parentWidget())->viewportEvent(e);
}
auto ScrollBar::shadowVisibilityChanged() const
-> rpl::producer<ScrollBar::ShadowVisibility> {
return _shadowVisibilityChanged.events();

View file

@ -88,6 +88,7 @@ protected:
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void wheelEvent(QWheelEvent *e) override;
private:
ScrollArea *area();

View file

@ -8,7 +8,7 @@
#include "ui/widgets/input_fields.h"
#include "ui/ui_utility.h"
#include "base/qt_adapters.h"
#include "base/qt/qt_common_adapters.h"
#include <QtCore/QRegularExpression>
#include <QTime>

View file

@ -85,7 +85,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
_point = m;
_st = st;
_text = Text::String(_st->textStyle, text, _textPlainOptions, _st->widthMax, true);
_text = Text::String(_st->textStyle, text, _textPlainOptions, _st->widthMax);
_useTransparency = Platform::TranslucentWindowsSupported(_point);
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);

View file

@ -857,7 +857,7 @@ defaultMenu: Menu {
arrow: defaultMenuArrow;
widthMin: 140px;
widthMin: 156px;
widthMax: 300px;
ripple: defaultRippleAnimation;
@ -1435,90 +1435,93 @@ shakeShift: 4px;
// Windows specific title
windowTitleButtonWidth: 36px;
windowTitleHeight: 24px;
windowTitleButton: IconButton {
width: 24px;
height: 21px;
width: windowTitleButtonWidth;
height: windowTitleHeight;
iconPosition: point(0px, 0px);
}
windowTitleButtonClose: IconButton(windowTitleButton) {
width: 25px;
width: windowTitleButtonWidth;
}
windowTitleButtonSize: size(windowTitleButtonWidth, windowTitleHeight);
defaultWindowTitle: WindowTitle {
height: 21px;
height: windowTitleHeight;
bg: titleBg;
bgActive: titleBgActive;
fg: titleFg;
fgActive: titleFgActive;
minimize: IconButton(windowTitleButton) {
icon: icon {
{ size(24px, 21px), titleButtonBg },
{ "title_button_minimize", titleButtonFg, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBg },
{ "title_button_minimize", titleButtonFg },
};
iconOver: icon {
{ size(24px, 21px), titleButtonBgOver },
{ "title_button_minimize", titleButtonFgOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgOver },
{ "title_button_minimize", titleButtonFgOver },
};
}
minimizeIconActive: icon {
{ size(24px, 21px), titleButtonBgActive },
{ "title_button_minimize", titleButtonFgActive, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgActive },
{ "title_button_minimize", titleButtonFgActive },
};
minimizeIconActiveOver: icon {
{ size(24px, 21px), titleButtonBgActiveOver },
{ "title_button_minimize", titleButtonFgActiveOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgActiveOver },
{ "title_button_minimize", titleButtonFgActiveOver },
};
maximize: IconButton(windowTitleButton) {
icon: icon {
{ size(24px, 21px), titleButtonBg },
{ "title_button_maximize", titleButtonFg, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBg },
{ "title_button_maximize", titleButtonFg },
};
iconOver: icon {
{ size(24px, 21px), titleButtonBgOver },
{ "title_button_maximize", titleButtonFgOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgOver },
{ "title_button_maximize", titleButtonFgOver },
};
}
maximizeIconActive: icon {
{ size(24px, 21px), titleButtonBgActive },
{ "title_button_maximize", titleButtonFgActive, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgActive },
{ "title_button_maximize", titleButtonFgActive },
};
maximizeIconActiveOver: icon {
{ size(24px, 21px), titleButtonBgActiveOver },
{ "title_button_maximize", titleButtonFgActiveOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgActiveOver },
{ "title_button_maximize", titleButtonFgActiveOver },
};
restoreIcon: icon {
{ size(24px, 21px), titleButtonBg },
{ "title_button_restore", titleButtonFg, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBg },
{ "title_button_restore", titleButtonFg },
};
restoreIconOver: icon {
{ size(24px, 21px), titleButtonBgOver },
{ "title_button_restore", titleButtonFgOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgOver },
{ "title_button_restore", titleButtonFgOver },
};
restoreIconActive: icon {
{ size(24px, 21px), titleButtonBgActive },
{ "title_button_restore", titleButtonFgActive, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgActive },
{ "title_button_restore", titleButtonFgActive },
};
restoreIconActiveOver: icon {
{ size(24px, 21px), titleButtonBgActiveOver },
{ "title_button_restore", titleButtonFgActiveOver, point(4px, 4px) },
{ windowTitleButtonSize, titleButtonBgActiveOver },
{ "title_button_restore", titleButtonFgActiveOver },
};
close: IconButton(windowTitleButtonClose) {
icon: icon {
{ size(25px, 21px), titleButtonCloseBg },
{ "title_button_close", titleButtonCloseFg, point(5px, 4px) },
{ windowTitleButtonSize, titleButtonCloseBg },
{ "title_button_close", titleButtonCloseFg },
};
iconOver: icon {
{ size(25px, 21px), titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver, point(5px, 4px) },
{ windowTitleButtonSize, titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver },
};
}
closeIconActive: icon {
{ size(25px, 21px), titleButtonCloseBgActive },
{ "title_button_close", titleButtonCloseFgActive, point(5px, 4px) },
{ windowTitleButtonSize, titleButtonCloseBgActive },
{ "title_button_close", titleButtonCloseFgActive },
};
closeIconActiveOver: icon {
{ size(25px, 21px), titleButtonCloseBgActiveOver },
{ "title_button_close", titleButtonCloseFgActiveOver, point(5px, 4px) },
{ windowTitleButtonSize, titleButtonCloseBgActiveOver },
{ "title_button_close", titleButtonCloseFgActiveOver },
};
}

View file

@ -28,6 +28,10 @@ VerticalLayoutReorder::VerticalLayoutReorder(
, _scrollAnimation([=] { updateScrollCallback(); }) {
}
VerticalLayoutReorder::VerticalLayoutReorder(not_null<VerticalLayout*> layout)
: _layout(layout) {
}
void VerticalLayoutReorder::cancel() {
if (_currentWidget) {
cancelCurrent(indexOf(_currentWidget));
@ -183,7 +187,9 @@ void VerticalLayoutReorder::cancelCurrent(int index) {
}
void VerticalLayoutReorder::finishReordering() {
_scrollAnimation.stop();
if (_scroll) {
_scrollAnimation.stop();
}
finishCurrent();
}
@ -281,6 +287,9 @@ auto VerticalLayoutReorder::updates() const -> rpl::producer<Single> {
}
void VerticalLayoutReorder::updateScrollCallback() {
if (!_scroll) {
return;
}
const auto delta = deltaFromEdge();
const auto oldTop = _scroll->scrollTop();
_scroll->scrollToY(oldTop + delta);
@ -293,7 +302,7 @@ void VerticalLayoutReorder::updateScrollCallback() {
}
void VerticalLayoutReorder::checkForScrollAnimation() {
if (!deltaFromEdge() || _scrollAnimation.animating()) {
if (!_scroll || !deltaFromEdge() || _scrollAnimation.animating()) {
return;
}
_scrollAnimation.start();
@ -301,6 +310,7 @@ void VerticalLayoutReorder::checkForScrollAnimation() {
int VerticalLayoutReorder::deltaFromEdge() {
Expects(_currentWidget != nullptr);
Expects(_scroll);
const auto globalPosition = _currentWidget->mapToGlobal(QPoint(0, 0));
const auto localTop = _scroll->mapFromGlobal(globalPosition).y();

View file

@ -31,6 +31,7 @@ public:
VerticalLayoutReorder(
not_null<VerticalLayout*> layout,
not_null<ScrollArea*> scroll);
VerticalLayoutReorder(not_null<VerticalLayout*> layout);
void start();
void cancel();
@ -68,7 +69,7 @@ private:
int deltaFromEdge();
const not_null<Ui::VerticalLayout*> _layout;
const not_null<Ui::ScrollArea*> _scroll;
Ui::ScrollArea *_scroll = nullptr;
Ui::Animations::Basic _scrollAnimation;