Updated lib_ui sources to TDesktop version 3.5.2+95e806c
|
Before Width: | Height: | Size: 164 B After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 542 B |
|
Before Width: | Height: | Size: 119 B After Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 100 B After Width: | Height: | Size: 217 B |
|
Before Width: | Height: | Size: 122 B After Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 359 B |
|
Before Width: | Height: | Size: 136 B After Width: | Height: | Size: 387 B |
|
Before Width: | Height: | Size: 160 B After Width: | Height: | Size: 526 B |
|
Before Width: | Height: | Size: 209 B After Width: | Height: | Size: 813 B |
BIN
icons/title_shadow_close.png
Normal file
|
After Width: | Height: | Size: 470 B |
BIN
icons/title_shadow_close@2x.png
Normal file
|
After Width: | Height: | Size: 890 B |
BIN
icons/title_shadow_close@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/title_shadow_maximize.png
Normal file
|
After Width: | Height: | Size: 457 B |
BIN
icons/title_shadow_maximize@2x.png
Normal file
|
After Width: | Height: | Size: 798 B |
BIN
icons/title_shadow_maximize@3x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/title_shadow_minimize.png
Normal file
|
After Width: | Height: | Size: 321 B |
BIN
icons/title_shadow_minimize@2x.png
Normal file
|
After Width: | Height: | Size: 486 B |
BIN
icons/title_shadow_minimize@3x.png
Normal file
|
After Width: | Height: | Size: 730 B |
BIN
icons/title_shadow_restore.png
Normal file
|
After Width: | Height: | Size: 528 B |
BIN
icons/title_shadow_restore@2x.png
Normal file
|
After Width: | Height: | Size: 1,006 B |
BIN
icons/title_shadow_restore@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -26,7 +26,9 @@ AbstractButton::AbstractButton(QWidget *parent) : RpWidget(parent) {
|
|||
}
|
||||
|
||||
void AbstractButton::leaveEventHook(QEvent *e) {
|
||||
if (_state & StateFlag::Down) return;
|
||||
if (_state & StateFlag::Down) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOver(false, StateChangeSource::ByHover);
|
||||
return TWidget::leaveEventHook(e);
|
||||
|
|
@ -48,12 +50,13 @@ void AbstractButton::checkIfOver(QPoint localPos) {
|
|||
|
||||
void AbstractButton::mousePressEvent(QMouseEvent *e) {
|
||||
checkIfOver(e->pos());
|
||||
if (_acceptBoth || (e->buttons() & Qt::LeftButton)) {
|
||||
if ((_state & StateFlag::Over) && !(_state & StateFlag::Down)) {
|
||||
auto was = _state;
|
||||
_state |= StateFlag::Down;
|
||||
onStateChanged(was, StateChangeSource::ByPress);
|
||||
|
||||
if (_state & StateFlag::Over) {
|
||||
const auto set = setDown(
|
||||
true,
|
||||
StateChangeSource::ByPress,
|
||||
e->modifiers(),
|
||||
e->button());
|
||||
if (set) {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
|
@ -68,21 +71,13 @@ void AbstractButton::mouseMoveEvent(QMouseEvent *e) {
|
|||
}
|
||||
|
||||
void AbstractButton::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_state & StateFlag::Down) {
|
||||
const auto was = _state;
|
||||
_state &= ~State(StateFlag::Down);
|
||||
|
||||
const auto weak = MakeWeak(this);
|
||||
onStateChanged(was, StateChangeSource::ByPress);
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (was & StateFlag::Over) {
|
||||
clicked(e->modifiers(), e->button());
|
||||
} else {
|
||||
setOver(false, StateChangeSource::ByHover);
|
||||
}
|
||||
const auto set = setDown(
|
||||
false,
|
||||
StateChangeSource::ByPress,
|
||||
e->modifiers(),
|
||||
e->button());
|
||||
if (set) {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +118,36 @@ void AbstractButton::setOver(bool over, StateChangeSource source) {
|
|||
updateCursor();
|
||||
}
|
||||
|
||||
bool AbstractButton::setDown(
|
||||
bool down,
|
||||
StateChangeSource source,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
Qt::MouseButton button) {
|
||||
if (down
|
||||
&& !(_state & StateFlag::Down)
|
||||
&& (_acceptBoth || button == Qt::LeftButton)) {
|
||||
auto was = _state;
|
||||
_state |= StateFlag::Down;
|
||||
onStateChanged(was, source);
|
||||
return true;
|
||||
} else if (!down && (_state & StateFlag::Down)) {
|
||||
const auto was = _state;
|
||||
_state &= ~State(StateFlag::Down);
|
||||
|
||||
const auto weak = MakeWeak(this);
|
||||
onStateChanged(was, source);
|
||||
if (weak) {
|
||||
if (was & StateFlag::Over) {
|
||||
clicked(modifiers, button);
|
||||
} else {
|
||||
setOver(false, source);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AbstractButton::updateCursor() {
|
||||
auto pointerCursor = _enablePointerCursor && (_state & StateFlag::Over);
|
||||
setCursor(pointerCursor ? style::cur_pointer : style::cur_default);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,11 @@ protected:
|
|||
ByHover = 0x02,
|
||||
};
|
||||
void setOver(bool over, StateChangeSource source = StateChangeSource::ByUser);
|
||||
bool setDown(
|
||||
bool down,
|
||||
StateChangeSource source,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
Qt::MouseButton button);
|
||||
|
||||
virtual void onStateChanged(State was, StateChangeSource source) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
#include "ui/text/text_entity.h"
|
||||
#include "ui/integration.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace anim {
|
|||
namespace {
|
||||
|
||||
rpl::variable<bool> AnimationsDisabled = false;
|
||||
int SlowMultiplierMinusOne/* = 0*/;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
|
@ -76,6 +77,16 @@ void SetDisabled(bool disabled) {
|
|||
AnimationsDisabled = disabled;
|
||||
}
|
||||
|
||||
int SlowMultiplier() {
|
||||
return (SlowMultiplierMinusOne + 1);
|
||||
}
|
||||
|
||||
void SetSlowMultiplier(int multiplier) {
|
||||
Expects(multiplier > 0);
|
||||
|
||||
SlowMultiplierMinusOne = multiplier - 1;
|
||||
}
|
||||
|
||||
void DrawStaticLoading(
|
||||
QPainter &p,
|
||||
QRectF rect,
|
||||
|
|
|
|||
|
|
@ -351,6 +351,8 @@ QPainterPath path(QPointF (&from)[N]) {
|
|||
rpl::producer<bool> Disables();
|
||||
bool Disabled();
|
||||
void SetDisabled(bool disabled);
|
||||
int SlowMultiplier();
|
||||
void SetSlowMultiplier(int multiplier); // 1 - default, 10 - slow x10.
|
||||
|
||||
void DrawStaticLoading(
|
||||
QPainter &p,
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ inline void Simple::startPrepared(
|
|||
anim::transition transition) {
|
||||
_data->from = _data->value;
|
||||
_data->delta = to - _data->from;
|
||||
_data->duration = duration;
|
||||
_data->duration = duration * anim::SlowMultiplier();
|
||||
_data->transition = transition;
|
||||
_data->animation.start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "base/basic_types.h"
|
||||
#include "base/binary_guard.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
#include "emoji.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "ui/gl/gl_shader.h"
|
||||
#include "ui/integration.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "base/options.h"
|
||||
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QFile>
|
||||
|
|
@ -34,6 +35,14 @@ bool ForceDisabled/* = false*/;
|
|||
ANGLE ResolvedANGLE = ANGLE::Auto;
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
base::options::toggle AllowLinuxNvidiaOpenGL({
|
||||
.id = kOptionAllowLinuxNvidiaOpenGL,
|
||||
.name = "Allow OpenGL on the NVIDIA drivers (Linux)",
|
||||
.description = "Qt+OpenGL have problems on Linux with NVIDIA drivers.",
|
||||
.scope = base::options::linux,
|
||||
.restartRequired = true,
|
||||
});
|
||||
|
||||
void CrashCheckStart() {
|
||||
auto f = QFile(Integration::Instance().openglCheckFilePath());
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
|
|
@ -44,6 +53,8 @@ void CrashCheckStart() {
|
|||
|
||||
} // namespace
|
||||
|
||||
const char kOptionAllowLinuxNvidiaOpenGL[] = "allow-linux-nvidia-opengl";
|
||||
|
||||
Capabilities CheckCapabilities(QWidget *widget) {
|
||||
if (ForceDisabled) {
|
||||
LOG_ONCE(("OpenGL: Force-disabled."));
|
||||
|
|
@ -169,8 +180,12 @@ Capabilities CheckCapabilities(QWidget *widget) {
|
|||
#ifdef Q_OS_LINUX
|
||||
if (version && QByteArray(version).contains("NVIDIA")) {
|
||||
// https://github.com/telegramdesktop/tdesktop/issues/16830
|
||||
LOG_ONCE(("OpenGL: Disable on NVIDIA driver on Linux."));
|
||||
return false;
|
||||
if (AllowLinuxNvidiaOpenGL.value()) {
|
||||
LOG_ONCE(("OpenGL: Allow on NVIDIA driver (experimental)."));
|
||||
} else {
|
||||
LOG_ONCE(("OpenGL: Disable on NVIDIA driver on Linux."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ class QOpenGLContext;
|
|||
|
||||
namespace Ui::GL {
|
||||
|
||||
extern const char kOptionAllowLinuxNvidiaOpenGL[];
|
||||
|
||||
enum class Backend {
|
||||
Raster,
|
||||
OpenGL,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include "zlib.h"
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtGui/QImageReader>
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
|
|
@ -27,19 +28,26 @@ namespace {
|
|||
// They should be smaller.
|
||||
constexpr auto kMaxGzipFileSize = 5 * 1024 * 1024;
|
||||
|
||||
TG_FORCE_INLINE uint64 blurGetColors(const uchar *p) {
|
||||
return (uint64)p[0] + ((uint64)p[1] << 16) + ((uint64)p[2] << 32) + ((uint64)p[3] << 48);
|
||||
TG_FORCE_INLINE uint64 BlurGetColors(const uchar *p) {
|
||||
return (uint64)p[0]
|
||||
+ ((uint64)p[1] << 16)
|
||||
+ ((uint64)p[2] << 32)
|
||||
+ ((uint64)p[3] << 48);
|
||||
}
|
||||
|
||||
const QImage &circleMask(QSize size) {
|
||||
uint64 key = (uint64(uint32(size.width())) << 32)
|
||||
const QImage &CircleMask(QSize size) {
|
||||
const auto key = (uint64(uint32(size.width())) << 32)
|
||||
| uint64(uint32(size.height()));
|
||||
|
||||
static auto masks = base::flat_map<uint64, QImage>();
|
||||
const auto i = masks.find(key);
|
||||
if (i != end(masks)) {
|
||||
static auto Masks = base::flat_map<uint64, QImage>();
|
||||
static auto Mutex = QMutex();
|
||||
auto lock = QMutexLocker(&Mutex);
|
||||
const auto i = Masks.find(key);
|
||||
if (i != end(Masks)) {
|
||||
return i->second;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
auto mask = QImage(
|
||||
size,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
|
|
@ -51,7 +59,9 @@ const QImage &circleMask(QSize size) {
|
|||
p.setPen(Qt::NoPen);
|
||||
p.drawEllipse(QRect(QPoint(), size));
|
||||
}
|
||||
return masks.emplace(key, std::move(mask)).first->second;
|
||||
|
||||
lock.relock();
|
||||
return Masks.emplace(key, std::move(mask)).first->second;
|
||||
}
|
||||
|
||||
std::array<QImage, 4> PrepareCornersMask(int radius) {
|
||||
|
|
@ -280,7 +290,10 @@ template <int kBits> // 4 means 16x16, 3 means 8x8
|
|||
auto exact = GenerateSmallComplexGradient(colors, rotation, progress);
|
||||
return (exact.size() == size)
|
||||
? exact
|
||||
: exact.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
: exact.scaled(
|
||||
size,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -449,101 +462,135 @@ ReadResult Read(ReadArgs &&args) {
|
|||
if (args.forceOpaque
|
||||
&& result.format != qstr("jpg")
|
||||
&& result.format != qstr("jpeg")) {
|
||||
result.image = prepareOpaque(std::move(result.image));
|
||||
result.image = Opaque(std::move(result.image));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage prepareBlur(QImage img) {
|
||||
if (img.isNull()) {
|
||||
return img;
|
||||
[[nodiscard]] Options RoundOptions(
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners) {
|
||||
const auto withCorners = [&](Option rounding) {
|
||||
if (rounding == Option::None) {
|
||||
return Options();
|
||||
}
|
||||
const auto corner = [&](RectPart part, Option skip) {
|
||||
return !(corners & part) ? skip : Option();
|
||||
};
|
||||
return rounding
|
||||
| corner(RectPart::TopLeft, Option::RoundSkipTopLeft)
|
||||
| corner(RectPart::TopRight, Option::RoundSkipTopRight)
|
||||
| corner(RectPart::BottomLeft, Option::RoundSkipBottomLeft)
|
||||
| corner(RectPart::BottomRight, Option::RoundSkipBottomRight);
|
||||
};
|
||||
return withCorners((radius == ImageRoundRadius::Large)
|
||||
? Option::RoundLarge
|
||||
: (radius == ImageRoundRadius::Small)
|
||||
? Option::RoundSmall
|
||||
: (radius == ImageRoundRadius::Ellipse)
|
||||
? Option::RoundCircle
|
||||
: Option::None);
|
||||
}
|
||||
|
||||
QImage Blur(QImage &&image) {
|
||||
if (image.isNull()) {
|
||||
return std::move(image);
|
||||
}
|
||||
const auto ratio = img.devicePixelRatio();
|
||||
const auto fmt = img.format();
|
||||
if (fmt != QImage::Format_RGB32 && fmt != QImage::Format_ARGB32_Premultiplied) {
|
||||
img = std::move(img).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
img.setDevicePixelRatio(ratio);
|
||||
const auto ratio = image.devicePixelRatio();
|
||||
const auto format = image.format();
|
||||
if (format != QImage::Format_RGB32
|
||||
&& format != QImage::Format_ARGB32_Premultiplied) {
|
||||
image = std::move(image).convertToFormat(
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(ratio);
|
||||
}
|
||||
|
||||
uchar *pix = img.bits();
|
||||
if (pix) {
|
||||
int w = img.width(), h = img.height();
|
||||
const int radius = 3;
|
||||
const int r1 = radius + 1;
|
||||
const int div = radius * 2 + 1;
|
||||
const int stride = w * 4;
|
||||
if (radius < 16 && div < w && div < h && stride <= w * 4) {
|
||||
bool withalpha = img.hasAlphaChannel();
|
||||
if (withalpha) {
|
||||
QImage imgsmall(w, h, img.format());
|
||||
{
|
||||
QPainter p(&imgsmall);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
auto pix = image.bits();
|
||||
if (!pix) {
|
||||
return std::move(image);
|
||||
}
|
||||
const auto w = image.width();
|
||||
const auto h = image.height();
|
||||
const auto radius = 3;
|
||||
const auto r1 = radius + 1;
|
||||
const auto div = radius * 2 + 1;
|
||||
const auto stride = w * 4;
|
||||
if (radius >= 16 || div >= w || div >= h || stride > w * 4) {
|
||||
return std::move(image);
|
||||
}
|
||||
const auto withalpha = image.hasAlphaChannel();
|
||||
if (withalpha) {
|
||||
auto smaller = QImage(image.size(), image.format());
|
||||
{
|
||||
QPainter p(&smaller);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.fillRect(0, 0, w, h, Qt::transparent);
|
||||
p.drawImage(QRect(radius, radius, w - 2 * radius, h - 2 * radius), img, QRect(0, 0, w, h));
|
||||
}
|
||||
imgsmall.setDevicePixelRatio(ratio);
|
||||
auto was = img;
|
||||
img = std::move(imgsmall);
|
||||
imgsmall = QImage();
|
||||
Assert(!img.isNull());
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
p.fillRect(0, 0, w, h, Qt::transparent);
|
||||
p.drawImage(
|
||||
QRect(radius, radius, w - 2 * radius, h - 2 * radius),
|
||||
image,
|
||||
QRect(0, 0, w, h));
|
||||
}
|
||||
smaller.setDevicePixelRatio(ratio);
|
||||
auto was = std::exchange(image, base::take(smaller));
|
||||
Assert(!image.isNull());
|
||||
|
||||
pix = img.bits();
|
||||
if (!pix) return was;
|
||||
}
|
||||
uint64 *rgb = new uint64[w * h];
|
||||
pix = image.bits();
|
||||
if (!pix) return was;
|
||||
}
|
||||
const auto buffer = std::make_unique<uint64[]>(w * h);
|
||||
const auto rgb = buffer.get();
|
||||
|
||||
int x, y, i;
|
||||
int x, y, i;
|
||||
|
||||
int yw = 0;
|
||||
const int we = w - r1;
|
||||
for (y = 0; y < h; y++) {
|
||||
uint64 cur = blurGetColors(&pix[yw]);
|
||||
uint64 rgballsum = -radius * cur;
|
||||
uint64 rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
|
||||
int yw = 0;
|
||||
const int we = w - r1;
|
||||
for (y = 0; y < h; y++) {
|
||||
uint64 cur = BlurGetColors(&pix[yw]);
|
||||
uint64 rgballsum = -radius * cur;
|
||||
uint64 rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
|
||||
|
||||
for (i = 1; i <= radius; i++) {
|
||||
uint64 cur = blurGetColors(&pix[yw + i * 4]);
|
||||
rgbsum += cur * (r1 - i);
|
||||
rgballsum += cur;
|
||||
}
|
||||
for (i = 1; i <= radius; i++) {
|
||||
uint64 cur = BlurGetColors(&pix[yw + i * 4]);
|
||||
rgbsum += cur * (r1 - i);
|
||||
rgballsum += cur;
|
||||
}
|
||||
|
||||
x = 0;
|
||||
x = 0;
|
||||
|
||||
#define update(start, middle, end) \
|
||||
rgb[y * w + x] = (rgbsum >> 4) & 0x00FF00FF00FF00FFLL; \
|
||||
rgballsum += blurGetColors(&pix[yw + (start) * 4]) - 2 * blurGetColors(&pix[yw + (middle) * 4]) + blurGetColors(&pix[yw + (end) * 4]); \
|
||||
rgballsum += BlurGetColors(&pix[yw + (start) * 4]) - 2 * BlurGetColors(&pix[yw + (middle) * 4]) + BlurGetColors(&pix[yw + (end) * 4]); \
|
||||
rgbsum += rgballsum; \
|
||||
x++;
|
||||
|
||||
while (x < r1) {
|
||||
update(0, x, x + r1);
|
||||
}
|
||||
while (x < we) {
|
||||
update(x - r1, x, x + r1);
|
||||
}
|
||||
while (x < w) {
|
||||
update(x - r1, x, w - 1);
|
||||
}
|
||||
while (x < r1) {
|
||||
update(0, x, x + r1);
|
||||
}
|
||||
while (x < we) {
|
||||
update(x - r1, x, x + r1);
|
||||
}
|
||||
while (x < w) {
|
||||
update(x - r1, x, w - 1);
|
||||
}
|
||||
|
||||
#undef update
|
||||
|
||||
yw += stride;
|
||||
}
|
||||
yw += stride;
|
||||
}
|
||||
|
||||
const int he = h - r1;
|
||||
for (x = 0; x < w; x++) {
|
||||
uint64 rgballsum = -radius * rgb[x];
|
||||
uint64 rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
|
||||
for (i = 1; i <= radius; i++) {
|
||||
rgbsum += rgb[i * w + x] * (r1 - i);
|
||||
rgballsum += rgb[i * w + x];
|
||||
}
|
||||
const int he = h - r1;
|
||||
for (x = 0; x < w; x++) {
|
||||
uint64 rgballsum = -radius * rgb[x];
|
||||
uint64 rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
|
||||
for (i = 1; i <= radius; i++) {
|
||||
rgbsum += rgb[i * w + x] * (r1 - i);
|
||||
rgballsum += rgb[i * w + x];
|
||||
}
|
||||
|
||||
y = 0;
|
||||
int yi = x * 4;
|
||||
y = 0;
|
||||
int yi = x * 4;
|
||||
|
||||
#define update(start, middle, end) \
|
||||
uint64 res = rgbsum >> 4; \
|
||||
|
|
@ -556,30 +603,27 @@ rgbsum += rgballsum; \
|
|||
y++; \
|
||||
yi += stride;
|
||||
|
||||
while (y < r1) {
|
||||
update(0, y, y + r1);
|
||||
}
|
||||
while (y < he) {
|
||||
update(y - r1, y, y + r1);
|
||||
}
|
||||
while (y < h) {
|
||||
update(y - r1, y, h - 1);
|
||||
}
|
||||
while (y < r1) {
|
||||
update(0, y, y + r1);
|
||||
}
|
||||
while (y < he) {
|
||||
update(y - r1, y, y + r1);
|
||||
}
|
||||
while (y < h) {
|
||||
update(y - r1, y, h - 1);
|
||||
}
|
||||
|
||||
#undef update
|
||||
}
|
||||
|
||||
delete[] rgb;
|
||||
}
|
||||
}
|
||||
return img;
|
||||
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
QImage BlurLargeImage(QImage image, int radius) {
|
||||
[[nodiscard]] QImage BlurLargeImage(QImage &&image, int radius) {
|
||||
const auto width = image.width();
|
||||
const auto height = image.height();
|
||||
if (width <= radius || height <= radius || radius < 1) {
|
||||
return image;
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
if (image.format() != QImage::Format_RGB32
|
||||
|
|
@ -787,10 +831,10 @@ QImage BlurLargeImage(QImage image, int radius) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage DitherImage(QImage image) {
|
||||
[[nodiscard]] QImage DitherImage(const QImage &image) {
|
||||
Expects(image.bytesPerLine() == image.width() * 4);
|
||||
|
||||
const auto width = image.width();
|
||||
|
|
@ -925,21 +969,32 @@ QImage GenerateShadow(
|
|||
return result;
|
||||
}
|
||||
|
||||
void prepareCircle(QImage &img) {
|
||||
Assert(!img.isNull());
|
||||
QImage Circle(QImage &&image, QRect target) {
|
||||
Expects(!image.isNull());
|
||||
|
||||
img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
Assert(!img.isNull());
|
||||
if (target.isNull()) {
|
||||
target = QRect(QPoint(), image.size());
|
||||
} else {
|
||||
Assert(QRect(QPoint(), image.size()).contains(target));
|
||||
}
|
||||
|
||||
QPainter p(&img);
|
||||
image = std::move(image).convertToFormat(
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
Assert(!image.isNull());
|
||||
|
||||
const auto ratio = image.devicePixelRatio();
|
||||
auto p = QPainter(&image);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
p.drawImage(
|
||||
QRect(QPoint(), img.size() / img.devicePixelRatio()),
|
||||
circleMask(img.size()));
|
||||
QRectF(target.topLeft() / ratio, target.size() / ratio),
|
||||
CircleMask(target.size()));
|
||||
p.end();
|
||||
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
void prepareRound(
|
||||
QImage &image,
|
||||
QImage Round(
|
||||
QImage &&image,
|
||||
gsl::span<const QImage, 4> cornerMasks,
|
||||
RectParts corners,
|
||||
QRect target) {
|
||||
|
|
@ -953,7 +1008,7 @@ void prepareRound(
|
|||
auto targetWidth = target.width();
|
||||
auto targetHeight = target.height();
|
||||
if (targetWidth < cornerWidth || targetHeight < cornerHeight) {
|
||||
return;
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
// We need to detach image first (if it is shared), before we
|
||||
|
|
@ -996,20 +1051,20 @@ void prepareRound(
|
|||
if (corners & RectPart::TopRight) maskCorner(intsTopRight, cornerMasks[1]);
|
||||
if (corners & RectPart::BottomLeft) maskCorner(intsBottomLeft, cornerMasks[2]);
|
||||
if (corners & RectPart::BottomRight) maskCorner(intsBottomRight, cornerMasks[3]);
|
||||
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
void prepareRound(
|
||||
QImage &image,
|
||||
QImage Round(
|
||||
QImage &&image,
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners,
|
||||
QRect target) {
|
||||
if (!static_cast<int>(corners)) {
|
||||
return;
|
||||
return std::move(image);
|
||||
} else if (radius == ImageRoundRadius::Ellipse) {
|
||||
Assert((corners & RectPart::AllCorners) == RectPart::AllCorners);
|
||||
Assert(target.isNull());
|
||||
prepareCircle(image);
|
||||
return;
|
||||
return Circle(std::move(image), target);
|
||||
}
|
||||
Assert(!image.isNull());
|
||||
|
||||
|
|
@ -1018,17 +1073,40 @@ void prepareRound(
|
|||
Assert(!image.isNull());
|
||||
|
||||
const auto masks = CornersMask(radius);
|
||||
prepareRound(image, masks, corners, target);
|
||||
return Round(std::move(image), masks, corners, target);
|
||||
}
|
||||
|
||||
QImage prepareColored(style::color add, QImage image) {
|
||||
return prepareColored(add->c, std::move(image));
|
||||
QImage Round(QImage &&image, Options options, QRect target) {
|
||||
if (options & Option::RoundCircle) {
|
||||
return Circle(std::move(image), target);
|
||||
} else if (!(options & (Option::RoundLarge | Option::RoundSmall))) {
|
||||
return std::move(image);
|
||||
}
|
||||
const auto corner = [&](Option skip, RectPart part) {
|
||||
return !(options & skip) ? part : RectPart::None;
|
||||
};
|
||||
return Round(
|
||||
std::move(image),
|
||||
((options & Option::RoundLarge)
|
||||
? ImageRoundRadius::Large
|
||||
: ImageRoundRadius::Small),
|
||||
(corner(Option::RoundSkipTopLeft, RectPart::TopLeft)
|
||||
| corner(Option::RoundSkipTopRight, RectPart::TopRight)
|
||||
| corner(Option::RoundSkipBottomLeft, RectPart::BottomLeft)
|
||||
| corner(Option::RoundSkipBottomRight, RectPart::BottomRight)),
|
||||
target);
|
||||
}
|
||||
|
||||
QImage prepareColored(QColor add, QImage image) {
|
||||
QImage Colored(QImage &&image, style::color add) {
|
||||
return Colored(std::move(image), add->c);
|
||||
}
|
||||
|
||||
QImage Colored(QImage &&image, QColor add) {
|
||||
const auto format = image.format();
|
||||
if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32_Premultiplied) {
|
||||
image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
if (format != QImage::Format_RGB32
|
||||
&& format != QImage::Format_ARGB32_Premultiplied) {
|
||||
image = std::move(image).convertToFormat(
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
}
|
||||
|
||||
if (const auto pix = image.bits()) {
|
||||
|
|
@ -1040,95 +1118,107 @@ QImage prepareColored(QColor add, QImage image) {
|
|||
const auto h = image.height();
|
||||
const auto size = w * h * 4;
|
||||
for (auto i = index_type(); i != size; i += 4) {
|
||||
int b = pix[i], g = pix[i + 1], r = pix[i + 2], a = pix[i + 3], aca = a * ca;
|
||||
const auto b = pix[i];
|
||||
const auto g = pix[i + 1];
|
||||
const auto r = pix[i + 2];
|
||||
const auto a = pix[i + 3];
|
||||
const auto aca = a * ca;
|
||||
pix[i + 0] = uchar(b + ((aca * (cb - b)) >> 16));
|
||||
pix[i + 1] = uchar(g + ((aca * (cg - g)) >> 16));
|
||||
pix[i + 2] = uchar(r + ((aca * (cr - r)) >> 16));
|
||||
pix[i + 3] = uchar(a + ((aca * (0xFF - a)) >> 16));
|
||||
}
|
||||
}
|
||||
return image;
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
QImage prepareOpaque(QImage image) {
|
||||
QImage Opaque(QImage &&image) {
|
||||
if (image.hasAlphaChannel()) {
|
||||
image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
image = std::move(image).convertToFormat(
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
auto ints = reinterpret_cast<uint32*>(image.bits());
|
||||
auto bg = anim::shifted(st::imageBgTransparent->c);
|
||||
auto width = image.width();
|
||||
auto height = image.height();
|
||||
auto addPerLine = (image.bytesPerLine() / sizeof(uint32)) - width;
|
||||
const auto bg = anim::shifted(QColor(Qt::white));
|
||||
const auto width = image.width();
|
||||
const auto height = image.height();
|
||||
const auto addPerLine = (image.bytesPerLine() / sizeof(uint32)) - width;
|
||||
for (auto y = 0; y != height; ++y) {
|
||||
for (auto x = 0; x != width; ++x) {
|
||||
auto components = anim::shifted(*ints);
|
||||
*ints++ = anim::unshifted(components * 256 + bg * (256 - anim::getAlpha(components)));
|
||||
const auto components = anim::shifted(*ints);
|
||||
*ints++ = anim::unshifted(components * 256
|
||||
+ bg * (256 - anim::getAlpha(components)));
|
||||
}
|
||||
ints += addPerLine;
|
||||
}
|
||||
}
|
||||
return image;
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
QImage prepare(QImage img, int w, int h, Images::Options options, int outerw, int outerh, const style::color *colored) {
|
||||
Assert(!img.isNull());
|
||||
if (options & Images::Option::Blurred) {
|
||||
img = prepareBlur(std::move(img));
|
||||
Assert(!img.isNull());
|
||||
QImage Prepare(QImage image, int w, int h, const PrepareArgs &args) {
|
||||
Expects(!image.isNull());
|
||||
|
||||
if (args.options & Option::Blur) {
|
||||
image = Blur(std::move(image));
|
||||
Assert(!image.isNull());
|
||||
}
|
||||
if (w <= 0 || (w == img.width() && (h <= 0 || h == img.height()))) {
|
||||
if (w <= 0
|
||||
|| (w == image.width() && (h <= 0 || h == image.height()))) {
|
||||
} else if (h <= 0) {
|
||||
img = img.scaledToWidth(w, (options & Images::Option::Smooth) ? Qt::SmoothTransformation : Qt::FastTransformation);
|
||||
Assert(!img.isNull());
|
||||
image = image.scaledToWidth(
|
||||
w,
|
||||
((args.options & Images::Option::FastTransform)
|
||||
? Qt::FastTransformation
|
||||
: Qt::SmoothTransformation));
|
||||
Assert(!image.isNull());
|
||||
} else {
|
||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, (options & Images::Option::Smooth) ? Qt::SmoothTransformation : Qt::FastTransformation);
|
||||
Assert(!img.isNull());
|
||||
image = image.scaled(
|
||||
w,
|
||||
h,
|
||||
Qt::IgnoreAspectRatio,
|
||||
((args.options & Images::Option::FastTransform)
|
||||
? Qt::FastTransformation
|
||||
: Qt::SmoothTransformation));
|
||||
Assert(!image.isNull());
|
||||
}
|
||||
if (outerw > 0 && outerh > 0) {
|
||||
const auto pixelRatio = style::DevicePixelRatio();
|
||||
outerw *= pixelRatio;
|
||||
outerh *= pixelRatio;
|
||||
if (outerw != w || outerh != h) {
|
||||
img.setDevicePixelRatio(pixelRatio);
|
||||
auto result = QImage(outerw, outerh, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(pixelRatio);
|
||||
if (options & Images::Option::TransparentBackground) {
|
||||
auto outer = args.outer;
|
||||
if (!outer.isEmpty()) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
outer *= ratio;
|
||||
if (outer != QSize(w, h)) {
|
||||
image.setDevicePixelRatio(ratio);
|
||||
auto result = QImage(outer, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
if (args.options & Images::Option::TransparentBackground) {
|
||||
result.fill(Qt::transparent);
|
||||
}
|
||||
{
|
||||
QPainter p(&result);
|
||||
if (!(options & Images::Option::TransparentBackground)) {
|
||||
if (w < outerw || h < outerh) {
|
||||
p.fillRect(0, 0, result.width(), result.height(), st::imageBg);
|
||||
if (!(args.options & Images::Option::TransparentBackground)) {
|
||||
if (w < outer.width() || h < outer.height()) {
|
||||
p.fillRect(
|
||||
QRect({}, result.size() / ratio),
|
||||
Qt::black);
|
||||
}
|
||||
}
|
||||
p.drawImage((result.width() - img.width()) / (2 * pixelRatio), (result.height() - img.height()) / (2 * pixelRatio), img);
|
||||
p.drawImage(
|
||||
(result.width() - image.width()) / (2 * ratio),
|
||||
(result.height() - image.height()) / (2 * ratio),
|
||||
image);
|
||||
}
|
||||
img = result;
|
||||
Assert(!img.isNull());
|
||||
image = std::move(result);
|
||||
Assert(!image.isNull());
|
||||
}
|
||||
}
|
||||
auto corners = [](Images::Options options) {
|
||||
return ((options & Images::Option::RoundedTopLeft) ? RectPart::TopLeft : RectPart::None)
|
||||
| ((options & Images::Option::RoundedTopRight) ? RectPart::TopRight : RectPart::None)
|
||||
| ((options & Images::Option::RoundedBottomLeft) ? RectPart::BottomLeft : RectPart::None)
|
||||
| ((options & Images::Option::RoundedBottomRight) ? RectPart::BottomRight : RectPart::None);
|
||||
};
|
||||
if (options & Images::Option::Circled) {
|
||||
prepareCircle(img);
|
||||
Assert(!img.isNull());
|
||||
} else if (options & Images::Option::RoundedLarge) {
|
||||
prepareRound(img, ImageRoundRadius::Large, corners(options));
|
||||
Assert(!img.isNull());
|
||||
} else if (options & Images::Option::RoundedSmall) {
|
||||
prepareRound(img, ImageRoundRadius::Small, corners(options));
|
||||
Assert(!img.isNull());
|
||||
|
||||
if (args.options
|
||||
& (Option::RoundCircle | Option::RoundLarge | Option::RoundSmall)) {
|
||||
image = Round(std::move(image), args.options);
|
||||
Assert(!image.isNull());
|
||||
}
|
||||
if (options & Images::Option::Colored) {
|
||||
Assert(colored != nullptr);
|
||||
img = prepareColored(*colored, std::move(img));
|
||||
if (args.colored) {
|
||||
image = Colored(std::move(image), *args.colored);
|
||||
}
|
||||
img.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
return img;
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
return image;
|
||||
}
|
||||
|
||||
} // namespace Images
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ enum class ImageRoundRadius {
|
|||
namespace Images {
|
||||
|
||||
[[nodiscard]] QPixmap PixmapFast(QImage &&image);
|
||||
[[nodiscard]] QImage BlurLargeImage(QImage image, int radius);
|
||||
[[nodiscard]] QImage DitherImage(QImage image);
|
||||
[[nodiscard]] QImage BlurLargeImage(QImage &&image, int radius);
|
||||
[[nodiscard]] QImage DitherImage(const QImage &image);
|
||||
|
||||
[[nodiscard]] QImage GenerateGradient(
|
||||
QSize size,
|
||||
|
|
@ -79,44 +79,78 @@ struct ReadResult {
|
|||
};
|
||||
[[nodiscard]] ReadResult Read(ReadArgs &&args);
|
||||
|
||||
QImage prepareBlur(QImage image);
|
||||
void prepareRound(
|
||||
QImage &image,
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners = RectPart::AllCorners,
|
||||
QRect target = QRect());
|
||||
void prepareRound(
|
||||
QImage &image,
|
||||
gsl::span<const QImage, 4> cornerMasks,
|
||||
RectParts corners = RectPart::AllCorners,
|
||||
QRect target = QRect());
|
||||
void prepareCircle(QImage &image);
|
||||
QImage prepareColored(style::color add, QImage image);
|
||||
QImage prepareColored(QColor add, QImage image);
|
||||
QImage prepareOpaque(QImage image);
|
||||
|
||||
enum class Option {
|
||||
None = 0,
|
||||
Smooth = (1 << 0),
|
||||
Blurred = (1 << 1),
|
||||
Circled = (1 << 2),
|
||||
RoundedLarge = (1 << 3),
|
||||
RoundedSmall = (1 << 4),
|
||||
RoundedTopLeft = (1 << 5),
|
||||
RoundedTopRight = (1 << 6),
|
||||
RoundedBottomLeft = (1 << 7),
|
||||
RoundedBottomRight = (1 << 8),
|
||||
RoundedAll = (None
|
||||
| RoundedTopLeft
|
||||
| RoundedTopRight
|
||||
| RoundedBottomLeft
|
||||
| RoundedBottomRight),
|
||||
Colored = (1 << 9),
|
||||
FastTransform = (1 << 0),
|
||||
Blur = (1 << 1),
|
||||
RoundCircle = (1 << 2),
|
||||
RoundLarge = (1 << 3),
|
||||
RoundSmall = (1 << 4),
|
||||
RoundSkipTopLeft = (1 << 5),
|
||||
RoundSkipTopRight = (1 << 6),
|
||||
RoundSkipBottomLeft = (1 << 7),
|
||||
RoundSkipBottomRight = (1 << 8),
|
||||
Colorize = (1 << 9),
|
||||
TransparentBackground = (1 << 10),
|
||||
};
|
||||
using Options = base::flags<Option>;
|
||||
inline constexpr auto is_flag_type(Option) { return true; };
|
||||
|
||||
QImage prepare(QImage img, int w, int h, Options options, int outerw, int outerh, const style::color *colored = nullptr);
|
||||
[[nodiscard]] Options RoundOptions(
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners = RectPart::AllCorners);
|
||||
|
||||
[[nodiscard]] QImage Blur(QImage &&image);
|
||||
[[nodiscard]] QImage Round(
|
||||
QImage &&image,
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners = RectPart::AllCorners,
|
||||
QRect target = QRect());
|
||||
[[nodiscard]] QImage Round(
|
||||
QImage &&image,
|
||||
gsl::span<const QImage, 4> cornerMasks,
|
||||
RectParts corners = RectPart::AllCorners,
|
||||
QRect target = QRect());
|
||||
[[nodiscard]] QImage Round(
|
||||
QImage &&image,
|
||||
Options options,
|
||||
QRect target = QRect());
|
||||
|
||||
[[nodiscard]] QImage Circle(QImage &&image, QRect target = QRect());
|
||||
[[nodiscard]] QImage Colored(QImage &&image, style::color add);
|
||||
[[nodiscard]] QImage Colored(QImage &&image, QColor add);
|
||||
[[nodiscard]] QImage Opaque(QImage &&image);
|
||||
|
||||
struct PrepareArgs {
|
||||
const style::color *colored = nullptr;
|
||||
Options options;
|
||||
QSize outer;
|
||||
|
||||
[[nodiscard]] PrepareArgs blurred() const {
|
||||
auto result = *this;
|
||||
result.options |= Option::Blur;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] QImage Prepare(
|
||||
QImage image,
|
||||
int w,
|
||||
int h,
|
||||
const PrepareArgs &args);
|
||||
|
||||
[[nodiscard]] inline QImage Prepare(
|
||||
QImage image,
|
||||
int w,
|
||||
const PrepareArgs &args) {
|
||||
return Prepare(std::move(image), w, 0, args);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline QImage Prepare(
|
||||
QImage image,
|
||||
QSize size,
|
||||
const PrepareArgs &args) {
|
||||
return Prepare(std::move(image), size.width(), size.height(), args);
|
||||
}
|
||||
|
||||
} // namespace Images
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "ui/platform/mac/ui_window_title_mac.h"
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
|
|
@ -59,20 +60,29 @@ void TitleWidget::init(int height) {
|
|||
}, lifetime());
|
||||
|
||||
const auto families = QStringList{
|
||||
QString(".SF NS Text"),
|
||||
QString("Helvetica Neue")
|
||||
u".AppleSystemUIFont"_q,
|
||||
u".SF NS Text"_q,
|
||||
u"Helvetica Neue"_q,
|
||||
};
|
||||
for (auto family : families) {
|
||||
_font.setFamily(family);
|
||||
if (QFontInfo(_font).family() == _font.family()) {
|
||||
static const auto logged = [&] {
|
||||
LOG(("Title Font: %1").arg(family));
|
||||
return true;
|
||||
}();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (QFontInfo(_font).family() == _font.family()) {
|
||||
_font.setPixelSize((height * 15) / 24);
|
||||
if (QFontInfo(_font).family() != _font.family()) {
|
||||
_font = st::semiboldFont;
|
||||
_font.setPixelSize(13);
|
||||
} else if (_font.family() == u".AppleSystemUIFont"_q) {
|
||||
_font.setBold(true);
|
||||
_font.setPixelSize(13);
|
||||
} else {
|
||||
_font = st::normalFont;
|
||||
_font.setPixelSize((height * 15) / 24);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ BasicWindowHelper::BasicWindowHelper(not_null<RpWidget*> window)
|
|||
_window->setWindowFlag(Qt::Window);
|
||||
}
|
||||
|
||||
void BasicWindowHelper::initInWindow(not_null<RpWindow*> window) {
|
||||
}
|
||||
|
||||
not_null<RpWidget*> BasicWindowHelper::body() {
|
||||
return _window;
|
||||
}
|
||||
|
|
@ -41,6 +44,27 @@ QMargins BasicWindowHelper::frameMargins() {
|
|||
return nativeFrameMargins();
|
||||
}
|
||||
|
||||
int BasicWindowHelper::additionalContentPadding() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rpl::producer<int> BasicWindowHelper::additionalContentPaddingValue() const {
|
||||
return rpl::single(0);
|
||||
}
|
||||
|
||||
auto BasicWindowHelper::hitTestRequests() const
|
||||
-> rpl::producer<not_null<HitTestRequest*>> {
|
||||
return rpl::never<not_null<HitTestRequest*>>();
|
||||
}
|
||||
|
||||
rpl::producer<HitTestResult> BasicWindowHelper::systemButtonOver() const {
|
||||
return rpl::never<HitTestResult>();
|
||||
}
|
||||
|
||||
rpl::producer<HitTestResult> BasicWindowHelper::systemButtonDown() const {
|
||||
return rpl::never<HitTestResult>();
|
||||
}
|
||||
|
||||
void BasicWindowHelper::setTitle(const QString &title) {
|
||||
_window->setWindowTitle(title);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@ struct WindowTitle;
|
|||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class RpWindow;
|
||||
enum class WindowTitleHitTestFlag;
|
||||
using WindowTitleHitTestFlags = base::flags<WindowTitleHitTestFlag>;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
struct HitTestRequest;
|
||||
enum class HitTestResult;
|
||||
class DefaultTitleWidget;
|
||||
|
||||
class BasicWindowHelper {
|
||||
|
|
@ -27,8 +30,22 @@ public:
|
|||
explicit BasicWindowHelper(not_null<RpWidget*> window);
|
||||
virtual ~BasicWindowHelper() = default;
|
||||
|
||||
[[nodiscard]] not_null<RpWidget*> window() const {
|
||||
return _window;
|
||||
}
|
||||
|
||||
virtual void initInWindow(not_null<RpWindow*> window);
|
||||
[[nodiscard]] virtual not_null<RpWidget*> body();
|
||||
[[nodiscard]] virtual QMargins frameMargins();
|
||||
[[nodiscard]] virtual int additionalContentPadding() const;
|
||||
[[nodiscard]] virtual auto additionalContentPaddingValue() const
|
||||
-> rpl::producer<int>;
|
||||
[[nodiscard]] virtual auto hitTestRequests() const
|
||||
-> rpl::producer<not_null<HitTestRequest*>>;
|
||||
[[nodiscard]] virtual auto systemButtonOver() const
|
||||
-> rpl::producer<HitTestResult>;
|
||||
[[nodiscard]] virtual auto systemButtonDown() const
|
||||
-> rpl::producer<HitTestResult>;
|
||||
virtual void setTitle(const QString &title);
|
||||
virtual void setTitleStyle(const style::WindowTitle &st);
|
||||
virtual void setNativeFrame(bool enabled);
|
||||
|
|
@ -43,9 +60,6 @@ public:
|
|||
void setBodyTitleArea(Fn<WindowTitleHitTestFlags(QPoint)> testMethod);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] not_null<RpWidget*> window() const {
|
||||
return _window;
|
||||
}
|
||||
[[nodiscard]] WindowTitleHitTestFlags bodyTitleAreaHit(
|
||||
QPoint point) const {
|
||||
return _bodyTitleAreaTestMethod
|
||||
|
|
@ -108,7 +122,7 @@ private:
|
|||
return std::make_unique<DefaultWindowHelper>(window);
|
||||
}
|
||||
|
||||
bool NativeWindowFrameSupported();
|
||||
[[nodiscard]] bool NativeWindowFrameSupported();
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@
|
|||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/palette.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QtEvents>
|
||||
|
|
@ -35,6 +37,50 @@ void RemoveDuplicates(std::vector<T> &v) {
|
|||
|
||||
} // namespace
|
||||
|
||||
bool SemiNativeSystemButtonProcessing() {
|
||||
return ::Platform::IsWindows11OrGreater();
|
||||
}
|
||||
|
||||
void SetupSemiNativeSystemButtons(
|
||||
not_null<TitleControls*> controls,
|
||||
not_null<RpWindow*> window,
|
||||
rpl::lifetime &lifetime,
|
||||
Fn<bool()> filter) {
|
||||
if (!SemiNativeSystemButtonProcessing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
window->systemButtonOver(
|
||||
) | rpl::filter([=](HitTestResult button) {
|
||||
return !filter || filter() || (button == HitTestResult::None);
|
||||
}) | rpl::start_with_next([=](HitTestResult button) {
|
||||
controls->buttonOver(button);
|
||||
}, lifetime);
|
||||
|
||||
window->systemButtonDown(
|
||||
) | rpl::filter([=](HitTestResult button) {
|
||||
return !filter || filter() || (button == HitTestResult::None);
|
||||
}) | rpl::start_with_next([=](HitTestResult button) {
|
||||
controls->buttonDown(button);
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
class TitleControls::Button final : public IconButton {
|
||||
public:
|
||||
using IconButton::IconButton;
|
||||
|
||||
void setOver(bool over) {
|
||||
IconButton::setOver(over, StateChangeSource::ByPress);
|
||||
}
|
||||
void setDown(bool down) {
|
||||
IconButton::setDown(
|
||||
down,
|
||||
StateChangeSource::ByPress,
|
||||
{},
|
||||
Qt::LeftButton);
|
||||
}
|
||||
};
|
||||
|
||||
TitleControls::TitleControls(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::WindowTitle &st,
|
||||
|
|
@ -172,7 +218,47 @@ void TitleControls::raise() {
|
|||
_close->raise();
|
||||
}
|
||||
|
||||
Ui::IconButton *TitleControls::controlWidget(Control control) const {
|
||||
HitTestResult TitleControls::hitTest(QPoint point) const {
|
||||
const auto test = [&](const object_ptr<Button> &button) {
|
||||
return button && button->geometry().contains(point);
|
||||
};
|
||||
if (test(_minimize)) {
|
||||
return HitTestResult::Minimize;
|
||||
} else if (test(_maximizeRestore)) {
|
||||
return HitTestResult::MaximizeRestore;
|
||||
} else if (test(_close)) {
|
||||
return HitTestResult::Close;
|
||||
}
|
||||
return HitTestResult::None;
|
||||
}
|
||||
|
||||
void TitleControls::buttonOver(HitTestResult testResult) {
|
||||
const auto update = [&](
|
||||
const object_ptr<Button> &button,
|
||||
HitTestResult buttonTestResult) {
|
||||
if (const auto raw = button.data()) {
|
||||
raw->setOver(testResult == buttonTestResult);
|
||||
}
|
||||
};
|
||||
update(_minimize, HitTestResult::Minimize);
|
||||
update(_maximizeRestore, HitTestResult::MaximizeRestore);
|
||||
update(_close, HitTestResult::Close);
|
||||
}
|
||||
|
||||
void TitleControls::buttonDown(HitTestResult testResult) {
|
||||
const auto update = [&](
|
||||
const object_ptr<Button> &button,
|
||||
HitTestResult buttonTestResult) {
|
||||
if (const auto raw = button.data()) {
|
||||
raw->setDown(testResult == buttonTestResult);
|
||||
}
|
||||
};
|
||||
update(_minimize, HitTestResult::Minimize);
|
||||
update(_maximizeRestore, HitTestResult::MaximizeRestore);
|
||||
update(_close, HitTestResult::Close);
|
||||
}
|
||||
|
||||
TitleControls::Button *TitleControls::controlWidget(Control control) const {
|
||||
switch (control) {
|
||||
case Control::Minimize: return _minimize;
|
||||
case Control::Maximize: return _maximizeRestore;
|
||||
|
|
@ -382,5 +468,50 @@ void DefaultTitleWidget::mouseDoubleClickEvent(QMouseEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
SeparateTitleControls::SeparateTitleControls(
|
||||
QWidget *parent,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize)
|
||||
: wrap(parent)
|
||||
, controls(&wrap, st, std::move(maximize)) {
|
||||
}
|
||||
|
||||
std::unique_ptr<SeparateTitleControls> SetupSeparateTitleControls(
|
||||
not_null<RpWindow*> window,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize) {
|
||||
auto result = std::make_unique<SeparateTitleControls>(
|
||||
window->body(),
|
||||
st,
|
||||
std::move(maximize));
|
||||
|
||||
const auto raw = result.get();
|
||||
auto &lifetime = raw->wrap.lifetime();
|
||||
rpl::combine(
|
||||
window->body()->widthValue(),
|
||||
window->additionalContentPaddingValue()
|
||||
) | rpl::start_with_next([=](int width, int padding) {
|
||||
raw->wrap.setGeometry(
|
||||
padding,
|
||||
0,
|
||||
width - 2 * padding,
|
||||
raw->controls.geometry().height());
|
||||
}, lifetime);
|
||||
|
||||
window->hitTestRequests(
|
||||
) | rpl::start_with_next([=](not_null<HitTestRequest*> request) {
|
||||
const auto origin = raw->wrap.pos();
|
||||
const auto relative = request->point - origin;
|
||||
const auto controlsResult = raw->controls.hitTest(relative);
|
||||
if (controlsResult != HitTestResult::None) {
|
||||
request->result = controlsResult;
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
SetupSemiNativeSystemButtons(&raw->controls, window, lifetime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -20,9 +20,41 @@ namespace Ui {
|
|||
|
||||
class IconButton;
|
||||
class PlainShadow;
|
||||
class RpWindow;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class TitleControls;
|
||||
|
||||
enum class HitTestResult {
|
||||
None = 0,
|
||||
Client,
|
||||
Minimize,
|
||||
MaximizeRestore,
|
||||
Close,
|
||||
Caption,
|
||||
Top,
|
||||
TopRight,
|
||||
Right,
|
||||
BottomRight,
|
||||
Bottom,
|
||||
BottomLeft,
|
||||
Left,
|
||||
TopLeft,
|
||||
};
|
||||
|
||||
struct HitTestRequest {
|
||||
QPoint point;
|
||||
HitTestResult result = HitTestResult::Client;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool SemiNativeSystemButtonProcessing();
|
||||
void SetupSemiNativeSystemButtons(
|
||||
not_null<TitleControls*> controls,
|
||||
not_null<RpWindow*> window,
|
||||
rpl::lifetime &lifetime,
|
||||
Fn<bool()> filter = nullptr);
|
||||
|
||||
class TitleControls final {
|
||||
public:
|
||||
TitleControls(
|
||||
|
|
@ -36,6 +68,11 @@ public:
|
|||
void setResizeEnabled(bool enabled);
|
||||
void raise();
|
||||
|
||||
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
|
||||
|
||||
void buttonOver(HitTestResult testResult);
|
||||
void buttonDown(HitTestResult testResult);
|
||||
|
||||
enum class Control {
|
||||
Unknown,
|
||||
Minimize,
|
||||
|
|
@ -49,9 +86,11 @@ public:
|
|||
};
|
||||
|
||||
private:
|
||||
class Button;
|
||||
|
||||
[[nodiscard]] not_null<RpWidget*> parent() const;
|
||||
[[nodiscard]] not_null<QWidget*> window() const;
|
||||
[[nodiscard]] Ui::IconButton *controlWidget(Control control) const;
|
||||
[[nodiscard]] Button *controlWidget(Control control) const;
|
||||
|
||||
void init(Fn<void(bool maximized)> maximize);
|
||||
void subscribeToStateChanges();
|
||||
|
|
@ -64,9 +103,9 @@ private:
|
|||
|
||||
not_null<const style::WindowTitle*> _st;
|
||||
|
||||
object_ptr<Ui::IconButton> _minimize;
|
||||
object_ptr<Ui::IconButton> _maximizeRestore;
|
||||
object_ptr<Ui::IconButton> _close;
|
||||
object_ptr<Button> _minimize;
|
||||
object_ptr<Button> _maximizeRestore;
|
||||
object_ptr<Button> _close;
|
||||
|
||||
bool _maximizedState = false;
|
||||
bool _activeState = false;
|
||||
|
|
@ -98,5 +137,21 @@ private:
|
|||
|
||||
};
|
||||
|
||||
struct SeparateTitleControls {
|
||||
SeparateTitleControls(
|
||||
QWidget *parent,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize);
|
||||
|
||||
RpWidget wrap;
|
||||
TitleControls controls;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto SetupSeparateTitleControls(
|
||||
not_null<RpWindow*> window,
|
||||
const style::WindowTitle &st,
|
||||
Fn<void(bool maximized)> maximize = nullptr)
|
||||
-> std::unique_ptr<SeparateTitleControls>;
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -6,9 +6,14 @@
|
|||
//
|
||||
#include "ui/platform/win/ui_window_title_win.h"
|
||||
|
||||
#include "ui/platform/win/ui_window_win.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
|
|
@ -16,28 +21,88 @@
|
|||
#include <QtGui/QtEvents>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellscalingapi.h>
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
HRESULT(__stdcall *GetScaleFactorForMonitor)(
|
||||
_In_ HMONITOR hMon,
|
||||
_Out_ DEVICE_SCALE_FACTOR *pScale);
|
||||
|
||||
[[nodiscard]] bool ScaleQuerySupported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto shcore = base::Platform::SafeLoadLibrary(L"Shcore.dll");
|
||||
return LOAD_SYMBOL(shcore, GetScaleFactorForMonitor);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct TitleWidget::PaddingHelper {
|
||||
explicit PaddingHelper(QWidget *parent) : controlsParent(parent) {
|
||||
}
|
||||
|
||||
RpWidget controlsParent;
|
||||
rpl::variable<int> padding = 0;
|
||||
};
|
||||
|
||||
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
|
||||
: RpWidget(parent)
|
||||
, _controls(this, st::defaultWindowTitle)
|
||||
, _paddingHelper(CheckTitlePaddingRequired()
|
||||
? std::make_unique<PaddingHelper>(this)
|
||||
: nullptr)
|
||||
, _controls(
|
||||
_paddingHelper ? &_paddingHelper->controlsParent : this,
|
||||
st::defaultWindowTitle)
|
||||
, _shadow(this, st::titleShadow) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
parent->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
setGeometry(0, 0, width, _controls.st()->height);
|
||||
refreshGeometryWithWidth(width);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void TitleWidget::initInWindow(not_null<RpWindow*> window) {
|
||||
window->hitTestRequests(
|
||||
) | rpl::filter([=](not_null<HitTestRequest*> request) {
|
||||
return !isHidden() && geometry().contains(request->point);
|
||||
}) | rpl::start_with_next([=](not_null<HitTestRequest*> request) {
|
||||
request->result = hitTest(request->point);
|
||||
}, lifetime());
|
||||
|
||||
SetupSemiNativeSystemButtons(&_controls, window, lifetime(), [=] {
|
||||
return !isHidden() && (_controls.st()->height > 0);
|
||||
});
|
||||
}
|
||||
|
||||
TitleWidget::~TitleWidget() = default;
|
||||
|
||||
void TitleWidget::setText(const QString &text) {
|
||||
window()->setWindowTitle(text);
|
||||
}
|
||||
|
||||
void TitleWidget::setStyle(const style::WindowTitle &st) {
|
||||
_controls.setStyle(st);
|
||||
setGeometry(0, 0, window()->width(), _controls.st()->height);
|
||||
refreshGeometryWithWidth(window()->width());
|
||||
}
|
||||
|
||||
void TitleWidget::refreshGeometryWithWidth(int width) {
|
||||
const auto add = additionalPadding();
|
||||
setGeometry(0, 0, width, _controls.st()->height + add);
|
||||
if (_paddingHelper) {
|
||||
_paddingHelper->controlsParent.setGeometry(
|
||||
add,
|
||||
add,
|
||||
width - 2 * add,
|
||||
_controls.st()->height);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
|
|
@ -57,16 +122,123 @@ void TitleWidget::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
|
||||
void TitleWidget::resizeEvent(QResizeEvent *e) {
|
||||
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
|
||||
const auto thickness = st::lineWidth;
|
||||
_shadow->setGeometry(0, height() - thickness, width(), thickness);
|
||||
}
|
||||
|
||||
HitTestResult TitleWidget::hitTest(QPoint point) const {
|
||||
if (_controls.geometry().contains(point)) {
|
||||
return HitTestResult::SysButton;
|
||||
} else if (rect().contains(point)) {
|
||||
return HitTestResult::Caption;
|
||||
const auto origin = _paddingHelper
|
||||
? _paddingHelper->controlsParent.pos()
|
||||
: QPoint();
|
||||
const auto controlsResult = _controls.hitTest(point - origin);
|
||||
return (controlsResult != HitTestResult::None)
|
||||
? controlsResult
|
||||
: HitTestResult::Caption;
|
||||
}
|
||||
|
||||
bool TitleWidget::additionalPaddingRequired() const {
|
||||
return _paddingHelper && !isHidden();
|
||||
}
|
||||
|
||||
void TitleWidget::refreshAdditionalPaddings() {
|
||||
if (!additionalPaddingRequired()) {
|
||||
return;
|
||||
}
|
||||
return HitTestResult::None;
|
||||
const auto handle = GetWindowHandle(this);
|
||||
if (!handle) {
|
||||
LOG(("System Error: GetWindowHandle failed."));
|
||||
return;
|
||||
}
|
||||
refreshAdditionalPaddings(handle);
|
||||
}
|
||||
|
||||
void TitleWidget::refreshAdditionalPaddings(HWND handle) {
|
||||
if (!additionalPaddingRequired()) {
|
||||
return;
|
||||
}
|
||||
auto placement = WINDOWPLACEMENT{
|
||||
.length = sizeof(WINDOWPLACEMENT),
|
||||
};
|
||||
if (!GetWindowPlacement(handle, &placement)) {
|
||||
LOG(("System Error: GetWindowPlacement failed."));
|
||||
return;
|
||||
}
|
||||
refreshAdditionalPaddings(handle, placement);
|
||||
}
|
||||
|
||||
void TitleWidget::refreshAdditionalPaddings(
|
||||
HWND handle,
|
||||
const WINDOWPLACEMENT &placement) {
|
||||
if (!additionalPaddingRequired()) {
|
||||
return;
|
||||
}
|
||||
auto geometry = RECT();
|
||||
if (!GetWindowRect(handle, &geometry)) {
|
||||
LOG(("System Error: GetWindowRect failed."));
|
||||
return;
|
||||
}
|
||||
const auto normal = placement.rcNormalPosition;
|
||||
const auto rounded = (normal.left == geometry.left)
|
||||
&& (normal.right == geometry.right)
|
||||
&& (normal.top == geometry.top)
|
||||
&& (normal.bottom == geometry.bottom);
|
||||
const auto padding = [&] {
|
||||
if (!rounded) {
|
||||
return 0;
|
||||
}
|
||||
const auto monitor = MonitorFromWindow(
|
||||
handle,
|
||||
MONITOR_DEFAULTTONEAREST);
|
||||
if (!monitor) {
|
||||
LOG(("System Error: MonitorFromWindow failed."));
|
||||
return -1;
|
||||
}
|
||||
auto factor = DEVICE_SCALE_FACTOR();
|
||||
if (!SUCCEEDED(GetScaleFactorForMonitor(monitor, &factor))) {
|
||||
LOG(("System Error: GetScaleFactorForMonitor failed."));
|
||||
return -1;
|
||||
} else if (factor < 100 || factor > 500) {
|
||||
LOG(("System Error: Bad scale factor %1.").arg(int(factor)));
|
||||
return -1;
|
||||
}
|
||||
const auto pixels = (factor + 50) / 100;
|
||||
return int(base::SafeRound(pixels / window()->devicePixelRatioF()));
|
||||
}();
|
||||
if (padding < 0) {
|
||||
return;
|
||||
}
|
||||
setAdditionalPadding(padding);
|
||||
}
|
||||
|
||||
int TitleWidget::additionalPadding() const {
|
||||
return _paddingHelper ? _paddingHelper->padding.current() : 0;
|
||||
}
|
||||
|
||||
rpl::producer<int> TitleWidget::additionalPaddingValue() const {
|
||||
return _paddingHelper ? _paddingHelper->padding.value() : rpl::single(0);
|
||||
}
|
||||
|
||||
void TitleWidget::setAdditionalPadding(int padding) {
|
||||
Expects(_paddingHelper != nullptr);
|
||||
|
||||
if (_paddingHelper->padding.current() == padding) {
|
||||
return;
|
||||
}
|
||||
_paddingHelper->padding = padding;
|
||||
refreshGeometryWithWidth(window()->width());
|
||||
}
|
||||
|
||||
void TitleWidget::setVisibleHook(bool visible) {
|
||||
RpWidget::setVisibleHook(visible);
|
||||
if (additionalPaddingRequired()) {
|
||||
PostponeCall(this, [=] {
|
||||
refreshAdditionalPaddings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckTitlePaddingRequired() {
|
||||
return ::Platform::IsWindows11OrGreater() && ScaleQuerySupported();
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
|||
|
|
@ -13,51 +13,60 @@
|
|||
#include <QtCore/QRect>
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
#include <Windows.h> // HWND, WINDOWPLACEMENT
|
||||
|
||||
namespace style {
|
||||
struct WindowTitle;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWindow;
|
||||
class IconButton;
|
||||
class PlainShadow;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
enum class HitTestResult {
|
||||
None = 0,
|
||||
Client,
|
||||
SysButton,
|
||||
Caption,
|
||||
Top,
|
||||
TopRight,
|
||||
Right,
|
||||
BottomRight,
|
||||
Bottom,
|
||||
BottomLeft,
|
||||
Left,
|
||||
TopLeft,
|
||||
};
|
||||
|
||||
class TitleWidget : public RpWidget {
|
||||
public:
|
||||
explicit TitleWidget(not_null<RpWidget*> parent);
|
||||
~TitleWidget();
|
||||
|
||||
void initInWindow(not_null<RpWindow*> window);
|
||||
void setText(const QString &text);
|
||||
void setStyle(const style::WindowTitle &st);
|
||||
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
|
||||
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
|
||||
void setResizeEnabled(bool enabled);
|
||||
|
||||
void refreshAdditionalPaddings();
|
||||
void refreshAdditionalPaddings(HWND handle);
|
||||
void refreshAdditionalPaddings(
|
||||
HWND handle,
|
||||
const WINDOWPLACEMENT &placement);
|
||||
[[nodiscard]] int additionalPadding() const;
|
||||
[[nodiscard]] rpl::producer<int> additionalPaddingValue() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void setVisibleHook(bool visible) override;
|
||||
|
||||
private:
|
||||
struct PaddingHelper;
|
||||
|
||||
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
|
||||
[[nodiscard]] bool additionalPaddingRequired() const;
|
||||
void refreshGeometryWithWidth(int width);
|
||||
void setAdditionalPadding(int padding);
|
||||
|
||||
std::unique_ptr<PaddingHelper> _paddingHelper;
|
||||
TitleControls _controls;
|
||||
object_ptr<Ui::PlainShadow> _shadow;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool CheckTitlePaddingRequired();
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
#include "ui/inactive_press.h"
|
||||
#include "ui/platform/win/ui_window_title_win.h"
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
#include "base/integration.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "styles/palette.h"
|
||||
|
|
@ -31,6 +31,7 @@ namespace Platform {
|
|||
namespace {
|
||||
|
||||
constexpr auto kDWMWCP_ROUND = DWORD(2);
|
||||
constexpr auto kDWMWCP_DONOTROUND = DWORD(1);
|
||||
constexpr auto kDWMWA_WINDOW_CORNER_PREFERENCE = DWORD(33);
|
||||
constexpr auto kDWMWA_CAPTION_COLOR = DWORD(35);
|
||||
constexpr auto kDWMWA_TEXT_COLOR = DWORD(36);
|
||||
|
|
@ -170,6 +171,10 @@ WindowHelper::~WindowHelper() {
|
|||
GetNativeFilter()->unregisterWindow(_handle);
|
||||
}
|
||||
|
||||
void WindowHelper::initInWindow(not_null<RpWindow*> window) {
|
||||
_title->initInWindow(window);
|
||||
}
|
||||
|
||||
not_null<RpWidget*> WindowHelper::body() {
|
||||
return _body;
|
||||
}
|
||||
|
|
@ -180,6 +185,19 @@ QMargins WindowHelper::frameMargins() {
|
|||
: QMargins{ 0, _title->height(), 0, 0 };
|
||||
}
|
||||
|
||||
int WindowHelper::additionalContentPadding() const {
|
||||
return _title->isHidden() ? 0 : _title->additionalPadding();
|
||||
}
|
||||
|
||||
rpl::producer<int> WindowHelper::additionalContentPaddingValue() const {
|
||||
return rpl::combine(
|
||||
_title->shownValue(),
|
||||
_title->additionalPaddingValue()
|
||||
) | rpl::map([](bool shown, int padding) {
|
||||
return shown ? padding : 0;
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
void WindowHelper::setTitle(const QString &title) {
|
||||
_title->setText(title);
|
||||
window()->setWindowTitle(title);
|
||||
|
|
@ -218,15 +236,19 @@ void WindowHelper::initialShadowUpdate() {
|
|||
} else {
|
||||
_shadow->update(Change::Moved | Change::Resized | Change::Shown);
|
||||
}
|
||||
updateCornersRounding();
|
||||
}
|
||||
|
||||
if (::Platform::IsWindows11OrGreater()) {
|
||||
auto preference = kDWMWCP_ROUND;
|
||||
DwmSetWindowAttribute(
|
||||
_handle,
|
||||
kDWMWA_WINDOW_CORNER_PREFERENCE,
|
||||
&preference,
|
||||
sizeof(preference));
|
||||
void WindowHelper::updateCornersRounding() {
|
||||
if (!::Platform::IsWindows11OrGreater()) {
|
||||
return;
|
||||
}
|
||||
auto preference = _isFullScreen ? kDWMWCP_DONOTROUND : kDWMWCP_ROUND;
|
||||
DwmSetWindowAttribute(
|
||||
_handle,
|
||||
kDWMWA_WINDOW_CORNER_PREFERENCE,
|
||||
&preference,
|
||||
sizeof(preference));
|
||||
}
|
||||
|
||||
void WindowHelper::setMinimumSize(QSize size) {
|
||||
|
|
@ -249,6 +271,7 @@ void WindowHelper::showFullScreen() {
|
|||
if (!_isFullScreen) {
|
||||
_isFullScreen = true;
|
||||
updateMargins();
|
||||
updateCornersRounding();
|
||||
}
|
||||
window()->showFullScreen();
|
||||
}
|
||||
|
|
@ -258,9 +281,23 @@ void WindowHelper::showNormal() {
|
|||
if (_isFullScreen) {
|
||||
_isFullScreen = false;
|
||||
updateMargins();
|
||||
updateCornersRounding();
|
||||
}
|
||||
}
|
||||
|
||||
auto WindowHelper::hitTestRequests() const
|
||||
-> rpl::producer<not_null<HitTestRequest*>> {
|
||||
return _hitTestRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<HitTestResult> WindowHelper::systemButtonOver() const {
|
||||
return _systemButtonOver.events();
|
||||
}
|
||||
|
||||
rpl::producer<HitTestResult> WindowHelper::systemButtonDown() const {
|
||||
return _systemButtonDown.events();
|
||||
}
|
||||
|
||||
void WindowHelper::init() {
|
||||
_title->show();
|
||||
GetNativeFilter()->registerWindow(_handle, this);
|
||||
|
|
@ -322,6 +359,10 @@ bool WindowHelper::handleNativeEvent(
|
|||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) {
|
||||
if (handleSystemButtonEvent(msg, wParam, lParam, result)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (msg) {
|
||||
|
||||
case WM_ACTIVATE: {
|
||||
|
|
@ -383,13 +424,11 @@ bool WindowHelper::handleNativeEvent(
|
|||
return true;
|
||||
}
|
||||
|
||||
case WM_NCLBUTTONDBLCLK:
|
||||
case WM_NCMBUTTONDBLCLK:
|
||||
case WM_NCRBUTTONDBLCLK:
|
||||
case WM_NCXBUTTONDBLCLK: {
|
||||
if (!fixedSize()) {
|
||||
case WM_NCRBUTTONUP: {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
|
||||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
|
|
@ -409,11 +448,16 @@ bool WindowHelper::handleNativeEvent(
|
|||
case WM_WINDOWPOSCHANGING:
|
||||
case WM_WINDOWPOSCHANGED: {
|
||||
if (_shadow) {
|
||||
WINDOWPLACEMENT wp;
|
||||
wp.length = sizeof(WINDOWPLACEMENT);
|
||||
if (GetWindowPlacement(_handle, &wp)
|
||||
&& (wp.showCmd == SW_SHOWMAXIMIZED
|
||||
|| wp.showCmd == SW_SHOWMINIMIZED)) {
|
||||
auto placement = WINDOWPLACEMENT{
|
||||
.length = sizeof(WINDOWPLACEMENT),
|
||||
};
|
||||
if (!GetWindowPlacement(_handle, &placement)) {
|
||||
LOG(("System Error: GetWindowPlacement failed."));
|
||||
return false;
|
||||
}
|
||||
_title->refreshAdditionalPaddings(_handle, placement);
|
||||
if (placement.showCmd == SW_SHOWMAXIMIZED
|
||||
|| placement.showCmd == SW_SHOWMINIMIZED) {
|
||||
_shadow->update(WindowShadow::Change::Hidden);
|
||||
} else {
|
||||
_shadow->update(
|
||||
|
|
@ -439,6 +483,7 @@ bool WindowHelper::handleNativeEvent(
|
|||
}
|
||||
updateMargins();
|
||||
if (_shadow) {
|
||||
_title->refreshAdditionalPaddings(_handle);
|
||||
const auto changes = (wParam == SIZE_MINIMIZED
|
||||
|| wParam == SIZE_MAXIMIZED)
|
||||
? WindowShadow::Change::Hidden
|
||||
|
|
@ -462,6 +507,7 @@ bool WindowHelper::handleNativeEvent(
|
|||
|
||||
case WM_MOVE: {
|
||||
if (_shadow) {
|
||||
_title->refreshAdditionalPaddings(_handle);
|
||||
_shadow->update(WindowShadow::Change::Moved);
|
||||
}
|
||||
} return false;
|
||||
|
|
@ -477,33 +523,35 @@ bool WindowHelper::handleNativeEvent(
|
|||
const auto mapped = QPoint(
|
||||
p.x - r.left + _marginsDelta.left(),
|
||||
p.y - r.top + _marginsDelta.top());
|
||||
if (!window()->rect().contains(mapped)) {
|
||||
*result = HTTRANSPARENT;
|
||||
} else if (_title->isHidden()
|
||||
|| !_title->geometry().contains(mapped)) {
|
||||
*result = HTCLIENT;
|
||||
} else switch (_title->hitTest(_title->pos() + mapped)) {
|
||||
case HitTestResult::Client:
|
||||
case HitTestResult::SysButton: *result = HTCLIENT; break;
|
||||
case HitTestResult::Caption: *result = HTCAPTION; break;
|
||||
case HitTestResult::Top: *result = HTTOP; break;
|
||||
case HitTestResult::TopRight: *result = HTTOPRIGHT; break;
|
||||
case HitTestResult::Right: *result = HTRIGHT; break;
|
||||
case HitTestResult::BottomRight: *result = HTBOTTOMRIGHT; break;
|
||||
case HitTestResult::Bottom: *result = HTBOTTOM; break;
|
||||
case HitTestResult::BottomLeft: *result = HTBOTTOMLEFT; break;
|
||||
case HitTestResult::Left: *result = HTLEFT; break;
|
||||
case HitTestResult::TopLeft: *result = HTTOPLEFT; break;
|
||||
case HitTestResult::None:
|
||||
default: *result = HTTRANSPARENT; break;
|
||||
};
|
||||
} return true;
|
||||
*result = [&] {
|
||||
if (!window()->rect().contains(mapped)) {
|
||||
return HTTRANSPARENT;
|
||||
}
|
||||
auto request = HitTestRequest{
|
||||
.point = mapped,
|
||||
};
|
||||
_hitTestRequests.fire(&request);
|
||||
switch (const auto result = request.result) {
|
||||
case HitTestResult::Client: return HTCLIENT;
|
||||
case HitTestResult::Caption: return HTCAPTION;
|
||||
case HitTestResult::Top: return HTTOP;
|
||||
case HitTestResult::TopRight: return HTTOPRIGHT;
|
||||
case HitTestResult::Right: return HTRIGHT;
|
||||
case HitTestResult::BottomRight: return HTBOTTOMRIGHT;
|
||||
case HitTestResult::Bottom: return HTBOTTOM;
|
||||
case HitTestResult::BottomLeft: return HTBOTTOMLEFT;
|
||||
case HitTestResult::Left: return HTLEFT;
|
||||
case HitTestResult::TopLeft: return HTTOPLEFT;
|
||||
|
||||
case WM_NCRBUTTONUP: {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
|
||||
case HitTestResult::Minimize:
|
||||
case HitTestResult::MaximizeRestore:
|
||||
case HitTestResult::Close: return systemButtonHitTest(result);
|
||||
|
||||
case HitTestResult::None:
|
||||
default: return HTTRANSPARENT;
|
||||
};
|
||||
}();
|
||||
_systemButtonOver.fire(systemButtonHitTest(*result));
|
||||
} return true;
|
||||
|
||||
case WM_SYSCOMMAND: {
|
||||
|
|
@ -553,6 +601,87 @@ bool WindowHelper::fixedSize() const {
|
|||
return window()->minimumSize() == window()->maximumSize();
|
||||
}
|
||||
|
||||
bool WindowHelper::handleSystemButtonEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
const auto testResult = LOWORD(wParam);
|
||||
const auto sysButtons = { HTMINBUTTON, HTMAXBUTTON, HTCLOSE };
|
||||
const auto overSysButton = ranges::contains(sysButtons, testResult);
|
||||
switch (msg) {
|
||||
case WM_NCLBUTTONDBLCLK:
|
||||
case WM_NCMBUTTONDBLCLK:
|
||||
case WM_NCRBUTTONDBLCLK:
|
||||
case WM_NCXBUTTONDBLCLK: {
|
||||
if (!overSysButton || fixedSize()) {
|
||||
return false;
|
||||
}
|
||||
// Ignore double clicks on system buttons.
|
||||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
case WM_NCLBUTTONDOWN:
|
||||
case WM_NCLBUTTONUP:
|
||||
_systemButtonDown.fire((msg == WM_NCLBUTTONDOWN)
|
||||
? systemButtonHitTest(testResult)
|
||||
: HitTestResult::None);
|
||||
if (overSysButton) {
|
||||
if (result) *result = 0;
|
||||
}
|
||||
return overSysButton;
|
||||
case WM_NCMBUTTONDOWN:
|
||||
case WM_NCMBUTTONUP:
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
case WM_NCXBUTTONDOWN:
|
||||
case WM_NCXBUTTONUP:
|
||||
if (!overSysButton) {
|
||||
return false;
|
||||
}
|
||||
if (result) *result = 0;
|
||||
return true;
|
||||
case WM_NCMOUSEHOVER:
|
||||
case WM_NCMOUSEMOVE:
|
||||
_systemButtonOver.fire(systemButtonHitTest(testResult));
|
||||
if (overSysButton) {
|
||||
if (result) *result = 0;
|
||||
}
|
||||
return overSysButton;
|
||||
case WM_NCMOUSELEAVE:
|
||||
_systemButtonOver.fire(HitTestResult::None);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int WindowHelper::systemButtonHitTest(HitTestResult result) const {
|
||||
if (!SemiNativeSystemButtonProcessing()) {
|
||||
return HTCLIENT;
|
||||
}
|
||||
switch (result) {
|
||||
case HitTestResult::Minimize: return HTMINBUTTON;
|
||||
case HitTestResult::MaximizeRestore: return HTMAXBUTTON;
|
||||
case HitTestResult::Close: return HTCLOSE;
|
||||
}
|
||||
return HTTRANSPARENT;
|
||||
}
|
||||
|
||||
HitTestResult WindowHelper::systemButtonHitTest(int result) const {
|
||||
if (!SemiNativeSystemButtonProcessing()) {
|
||||
return HitTestResult::None;
|
||||
}
|
||||
switch (result) {
|
||||
case HTMINBUTTON: return HitTestResult::Minimize;
|
||||
case HTMAXBUTTON: return HitTestResult::MaximizeRestore;
|
||||
case HTCLOSE: return HitTestResult::Close;
|
||||
}
|
||||
return HitTestResult::None;
|
||||
}
|
||||
|
||||
int WindowHelper::titleHeight() const {
|
||||
return _title->isHidden() ? 0 : _title->height();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,19 @@ namespace Ui {
|
|||
namespace Platform {
|
||||
|
||||
class TitleWidget;
|
||||
struct HitTestRequest;
|
||||
enum class HitTestResult;
|
||||
|
||||
class WindowHelper final : public BasicWindowHelper {
|
||||
public:
|
||||
explicit WindowHelper(not_null<RpWidget*> window);
|
||||
~WindowHelper();
|
||||
|
||||
void initInWindow(not_null<RpWindow*> window) override;
|
||||
not_null<RpWidget*> body() override;
|
||||
QMargins frameMargins() override;
|
||||
int additionalContentPadding() const override;
|
||||
rpl::producer<int> additionalContentPaddingValue() const override;
|
||||
void setTitle(const QString &title) override;
|
||||
void setTitleStyle(const style::WindowTitle &st) override;
|
||||
void setNativeFrame(bool enabled) override;
|
||||
|
|
@ -32,6 +37,13 @@ public:
|
|||
void showFullScreen() override;
|
||||
void showNormal() override;
|
||||
|
||||
[[nodiscard]] auto hitTestRequests() const
|
||||
-> rpl::producer<not_null<HitTestRequest*>> override;
|
||||
[[nodiscard]] auto systemButtonOver() const
|
||||
-> rpl::producer<HitTestResult> override;
|
||||
[[nodiscard]] auto systemButtonDown() const
|
||||
-> rpl::producer<HitTestResult> override;
|
||||
|
||||
private:
|
||||
class NativeFilter;
|
||||
friend class NativeFilter;
|
||||
|
|
@ -43,13 +55,21 @@ private:
|
|||
void updateSystemMenu();
|
||||
void updateSystemMenu(Qt::WindowState state);
|
||||
void initialShadowUpdate();
|
||||
void updateCornersRounding();
|
||||
void fixMaximizedWindow();
|
||||
[[nodiscard]] bool handleNativeEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result);
|
||||
[[nodiscard]] bool handleSystemButtonEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result);
|
||||
[[nodiscard]] bool fixedSize() const;
|
||||
[[nodiscard]] int systemButtonHitTest(HitTestResult result) const;
|
||||
[[nodiscard]] HitTestResult systemButtonHitTest(int result) const;
|
||||
|
||||
[[nodiscard]] int titleHeight() const;
|
||||
static not_null<NativeFilter*> GetNativeFilter();
|
||||
|
|
@ -57,10 +77,13 @@ private:
|
|||
const HWND _handle = nullptr;
|
||||
const not_null<TitleWidget*> _title;
|
||||
const not_null<RpWidget*> _body;
|
||||
rpl::event_stream<not_null<HitTestRequest*>> _hitTestRequests;
|
||||
rpl::event_stream<HitTestResult> _systemButtonOver;
|
||||
rpl::event_stream<HitTestResult> _systemButtonDown;
|
||||
std::optional<WindowShadow> _shadow;
|
||||
bool _updatingMargins = false;
|
||||
QMargins _marginsDelta;
|
||||
HMENU _menu = nullptr;
|
||||
bool _updatingMargins = false;
|
||||
bool _isFullScreen = false;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,12 +25,22 @@ void DrawRoundedRect(
|
|||
const auto h = rect.height();
|
||||
const auto cornerWidth = corners[0].width() / pixelRatio;
|
||||
const auto cornerHeight = corners[0].height() / pixelRatio;
|
||||
if (w < 2 * cornerWidth || h < 2 * cornerHeight) return;
|
||||
|
||||
const auto hasLeft = (parts & RectPart::Left) != 0;
|
||||
const auto hasRight = (parts & RectPart::Right) != 0;
|
||||
const auto hasTop = (parts & RectPart::Top) != 0;
|
||||
const auto hasBottom = (parts & RectPart::Bottom) != 0;
|
||||
if (w < ((hasLeft ? cornerWidth : 0) + (hasRight ? cornerWidth : 0))) {
|
||||
return;
|
||||
}
|
||||
if (h < ((hasTop ? cornerHeight : 0) + (hasBottom ? cornerHeight : 0))) {
|
||||
return;
|
||||
}
|
||||
if (w > 2 * cornerWidth) {
|
||||
if (parts & RectPart::Top) {
|
||||
if (hasTop) {
|
||||
p.fillRect(x + cornerWidth, y, w - 2 * cornerWidth, cornerHeight, brush);
|
||||
}
|
||||
if (parts & RectPart::Bottom) {
|
||||
if (hasBottom) {
|
||||
p.fillRect(x + cornerWidth, y + h - cornerHeight, w - 2 * cornerWidth, cornerHeight, brush);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,13 +48,13 @@ void DrawRoundedRect(
|
|||
if ((parts & RectPart::NoTopBottom) == RectPart::NoTopBottom) {
|
||||
p.fillRect(x, y + cornerHeight, w, h - 2 * cornerHeight, brush);
|
||||
} else {
|
||||
if (parts & RectPart::Left) {
|
||||
if (hasLeft) {
|
||||
p.fillRect(x, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush);
|
||||
}
|
||||
if ((parts & RectPart::Center) && w > 2 * cornerWidth) {
|
||||
p.fillRect(x + cornerWidth, y + cornerHeight, w - 2 * cornerWidth, h - 2 * cornerHeight, brush);
|
||||
}
|
||||
if (parts & RectPart::Right) {
|
||||
if (hasRight) {
|
||||
p.fillRect(x + w - cornerWidth, y + cornerHeight, cornerWidth, h - 2 * cornerHeight, brush);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
689
ui/text/text.cpp
|
|
@ -22,25 +22,6 @@ static const auto kQEllipsis = QStringLiteral("...");
|
|||
} // namespace Ui
|
||||
|
||||
static const QChar TextCommand(0x0010);
|
||||
enum TextCommands {
|
||||
TextCommandBold = 0x01,
|
||||
TextCommandNoBold = 0x02,
|
||||
TextCommandItalic = 0x03,
|
||||
TextCommandNoItalic = 0x04,
|
||||
TextCommandUnderline = 0x05,
|
||||
TextCommandNoUnderline = 0x06,
|
||||
TextCommandStrikeOut = 0x07,
|
||||
TextCommandNoStrikeOut = 0x08,
|
||||
TextCommandSemibold = 0x09,
|
||||
TextCommandNoSemibold = 0x0A,
|
||||
TextCommandLinkIndex = 0x0B, // 0 - NoLink
|
||||
TextCommandLinkText = 0x0C,
|
||||
TextCommandSkipBlock = 0x0D,
|
||||
TextCommandSpoiler = 0x0E,
|
||||
TextCommandNoSpoiler = 0x0F,
|
||||
|
||||
TextCommandLangTag = 0x20,
|
||||
};
|
||||
|
||||
struct TextParseOptions {
|
||||
int32 flags;
|
||||
|
|
@ -122,8 +103,7 @@ public:
|
|||
const style::TextStyle &st,
|
||||
const QString &text,
|
||||
const TextParseOptions &options = _defaultOptions,
|
||||
int32 minResizeWidth = QFIXED_MAX,
|
||||
bool richText = false);
|
||||
int32 minResizeWidth = QFIXED_MAX);
|
||||
String(const String &other) = default;
|
||||
String(String &&other) = default;
|
||||
String &operator=(const String &other) = default;
|
||||
|
|
@ -134,7 +114,6 @@ public:
|
|||
int countHeight(int width, bool breakEverywhere = false) const;
|
||||
void countLineWidths(int width, QVector<int> *lineWidths, bool breakEverywhere = false) const;
|
||||
void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions);
|
||||
void setRichText(const style::TextStyle &st, const QString &text, TextParseOptions options = _defaultOptions);
|
||||
void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions, const std::any &context = {});
|
||||
|
||||
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
|
||||
|
|
@ -253,10 +232,10 @@ private:
|
|||
[[nodiscard]] bool IsAlmostLinkEnd(QChar ch);
|
||||
[[nodiscard]] bool IsLinkEnd(QChar ch);
|
||||
[[nodiscard]] bool IsNewline(QChar ch);
|
||||
[[nodiscard]] bool IsSpace(QChar ch, bool rich = false);
|
||||
[[nodiscard]] bool IsSpace(QChar ch);
|
||||
[[nodiscard]] bool IsDiac(QChar ch);
|
||||
[[nodiscard]] bool IsReplacedBySpace(QChar ch);
|
||||
[[nodiscard]] bool IsTrimmed(QChar ch, bool rich = false);
|
||||
[[nodiscard]] bool IsTrimmed(QChar ch);
|
||||
|
||||
} // namespace Text
|
||||
} // namespace Ui
|
||||
|
|
@ -276,18 +255,3 @@ inline TextSelection shiftSelection(TextSelection selection, const Ui::Text::Str
|
|||
inline TextSelection unshiftSelection(TextSelection selection, const Ui::Text::String &byText) {
|
||||
return unshiftSelection(selection, byText.length());
|
||||
}
|
||||
|
||||
// textcmd
|
||||
QString textcmdSkipBlock(ushort w, ushort h);
|
||||
QString textcmdStartLink(ushort lnkIndex);
|
||||
QString textcmdStartLink(const QString &url);
|
||||
QString textcmdStopLink();
|
||||
QString textcmdLink(ushort lnkIndex, const QString &text);
|
||||
QString textcmdLink(const QString &url, const QString &text);
|
||||
QString textcmdStartSemibold();
|
||||
QString textcmdStopSemibold();
|
||||
|
||||
QString textcmdStartSpoiler();
|
||||
QString textcmdStopSpoiler();
|
||||
|
||||
const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true);
|
||||
|
|
|
|||
|
|
@ -345,11 +345,11 @@ AbstractBlock::AbstractBlock(
|
|||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex)
|
||||
: _from(from)
|
||||
, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12))
|
||||
, _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14))
|
||||
, _spoilerIndex(spoilerIndex) {
|
||||
}
|
||||
|
||||
|
|
@ -374,11 +374,11 @@ QFixed AbstractBlock::f_rpadding() const {
|
|||
}
|
||||
|
||||
uint16 AbstractBlock::lnkIndex() const {
|
||||
return (_flags >> 12) & 0xFFFF;
|
||||
return (_flags >> 14) & 0xFFFF;
|
||||
}
|
||||
|
||||
void AbstractBlock::setLnkIndex(uint16 lnkIndex) {
|
||||
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
|
||||
_flags = (_flags & ~(0xFFFF << 14)) | (lnkIndex << 14);
|
||||
}
|
||||
|
||||
uint16 AbstractBlock::spoilerIndex() const {
|
||||
|
|
@ -390,11 +390,11 @@ void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) {
|
|||
}
|
||||
|
||||
TextBlockType AbstractBlock::type() const {
|
||||
return TextBlockType((_flags >> 8) & 0x0F);
|
||||
return TextBlockType((_flags >> 10) & 0x0F);
|
||||
}
|
||||
|
||||
int32 AbstractBlock::flags() const {
|
||||
return (_flags & 0xFFF);
|
||||
return (_flags & 0b1111111111);
|
||||
}
|
||||
|
||||
QFixed AbstractBlock::f_rbearing() const {
|
||||
|
|
@ -409,11 +409,11 @@ TextBlock::TextBlock(
|
|||
QFixed minResizeWidth,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex)
|
||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
|
||||
_flags |= ((TextBlockTText & 0x0F) << 8);
|
||||
_flags |= ((TextBlockTText & 0x0F) << 10);
|
||||
if (length) {
|
||||
style::font blockFont = font;
|
||||
if (!flags && lnkIndex) {
|
||||
|
|
@ -446,18 +446,22 @@ TextBlock::TextBlock(
|
|||
}
|
||||
}
|
||||
|
||||
QFixed TextBlock::real_f_rbearing() const {
|
||||
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
|
||||
}
|
||||
|
||||
EmojiBlock::EmojiBlock(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex,
|
||||
EmojiPtr emoji)
|
||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex)
|
||||
, _emoji(emoji) {
|
||||
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
|
||||
_flags |= ((TextBlockTEmoji & 0x0F) << 10);
|
||||
_width = int(st::emojiSize + 2 * st::emojiPadding);
|
||||
_rpadding = 0;
|
||||
for (auto i = length; i != 0;) {
|
||||
|
|
@ -470,5 +474,279 @@ EmojiBlock::EmojiBlock(
|
|||
}
|
||||
}
|
||||
|
||||
NewlineBlock::NewlineBlock(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex)
|
||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
|
||||
_flags |= ((TextBlockTNewline & 0x0F) << 10);
|
||||
}
|
||||
|
||||
Qt::LayoutDirection NewlineBlock::nextDirection() const {
|
||||
return _nextDir;
|
||||
}
|
||||
|
||||
SkipBlock::SkipBlock(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
int32 w,
|
||||
int32 h,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex)
|
||||
: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex)
|
||||
, _height(h) {
|
||||
_flags |= ((TextBlockTSkip & 0x0F) << 10);
|
||||
_width = w;
|
||||
}
|
||||
|
||||
int SkipBlock::height() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
|
||||
TextWord::TextWord(
|
||||
uint16 from,
|
||||
QFixed width,
|
||||
QFixed rbearing,
|
||||
QFixed rpadding)
|
||||
: _from(from)
|
||||
, _rbearing((rbearing.value() > 0x7FFF)
|
||||
? 0x7FFF
|
||||
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
|
||||
, _width(width)
|
||||
, _rpadding(rpadding) {
|
||||
}
|
||||
|
||||
uint16 TextWord::from() const {
|
||||
return _from;
|
||||
}
|
||||
|
||||
QFixed TextWord::f_rbearing() const {
|
||||
return QFixed::fromFixed(_rbearing);
|
||||
}
|
||||
|
||||
QFixed TextWord::f_width() const {
|
||||
return _width;
|
||||
}
|
||||
|
||||
QFixed TextWord::f_rpadding() const {
|
||||
return _rpadding;
|
||||
}
|
||||
|
||||
void TextWord::add_rpadding(QFixed padding) {
|
||||
_rpadding += padding;
|
||||
}
|
||||
|
||||
Block::Block() {
|
||||
Unexpected("Should not be called.");
|
||||
}
|
||||
|
||||
Block::Block(const Block &other) {
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(other.unsafe<TextBlock>());
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in Block(const Block&).");
|
||||
}
|
||||
}
|
||||
|
||||
Block::Block(Block &&other) {
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in Block(Block&&).");
|
||||
}
|
||||
}
|
||||
|
||||
Block &Block::operator=(const Block &other) {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
destroy();
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(other.unsafe<TextBlock>());
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in operator=(const Block&).");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Block &Block::operator=(Block &&other) {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
destroy();
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in operator=(Block&&).");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Block::~Block() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
Block Block::Newline(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex) {
|
||||
return New<NewlineBlock>(
|
||||
font,
|
||||
str,
|
||||
from,
|
||||
length,
|
||||
flags,
|
||||
lnkIndex,
|
||||
spoilerIndex);
|
||||
}
|
||||
|
||||
Block Block::Text(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
QFixed minResizeWidth,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex) {
|
||||
return New<TextBlock>(
|
||||
font,
|
||||
str,
|
||||
minResizeWidth,
|
||||
from,
|
||||
length,
|
||||
flags,
|
||||
lnkIndex,
|
||||
spoilerIndex);
|
||||
}
|
||||
|
||||
Block Block::Emoji(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex,
|
||||
EmojiPtr emoji) {
|
||||
return New<EmojiBlock>(
|
||||
font,
|
||||
str,
|
||||
from,
|
||||
length,
|
||||
flags,
|
||||
lnkIndex,
|
||||
spoilerIndex,
|
||||
emoji);
|
||||
}
|
||||
|
||||
Block Block::Skip(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
int32 w,
|
||||
int32 h,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex) {
|
||||
return New<SkipBlock>(font, str, from, w, h, lnkIndex, spoilerIndex);
|
||||
}
|
||||
|
||||
AbstractBlock *Block::get() {
|
||||
return &unsafe<AbstractBlock>();
|
||||
}
|
||||
|
||||
const AbstractBlock *Block::get() const {
|
||||
return &unsafe<AbstractBlock>();
|
||||
}
|
||||
|
||||
AbstractBlock *Block::operator->() {
|
||||
return get();
|
||||
}
|
||||
|
||||
const AbstractBlock *Block::operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
AbstractBlock &Block::operator*() {
|
||||
return *get();
|
||||
}
|
||||
|
||||
const AbstractBlock &Block::operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
void Block::destroy() {
|
||||
switch (get()->type()) {
|
||||
case TextBlockTNewline:
|
||||
unsafe<NewlineBlock>().~NewlineBlock();
|
||||
break;
|
||||
case TextBlockTText:
|
||||
unsafe<TextBlock>().~TextBlock();
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
unsafe<EmojiBlock>().~EmojiBlock();
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
unsafe<SkipBlock>().~SkipBlock();
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in Block(Block&&).");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Text
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ enum TextBlockFlags {
|
|||
TextBlockFSemibold = 0x20,
|
||||
TextBlockFCode = 0x40,
|
||||
TextBlockFPre = 0x80,
|
||||
TextBlockFPlainLink = 0x100,
|
||||
};
|
||||
|
||||
class AbstractBlock {
|
||||
|
|
@ -58,13 +59,13 @@ protected:
|
|||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex);
|
||||
|
||||
uint16 _from = 0;
|
||||
|
||||
uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
|
||||
uint32 _flags = 0; // 2 bits empty, 16 bits lnkIndex, 4 bits type, 10 bits flags
|
||||
|
||||
uint16 _spoilerIndex = 0;
|
||||
|
||||
|
|
@ -86,16 +87,11 @@ public:
|
|||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex)
|
||||
: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) {
|
||||
_flags |= ((TextBlockTNewline & 0x0F) << 8);
|
||||
}
|
||||
uint16 spoilerIndex);
|
||||
|
||||
Qt::LayoutDirection nextDirection() const {
|
||||
return _nextDir;
|
||||
}
|
||||
Qt::LayoutDirection nextDirection() const;
|
||||
|
||||
private:
|
||||
Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto;
|
||||
|
|
@ -109,29 +105,12 @@ private:
|
|||
class TextWord final {
|
||||
public:
|
||||
TextWord() = default;
|
||||
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0)
|
||||
: _from(from)
|
||||
, _rbearing((rbearing.value() > 0x7FFF)
|
||||
? 0x7FFF
|
||||
: (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value()))
|
||||
, _width(width)
|
||||
, _rpadding(rpadding) {
|
||||
}
|
||||
uint16 from() const {
|
||||
return _from;
|
||||
}
|
||||
QFixed f_rbearing() const {
|
||||
return QFixed::fromFixed(_rbearing);
|
||||
}
|
||||
QFixed f_width() const {
|
||||
return _width;
|
||||
}
|
||||
QFixed f_rpadding() const {
|
||||
return _rpadding;
|
||||
}
|
||||
void add_rpadding(QFixed padding) {
|
||||
_rpadding += padding;
|
||||
}
|
||||
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0);
|
||||
uint16 from() const;
|
||||
QFixed f_rbearing() const;
|
||||
QFixed f_width() const;
|
||||
QFixed f_rpadding() const;
|
||||
void add_rpadding(QFixed padding);
|
||||
|
||||
private:
|
||||
uint16 _from = 0;
|
||||
|
|
@ -148,14 +127,12 @@ public:
|
|||
QFixed minResizeWidth,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex);
|
||||
|
||||
private:
|
||||
QFixed real_f_rbearing() const {
|
||||
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
|
||||
}
|
||||
QFixed real_f_rbearing() const;
|
||||
|
||||
QVector<TextWord> _words;
|
||||
|
||||
|
|
@ -174,7 +151,7 @@ public:
|
|||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex,
|
||||
EmojiPtr emoji);
|
||||
|
|
@ -197,16 +174,9 @@ public:
|
|||
int32 w,
|
||||
int32 h,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex)
|
||||
: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex)
|
||||
, _height(h) {
|
||||
_flags |= ((TextBlockTSkip & 0x0F) << 8);
|
||||
_width = w;
|
||||
}
|
||||
uint16 spoilerIndex);
|
||||
|
||||
int height() const {
|
||||
return _height;
|
||||
}
|
||||
int height() const;
|
||||
|
||||
private:
|
||||
int _height = 0;
|
||||
|
|
@ -219,105 +189,21 @@ private:
|
|||
|
||||
class Block final {
|
||||
public:
|
||||
Block() {
|
||||
Unexpected("Should not be called.");
|
||||
}
|
||||
Block(const Block &other) {
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(other.unsafe<TextBlock>());
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in Block(const Block&).");
|
||||
}
|
||||
}
|
||||
Block(Block &&other) {
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in Block(Block&&).");
|
||||
}
|
||||
}
|
||||
Block &operator=(const Block &other) {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
destroy();
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(other.unsafe<NewlineBlock>());
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(other.unsafe<TextBlock>());
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(other.unsafe<EmojiBlock>());
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(other.unsafe<SkipBlock>());
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in operator=(const Block&).");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
Block &operator=(Block &&other) {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
destroy();
|
||||
switch (other->type()) {
|
||||
case TextBlockTNewline:
|
||||
emplace<NewlineBlock>(std::move(other.unsafe<NewlineBlock>()));
|
||||
break;
|
||||
case TextBlockTText:
|
||||
emplace<TextBlock>(std::move(other.unsafe<TextBlock>()));
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
emplace<EmojiBlock>(std::move(other.unsafe<EmojiBlock>()));
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
emplace<SkipBlock>(std::move(other.unsafe<SkipBlock>()));
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in operator=(Block&&).");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
~Block() {
|
||||
destroy();
|
||||
}
|
||||
Block();
|
||||
Block(const Block &other);
|
||||
Block(Block &&other);
|
||||
Block &operator=(const Block &other);
|
||||
Block &operator=(Block &&other);
|
||||
~Block();
|
||||
|
||||
[[nodiscard]] static Block Newline(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex) {
|
||||
return New<NewlineBlock>(font, str, from, length, flags, lnkIndex, spoilerIndex);
|
||||
}
|
||||
uint16 spoilerIndex);
|
||||
|
||||
[[nodiscard]] static Block Text(
|
||||
const style::font &font,
|
||||
|
|
@ -325,39 +211,19 @@ public:
|
|||
QFixed minResizeWidth,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex) {
|
||||
return New<TextBlock>(
|
||||
font,
|
||||
str,
|
||||
minResizeWidth,
|
||||
from,
|
||||
length,
|
||||
flags,
|
||||
lnkIndex,
|
||||
spoilerIndex);
|
||||
}
|
||||
uint16 spoilerIndex);
|
||||
|
||||
[[nodiscard]] static Block Emoji(
|
||||
const style::font &font,
|
||||
const QString &str,
|
||||
uint16 from,
|
||||
uint16 length,
|
||||
uchar flags,
|
||||
uint16 flags,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex,
|
||||
EmojiPtr emoji) {
|
||||
return New<EmojiBlock>(
|
||||
font,
|
||||
str,
|
||||
from,
|
||||
length,
|
||||
flags,
|
||||
lnkIndex,
|
||||
spoilerIndex,
|
||||
emoji);
|
||||
}
|
||||
EmojiPtr emoji);
|
||||
|
||||
[[nodiscard]] static Block Skip(
|
||||
const style::font &font,
|
||||
|
|
@ -366,9 +232,7 @@ public:
|
|||
int32 w,
|
||||
int32 h,
|
||||
uint16 lnkIndex,
|
||||
uint16 spoilerIndex) {
|
||||
return New<SkipBlock>(font, str, from, w, h, lnkIndex, spoilerIndex);
|
||||
}
|
||||
uint16 spoilerIndex);
|
||||
|
||||
template <typename FinalBlock>
|
||||
[[nodiscard]] FinalBlock &unsafe() {
|
||||
|
|
@ -380,29 +244,14 @@ public:
|
|||
return *reinterpret_cast<const FinalBlock*>(&_data);
|
||||
}
|
||||
|
||||
[[nodiscard]] AbstractBlock *get() {
|
||||
return &unsafe<AbstractBlock>();
|
||||
}
|
||||
[[nodiscard]] AbstractBlock *get();
|
||||
[[nodiscard]] const AbstractBlock *get() const;
|
||||
|
||||
[[nodiscard]] const AbstractBlock *get() const {
|
||||
return &unsafe<AbstractBlock>();
|
||||
}
|
||||
[[nodiscard]] AbstractBlock *operator->();
|
||||
[[nodiscard]] const AbstractBlock *operator->() const;
|
||||
|
||||
[[nodiscard]] AbstractBlock *operator->() {
|
||||
return get();
|
||||
}
|
||||
|
||||
[[nodiscard]] const AbstractBlock *operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
[[nodiscard]] AbstractBlock &operator*() {
|
||||
return *get();
|
||||
}
|
||||
|
||||
[[nodiscard]] const AbstractBlock &operator*() const {
|
||||
return *get();
|
||||
}
|
||||
[[nodiscard]] AbstractBlock &operator*();
|
||||
[[nodiscard]] const AbstractBlock &operator*() const;
|
||||
|
||||
private:
|
||||
struct Tag {
|
||||
|
|
@ -423,24 +272,7 @@ private:
|
|||
new (&_data) FinalType(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
switch (get()->type()) {
|
||||
case TextBlockTNewline:
|
||||
unsafe<NewlineBlock>().~NewlineBlock();
|
||||
break;
|
||||
case TextBlockTText:
|
||||
unsafe<TextBlock>().~TextBlock();
|
||||
break;
|
||||
case TextBlockTEmoji:
|
||||
unsafe<EmojiBlock>().~EmojiBlock();
|
||||
break;
|
||||
case TextBlockTSkip:
|
||||
unsafe<SkipBlock>().~SkipBlock();
|
||||
break;
|
||||
default:
|
||||
Unexpected("Bad text block type in Block(Block&&).");
|
||||
}
|
||||
}
|
||||
void destroy();
|
||||
|
||||
static_assert(sizeof(NewlineBlock) <= sizeof(TextBlock));
|
||||
static_assert(alignof(NewlineBlock) <= alignof(void*));
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
#include "ui/text/text.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtCore/QStack>
|
||||
#include <QtCore/QMimeData>
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
namespace TextUtilities {
|
||||
namespace {
|
||||
|
||||
constexpr auto kTagSeparator = '$';
|
||||
constexpr auto kTagSeparator = '\\';
|
||||
|
||||
using namespace Ui::Text;
|
||||
|
||||
|
|
@ -1293,35 +1293,17 @@ bool IsValidTopDomain(const QString &protocol) {
|
|||
return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar)));
|
||||
}
|
||||
|
||||
QString Clean(const QString &text, bool keepSpoilers) {
|
||||
auto result = text;
|
||||
for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch) {
|
||||
if (keepSpoilers && (*ch == TextCommand)) {
|
||||
if ((*(ch + 1) == TextCommandSpoiler)
|
||||
|| (*(ch - 1) == TextCommandSpoiler)
|
||||
|| (*(ch + 1) == TextCommandNoSpoiler)
|
||||
|| (*(ch - 1) == TextCommandNoSpoiler)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (*ch == TextCommand) {
|
||||
result[int(ch - s)] = QChar::Space;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString EscapeForRichParsing(const QString &text) {
|
||||
QString result;
|
||||
result.reserve(text.size());
|
||||
auto s = text.constData(), ch = s;
|
||||
for (const QChar *e = s + text.size(); ch != e; ++ch) {
|
||||
if (*ch == TextCommand) {
|
||||
if (ch > s) result.append(s, ch - s);
|
||||
result.append(QChar::Space);
|
||||
s = ch + 1;
|
||||
continue;
|
||||
}
|
||||
// if (*ch == TextCommand) {
|
||||
// if (ch > s) result.append(s, ch - s);
|
||||
// result.append(QChar::Space);
|
||||
// s = ch + 1;
|
||||
// continue;
|
||||
// }
|
||||
if (ch->unicode() == '\\' || ch->unicode() == '[') {
|
||||
if (ch > s) result.append(s, ch - s);
|
||||
result.append('\\');
|
||||
|
|
@ -1349,7 +1331,7 @@ QString SingleLine(const QString &text) {
|
|||
}
|
||||
|
||||
for (auto ch = s; ch != e; ++ch) {
|
||||
if (IsNewline(*ch) || *ch == TextCommand) {
|
||||
if (IsNewline(*ch)/* || *ch == TextCommand*/) {
|
||||
result[int(ch - s)] = QChar::Space;
|
||||
}
|
||||
}
|
||||
|
|
@ -1545,56 +1527,14 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool textcmdStartsLink(const QChar *start, int32 len, int32 commandOffset) {
|
||||
if (commandOffset + 2 < len) {
|
||||
if (*(start + commandOffset + 1) == TextCommandLinkIndex) {
|
||||
return (*(start + commandOffset + 2) != 0);
|
||||
}
|
||||
return (*(start + commandOffset + 1) != TextCommandLinkText);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool checkTagStartInCommand(const QChar *start, int32 len, int32 tagStart, int32 &commandOffset, bool &commandIsLink, bool &inLink) {
|
||||
bool inCommand = false;
|
||||
const QChar *commandEnd = start + commandOffset;
|
||||
while (commandOffset < len && tagStart > commandOffset) { // skip commands, evaluating are we in link or not
|
||||
commandEnd = textSkipCommand(start + commandOffset, start + len);
|
||||
if (commandEnd > start + commandOffset) {
|
||||
if (tagStart < (commandEnd - start)) {
|
||||
inCommand = true;
|
||||
break;
|
||||
}
|
||||
for (commandOffset = commandEnd - start; commandOffset < len; ++commandOffset) {
|
||||
if (*(start + commandOffset) == TextCommand) {
|
||||
inLink = commandIsLink;
|
||||
commandIsLink = textcmdStartsLink(start, len, commandOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (commandOffset >= len) {
|
||||
inLink = commandIsLink;
|
||||
commandIsLink = false;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inCommand) {
|
||||
commandOffset = commandEnd - start;
|
||||
}
|
||||
return inCommand;
|
||||
}
|
||||
|
||||
TextWithEntities ParseEntities(const QString &text, int32 flags) {
|
||||
const auto rich = ((flags & TextParseRichText) != 0);
|
||||
auto result = TextWithEntities{ text, EntitiesInText() };
|
||||
ParseEntities(result, flags, rich);
|
||||
ParseEntities(result, flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Some code is duplicated in message_field.cpp!
|
||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
||||
void ParseEntities(TextWithEntities &result, int32 flags) {
|
||||
constexpr auto kNotFound = std::numeric_limits<int>::max();
|
||||
|
||||
auto newEntities = EntitiesInText();
|
||||
|
|
@ -1605,20 +1545,10 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
|||
int existingEntityIndex = 0, existingEntitiesCount = result.entities.size();
|
||||
int existingEntityEnd = 0;
|
||||
|
||||
int32 len = result.text.size(), commandOffset = rich ? 0 : len;
|
||||
bool inLink = false, commandIsLink = false;
|
||||
int32 len = result.text.size();
|
||||
const auto start = result.text.constData();
|
||||
const auto end = start + result.text.size();
|
||||
for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) {
|
||||
if (commandOffset <= offset) {
|
||||
for (commandOffset = offset; commandOffset < len; ++commandOffset) {
|
||||
if (*(start + commandOffset) == TextCommand) {
|
||||
inLink = commandIsLink;
|
||||
commandIsLink = textcmdStartsLink(start, len, commandOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto mDomain = qthelp::RegExpDomain().match(result.text, matchOffset);
|
||||
auto mExplicitDomain = qthelp::RegExpDomainExplicit().match(result.text, matchOffset);
|
||||
auto mHashtag = withHashtags ? RegExpHashtag().match(result.text, matchOffset) : QRegularExpressionMatch();
|
||||
|
|
@ -1706,17 +1636,6 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
|||
offset = matchOffset = mentionEnd;
|
||||
continue;
|
||||
}
|
||||
const auto inCommand = checkTagStartInCommand(
|
||||
start,
|
||||
len,
|
||||
mentionStart,
|
||||
commandOffset,
|
||||
commandIsLink,
|
||||
inLink);
|
||||
if (inCommand || inLink) {
|
||||
offset = matchOffset = commandOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
lnkType = EntityType::Mention;
|
||||
lnkStart = mentionStart;
|
||||
|
|
@ -1727,50 +1646,15 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) {
|
|||
offset = matchOffset = hashtagEnd;
|
||||
continue;
|
||||
}
|
||||
const auto inCommand = checkTagStartInCommand(
|
||||
start,
|
||||
len,
|
||||
hashtagStart,
|
||||
commandOffset,
|
||||
commandIsLink,
|
||||
inLink);
|
||||
if (inCommand || inLink) {
|
||||
offset = matchOffset = commandOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
lnkType = EntityType::Hashtag;
|
||||
lnkStart = hashtagStart;
|
||||
lnkLength = hashtagEnd - hashtagStart;
|
||||
} else if (botCommandStart < domainStart) {
|
||||
const auto inCommand = checkTagStartInCommand(
|
||||
start,
|
||||
len,
|
||||
botCommandStart,
|
||||
commandOffset,
|
||||
commandIsLink,
|
||||
inLink);
|
||||
if (inCommand || inLink) {
|
||||
offset = matchOffset = commandOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
lnkType = EntityType::BotCommand;
|
||||
lnkStart = botCommandStart;
|
||||
lnkLength = botCommandEnd - botCommandStart;
|
||||
} else {
|
||||
const auto inCommand = checkTagStartInCommand(
|
||||
start,
|
||||
len,
|
||||
domainStart,
|
||||
commandOffset,
|
||||
commandIsLink,
|
||||
inLink);
|
||||
if (inCommand || inLink) {
|
||||
offset = matchOffset = commandOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto protocol = mDomain.captured(1).toLower();
|
||||
auto topDomain = mDomain.captured(3).toLower();
|
||||
auto isProtocolValid = protocol.isEmpty() || IsValidProtocol(protocol);
|
||||
|
|
@ -2332,36 +2216,6 @@ void SetClipboardText(
|
|||
}
|
||||
}
|
||||
|
||||
QString TextWithSpoilerCommands(const TextWithEntities &textWithEntities) {
|
||||
auto text = textWithEntities.text;
|
||||
auto offset = 0;
|
||||
const auto start = textcmdStartSpoiler();
|
||||
const auto stop = textcmdStopSpoiler();
|
||||
for (const auto &e : textWithEntities.entities) {
|
||||
if (e.type() == EntityType::Spoiler) {
|
||||
text.insert(e.offset() + offset, start);
|
||||
offset += start.size();
|
||||
text.insert(e.offset() + e.length() + offset, stop);
|
||||
offset += stop.size();
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
QString CutTextWithCommands(
|
||||
QString text,
|
||||
int length,
|
||||
const QString &start,
|
||||
const QString &stop) {
|
||||
text = text.mid(0, length);
|
||||
const auto lastStart = text.lastIndexOf(start);
|
||||
const auto lastStop = text.lastIndexOf(stop);
|
||||
const auto additional = ((lastStart == -1) || (lastStart < lastStop))
|
||||
? QString()
|
||||
: stop;
|
||||
return text + additional + Ui::kQEllipsis;
|
||||
}
|
||||
|
||||
} // namespace TextUtilities
|
||||
|
||||
EntityInText::EntityInText(
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ enum class EntityType : uchar {
|
|||
MentionName,
|
||||
BotCommand,
|
||||
MediaTimestamp,
|
||||
PlainLink, // Senders in chat list, attachements in chat list, etc.
|
||||
|
||||
Bold,
|
||||
Semibold,
|
||||
|
|
@ -239,11 +240,11 @@ struct TextForMimeData {
|
|||
enum {
|
||||
TextParseMultiline = 0x001,
|
||||
TextParseLinks = 0x002,
|
||||
TextParseRichText = 0x004,
|
||||
TextParseMentions = 0x008,
|
||||
TextParseHashtags = 0x010,
|
||||
TextParseBotCommands = 0x020,
|
||||
TextParseMarkdown = 0x040,
|
||||
TextParseMentions = 0x004,
|
||||
TextParseHashtags = 0x008,
|
||||
TextParseBotCommands = 0x010,
|
||||
TextParseMarkdown = 0x020,
|
||||
TextParsePlainLinks = 0x040,
|
||||
};
|
||||
|
||||
struct TextWithTags {
|
||||
|
|
@ -298,7 +299,6 @@ QString MarkdownSpoilerGoodBefore();
|
|||
QString MarkdownSpoilerBadAfter();
|
||||
|
||||
// Text preprocess.
|
||||
QString Clean(const QString &text, bool keepSpoilers = false);
|
||||
QString EscapeForRichParsing(const QString &text);
|
||||
QString SingleLine(const QString &text);
|
||||
TextWithEntities SingleLine(const TextWithEntities &text);
|
||||
|
|
@ -337,7 +337,7 @@ inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
|
|||
// New entities are added to the ones that are already in result.
|
||||
// Changes text if (flags & TextParseMarkdown).
|
||||
TextWithEntities ParseEntities(const QString &text, int32 flags);
|
||||
void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
|
||||
void ParseEntities(TextWithEntities &result, int32 flags);
|
||||
|
||||
void PrepareForSending(TextWithEntities &result, int32 flags);
|
||||
void Trim(TextWithEntities &result);
|
||||
|
|
@ -384,12 +384,4 @@ void SetClipboardText(
|
|||
const TextForMimeData &text,
|
||||
QClipboard::Mode mode = QClipboard::Clipboard);
|
||||
|
||||
[[nodiscard]] QString TextWithSpoilerCommands(
|
||||
const TextWithEntities &textWithEntities);
|
||||
[[nodiscard]] QString CutTextWithCommands(
|
||||
QString text,
|
||||
int length,
|
||||
const QString &start,
|
||||
const QString &stop);
|
||||
|
||||
} // namespace TextUtilities
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include "ui/text/text_utilities.h"
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
|
|
@ -42,6 +42,25 @@ TextWithEntities Link(const QString &text, const QString &url) {
|
|||
return WithSingleEntity(text, EntityType::CustomUrl, url);
|
||||
}
|
||||
|
||||
TextWithEntities Link(const QString &text, int index) {
|
||||
Expects(index > 0 && index < 10);
|
||||
return Link(text, QString("internal:index") + QChar('0' + index));
|
||||
}
|
||||
|
||||
TextWithEntities PlainLink(const QString &text) {
|
||||
return WithSingleEntity(text, EntityType::PlainLink);
|
||||
}
|
||||
|
||||
TextWithEntities Wrapped(
|
||||
TextWithEntities text,
|
||||
EntityType type,
|
||||
const QString &data) {
|
||||
text.entities.insert(
|
||||
text.entities.begin(),
|
||||
{ type, 0, int(text.text.size()), data });
|
||||
return text;
|
||||
}
|
||||
|
||||
TextWithEntities RichLangValue(const QString &text) {
|
||||
static const auto kStart = QRegularExpression("(\\*\\*|__)");
|
||||
|
||||
|
|
@ -74,5 +93,48 @@ TextWithEntities RichLangValue(const QString &text) {
|
|||
return result;
|
||||
}
|
||||
|
||||
TextWithEntities Mid(const TextWithEntities &text, int position, int n) {
|
||||
if (n == -1) {
|
||||
n = int(text.text.size()) - position;
|
||||
}
|
||||
const auto midEnd = (position + n);
|
||||
auto entities = ranges::views::all(
|
||||
text.entities
|
||||
) | ranges::views::filter([&](const EntityInText &entity) {
|
||||
// Intersects of ranges.
|
||||
const auto l1 = entity.offset();
|
||||
const auto r1 = entity.offset() + entity.length() - 1;
|
||||
const auto l2 = position;
|
||||
const auto r2 = midEnd - 1;
|
||||
return !(l1 > r2 || l2 > r1);
|
||||
}) | ranges::views::transform([&](const EntityInText &entity) {
|
||||
if ((entity.offset() == position) && (entity.length() == n)) {
|
||||
return entity;
|
||||
}
|
||||
const auto start = std::max(entity.offset(), position);
|
||||
const auto end = std::min(entity.offset() + entity.length(), midEnd);
|
||||
return EntityInText(
|
||||
entity.type(),
|
||||
start - position,
|
||||
end - start,
|
||||
entity.data());
|
||||
}) | ranges::to<EntitiesInText>();
|
||||
return {
|
||||
.text = text.text.mid(position, n),
|
||||
.entities = std::move(entities),
|
||||
};
|
||||
}
|
||||
|
||||
TextWithEntities Filtered(
|
||||
const TextWithEntities &text,
|
||||
const std::vector<EntityType> &types) {
|
||||
auto result = ranges::views::all(
|
||||
text.entities
|
||||
) | ranges::views::filter([&](const EntityInText &entity) {
|
||||
return ranges::contains(types, entity.type());
|
||||
}) | ranges::to<EntitiesInText>();
|
||||
return { .text = text.text, .entities = std::move(result) };
|
||||
}
|
||||
|
||||
} // namespace Text
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ inline constexpr auto Upper = details::ToUpperType{};
|
|||
[[nodiscard]] TextWithEntities Link(
|
||||
const QString &text,
|
||||
const QString &url = "internal:action");
|
||||
[[nodiscard]] TextWithEntities Link(const QString &text, int index);
|
||||
[[nodiscard]] TextWithEntities PlainLink(const QString &text);
|
||||
[[nodiscard]] TextWithEntities Wrapped(
|
||||
TextWithEntities text,
|
||||
EntityType type,
|
||||
const QString &data = QString());
|
||||
[[nodiscard]] TextWithEntities RichLangValue(const QString &text);
|
||||
[[nodiscard]] inline TextWithEntities WithEntities(const QString &text) {
|
||||
return { text };
|
||||
|
|
@ -65,5 +71,13 @@ inline constexpr auto Upper = details::ToUpperType{};
|
|||
return rpl::map(WithEntities);
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities Mid(
|
||||
const TextWithEntities &text,
|
||||
int position,
|
||||
int n = -1);
|
||||
[[nodiscard]] TextWithEntities Filtered(
|
||||
const TextWithEntities &result,
|
||||
const std::vector<EntityType> &types);
|
||||
|
||||
} // namespace Text
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -741,19 +741,35 @@ void SettingsButton::setColorOverride(std::optional<QColor> textColorOverride) {
|
|||
update();
|
||||
}
|
||||
|
||||
const style::SettingsButton &SettingsButton::st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
void SettingsButton::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
auto paintOver = (isOver() || isDown()) && !isDisabled();
|
||||
p.fillRect(e->rect(), paintOver ? _st.textBgOver : _st.textBg);
|
||||
const auto paintOver = (isOver() || isDown()) && !isDisabled();
|
||||
paintBg(p, e->rect(), paintOver);
|
||||
|
||||
paintRipple(p, 0, 0);
|
||||
|
||||
auto outerw = width();
|
||||
const auto outerw = width();
|
||||
paintText(p, paintOver, outerw);
|
||||
|
||||
if (_toggle) {
|
||||
paintToggle(p, outerw);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsButton::paintBg(Painter &p, const QRect &rect, bool over) const {
|
||||
p.fillRect(rect, over ? _st.textBgOver : _st.textBg);
|
||||
}
|
||||
|
||||
void SettingsButton::paintText(Painter &p, bool over, int outerw) const {
|
||||
p.setFont(_st.font);
|
||||
p.setPen(_textColorOverride
|
||||
? QPen(*_textColorOverride)
|
||||
: paintOver
|
||||
: over
|
||||
? _st.textFgOver
|
||||
: _st.textFg);
|
||||
p.drawTextLeft(
|
||||
|
|
@ -762,7 +778,9 @@ void SettingsButton::paintEvent(QPaintEvent *e) {
|
|||
outerw,
|
||||
_text,
|
||||
_textWidth);
|
||||
}
|
||||
|
||||
void SettingsButton::paintToggle(Painter &p, int outerw) const {
|
||||
if (_toggle) {
|
||||
auto rect = toggleRect();
|
||||
_toggle->paint(p, rect.left(), rect.top(), outerw);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RippleAnimation;
|
||||
|
|
@ -273,6 +275,11 @@ protected:
|
|||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
const style::SettingsButton &st() const;
|
||||
void paintBg(Painter &p, const QRect &rect, bool over) const;
|
||||
void paintText(Painter &p, bool over, int outerw) const;
|
||||
void paintToggle(Painter &p, int outerw) const;
|
||||
|
||||
private:
|
||||
void setText(QString &&text);
|
||||
QRect toggleRect() const;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ TextParseOptions _checkboxOptions = {
|
|||
};
|
||||
|
||||
TextParseOptions _checkboxRichOptions = {
|
||||
TextParseMultiline | TextParseRichText, // flags
|
||||
TextParseMultiline, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ public:
|
|||
QImage prepareRippleMask() const override;
|
||||
bool checkRippleStartPosition(QPoint position) const override;
|
||||
void setLocked(bool locked);
|
||||
bool isLocked() {
|
||||
return _locked;
|
||||
}
|
||||
|
||||
private:
|
||||
void paintXV(Painter &p, int left, int top, int outerWidth, float64 toggled, const QBrush &brush);
|
||||
|
|
@ -203,6 +206,10 @@ public:
|
|||
}
|
||||
QRect checkRect() const;
|
||||
|
||||
not_null<AbstractCheckView*> checkView() const {
|
||||
return _check.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
#include "base/platform/base_platform_info.h"
|
||||
#include "emoji_suggestions_helper.h"
|
||||
#include "styles/palette.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtWidgets/QCommonStyle>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
|
|
@ -706,6 +706,7 @@ void RemoveDocumentTags(
|
|||
format.setProperty(kTagProperty, QString());
|
||||
format.setProperty(kReplaceTagId, QString());
|
||||
format.setForeground(st.textFg);
|
||||
format.setBackground(QBrush());
|
||||
format.setFont(st.font);
|
||||
cursor.mergeCharFormat(format);
|
||||
}
|
||||
|
|
@ -752,6 +753,7 @@ void ApplyTagFormat(QTextCharFormat &to, const QTextCharFormat &from) {
|
|||
to.setProperty(kReplaceTagId, from.property(kReplaceTagId));
|
||||
to.setFont(from.font());
|
||||
to.setForeground(from.foreground());
|
||||
to.setBackground(from.background());
|
||||
}
|
||||
|
||||
// Returns the position of the first inserted tag or "changedEnd" value if none found.
|
||||
|
|
@ -1467,7 +1469,9 @@ void InputField::updatePalette() {
|
|||
|
||||
auto format = fragment.charFormat();
|
||||
const auto tag = format.property(kTagProperty).toString();
|
||||
format.setForeground(PrepareTagFormat(_st, tag).foreground());
|
||||
const auto updatedFormat = PrepareTagFormat(_st, tag);
|
||||
format.setForeground(updatedFormat.foreground());
|
||||
format.setBackground(updatedFormat.background());
|
||||
cursor.setPosition(fragment.position());
|
||||
cursor.setPosition(till, QTextCursor::KeepAnchor);
|
||||
cursor.mergeCharFormat(format);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
#include "ui/widgets/box_content_divider.h"
|
||||
#include "ui/basic_click_handlers.h" // UrlClickHandler
|
||||
#include "ui/inactive_press.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
|
@ -32,7 +32,7 @@ TextParseOptions _labelOptions = {
|
|||
};
|
||||
|
||||
TextParseOptions _labelMarkedOptions = {
|
||||
TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
|
||||
TextParseMultiline | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
|
@ -257,11 +257,6 @@ void FlatLabel::setText(const QString &text) {
|
|||
textUpdated();
|
||||
}
|
||||
|
||||
void FlatLabel::setRichText(const QString &text) {
|
||||
_text.setRichText(_st.style, text, _labelOptions);
|
||||
textUpdated();
|
||||
}
|
||||
|
||||
void FlatLabel::setMarkedText(const TextWithEntities &textWithEntities) {
|
||||
_text.setMarkedText(_st.style, textWithEntities, _labelMarkedOptions);
|
||||
textUpdated();
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ public:
|
|||
void setTextColorOverride(std::optional<QColor> color);
|
||||
|
||||
void setText(const QString &text);
|
||||
void setRichText(const QString &text);
|
||||
void setMarkedText(const TextWithEntities &textWithEntities);
|
||||
void setSelectable(bool selectable);
|
||||
void setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace {
|
|||
}
|
||||
|
||||
TextParseOptions MenuTextOptions = {
|
||||
TextParseLinks | TextParseRichText, // flags
|
||||
TextParseLinks, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ rpl::producer<CallbackData> ItemBase::clicks() const {
|
|||
AbstractButton::clicks() | rpl::to_empty,
|
||||
_clicks.events()
|
||||
) | rpl::filter([=] {
|
||||
return isEnabled();
|
||||
return isEnabled() && !AbstractButton::isDisabled();
|
||||
}) | rpl::map([=]() -> CallbackData {
|
||||
return { action(), y(), _lastTriggeredSource, _index, true };
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ RpWindow::RpWindow(QWidget *parent)
|
|||
, _helper(Platform::CreateWindowHelper(this)) {
|
||||
Expects(_helper != nullptr);
|
||||
|
||||
_helper->initInWindow(this);
|
||||
hide();
|
||||
}
|
||||
|
||||
|
|
@ -32,6 +33,27 @@ QMargins RpWindow::frameMargins() const {
|
|||
return _helper->frameMargins();
|
||||
}
|
||||
|
||||
int RpWindow::additionalContentPadding() const {
|
||||
return _helper->additionalContentPadding();
|
||||
}
|
||||
|
||||
rpl::producer<int> RpWindow::additionalContentPaddingValue() const {
|
||||
return _helper->additionalContentPaddingValue();
|
||||
}
|
||||
|
||||
auto RpWindow::hitTestRequests() const
|
||||
-> rpl::producer<not_null<Platform::HitTestRequest*>> {
|
||||
return _helper->hitTestRequests();
|
||||
}
|
||||
|
||||
rpl::producer<Platform::HitTestResult> RpWindow::systemButtonOver() const {
|
||||
return _helper->systemButtonOver();
|
||||
}
|
||||
|
||||
rpl::producer<Platform::HitTestResult> RpWindow::systemButtonDown() const {
|
||||
return _helper->systemButtonDown();
|
||||
}
|
||||
|
||||
void RpWindow::setTitle(const QString &title) {
|
||||
_helper->setTitle(title);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ struct WindowTitle;
|
|||
namespace Ui {
|
||||
namespace Platform {
|
||||
class BasicWindowHelper;
|
||||
struct HitTestRequest;
|
||||
enum class HitTestResult;
|
||||
} // namespace Platform
|
||||
|
||||
enum class WindowTitleHitTestFlag {
|
||||
|
|
@ -38,6 +40,21 @@ public:
|
|||
[[nodiscard]] not_null<const RpWidget*> body() const;
|
||||
[[nodiscard]] QMargins frameMargins() const;
|
||||
|
||||
// In Windows 11 the window rounding shadow takes about
|
||||
// round(1px * system_scale) from the window geometry on each side.
|
||||
//
|
||||
// Top shift is made by the TitleWidget height, but the rest of the
|
||||
// side shifts are left for the RpWindow client to consider.
|
||||
[[nodiscard]] int additionalContentPadding() const;
|
||||
[[nodiscard]] rpl::producer<int> additionalContentPaddingValue() const;
|
||||
|
||||
[[nodiscard]] auto hitTestRequests() const
|
||||
-> rpl::producer<not_null<Platform::HitTestRequest*>>;
|
||||
[[nodiscard]] auto systemButtonOver() const
|
||||
-> rpl::producer<Platform::HitTestResult>;
|
||||
[[nodiscard]] auto systemButtonDown() const
|
||||
-> rpl::producer<Platform::HitTestResult>;
|
||||
|
||||
void setTitle(const QString &title);
|
||||
void setTitleStyle(const style::WindowTitle &st);
|
||||
void setNativeFrame(bool enabled);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtWidgets/QScrollBar>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
|
@ -284,6 +284,10 @@ void ScrollBar::resizeEvent(QResizeEvent *e) {
|
|||
updateBar();
|
||||
}
|
||||
|
||||
void ScrollBar::wheelEvent(QWheelEvent *e) {
|
||||
static_cast<ScrollArea*>(parentWidget())->viewportEvent(e);
|
||||
}
|
||||
|
||||
auto ScrollBar::shadowVisibilityChanged() const
|
||||
-> rpl::producer<ScrollBar::ShadowVisibility> {
|
||||
return _shadowVisibilityChanged.events();
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ protected:
|
|||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void wheelEvent(QWheelEvent *e) override;
|
||||
|
||||
private:
|
||||
ScrollArea *area();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QTime>
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
|
|||
|
||||
_point = m;
|
||||
_st = st;
|
||||
_text = Text::String(_st->textStyle, text, _textPlainOptions, _st->widthMax, true);
|
||||
_text = Text::String(_st->textStyle, text, _textPlainOptions, _st->widthMax);
|
||||
|
||||
_useTransparency = Platform::TranslucentWindowsSupported(_point);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
|
||||
|
|
|
|||
|
|
@ -857,7 +857,7 @@ defaultMenu: Menu {
|
|||
|
||||
arrow: defaultMenuArrow;
|
||||
|
||||
widthMin: 140px;
|
||||
widthMin: 156px;
|
||||
widthMax: 300px;
|
||||
|
||||
ripple: defaultRippleAnimation;
|
||||
|
|
@ -1435,90 +1435,93 @@ shakeShift: 4px;
|
|||
|
||||
// Windows specific title
|
||||
|
||||
windowTitleButtonWidth: 36px;
|
||||
windowTitleHeight: 24px;
|
||||
windowTitleButton: IconButton {
|
||||
width: 24px;
|
||||
height: 21px;
|
||||
width: windowTitleButtonWidth;
|
||||
height: windowTitleHeight;
|
||||
iconPosition: point(0px, 0px);
|
||||
}
|
||||
windowTitleButtonClose: IconButton(windowTitleButton) {
|
||||
width: 25px;
|
||||
width: windowTitleButtonWidth;
|
||||
}
|
||||
|
||||
windowTitleButtonSize: size(windowTitleButtonWidth, windowTitleHeight);
|
||||
defaultWindowTitle: WindowTitle {
|
||||
height: 21px;
|
||||
height: windowTitleHeight;
|
||||
bg: titleBg;
|
||||
bgActive: titleBgActive;
|
||||
fg: titleFg;
|
||||
fgActive: titleFgActive;
|
||||
minimize: IconButton(windowTitleButton) {
|
||||
icon: icon {
|
||||
{ size(24px, 21px), titleButtonBg },
|
||||
{ "title_button_minimize", titleButtonFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBg },
|
||||
{ "title_button_minimize", titleButtonFg },
|
||||
};
|
||||
iconOver: icon {
|
||||
{ size(24px, 21px), titleButtonBgOver },
|
||||
{ "title_button_minimize", titleButtonFgOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgOver },
|
||||
{ "title_button_minimize", titleButtonFgOver },
|
||||
};
|
||||
}
|
||||
minimizeIconActive: icon {
|
||||
{ size(24px, 21px), titleButtonBgActive },
|
||||
{ "title_button_minimize", titleButtonFgActive, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgActive },
|
||||
{ "title_button_minimize", titleButtonFgActive },
|
||||
};
|
||||
minimizeIconActiveOver: icon {
|
||||
{ size(24px, 21px), titleButtonBgActiveOver },
|
||||
{ "title_button_minimize", titleButtonFgActiveOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgActiveOver },
|
||||
{ "title_button_minimize", titleButtonFgActiveOver },
|
||||
};
|
||||
maximize: IconButton(windowTitleButton) {
|
||||
icon: icon {
|
||||
{ size(24px, 21px), titleButtonBg },
|
||||
{ "title_button_maximize", titleButtonFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBg },
|
||||
{ "title_button_maximize", titleButtonFg },
|
||||
};
|
||||
iconOver: icon {
|
||||
{ size(24px, 21px), titleButtonBgOver },
|
||||
{ "title_button_maximize", titleButtonFgOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgOver },
|
||||
{ "title_button_maximize", titleButtonFgOver },
|
||||
};
|
||||
}
|
||||
maximizeIconActive: icon {
|
||||
{ size(24px, 21px), titleButtonBgActive },
|
||||
{ "title_button_maximize", titleButtonFgActive, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgActive },
|
||||
{ "title_button_maximize", titleButtonFgActive },
|
||||
};
|
||||
maximizeIconActiveOver: icon {
|
||||
{ size(24px, 21px), titleButtonBgActiveOver },
|
||||
{ "title_button_maximize", titleButtonFgActiveOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgActiveOver },
|
||||
{ "title_button_maximize", titleButtonFgActiveOver },
|
||||
};
|
||||
restoreIcon: icon {
|
||||
{ size(24px, 21px), titleButtonBg },
|
||||
{ "title_button_restore", titleButtonFg, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBg },
|
||||
{ "title_button_restore", titleButtonFg },
|
||||
};
|
||||
restoreIconOver: icon {
|
||||
{ size(24px, 21px), titleButtonBgOver },
|
||||
{ "title_button_restore", titleButtonFgOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgOver },
|
||||
{ "title_button_restore", titleButtonFgOver },
|
||||
};
|
||||
restoreIconActive: icon {
|
||||
{ size(24px, 21px), titleButtonBgActive },
|
||||
{ "title_button_restore", titleButtonFgActive, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgActive },
|
||||
{ "title_button_restore", titleButtonFgActive },
|
||||
};
|
||||
restoreIconActiveOver: icon {
|
||||
{ size(24px, 21px), titleButtonBgActiveOver },
|
||||
{ "title_button_restore", titleButtonFgActiveOver, point(4px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonBgActiveOver },
|
||||
{ "title_button_restore", titleButtonFgActiveOver },
|
||||
};
|
||||
close: IconButton(windowTitleButtonClose) {
|
||||
icon: icon {
|
||||
{ size(25px, 21px), titleButtonCloseBg },
|
||||
{ "title_button_close", titleButtonCloseFg, point(5px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonCloseBg },
|
||||
{ "title_button_close", titleButtonCloseFg },
|
||||
};
|
||||
iconOver: icon {
|
||||
{ size(25px, 21px), titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver, point(5px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgOver },
|
||||
{ "title_button_close", titleButtonCloseFgOver },
|
||||
};
|
||||
}
|
||||
closeIconActive: icon {
|
||||
{ size(25px, 21px), titleButtonCloseBgActive },
|
||||
{ "title_button_close", titleButtonCloseFgActive, point(5px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgActive },
|
||||
{ "title_button_close", titleButtonCloseFgActive },
|
||||
};
|
||||
closeIconActiveOver: icon {
|
||||
{ size(25px, 21px), titleButtonCloseBgActiveOver },
|
||||
{ "title_button_close", titleButtonCloseFgActiveOver, point(5px, 4px) },
|
||||
{ windowTitleButtonSize, titleButtonCloseBgActiveOver },
|
||||
{ "title_button_close", titleButtonCloseFgActiveOver },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ VerticalLayoutReorder::VerticalLayoutReorder(
|
|||
, _scrollAnimation([=] { updateScrollCallback(); }) {
|
||||
}
|
||||
|
||||
VerticalLayoutReorder::VerticalLayoutReorder(not_null<VerticalLayout*> layout)
|
||||
: _layout(layout) {
|
||||
}
|
||||
|
||||
void VerticalLayoutReorder::cancel() {
|
||||
if (_currentWidget) {
|
||||
cancelCurrent(indexOf(_currentWidget));
|
||||
|
|
@ -183,7 +187,9 @@ void VerticalLayoutReorder::cancelCurrent(int index) {
|
|||
}
|
||||
|
||||
void VerticalLayoutReorder::finishReordering() {
|
||||
_scrollAnimation.stop();
|
||||
if (_scroll) {
|
||||
_scrollAnimation.stop();
|
||||
}
|
||||
finishCurrent();
|
||||
}
|
||||
|
||||
|
|
@ -281,6 +287,9 @@ auto VerticalLayoutReorder::updates() const -> rpl::producer<Single> {
|
|||
}
|
||||
|
||||
void VerticalLayoutReorder::updateScrollCallback() {
|
||||
if (!_scroll) {
|
||||
return;
|
||||
}
|
||||
const auto delta = deltaFromEdge();
|
||||
const auto oldTop = _scroll->scrollTop();
|
||||
_scroll->scrollToY(oldTop + delta);
|
||||
|
|
@ -293,7 +302,7 @@ void VerticalLayoutReorder::updateScrollCallback() {
|
|||
}
|
||||
|
||||
void VerticalLayoutReorder::checkForScrollAnimation() {
|
||||
if (!deltaFromEdge() || _scrollAnimation.animating()) {
|
||||
if (!_scroll || !deltaFromEdge() || _scrollAnimation.animating()) {
|
||||
return;
|
||||
}
|
||||
_scrollAnimation.start();
|
||||
|
|
@ -301,6 +310,7 @@ void VerticalLayoutReorder::checkForScrollAnimation() {
|
|||
|
||||
int VerticalLayoutReorder::deltaFromEdge() {
|
||||
Expects(_currentWidget != nullptr);
|
||||
Expects(_scroll);
|
||||
|
||||
const auto globalPosition = _currentWidget->mapToGlobal(QPoint(0, 0));
|
||||
const auto localTop = _scroll->mapFromGlobal(globalPosition).y();
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public:
|
|||
VerticalLayoutReorder(
|
||||
not_null<VerticalLayout*> layout,
|
||||
not_null<ScrollArea*> scroll);
|
||||
VerticalLayoutReorder(not_null<VerticalLayout*> layout);
|
||||
|
||||
void start();
|
||||
void cancel();
|
||||
|
|
@ -68,7 +69,7 @@ private:
|
|||
int deltaFromEdge();
|
||||
|
||||
const not_null<Ui::VerticalLayout*> _layout;
|
||||
const not_null<Ui::ScrollArea*> _scroll;
|
||||
Ui::ScrollArea *_scroll = nullptr;
|
||||
|
||||
Ui::Animations::Basic _scrollAnimation;
|
||||
|
||||
|
|
|
|||