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