Optimize simple Ui::Text::String instances.
This commit is contained in:
parent
44f8d862ff
commit
ab5057f001
17 changed files with 291 additions and 159 deletions
|
|
@ -165,13 +165,13 @@ PRIVATE
|
||||||
ui/text/text_custom_emoji.h
|
ui/text/text_custom_emoji.h
|
||||||
ui/text/text_entity.cpp
|
ui/text/text_entity.cpp
|
||||||
ui/text/text_entity.h
|
ui/text/text_entity.h
|
||||||
|
ui/text/text_extended_data.cpp
|
||||||
|
ui/text/text_extended_data.h
|
||||||
ui/text/text_isolated_emoji.h
|
ui/text/text_isolated_emoji.h
|
||||||
ui/text/text_parser.cpp
|
ui/text/text_parser.cpp
|
||||||
ui/text/text_parser.h
|
ui/text/text_parser.h
|
||||||
ui/text/text_renderer.cpp
|
ui/text/text_renderer.cpp
|
||||||
ui/text/text_renderer.h
|
ui/text/text_renderer.h
|
||||||
ui/text/text_spoiler_data.cpp
|
|
||||||
ui/text/text_spoiler_data.h
|
|
||||||
ui/text/text_utilities.cpp
|
ui/text/text_utilities.cpp
|
||||||
ui/text/text_utilities.h
|
ui/text/text_utilities.h
|
||||||
ui/text/text_variant.cpp
|
ui/text/text_variant.cpp
|
||||||
|
|
|
||||||
172
ui/text/text.cpp
172
ui/text/text.cpp
|
|
@ -6,10 +6,11 @@
|
||||||
//
|
//
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
|
||||||
|
#include "ui/effects/spoiler_mess.h"
|
||||||
|
#include "ui/text/text_extended_data.h"
|
||||||
#include "ui/text/text_isolated_emoji.h"
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
#include "ui/text/text_parser.h"
|
#include "ui/text/text_parser.h"
|
||||||
#include "ui/text/text_renderer.h"
|
#include "ui/text/text_renderer.h"
|
||||||
#include "ui/text/text_spoiler_data.h"
|
|
||||||
#include "ui/basic_click_handlers.h"
|
#include "ui/basic_click_handlers.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
|
|
@ -91,12 +92,19 @@ const TextParseOptions kPlainTextOptions = {
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
|
struct SpoilerMessCache::Entry {
|
||||||
|
SpoilerMessCached mess;
|
||||||
|
QColor color;
|
||||||
|
};
|
||||||
|
|
||||||
SpoilerMessCache::SpoilerMessCache(int capacity) : _capacity(capacity) {
|
SpoilerMessCache::SpoilerMessCache(int capacity) : _capacity(capacity) {
|
||||||
Expects(capacity > 0);
|
Expects(capacity > 0);
|
||||||
|
|
||||||
_cache.reserve(capacity);
|
_cache.reserve(capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpoilerMessCache::~SpoilerMessCache() = default;
|
||||||
|
|
||||||
not_null<SpoilerMessCached*> SpoilerMessCache::lookup(QColor color) {
|
not_null<SpoilerMessCached*> SpoilerMessCache::lookup(QColor color) {
|
||||||
for (auto &entry : _cache) {
|
for (auto &entry : _cache) {
|
||||||
if (entry.color == color) {
|
if (entry.color == color) {
|
||||||
|
|
@ -139,25 +147,25 @@ GeometryDescriptor SimpleGeometry(
|
||||||
bool elisionOneLine,
|
bool elisionOneLine,
|
||||||
bool elisionBreakEverywhere) {
|
bool elisionBreakEverywhere) {
|
||||||
constexpr auto wrap = [](
|
constexpr auto wrap = [](
|
||||||
Fn<LineGeometry(LineGeometry, uint16)> layout,
|
Fn<LineGeometry(LineGeometry)> layout,
|
||||||
bool breakEverywhere = false) {
|
bool breakEverywhere = false) {
|
||||||
return GeometryDescriptor{ std::move(layout), breakEverywhere };
|
return GeometryDescriptor{ std::move(layout), breakEverywhere };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to minimize captured values (to minimize Fn allocations).
|
// Try to minimize captured values (to minimize Fn allocations).
|
||||||
if (!elisionOneLine && !elisionHeight) {
|
if (!elisionOneLine && !elisionHeight) {
|
||||||
return wrap([=](LineGeometry line, uint16 positino) {
|
return wrap([=](LineGeometry line) {
|
||||||
line.width = availableWidth;
|
line.width = availableWidth;
|
||||||
return line;
|
return line;
|
||||||
});
|
});
|
||||||
} else if (elisionOneLine) {
|
} else if (elisionOneLine) {
|
||||||
return wrap([=](LineGeometry line, uint16 position) {
|
return wrap([=](LineGeometry line) {
|
||||||
line.elided = true;
|
line.elided = true;
|
||||||
line.width = availableWidth - elisionRemoveFromEnd;
|
line.width = availableWidth - elisionRemoveFromEnd;
|
||||||
return line;
|
return line;
|
||||||
}, elisionBreakEverywhere);
|
}, elisionBreakEverywhere);
|
||||||
} else if (!elisionRemoveFromEnd) {
|
} else if (!elisionRemoveFromEnd) {
|
||||||
return wrap([=](LineGeometry line, uint16 position) {
|
return wrap([=](LineGeometry line) {
|
||||||
if (line.top + fontHeight * 2 > elisionHeight) {
|
if (line.top + fontHeight * 2 > elisionHeight) {
|
||||||
line.elided = true;
|
line.elided = true;
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +173,7 @@ GeometryDescriptor SimpleGeometry(
|
||||||
return line;
|
return line;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return wrap([=](LineGeometry line, uint16 position) {
|
return wrap([=](LineGeometry line) {
|
||||||
if (line.top + fontHeight * 2 > elisionHeight) {
|
if (line.top + fontHeight * 2 > elisionHeight) {
|
||||||
line.elided = true;
|
line.elided = true;
|
||||||
line.width = availableWidth - elisionRemoveFromEnd;
|
line.width = availableWidth - elisionRemoveFromEnd;
|
||||||
|
|
@ -177,27 +185,44 @@ GeometryDescriptor SimpleGeometry(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
String::SpoilerDataWrap::SpoilerDataWrap() noexcept = default;
|
String::ExtendedWrap::ExtendedWrap() noexcept = default;
|
||||||
|
|
||||||
String::SpoilerDataWrap::SpoilerDataWrap(SpoilerDataWrap &&other) noexcept
|
String::ExtendedWrap::ExtendedWrap(ExtendedWrap &&other) noexcept
|
||||||
: data(std::move(other.data)) {
|
: unique_ptr(std::move(other)) {
|
||||||
adjustFrom(&other);
|
adjustFrom(&other);
|
||||||
}
|
}
|
||||||
|
|
||||||
String::SpoilerDataWrap &String::SpoilerDataWrap::operator=(
|
String::ExtendedWrap &String::ExtendedWrap::operator=(
|
||||||
SpoilerDataWrap &&other) noexcept {
|
ExtendedWrap &&other) noexcept {
|
||||||
data = std::move(other.data);
|
*static_cast<unique_ptr*>(this) = std::move(other);
|
||||||
adjustFrom(&other);
|
adjustFrom(&other);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::SpoilerDataWrap::adjustFrom(const SpoilerDataWrap *other) {
|
String::ExtendedWrap::ExtendedWrap(
|
||||||
if (data && data->link) {
|
std::unique_ptr<ExtendedData> &&other) noexcept
|
||||||
|
: unique_ptr(std::move(other)) {
|
||||||
|
Assert(!get() || !get()->spoiler);
|
||||||
|
}
|
||||||
|
|
||||||
|
String::ExtendedWrap &String::ExtendedWrap::operator=(
|
||||||
|
std::unique_ptr<ExtendedData> &&other) noexcept {
|
||||||
|
*static_cast<unique_ptr*>(this) = std::move(other);
|
||||||
|
Assert(!get() || !get()->spoiler);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::ExtendedWrap::~ExtendedWrap() = default;
|
||||||
|
|
||||||
|
void String::ExtendedWrap::adjustFrom(const ExtendedWrap *other) {
|
||||||
|
const auto data = get();
|
||||||
|
if (data && data->spoiler) {
|
||||||
const auto raw = [](auto pointer) {
|
const auto raw = [](auto pointer) {
|
||||||
return reinterpret_cast<quintptr>(pointer);
|
return reinterpret_cast<quintptr>(pointer);
|
||||||
};
|
};
|
||||||
data->link->setText(reinterpret_cast<String*>(
|
const auto otherText = raw(data->spoiler->link->text().get());
|
||||||
raw(data->link->text().get()) + raw(this) - raw(other)));
|
data->spoiler->link->setText(
|
||||||
|
reinterpret_cast<String*>(otherText + raw(this) - raw(other)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,31 +268,35 @@ void String::setText(const style::TextStyle &st, const QString &text, const Text
|
||||||
void String::recountNaturalSize(
|
void String::recountNaturalSize(
|
||||||
bool initial,
|
bool initial,
|
||||||
Qt::LayoutDirection optionsDirection) {
|
Qt::LayoutDirection optionsDirection) {
|
||||||
NewlineBlock *lastNewline = 0;
|
auto lastNewline = (NewlineBlock*)nullptr;
|
||||||
|
auto lastNewlineStart = 0;
|
||||||
|
const auto computeParagraphDirection = [&](int paragraphEnd) {
|
||||||
|
const auto direction = (optionsDirection != Qt::LayoutDirectionAuto)
|
||||||
|
? optionsDirection
|
||||||
|
: StringDirection(_text, lastNewlineStart, paragraphEnd);
|
||||||
|
if (lastNewline) {
|
||||||
|
lastNewline->_paragraphLTR = (direction == Qt::LeftToRight);
|
||||||
|
lastNewline->_paragraphRTL = (direction == Qt::RightToLeft);
|
||||||
|
} else {
|
||||||
|
_startParagraphLTR = (direction == Qt::LeftToRight);
|
||||||
|
_startParagraphRTL = (direction == Qt::RightToLeft);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_maxWidth = _minHeight = 0;
|
_maxWidth = _minHeight = 0;
|
||||||
int32 lineHeight = 0;
|
int32 lineHeight = 0;
|
||||||
int32 lastNewlineStart = 0;
|
QFixed maxWidth = 0;
|
||||||
QFixed _width = 0, last_rBearing = 0, last_rPadding = 0;
|
QFixed width = 0, last_rBearing = 0, last_rPadding = 0;
|
||||||
for (auto &block : _blocks) {
|
for (auto &block : _blocks) {
|
||||||
auto b = block.get();
|
auto b = block.get();
|
||||||
auto _btype = b->type();
|
auto _btype = b->type();
|
||||||
auto blockHeight = CountBlockHeight(b, _st);
|
auto blockHeight = CountBlockHeight(b, _st);
|
||||||
if (_btype == TextBlockType::Newline) {
|
if (_btype == TextBlockType::Newline) {
|
||||||
if (!lineHeight) lineHeight = blockHeight;
|
if (!lineHeight) {
|
||||||
|
lineHeight = blockHeight;
|
||||||
|
}
|
||||||
if (initial) {
|
if (initial) {
|
||||||
Qt::LayoutDirection direction = optionsDirection;
|
computeParagraphDirection(b->position());
|
||||||
if (direction == Qt::LayoutDirectionAuto) {
|
|
||||||
direction = StringDirection(
|
|
||||||
_text,
|
|
||||||
lastNewlineStart,
|
|
||||||
b->position());
|
|
||||||
}
|
|
||||||
if (lastNewline) {
|
|
||||||
lastNewline->_nextDirection = direction;
|
|
||||||
} else {
|
|
||||||
_startDirection = direction;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lastNewlineStart = b->position();
|
lastNewlineStart = b->position();
|
||||||
lastNewline = &block.unsafe<NewlineBlock>();
|
lastNewline = &block.unsafe<NewlineBlock>();
|
||||||
|
|
@ -277,8 +306,8 @@ void String::recountNaturalSize(
|
||||||
last_rBearing = b->f_rbearing();
|
last_rBearing = b->f_rbearing();
|
||||||
last_rPadding = b->f_rpadding();
|
last_rPadding = b->f_rpadding();
|
||||||
|
|
||||||
accumulate_max(_maxWidth, _width);
|
accumulate_max(maxWidth, width);
|
||||||
_width = (b->f_width() - last_rBearing);
|
width = (b->f_width() - last_rBearing);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,9 +320,9 @@ void String::recountNaturalSize(
|
||||||
// But when we layout block and we're sure that _maxWidth is enough
|
// But when we layout block and we're sure that _maxWidth is enough
|
||||||
// for all the blocks to fit on their line we check each block, even the
|
// for all the blocks to fit on their line we check each block, even the
|
||||||
// intermediate one with a large negative right bearing.
|
// intermediate one with a large negative right bearing.
|
||||||
accumulate_max(_maxWidth, _width);
|
accumulate_max(maxWidth, width);
|
||||||
|
|
||||||
_width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing);
|
width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing);
|
||||||
lineHeight = qMax(lineHeight, blockHeight);
|
lineHeight = qMax(lineHeight, blockHeight);
|
||||||
|
|
||||||
last_rBearing = b__f_rbearing;
|
last_rBearing = b__f_rbearing;
|
||||||
|
|
@ -301,21 +330,14 @@ void String::recountNaturalSize(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (initial) {
|
if (initial) {
|
||||||
Qt::LayoutDirection direction = optionsDirection;
|
computeParagraphDirection(_text.size());
|
||||||
if (direction == Qt::LayoutDirectionAuto) {
|
|
||||||
direction = StringDirection(_text, lastNewlineStart, _text.size());
|
|
||||||
}
|
}
|
||||||
if (lastNewline) {
|
if (width > 0) {
|
||||||
lastNewline->_nextDirection = direction;
|
|
||||||
} else {
|
|
||||||
_startDirection = direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_width > 0) {
|
|
||||||
if (!lineHeight) lineHeight = CountBlockHeight(_blocks.back().get(), _st);
|
if (!lineHeight) lineHeight = CountBlockHeight(_blocks.back().get(), _st);
|
||||||
_minHeight += lineHeight;
|
_minHeight += lineHeight;
|
||||||
accumulate_max(_maxWidth, _width);
|
accumulate_max(maxWidth, width);
|
||||||
}
|
}
|
||||||
|
_maxWidth = maxWidth.ceil().toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::countMaxMonospaceWidth() const {
|
int String::countMaxMonospaceWidth() const {
|
||||||
|
|
@ -405,13 +427,14 @@ void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &t
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::setLink(uint16 index, const ClickHandlerPtr &link) {
|
void String::setLink(uint16 index, const ClickHandlerPtr &link) {
|
||||||
if (index > 0 && index <= _links.size()) {
|
const auto extended = _extended.get();
|
||||||
_links[index - 1] = link;
|
if (extended && index > 0 && index <= extended->links.size()) {
|
||||||
|
extended->links[index - 1] = link;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
|
void String::setSpoilerRevealed(bool revealed, anim::type animated) {
|
||||||
const auto data = _spoiler.data.get();
|
const auto data = _extended ? _extended->spoiler.get() : nullptr;
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
} else if (data->revealed == revealed) {
|
} else if (data->revealed == revealed) {
|
||||||
|
|
@ -436,19 +459,19 @@ void String::setSpoilerRevealed(bool revealed, anim::type animated) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter) {
|
void String::setSpoilerLinkFilter(Fn<bool(const ClickContext&)> filter) {
|
||||||
Expects(_spoiler.data != nullptr);
|
Expects(_extended && _extended->spoiler);
|
||||||
|
|
||||||
_spoiler.data->link = std::make_shared<SpoilerClickHandler>(
|
_extended->spoiler->link = std::make_shared<SpoilerClickHandler>(
|
||||||
this,
|
this,
|
||||||
std::move(filter));
|
std::move(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::hasLinks() const {
|
bool String::hasLinks() const {
|
||||||
return !_links.isEmpty();
|
return _extended && !_extended->links.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::hasSpoilers() const {
|
bool String::hasSpoilers() const {
|
||||||
return (_spoiler.data != nullptr);
|
return _extended && (_extended->spoiler != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::hasSkipBlock() const {
|
bool String::hasSkipBlock() const {
|
||||||
|
|
@ -490,7 +513,7 @@ bool String::removeSkipBlock() {
|
||||||
|
|
||||||
int String::countWidth(int width, bool breakEverywhere) const {
|
int String::countWidth(int width, bool breakEverywhere) const {
|
||||||
if (QFixed(width) >= _maxWidth) {
|
if (QFixed(width) >= _maxWidth) {
|
||||||
return _maxWidth.ceil().toInt();
|
return _maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFixed maxLineWidth = 0;
|
QFixed maxLineWidth = 0;
|
||||||
|
|
@ -822,6 +845,13 @@ bool String::isEmpty() const {
|
||||||
return _blocks.empty() || _blocks[0]->type() == TextBlockType::Skip;
|
return _blocks.empty() || _blocks[0]->type() == TextBlockType::Skip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
not_null<ExtendedData*> String::ensureExtended() {
|
||||||
|
if (!_extended) {
|
||||||
|
_extended = std::make_unique<ExtendedData>();
|
||||||
|
}
|
||||||
|
return _extended.get();
|
||||||
|
}
|
||||||
|
|
||||||
uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const {
|
uint16 String::countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const {
|
||||||
return (i + 1 == e) ? _text.size() : (*(i + 1))->position();
|
return (i + 1 == e) ? _text.size() : (*(i + 1))->position();
|
||||||
}
|
}
|
||||||
|
|
@ -857,7 +887,9 @@ void String::enumerateText(
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const auto result = (*i)->linkIndex();
|
const auto result = (*i)->linkIndex();
|
||||||
return (result && _links.at(result - 1)) ? result : 0;
|
return (result && _extended && _extended->links[result - 1])
|
||||||
|
? result
|
||||||
|
: 0;
|
||||||
}();
|
}();
|
||||||
if (blockLinkIndex != linkIndex) {
|
if (blockLinkIndex != linkIndex) {
|
||||||
if (linkIndex) {
|
if (linkIndex) {
|
||||||
|
|
@ -866,9 +898,11 @@ void String::enumerateText(
|
||||||
if (rangeTo > rangeFrom) { // handle click handler
|
if (rangeTo > rangeFrom) { // handle click handler
|
||||||
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
||||||
// Ignore links that are partially copied.
|
// Ignore links that are partially copied.
|
||||||
const auto handler = (linkPosition != rangeFrom || blockPosition != rangeTo)
|
const auto handler = (linkPosition != rangeFrom
|
||||||
|
|| blockPosition != rangeTo
|
||||||
|
|| !_extended)
|
||||||
? nullptr
|
? nullptr
|
||||||
: _links.at(linkIndex - 1);
|
: _extended->links[linkIndex - 1];
|
||||||
const auto type = handler
|
const auto type = handler
|
||||||
? handler->getTextEntity().type
|
? handler->getTextEntity().type
|
||||||
: EntityType::Invalid;
|
: EntityType::Invalid;
|
||||||
|
|
@ -878,7 +912,9 @@ void String::enumerateText(
|
||||||
linkIndex = blockLinkIndex;
|
linkIndex = blockLinkIndex;
|
||||||
if (linkIndex) {
|
if (linkIndex) {
|
||||||
linkPosition = blockPosition;
|
linkPosition = blockPosition;
|
||||||
const auto handler = _links.at(linkIndex - 1);
|
const auto handler = _extended
|
||||||
|
? _extended->links[linkIndex - 1]
|
||||||
|
: nullptr;
|
||||||
clickHandlerStartCallback(handler
|
clickHandlerStartCallback(handler
|
||||||
? handler->getTextEntity().type
|
? handler->getTextEntity().type
|
||||||
: EntityType::Invalid);
|
: EntityType::Invalid);
|
||||||
|
|
@ -916,7 +952,7 @@ void String::enumerateText(
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::hasPersistentAnimation() const {
|
bool String::hasPersistentAnimation() const {
|
||||||
return _hasCustomEmoji || _spoiler.data;
|
return _hasCustomEmoji || hasSpoilers();
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::unloadPersistentAnimation() {
|
void String::unloadPersistentAnimation() {
|
||||||
|
|
@ -958,6 +994,11 @@ bool String::hasNotEmojiAndSpaces() const {
|
||||||
return _hasNotEmojiAndSpaces;
|
return _hasNotEmojiAndSpaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base::flat_map<int, int> &String::modifications() const {
|
||||||
|
static const auto kEmpty = base::flat_map<int, int>();
|
||||||
|
return _extended ? _extended->modifications : kEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
QString String::toString(TextSelection selection) const {
|
QString String::toString(TextSelection selection) const {
|
||||||
return toText(selection, false, false).rich.text;
|
return toText(selection, false, false).rich.text;
|
||||||
}
|
}
|
||||||
|
|
@ -1141,16 +1182,13 @@ IsolatedEmoji String::toIsolatedEmoji() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::clear() {
|
void String::clear() {
|
||||||
clearFields();
|
|
||||||
_text.clear();
|
_text.clear();
|
||||||
}
|
|
||||||
|
|
||||||
void String::clearFields() {
|
|
||||||
_blocks.clear();
|
_blocks.clear();
|
||||||
_links.clear();
|
_extended = nullptr;
|
||||||
_spoiler.data = nullptr;
|
|
||||||
_maxWidth = _minHeight = 0;
|
_maxWidth = _minHeight = 0;
|
||||||
_startDirection = Qt::LayoutDirectionAuto;
|
_startParagraphIndex = 0;
|
||||||
|
_startParagraphLTR = false;
|
||||||
|
_startParagraphRTL = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsBad(QChar ch) {
|
bool IsBad(QChar ch) {
|
||||||
|
|
|
||||||
116
ui/text/text.h
116
ui/text/text.h
|
|
@ -7,12 +7,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/text/text_entity.h"
|
#include "ui/text/text_entity.h"
|
||||||
#include "ui/text/text_block.h"
|
|
||||||
#include "ui/effects/spoiler_mess.h"
|
|
||||||
#include "ui/click_handler.h"
|
#include "ui/click_handler.h"
|
||||||
#include "base/flags.h"
|
#include "base/flags.h"
|
||||||
|
#include "ui/style/style_core_types.h"
|
||||||
|
|
||||||
|
#include <crl/crl_time.h>
|
||||||
|
|
||||||
#include <private/qfixed_p.h>
|
|
||||||
#include <any>
|
#include <any>
|
||||||
|
|
||||||
class Painter;
|
class Painter;
|
||||||
|
|
@ -22,11 +22,18 @@ enum class type : uchar;
|
||||||
} // namespace anim
|
} // namespace anim
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
|
struct TextStyle;
|
||||||
struct TextPalette;
|
struct TextPalette;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
|
class SpoilerMessCached;
|
||||||
|
|
||||||
extern const QString kQEllipsis;
|
extern const QString kQEllipsis;
|
||||||
|
|
||||||
|
inline constexpr auto kQFixedMax = (INT_MAX / 256);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
struct TextParseOptions {
|
struct TextParseOptions {
|
||||||
|
|
@ -68,9 +75,12 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
|
struct Block;
|
||||||
struct IsolatedEmoji;
|
struct IsolatedEmoji;
|
||||||
struct OnlyCustomEmoji;
|
struct OnlyCustomEmoji;
|
||||||
struct SpoilerData;
|
struct SpoilerData;
|
||||||
|
struct ParagraphDetails;
|
||||||
|
struct ExtendedData;
|
||||||
|
|
||||||
struct StateRequest {
|
struct StateRequest {
|
||||||
enum class Flag {
|
enum class Flag {
|
||||||
|
|
@ -108,15 +118,13 @@ struct StateRequestElided : StateRequest {
|
||||||
class SpoilerMessCache {
|
class SpoilerMessCache {
|
||||||
public:
|
public:
|
||||||
explicit SpoilerMessCache(int capacity);
|
explicit SpoilerMessCache(int capacity);
|
||||||
|
~SpoilerMessCache();
|
||||||
|
|
||||||
[[nodiscard]] not_null<SpoilerMessCached*> lookup(QColor color);
|
[[nodiscard]] not_null<SpoilerMessCached*> lookup(QColor color);
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Entry {
|
struct Entry;
|
||||||
SpoilerMessCached mess;
|
|
||||||
QColor color;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Entry> _cache;
|
std::vector<Entry> _cache;
|
||||||
const int _capacity = 0;
|
const int _capacity = 0;
|
||||||
|
|
@ -135,7 +143,7 @@ struct LineGeometry {
|
||||||
bool elided = false;
|
bool elided = false;
|
||||||
};
|
};
|
||||||
struct GeometryDescriptor {
|
struct GeometryDescriptor {
|
||||||
Fn<LineGeometry(LineGeometry line, uint16 position)> layout;
|
Fn<LineGeometry(LineGeometry line)> layout;
|
||||||
bool breakEverywhere = false;
|
bool breakEverywhere = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -176,17 +184,17 @@ struct PaintContext {
|
||||||
|
|
||||||
class String {
|
class String {
|
||||||
public:
|
public:
|
||||||
String(int32 minResizeWidth = QFIXED_MAX);
|
String(int minResizeWidth = kQFixedMax);
|
||||||
String(
|
String(
|
||||||
const style::TextStyle &st,
|
const style::TextStyle &st,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
const TextParseOptions &options = kDefaultTextOptions,
|
const TextParseOptions &options = kDefaultTextOptions,
|
||||||
int32 minResizeWidth = QFIXED_MAX);
|
int minResizeWidth = kQFixedMax);
|
||||||
String(
|
String(
|
||||||
const style::TextStyle &st,
|
const style::TextStyle &st,
|
||||||
const TextWithEntities &textWithEntities,
|
const TextWithEntities &textWithEntities,
|
||||||
const TextParseOptions &options = kMarkupTextOptions,
|
const TextParseOptions &options = kMarkupTextOptions,
|
||||||
int32 minResizeWidth = QFIXED_MAX,
|
int minResizeWidth = kQFixedMax,
|
||||||
const std::any &context = {});
|
const std::any &context = {});
|
||||||
String(String &&other);
|
String(String &&other);
|
||||||
String &operator=(String &&other);
|
String &operator=(String &&other);
|
||||||
|
|
@ -238,7 +246,7 @@ public:
|
||||||
bool removeSkipBlock();
|
bool removeSkipBlock();
|
||||||
|
|
||||||
[[nodiscard]] int maxWidth() const {
|
[[nodiscard]] int maxWidth() const {
|
||||||
return _maxWidth.ceil().toInt();
|
return _maxWidth;
|
||||||
}
|
}
|
||||||
[[nodiscard]] int minHeight() const {
|
[[nodiscard]] int minHeight() const {
|
||||||
return _minHeight;
|
return _minHeight;
|
||||||
|
|
@ -293,9 +301,7 @@ public:
|
||||||
[[nodiscard]] OnlyCustomEmoji toOnlyCustomEmoji() const;
|
[[nodiscard]] OnlyCustomEmoji toOnlyCustomEmoji() const;
|
||||||
|
|
||||||
[[nodiscard]] bool hasNotEmojiAndSpaces() const;
|
[[nodiscard]] bool hasNotEmojiAndSpaces() const;
|
||||||
[[nodiscard]] const base::flat_map<int, int> &modifications() const {
|
[[nodiscard]] const base::flat_map<int, int> &modifications() const;
|
||||||
return _modifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] const style::TextStyle *style() const {
|
[[nodiscard]] const style::TextStyle *style() const {
|
||||||
return _st;
|
return _st;
|
||||||
|
|
@ -305,64 +311,80 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using TextBlocks = std::vector<Block>;
|
using TextBlocks = std::vector<Block>;
|
||||||
using TextLinks = QVector<ClickHandlerPtr>;
|
|
||||||
|
|
||||||
class SpoilerDataWrap {
|
class ExtendedWrap : public std::unique_ptr<ExtendedData> {
|
||||||
public:
|
public:
|
||||||
SpoilerDataWrap() noexcept;
|
ExtendedWrap() noexcept;
|
||||||
SpoilerDataWrap(SpoilerDataWrap &&other) noexcept;
|
ExtendedWrap(ExtendedWrap &&other) noexcept;
|
||||||
SpoilerDataWrap &operator=(SpoilerDataWrap &&other) noexcept;
|
ExtendedWrap &operator=(ExtendedWrap &&other) noexcept;
|
||||||
|
~ExtendedWrap();
|
||||||
|
|
||||||
std::unique_ptr<SpoilerData> data;
|
ExtendedWrap(
|
||||||
|
std::unique_ptr<ExtendedData> &&other) noexcept;
|
||||||
|
ExtendedWrap &operator=(
|
||||||
|
std::unique_ptr<ExtendedData> &&other) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void adjustFrom(const SpoilerDataWrap *other);
|
void adjustFrom(const ExtendedWrap *other);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
uint16 countBlockEnd(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
|
[[nodiscard]] not_null<ExtendedData*> ensureExtended();
|
||||||
uint16 countBlockLength(const TextBlocks::const_iterator &i, const TextBlocks::const_iterator &e) const;
|
|
||||||
|
[[nodiscard]] uint16 countBlockEnd(
|
||||||
|
const TextBlocks::const_iterator &i,
|
||||||
|
const TextBlocks::const_iterator &e) const;
|
||||||
|
[[nodiscard]] uint16 countBlockLength(
|
||||||
|
const TextBlocks::const_iterator &i,
|
||||||
|
const TextBlocks::const_iterator &e) const;
|
||||||
|
|
||||||
// Template method for originalText(), originalTextWithEntities().
|
// Template method for originalText(), originalTextWithEntities().
|
||||||
template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
|
template <
|
||||||
void enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const;
|
typename AppendPartCallback,
|
||||||
|
typename ClickHandlerStartCallback,
|
||||||
|
typename ClickHandlerFinishCallback,
|
||||||
|
typename FlagsChangeCallback>
|
||||||
|
void enumerateText(
|
||||||
|
TextSelection selection,
|
||||||
|
AppendPartCallback appendPartCallback,
|
||||||
|
ClickHandlerStartCallback clickHandlerStartCallback,
|
||||||
|
ClickHandlerFinishCallback clickHandlerFinishCallback,
|
||||||
|
FlagsChangeCallback flagsChangeCallback) const;
|
||||||
|
|
||||||
// Template method for countWidth(), countHeight(), countLineWidths().
|
// Template method for countWidth(), countHeight(), countLineWidths().
|
||||||
// callback(lineWidth, lineHeight) will be called for all lines with:
|
// callback(lineWidth, lineHeight) will be called for all lines with:
|
||||||
// QFixed lineWidth, int lineHeight
|
// QFixed lineWidth, int lineHeight
|
||||||
template <typename Callback>
|
template <typename Callback>
|
||||||
void enumerateLines(int w, bool breakEverywhere, Callback callback) const;
|
void enumerateLines(
|
||||||
|
int w,
|
||||||
|
bool breakEverywhere,
|
||||||
|
Callback callback) const;
|
||||||
|
|
||||||
void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
|
void recountNaturalSize(
|
||||||
|
bool initial,
|
||||||
|
Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
|
||||||
|
|
||||||
// clear() deletes all blocks and calls this method
|
[[nodiscard]] TextForMimeData toText(
|
||||||
// it is also called from move constructor / assignment operator
|
|
||||||
void clearFields();
|
|
||||||
|
|
||||||
TextForMimeData toText(
|
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
bool composeExpanded,
|
bool composeExpanded,
|
||||||
bool composeEntities) const;
|
bool composeEntities) const;
|
||||||
|
|
||||||
QFixed _minResizeWidth;
|
const style::TextStyle *_st = nullptr;
|
||||||
QFixed _maxWidth = 0;
|
QString _text;
|
||||||
int32 _minHeight = 0;
|
TextBlocks _blocks;
|
||||||
|
ExtendedWrap _extended;
|
||||||
|
|
||||||
|
int _minResizeWidth = 0;
|
||||||
|
int _maxWidth = 0;
|
||||||
|
int _minHeight = 0;
|
||||||
|
int16 _startParagraphIndex = 0;
|
||||||
|
bool _startParagraphLTR : 1 = false;
|
||||||
|
bool _startParagraphRTL : 1 = false;
|
||||||
bool _hasCustomEmoji : 1 = false;
|
bool _hasCustomEmoji : 1 = false;
|
||||||
bool _isIsolatedEmoji : 1 = false;
|
bool _isIsolatedEmoji : 1 = false;
|
||||||
bool _isOnlyCustomEmoji : 1 = false;
|
bool _isOnlyCustomEmoji : 1 = false;
|
||||||
bool _hasNotEmojiAndSpaces : 1 = false;
|
bool _hasNotEmojiAndSpaces : 1 = false;
|
||||||
|
|
||||||
QString _text;
|
|
||||||
base::flat_map<int, int> _modifications;
|
|
||||||
const style::TextStyle *_st = nullptr;
|
|
||||||
|
|
||||||
TextBlocks _blocks;
|
|
||||||
TextLinks _links;
|
|
||||||
|
|
||||||
Qt::LayoutDirection _startDirection = Qt::LayoutDirectionAuto;
|
|
||||||
|
|
||||||
SpoilerDataWrap _spoiler;
|
|
||||||
|
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
friend class Renderer;
|
friend class Renderer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -409,6 +409,14 @@ style::font WithFlags(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Qt::LayoutDirection UnpackParagraphDirection(bool ltr, bool rtl) {
|
||||||
|
return ltr
|
||||||
|
? Qt::LeftToRight
|
||||||
|
: rtl
|
||||||
|
? Qt::RightToLeft
|
||||||
|
: Qt::LayoutDirectionAuto;
|
||||||
|
}
|
||||||
|
|
||||||
AbstractBlock::AbstractBlock(
|
AbstractBlock::AbstractBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
|
|
@ -580,10 +588,6 @@ NewlineBlock::NewlineBlock(
|
||||||
colorIndex) {
|
colorIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::LayoutDirection NewlineBlock::nextDirection() const {
|
|
||||||
return _nextDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
SkipBlock::SkipBlock(
|
SkipBlock::SkipBlock(
|
||||||
const style::font &font,
|
const style::font &font,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@
|
||||||
#include "ui/style/style_core.h"
|
#include "ui/style/style_core.h"
|
||||||
#include "ui/emoji_config.h"
|
#include "ui/emoji_config.h"
|
||||||
|
|
||||||
#include <private/qfixed_p.h>
|
|
||||||
|
|
||||||
#include <crl/crl_time.h>
|
#include <crl/crl_time.h>
|
||||||
|
|
||||||
|
#include <private/qfixed_p.h>
|
||||||
|
|
||||||
namespace style {
|
namespace style {
|
||||||
struct TextStyle;
|
struct TextStyle;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
|
@ -49,6 +49,10 @@ using TextBlockFlags = base::flags<TextBlockFlag>;
|
||||||
TextBlockFlags flags,
|
TextBlockFlags flags,
|
||||||
uint32 fontFlags = 0);
|
uint32 fontFlags = 0);
|
||||||
|
|
||||||
|
[[nodiscard]] Qt::LayoutDirection UnpackParagraphDirection(
|
||||||
|
bool ltr,
|
||||||
|
bool rtl);
|
||||||
|
|
||||||
class AbstractBlock {
|
class AbstractBlock {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] uint16 position() const;
|
[[nodiscard]] uint16 position() const;
|
||||||
|
|
@ -103,10 +107,17 @@ public:
|
||||||
uint16 linkIndex,
|
uint16 linkIndex,
|
||||||
uint16 colorIndex);
|
uint16 colorIndex);
|
||||||
|
|
||||||
[[nodiscard]] Qt::LayoutDirection nextDirection() const;
|
[[nodiscard]] int16 paragraphIndex() const {
|
||||||
|
return _paragraphIndex;
|
||||||
|
}
|
||||||
|
[[nodiscard]] Qt::LayoutDirection paragraphDirection() const {
|
||||||
|
return UnpackParagraphDirection(_paragraphLTR, _paragraphRTL);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Qt::LayoutDirection _nextDirection = Qt::LayoutDirectionAuto;
|
int16 _paragraphIndex = 0;
|
||||||
|
bool _paragraphLTR : 1 = false;
|
||||||
|
bool _paragraphRTL : 1 = false;
|
||||||
|
|
||||||
friend class String;
|
friend class String;
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// For license and copyright information please follow this link:
|
// For license and copyright information please follow this link:
|
||||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
//
|
//
|
||||||
#include "ui/text/text_spoiler_data.h"
|
#include "ui/text/text_extended_data.h"
|
||||||
|
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
|
||||||
|
|
@ -42,4 +42,22 @@ struct SpoilerData {
|
||||||
bool revealed = false;
|
bool revealed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ParagraphDetails {
|
||||||
|
QString language;
|
||||||
|
ClickHandlerPtr copy;
|
||||||
|
int copyWidth = 0;
|
||||||
|
int maxWidth = 0;
|
||||||
|
int minHeight = 0;
|
||||||
|
int scrollLeft = 0;
|
||||||
|
bool blockquote = false;
|
||||||
|
bool pre = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExtendedData {
|
||||||
|
std::vector<ClickHandlerPtr> links;
|
||||||
|
std::vector<ParagraphDetails> paragraphs;
|
||||||
|
std::unique_ptr<SpoilerData> spoiler;
|
||||||
|
base::flat_map<int, int> modifications;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Ui::Text
|
} // namespace Ui::Text
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base/variant.h"
|
#include "base/variant.h"
|
||||||
|
#include "ui/emoji_config.h"
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@
|
||||||
|
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "ui/integration.h"
|
#include "ui/integration.h"
|
||||||
|
#include "ui/text/text_extended_data.h"
|
||||||
#include "ui/text/text_isolated_emoji.h"
|
#include "ui/text/text_isolated_emoji.h"
|
||||||
#include "ui/text/text_spoiler_data.h"
|
|
||||||
#include "styles/style_basic.h"
|
#include "styles/style_basic.h"
|
||||||
|
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
#include <private/qfixed_p.h>
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -182,7 +183,7 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
if (_newlineAwaited) {
|
if (_newlineAwaited) {
|
||||||
_newlineAwaited = false;
|
_newlineAwaited = false;
|
||||||
if (!newline) {
|
if (!newline) {
|
||||||
++_t->_modifications[_blockStart];
|
updateModifications(_blockStart, 1);
|
||||||
_t->_text.insert(_blockStart, QChar::LineFeed);
|
_t->_text.insert(_blockStart, QChar::LineFeed);
|
||||||
createBlock(skipBack - length);
|
createBlock(skipBack - length);
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +224,7 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
|
|
||||||
void Parser::createNewlineBlock(bool fromOriginalText) {
|
void Parser::createNewlineBlock(bool fromOriginalText) {
|
||||||
if (!fromOriginalText) {
|
if (!fromOriginalText) {
|
||||||
++_t->_modifications[_t->_text.size()];
|
updateModifications(_t->_text.size(), 1);
|
||||||
}
|
}
|
||||||
_t->_text.push_back(QChar::LineFeed);
|
_t->_text.push_back(QChar::LineFeed);
|
||||||
_allowDiacritic = false;
|
_allowDiacritic = false;
|
||||||
|
|
@ -500,7 +501,7 @@ void Parser::parseCurrentChar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skip) {
|
if (skip) {
|
||||||
--_t->_modifications[_t->_text.size()];
|
updateModifications(_t->_text.size(), -1);
|
||||||
_ch = 0;
|
_ch = 0;
|
||||||
_allowDiacritic = false;
|
_allowDiacritic = false;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -549,7 +550,7 @@ void Parser::parseEmojiFromCurrent() {
|
||||||
Assert(!_t->_text.isEmpty());
|
Assert(!_t->_text.isEmpty());
|
||||||
const auto last = _t->_text[_t->_text.size() - 1];
|
const auto last = _t->_text[_t->_text.size() - 1];
|
||||||
if (last.unicode() != Emoji::kPostfix) {
|
if (last.unicode() != Emoji::kPostfix) {
|
||||||
++_t->_modifications[_t->_text.size()];
|
updateModifications(_t->_text.size(), 1);
|
||||||
_t->_text.push_back(QChar(Emoji::kPostfix));
|
_t->_text.push_back(QChar(Emoji::kPostfix));
|
||||||
++len;
|
++len;
|
||||||
}
|
}
|
||||||
|
|
@ -579,16 +580,22 @@ bool Parser::isLinkEntity(const EntityInText &entity) const {
|
||||||
return ranges::find(urls, type) != std::end(urls);
|
return ranges::find(urls, type) != std::end(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Parser::updateModifications(int index, int delta) {
|
||||||
|
_t->ensureExtended()->modifications[index] += delta;
|
||||||
|
}
|
||||||
|
|
||||||
void Parser::parse(const TextParseOptions &options) {
|
void Parser::parse(const TextParseOptions &options) {
|
||||||
skipBadEntities();
|
skipBadEntities();
|
||||||
trimSourceRange();
|
trimSourceRange();
|
||||||
|
|
||||||
_t->_text.resize(0);
|
_t->_text.resize(0);
|
||||||
_t->_modifications = {};
|
if (_t->_extended) {
|
||||||
|
base::take(_t->_extended->modifications);
|
||||||
|
}
|
||||||
_t->_text.reserve(_end - _ptr);
|
_t->_text.reserve(_end - _ptr);
|
||||||
|
|
||||||
if (_ptr > _start) {
|
if (_ptr > _start) {
|
||||||
_t->_modifications[0] = -(_ptr - _start);
|
updateModifications(0, -(_ptr - _start));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; _ptr <= _end; ++_ptr) {
|
for (; _ptr <= _end; ++_ptr) {
|
||||||
|
|
@ -631,7 +638,12 @@ void Parser::trimSourceRange() {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
void Parser::finalize(const TextParseOptions &options) {
|
void Parser::finalize(const TextParseOptions &options) {
|
||||||
_t->_links.resize(_maxLinkIndex + _maxShiftedLinkIndex);
|
auto links = (_maxLinkIndex || _maxShiftedLinkIndex)
|
||||||
|
? &_t->ensureExtended()->links
|
||||||
|
: nullptr;
|
||||||
|
if (links) {
|
||||||
|
links->resize(_maxLinkIndex + _maxShiftedLinkIndex);
|
||||||
|
}
|
||||||
auto counterCustomIndex = uint16(0);
|
auto counterCustomIndex = uint16(0);
|
||||||
auto currentIndex = uint16(0); // Current the latest index of _t->_links.
|
auto currentIndex = uint16(0); // Current the latest index of _t->_links.
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -687,8 +699,9 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (block->flags() & TextBlockFlag::Spoiler) {
|
if (block->flags() & TextBlockFlag::Spoiler) {
|
||||||
if (!_t->_spoiler.data) {
|
auto &spoiler = _t->ensureExtended()->spoiler;
|
||||||
_t->_spoiler.data = std::make_unique<SpoilerData>(
|
if (!spoiler) {
|
||||||
|
spoiler = std::make_unique<SpoilerData>(
|
||||||
Integration::Instance().createSpoilerRepaint(_context));
|
Integration::Instance().createSpoilerRepaint(_context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -709,7 +722,10 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
const auto handler = Integration::Instance().createLinkHandler(
|
const auto handler = Integration::Instance().createLinkHandler(
|
||||||
_monos[monoIndex - 1],
|
_monos[monoIndex - 1],
|
||||||
_context);
|
_context);
|
||||||
_t->_links.resize(currentIndex);
|
if (!links) {
|
||||||
|
links = &_t->ensureExtended()->links;
|
||||||
|
}
|
||||||
|
links->resize(currentIndex);
|
||||||
if (handler) {
|
if (handler) {
|
||||||
_t->setLink(currentIndex, handler);
|
_t->setLink(currentIndex, handler);
|
||||||
}
|
}
|
||||||
|
|
@ -740,7 +756,9 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
block->setLinkIndex(usedIndex());
|
block->setLinkIndex(usedIndex());
|
||||||
|
|
||||||
_t->_links.resize(std::max(usedIndex(), uint16(_t->_links.size())));
|
if (links) {
|
||||||
|
links->resize(std::max(usedIndex(), uint16(links->size())));
|
||||||
|
}
|
||||||
const auto handler = Integration::Instance().createLinkHandler(
|
const auto handler = Integration::Instance().createLinkHandler(
|
||||||
_links[realIndex - 1],
|
_links[realIndex - 1],
|
||||||
_context);
|
_context);
|
||||||
|
|
@ -749,10 +767,11 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
lastHandlerIndex.lnk = realIndex;
|
lastHandlerIndex.lnk = realIndex;
|
||||||
}
|
}
|
||||||
if (!_t->_hasCustomEmoji || _t->_spoiler.data) {
|
const auto hasSpoiler = (_t->_extended && _t->_extended->spoiler);
|
||||||
|
if (!_t->_hasCustomEmoji || hasSpoiler) {
|
||||||
_t->_isOnlyCustomEmoji = false;
|
_t->_isOnlyCustomEmoji = false;
|
||||||
}
|
}
|
||||||
if (_t->_blocks.empty() || _t->_spoiler.data) {
|
if (_t->_blocks.empty() || hasSpoiler) {
|
||||||
_t->_isIsolatedEmoji = false;
|
_t->_isIsolatedEmoji = false;
|
||||||
}
|
}
|
||||||
if (!_t->_hasNotEmojiAndSpaces && spacesCheckFrom != uint16(-1)) {
|
if (!_t->_hasNotEmojiAndSpaces && spacesCheckFrom != uint16(-1)) {
|
||||||
|
|
@ -765,10 +784,12 @@ void Parser::finalize(const TextParseOptions &options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_t->_links.squeeze();
|
|
||||||
_t->_blocks.shrink_to_fit();
|
|
||||||
_t->_text.squeeze();
|
_t->_text.squeeze();
|
||||||
_t->_modifications.shrink_to_fit();
|
_t->_blocks.shrink_to_fit();
|
||||||
|
if (const auto extended = _t->_extended.get()) {
|
||||||
|
extended->links.shrink_to_fit();
|
||||||
|
extended->modifications.shrink_to_fit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::computeLinkText(
|
void Parser::computeLinkText(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
#include "ui/text/text_block.h"
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
|
|
@ -80,6 +81,8 @@ private:
|
||||||
QString *outLinkText,
|
QString *outLinkText,
|
||||||
EntityLinkShown *outShown);
|
EntityLinkShown *outShown);
|
||||||
|
|
||||||
|
void updateModifications(int index, int delta);
|
||||||
|
|
||||||
const not_null<String*> _t;
|
const not_null<String*> _t;
|
||||||
const TextWithEntities _source;
|
const TextWithEntities _source;
|
||||||
const std::any &_context;
|
const std::any &_context;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
//
|
//
|
||||||
#include "ui/text/text_renderer.h"
|
#include "ui/text/text_renderer.h"
|
||||||
|
|
||||||
#include "ui/text/text_spoiler_data.h"
|
#include "ui/text/text_extended_data.h"
|
||||||
#include "styles/style_basic.h"
|
#include "styles/style_basic.h"
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
|
@ -155,7 +155,7 @@ bool Distinct(FixedRange a, FixedRange b) {
|
||||||
|
|
||||||
Renderer::Renderer(const Ui::Text::String &t)
|
Renderer::Renderer(const Ui::Text::String &t)
|
||||||
: _t(&t)
|
: _t(&t)
|
||||||
, _spoiler(_t->_spoiler.data.get()) {
|
, _spoiler(_t->_extended ? _t->_extended->spoiler.get() : nullptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer::~Renderer() {
|
Renderer::~Renderer() {
|
||||||
|
|
@ -229,7 +229,11 @@ void Renderer::enumerate() {
|
||||||
_startTop = _y;
|
_startTop = _y;
|
||||||
|
|
||||||
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
|
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
|
||||||
initNextParagraph(_t->_blocks.cbegin(), _t->_startDirection);
|
initNextParagraph(
|
||||||
|
_t->_blocks.cbegin(),
|
||||||
|
UnpackParagraphDirection(
|
||||||
|
_t->_startParagraphLTR,
|
||||||
|
_t->_startParagraphRTL));
|
||||||
}
|
}
|
||||||
|
|
||||||
_lineHeight = 0;
|
_lineHeight = 0;
|
||||||
|
|
@ -267,7 +271,7 @@ void Renderer::enumerate() {
|
||||||
|
|
||||||
initNextParagraph(
|
initNextParagraph(
|
||||||
i + 1,
|
i + 1,
|
||||||
static_cast<const NewlineBlock*>(b)->nextDirection());
|
static_cast<const NewlineBlock*>(b)->paragraphDirection());
|
||||||
|
|
||||||
longWordLine = true;
|
longWordLine = true;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -465,7 +469,7 @@ void Renderer::initNextLine() {
|
||||||
.left = 0,
|
.left = 0,
|
||||||
.top = (_y - _startTop),
|
.top = (_y - _startTop),
|
||||||
.width = _paragraphWidthRemaining.ceil().toInt(),
|
.width = _paragraphWidthRemaining.ceil().toInt(),
|
||||||
}, _lineStart);
|
});
|
||||||
_x = _startLeft + line.left;
|
_x = _startLeft + line.left;
|
||||||
_y = _startTop + line.top;
|
_y = _startTop + line.top;
|
||||||
_lineWidth = _wLeft = line.width;
|
_lineWidth = _wLeft = line.width;
|
||||||
|
|
@ -1415,7 +1419,9 @@ void Renderer::eSetFont(const AbstractBlock *block) {
|
||||||
? false
|
? false
|
||||||
: (underline == st::kLinkUnderlineActive)
|
: (underline == st::kLinkUnderlineActive)
|
||||||
? ((_palette && _palette->linkAlwaysActive)
|
? ((_palette && _palette->linkAlwaysActive)
|
||||||
|| ClickHandler::showAsActive(_t->_links.at(index - 1)))
|
|| ClickHandler::showAsActive(_t->_extended
|
||||||
|
? _t->_extended->links[index - 1]
|
||||||
|
: nullptr))
|
||||||
: true;
|
: true;
|
||||||
return underlined ? _t->_st->font->underline() : _t->_st->font;
|
return underlined ? _t->_st->font->underline() : _t->_st->font;
|
||||||
}
|
}
|
||||||
|
|
@ -2016,8 +2022,10 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
||||||
if (isMono
|
if (isMono
|
||||||
&& block->linkIndex()
|
&& block->linkIndex()
|
||||||
&& (!_background.spoiler || _spoiler->revealed)) {
|
&& (!_background.spoiler || _spoiler->revealed)) {
|
||||||
_background.selectActiveBlock = ClickHandler::showAsPressed(
|
const auto pressed = ClickHandler::showAsPressed(_t->_extended
|
||||||
_t->_links.at(block->linkIndex() - 1));
|
? _t->_extended->links[block->linkIndex() - 1]
|
||||||
|
: nullptr);
|
||||||
|
_background.selectActiveBlock = pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto color = block->colorIndex()) {
|
if (const auto color = block->colorIndex()) {
|
||||||
|
|
@ -2050,9 +2058,9 @@ ClickHandlerPtr Renderer::lookupLink(const AbstractBlock *block) const {
|
||||||
&& (block->flags() & TextBlockFlag::Spoiler))
|
&& (block->flags() & TextBlockFlag::Spoiler))
|
||||||
? _spoiler->link
|
? _spoiler->link
|
||||||
: ClickHandlerPtr();
|
: ClickHandlerPtr();
|
||||||
return (spoilerLink || !block->linkIndex())
|
return (spoilerLink || !block->linkIndex() || !_t->_extended)
|
||||||
? spoilerLink
|
? spoilerLink
|
||||||
: _t->_links.at(block->linkIndex() - 1);
|
: _t->_extended->links[block->linkIndex() - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ui::Text
|
} // namespace Ui::Text
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
#include "ui/text/text_block.h"
|
||||||
|
#include "ui/text/text_custom_emoji.h"
|
||||||
|
|
||||||
#include <private/qtextengine_p.h>
|
#include <private/qtextengine_p.h>
|
||||||
|
|
||||||
|
|
@ -15,6 +17,8 @@ struct QScriptLine;
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
|
struct AbstractBlock;
|
||||||
|
|
||||||
struct FixedRange {
|
struct FixedRange {
|
||||||
QFixed from;
|
QFixed from;
|
||||||
QFixed till;
|
QFixed till;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ Widget::Widget(QWidget *parent, const Config &config)
|
||||||
, _maxTextWidth(widthWithoutPadding(_st->maxWidth))
|
, _maxTextWidth(widthWithoutPadding(_st->maxWidth))
|
||||||
, _maxTextHeight(
|
, _maxTextHeight(
|
||||||
config.st->style.font->height * (_multiline ? config.maxLines : 1))
|
config.st->style.font->height * (_multiline ? config.maxLines : 1))
|
||||||
, _text(_multiline ? widthWithoutPadding(config.st->minWidth) : QFIXED_MAX)
|
, _text(_multiline ? widthWithoutPadding(config.st->minWidth) : kQFixedMax)
|
||||||
, _clickHandlerFilter(config.filter) {
|
, _clickHandlerFilter(config.filter) {
|
||||||
const auto toastOptions = TextParseOptions{
|
const auto toastOptions = TextParseOptions{
|
||||||
TextParseMultiline,
|
TextParseMultiline,
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,7 @@ int Checkbox::countTextMinWidth() const {
|
||||||
+ _st.textPosition.x();
|
+ _st.textPosition.x();
|
||||||
return (_st.width > 0)
|
return (_st.width > 0)
|
||||||
? std::max(_st.width - leftSkip, 1)
|
? std::max(_st.width - leftSkip, 1)
|
||||||
: QFIXED_MAX;
|
: kQFixedMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect Checkbox::checkRect() const {
|
QRect Checkbox::checkRect() const {
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ FlatLabel::FlatLabel(
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::PopupMenu &stMenu)
|
const style::PopupMenu &stMenu)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
|
, _text(st.minWidth ? st.minWidth : kQFixedMax)
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _stMenu(stMenu) {
|
, _stMenu(stMenu) {
|
||||||
init();
|
init();
|
||||||
|
|
@ -211,7 +211,7 @@ FlatLabel::FlatLabel(
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::PopupMenu &stMenu)
|
const style::PopupMenu &stMenu)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
|
, _text(st.minWidth ? st.minWidth : kQFixedMax)
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _stMenu(stMenu) {
|
, _stMenu(stMenu) {
|
||||||
setText(text);
|
setText(text);
|
||||||
|
|
@ -224,7 +224,7 @@ FlatLabel::FlatLabel(
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::PopupMenu &stMenu)
|
const style::PopupMenu &stMenu)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
|
, _text(st.minWidth ? st.minWidth : kQFixedMax)
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _stMenu(stMenu) {
|
, _stMenu(stMenu) {
|
||||||
textUpdated();
|
textUpdated();
|
||||||
|
|
@ -242,7 +242,7 @@ FlatLabel::FlatLabel(
|
||||||
const style::FlatLabel &st,
|
const style::FlatLabel &st,
|
||||||
const style::PopupMenu &stMenu)
|
const style::PopupMenu &stMenu)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _text(st.minWidth ? st.minWidth : QFIXED_MAX)
|
, _text(st.minWidth ? st.minWidth : kQFixedMax)
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _stMenu(stMenu)
|
, _stMenu(stMenu)
|
||||||
, _touchSelectTimer([=] { touchSelect(); }) {
|
, _touchSelectTimer([=] { touchSelect(); }) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "ui/widgets/time_input.h"
|
#include "ui/widgets/time_input.h"
|
||||||
|
|
||||||
#include "ui/widgets/fields/time_part_input.h"
|
#include "ui/widgets/fields/time_part_input.h"
|
||||||
|
#include "base/qt/qt_string_view.h"
|
||||||
#include "base/invoke_queued.h"
|
#include "base/invoke_queued.h"
|
||||||
|
|
||||||
#include <QtCore/QRegularExpression>
|
#include <QtCore/QRegularExpression>
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,8 @@ void Tooltip::paintEvent(QPaintEvent *e) {
|
||||||
p.fillRect(QRect(0, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder);
|
p.fillRect(QRect(0, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder);
|
||||||
p.fillRect(QRect(width() - st::lineWidth, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder);
|
p.fillRect(QRect(width() - st::lineWidth, st::lineWidth, st::lineWidth, height() - 2 * st::lineWidth), _st->textBorder);
|
||||||
}
|
}
|
||||||
int32 lines = qFloor((height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom()) / _st->textStyle.font->height);
|
const auto lines = (height() - 2 * st::lineWidth - _st->textPadding.top() - _st->textPadding.bottom())
|
||||||
|
/ _st->textStyle.font->height;
|
||||||
|
|
||||||
p.setPen(_st->textFg);
|
p.setPen(_st->textFg);
|
||||||
_text.drawElided(p, st::lineWidth + _st->textPadding.left(), st::lineWidth + _st->textPadding.top(), width() - 2 * st::lineWidth - _st->textPadding.left() - _st->textPadding.right(), lines);
|
_text.drawElided(p, st::lineWidth + _st->textPadding.left(), st::lineWidth + _st->textPadding.top(), width() - 2 * st::lineWidth - _st->textPadding.left() - _st->textPadding.right(), lines);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue