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

View file

@ -80,6 +80,11 @@ protected:
ByHover = 0x02, ByHover = 0x02,
}; };
void setOver(bool over, StateChangeSource source = StateChangeSource::ByUser); 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) { virtual void onStateChanged(State was, StateChangeSource source) {
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -26,8 +26,8 @@ enum class ImageRoundRadius {
namespace Images { namespace Images {
[[nodiscard]] QPixmap PixmapFast(QImage &&image); [[nodiscard]] QPixmap PixmapFast(QImage &&image);
[[nodiscard]] QImage BlurLargeImage(QImage image, int radius); [[nodiscard]] QImage BlurLargeImage(QImage &&image, int radius);
[[nodiscard]] QImage DitherImage(QImage image); [[nodiscard]] QImage DitherImage(const QImage &image);
[[nodiscard]] QImage GenerateGradient( [[nodiscard]] QImage GenerateGradient(
QSize size, QSize size,
@ -79,44 +79,78 @@ struct ReadResult {
}; };
[[nodiscard]] ReadResult Read(ReadArgs &&args); [[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 { enum class Option {
None = 0, None = 0,
Smooth = (1 << 0), FastTransform = (1 << 0),
Blurred = (1 << 1), Blur = (1 << 1),
Circled = (1 << 2), RoundCircle = (1 << 2),
RoundedLarge = (1 << 3), RoundLarge = (1 << 3),
RoundedSmall = (1 << 4), RoundSmall = (1 << 4),
RoundedTopLeft = (1 << 5), RoundSkipTopLeft = (1 << 5),
RoundedTopRight = (1 << 6), RoundSkipTopRight = (1 << 6),
RoundedBottomLeft = (1 << 7), RoundSkipBottomLeft = (1 << 7),
RoundedBottomRight = (1 << 8), RoundSkipBottomRight = (1 << 8),
RoundedAll = (None Colorize = (1 << 9),
| RoundedTopLeft
| RoundedTopRight
| RoundedBottomLeft
| RoundedBottomRight),
Colored = (1 << 9),
TransparentBackground = (1 << 10), TransparentBackground = (1 << 10),
}; };
using Options = base::flags<Option>; using Options = base::flags<Option>;
inline constexpr auto is_flag_type(Option) { return true; }; 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 } // namespace Images

View file

@ -8,7 +8,7 @@
#include "ui/platform/mac/ui_window_title_mac.h" #include "ui/platform/mac/ui_window_title_mac.h"
#include "ui/widgets/rp_window.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 "base/platform/base_platform_info.h"
#include "styles/palette.h" #include "styles/palette.h"

View file

@ -9,6 +9,7 @@
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "base/debug_log.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
#include "styles/palette.h" #include "styles/palette.h"
@ -59,20 +60,29 @@ void TitleWidget::init(int height) {
}, lifetime()); }, lifetime());
const auto families = QStringList{ const auto families = QStringList{
QString(".SF NS Text"), u".AppleSystemUIFont"_q,
QString("Helvetica Neue") u".SF NS Text"_q,
u"Helvetica Neue"_q,
}; };
for (auto family : families) { for (auto family : families) {
_font.setFamily(family); _font.setFamily(family);
if (QFontInfo(_font).family() == _font.family()) { if (QFontInfo(_font).family() == _font.family()) {
static const auto logged = [&] {
LOG(("Title Font: %1").arg(family));
return true;
}();
break; break;
} }
} }
if (QFontInfo(_font).family() == _font.family()) { if (QFontInfo(_font).family() != _font.family()) {
_font.setPixelSize((height * 15) / 24); _font = st::semiboldFont;
_font.setPixelSize(13);
} else if (_font.family() == u".AppleSystemUIFont"_q) {
_font.setBold(true);
_font.setPixelSize(13);
} else { } 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); _window->setWindowFlag(Qt::Window);
} }
void BasicWindowHelper::initInWindow(not_null<RpWindow*> window) {
}
not_null<RpWidget*> BasicWindowHelper::body() { not_null<RpWidget*> BasicWindowHelper::body() {
return _window; return _window;
} }
@ -41,6 +44,27 @@ QMargins BasicWindowHelper::frameMargins() {
return nativeFrameMargins(); 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) { void BasicWindowHelper::setTitle(const QString &title) {
_window->setWindowTitle(title); _window->setWindowTitle(title);
} }

View file

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

View file

@ -10,10 +10,12 @@
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/widgets/rp_window.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
#include "styles/palette.h" #include "styles/palette.h"
#include "base/algorithm.h" #include "base/algorithm.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/platform/base_platform_info.h"
#include <QtGui/QPainter> #include <QtGui/QPainter>
#include <QtGui/QtEvents> #include <QtGui/QtEvents>
@ -35,6 +37,50 @@ void RemoveDuplicates(std::vector<T> &v) {
} // namespace } // 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( TitleControls::TitleControls(
not_null<RpWidget*> parent, not_null<RpWidget*> parent,
const style::WindowTitle &st, const style::WindowTitle &st,
@ -172,7 +218,47 @@ void TitleControls::raise() {
_close->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) { switch (control) {
case Control::Minimize: return _minimize; case Control::Minimize: return _minimize;
case Control::Maximize: return _maximizeRestore; 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 Platform
} // namespace Ui } // namespace Ui

View file

@ -20,9 +20,41 @@ namespace Ui {
class IconButton; class IconButton;
class PlainShadow; class PlainShadow;
class RpWindow;
namespace Platform { 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 { class TitleControls final {
public: public:
TitleControls( TitleControls(
@ -36,6 +68,11 @@ public:
void setResizeEnabled(bool enabled); void setResizeEnabled(bool enabled);
void raise(); void raise();
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
void buttonOver(HitTestResult testResult);
void buttonDown(HitTestResult testResult);
enum class Control { enum class Control {
Unknown, Unknown,
Minimize, Minimize,
@ -49,9 +86,11 @@ public:
}; };
private: private:
class Button;
[[nodiscard]] not_null<RpWidget*> parent() const; [[nodiscard]] not_null<RpWidget*> parent() const;
[[nodiscard]] not_null<QWidget*> window() 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 init(Fn<void(bool maximized)> maximize);
void subscribeToStateChanges(); void subscribeToStateChanges();
@ -64,9 +103,9 @@ private:
not_null<const style::WindowTitle*> _st; not_null<const style::WindowTitle*> _st;
object_ptr<Ui::IconButton> _minimize; object_ptr<Button> _minimize;
object_ptr<Ui::IconButton> _maximizeRestore; object_ptr<Button> _maximizeRestore;
object_ptr<Ui::IconButton> _close; object_ptr<Button> _close;
bool _maximizedState = false; bool _maximizedState = false;
bool _activeState = 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 Platform
} // namespace Ui } // namespace Ui

View file

@ -6,9 +6,14 @@
// //
#include "ui/platform/win/ui_window_title_win.h" #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/buttons.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/rp_window.h"
#include "ui/ui_utility.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/style_widgets.h"
#include "styles/palette.h" #include "styles/palette.h"
@ -16,28 +21,88 @@
#include <QtGui/QtEvents> #include <QtGui/QtEvents>
#include <QtGui/QWindow> #include <QtGui/QWindow>
#include <windows.h>
#include <shellscalingapi.h>
namespace Ui { namespace Ui {
namespace Platform { 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) TitleWidget::TitleWidget(not_null<RpWidget*> parent)
: 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) { , _shadow(this, st::titleShadow) {
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
parent->widthValue( parent->widthValue(
) | rpl::start_with_next([=](int width) { ) | rpl::start_with_next([=](int width) {
setGeometry(0, 0, width, _controls.st()->height); refreshGeometryWithWidth(width);
}, lifetime()); }, 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) { void TitleWidget::setText(const QString &text) {
window()->setWindowTitle(text); window()->setWindowTitle(text);
} }
void TitleWidget::setStyle(const style::WindowTitle &st) { void TitleWidget::setStyle(const style::WindowTitle &st) {
_controls.setStyle(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(); update();
} }
@ -57,16 +122,123 @@ void TitleWidget::paintEvent(QPaintEvent *e) {
} }
void TitleWidget::resizeEvent(QResizeEvent *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 { HitTestResult TitleWidget::hitTest(QPoint point) const {
if (_controls.geometry().contains(point)) { const auto origin = _paddingHelper
return HitTestResult::SysButton; ? _paddingHelper->controlsParent.pos()
} else if (rect().contains(point)) { : QPoint();
return HitTestResult::Caption; 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 } // namespace Platform

View file

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

View file

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

View file

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

View file

@ -25,12 +25,22 @@ void DrawRoundedRect(
const auto h = rect.height(); const auto h = rect.height();
const auto cornerWidth = corners[0].width() / pixelRatio; const auto cornerWidth = corners[0].width() / pixelRatio;
const auto cornerHeight = corners[0].height() / 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 (w > 2 * cornerWidth) {
if (parts & RectPart::Top) { if (hasTop) {
p.fillRect(x + cornerWidth, y, w - 2 * cornerWidth, cornerHeight, brush); 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); p.fillRect(x + cornerWidth, y + h - cornerHeight, w - 2 * cornerWidth, cornerHeight, brush);
} }
} }
@ -38,13 +48,13 @@ void DrawRoundedRect(
if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) { if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) {
p.fillRect(x, y + cornerHeight, w, h - 2 * cornerHeight, brush); p.fillRect(x, y + cornerHeight, w, h - 2 * cornerHeight, brush);
} else { } else {
if (parts & RectPart::Left) { if (hasLeft) {
p.fillRect(x, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush); p.fillRect(x, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush);
} }
if ((parts & RectPart::Center) && w > 2 * cornerWidth) { if ((parts & RectPart::Center) && w > 2 * cornerWidth) {
p.fillRect(x + cornerWidth, y + cornerHeight, w - 2 * cornerWidth, h - 2 * cornerHeight, brush); 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); 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 } // namespace Ui
static const QChar TextCommand(0x0010); 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 { struct TextParseOptions {
int32 flags; int32 flags;
@ -122,8 +103,7 @@ public:
const style::TextStyle &st, const style::TextStyle &st,
const QString &text, const QString &text,
const TextParseOptions &options = _defaultOptions, const TextParseOptions &options = _defaultOptions,
int32 minResizeWidth = QFIXED_MAX, int32 minResizeWidth = QFIXED_MAX);
bool richText = false);
String(const String &other) = default; String(const String &other) = default;
String(String &&other) = default; String(String &&other) = default;
String &operator=(const String &other) = default; String &operator=(const String &other) = default;
@ -134,7 +114,6 @@ public:
int countHeight(int width, bool breakEverywhere = false) const; int countHeight(int width, bool breakEverywhere = false) const;
void countLineWidths(int width, QVector<int> *lineWidths, 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 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 setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions, const std::any &context = {});
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
@ -253,10 +232,10 @@ private:
[[nodiscard]] bool IsAlmostLinkEnd(QChar ch); [[nodiscard]] bool IsAlmostLinkEnd(QChar ch);
[[nodiscard]] bool IsLinkEnd(QChar ch); [[nodiscard]] bool IsLinkEnd(QChar ch);
[[nodiscard]] bool IsNewline(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 IsDiac(QChar ch);
[[nodiscard]] bool IsReplacedBySpace(QChar ch); [[nodiscard]] bool IsReplacedBySpace(QChar ch);
[[nodiscard]] bool IsTrimmed(QChar ch, bool rich = false); [[nodiscard]] bool IsTrimmed(QChar ch);
} // namespace Text } // namespace Text
} // namespace Ui } // 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) { inline TextSelection unshiftSelection(TextSelection selection, const Ui::Text::String &byText) {
return unshiftSelection(selection, byText.length()); 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, const QString &str,
uint16 from, uint16 from,
uint16 length, uint16 length,
uchar flags, uint16 flags,
uint16 lnkIndex, uint16 lnkIndex,
uint16 spoilerIndex) uint16 spoilerIndex)
: _from(from) : _from(from)
, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) , _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14))
, _spoilerIndex(spoilerIndex) { , _spoilerIndex(spoilerIndex) {
} }
@ -374,11 +374,11 @@ QFixed AbstractBlock::f_rpadding() const {
} }
uint16 AbstractBlock::lnkIndex() const { uint16 AbstractBlock::lnkIndex() const {
return (_flags >> 12) & 0xFFFF; return (_flags >> 14) & 0xFFFF;
} }
void AbstractBlock::setLnkIndex(uint16 lnkIndex) { void AbstractBlock::setLnkIndex(uint16 lnkIndex) {
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12); _flags = (_flags & ~(0xFFFF << 14)) | (lnkIndex << 14);
} }
uint16 AbstractBlock::spoilerIndex() const { uint16 AbstractBlock::spoilerIndex() const {
@ -390,11 +390,11 @@ void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) {
} }
TextBlockType AbstractBlock::type() const { TextBlockType AbstractBlock::type() const {
return TextBlockType((_flags >> 8) & 0x0F); return TextBlockType((_flags >> 10) & 0x0F);
} }
int32 AbstractBlock::flags() const { int32 AbstractBlock::flags() const {
return (_flags & 0xFFF); return (_flags & 0b1111111111);
} }
QFixed AbstractBlock::f_rbearing() const { QFixed AbstractBlock::f_rbearing() const {
@ -409,11 +409,11 @@ TextBlock::TextBlock(
QFixed minResizeWidth, QFixed minResizeWidth,
uint16 from, uint16 from,
uint16 length, uint16 length,
uchar flags, uint16 flags,
uint16 lnkIndex, uint16 lnkIndex,
uint16 spoilerIndex) uint16 spoilerIndex)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { : AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
_flags |= ((TextBlockTText & 0x0F) << 8); _flags |= ((TextBlockTText & 0x0F) << 10);
if (length) { if (length) {
style::font blockFont = font; style::font blockFont = font;
if (!flags && lnkIndex) { 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( EmojiBlock::EmojiBlock(
const style::font &font, const style::font &font,
const QString &str, const QString &str,
uint16 from, uint16 from,
uint16 length, uint16 length,
uchar flags, uint16 flags,
uint16 lnkIndex, uint16 lnkIndex,
uint16 spoilerIndex, uint16 spoilerIndex,
EmojiPtr emoji) EmojiPtr emoji)
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex)
, _emoji(emoji) { , _emoji(emoji) {
_flags |= ((TextBlockTEmoji & 0x0F) << 8); _flags |= ((TextBlockTEmoji & 0x0F) << 10);
_width = int(st::emojiSize + 2 * st::emojiPadding); _width = int(st::emojiSize + 2 * st::emojiPadding);
_rpadding = 0; _rpadding = 0;
for (auto i = length; i != 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 Text
} // namespace Ui } // namespace Ui

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "base/algorithm.h" #include "base/algorithm.h"
#include "base/qt_adapters.h" #include "base/qt/qt_string_view.h"
#include <QtCore/QRegularExpression> #include <QtCore/QRegularExpression>
@ -42,6 +42,25 @@ TextWithEntities Link(const QString &text, const QString &url) {
return WithSingleEntity(text, EntityType::CustomUrl, 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) { TextWithEntities RichLangValue(const QString &text) {
static const auto kStart = QRegularExpression("(\\*\\*|__)"); static const auto kStart = QRegularExpression("(\\*\\*|__)");
@ -74,5 +93,48 @@ TextWithEntities RichLangValue(const QString &text) {
return result; 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 Text
} // namespace Ui } // namespace Ui

View file

@ -30,6 +30,12 @@ inline constexpr auto Upper = details::ToUpperType{};
[[nodiscard]] TextWithEntities Link( [[nodiscard]] TextWithEntities Link(
const QString &text, const QString &text,
const QString &url = "internal:action"); 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]] TextWithEntities RichLangValue(const QString &text);
[[nodiscard]] inline TextWithEntities WithEntities(const QString &text) { [[nodiscard]] inline TextWithEntities WithEntities(const QString &text) {
return { text }; return { text };
@ -65,5 +71,13 @@ inline constexpr auto Upper = details::ToUpperType{};
return rpl::map(WithEntities); 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 Text
} // namespace Ui } // namespace Ui

View file

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

View file

@ -13,6 +13,8 @@
#include <memory> #include <memory>
class Painter;
namespace Ui { namespace Ui {
class RippleAnimation; class RippleAnimation;
@ -273,6 +275,11 @@ protected:
void paintEvent(QPaintEvent *e) override; 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: private:
void setText(QString &&text); void setText(QString &&text);
QRect toggleRect() const; QRect toggleRect() const;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,6 +15,7 @@ RpWindow::RpWindow(QWidget *parent)
, _helper(Platform::CreateWindowHelper(this)) { , _helper(Platform::CreateWindowHelper(this)) {
Expects(_helper != nullptr); Expects(_helper != nullptr);
_helper->initInWindow(this);
hide(); hide();
} }
@ -32,6 +33,27 @@ QMargins RpWindow::frameMargins() const {
return _helper->frameMargins(); 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) { void RpWindow::setTitle(const QString &title) {
_helper->setTitle(title); _helper->setTitle(title);
} }

View file

@ -16,6 +16,8 @@ struct WindowTitle;
namespace Ui { namespace Ui {
namespace Platform { namespace Platform {
class BasicWindowHelper; class BasicWindowHelper;
struct HitTestRequest;
enum class HitTestResult;
} // namespace Platform } // namespace Platform
enum class WindowTitleHitTestFlag { enum class WindowTitleHitTestFlag {
@ -38,6 +40,21 @@ public:
[[nodiscard]] not_null<const RpWidget*> body() const; [[nodiscard]] not_null<const RpWidget*> body() const;
[[nodiscard]] QMargins frameMargins() 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 setTitle(const QString &title);
void setTitleStyle(const style::WindowTitle &st); void setTitleStyle(const style::WindowTitle &st);
void setNativeFrame(bool enabled); void setNativeFrame(bool enabled);

View file

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

View file

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

View file

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

View file

@ -85,7 +85,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
_point = m; _point = m;
_st = st; _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); _useTransparency = Platform::TranslucentWindowsSupported(_point);
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);

View file

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

View file

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

View file

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