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