Improve custom fonts support in the InputField.
This commit is contained in:
parent
f76ddd841c
commit
41d1fe9232
2 changed files with 95 additions and 22 deletions
|
|
@ -698,16 +698,16 @@ QString AccumulateText(Iterator begin, Iterator end) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, const QFont &font) {
|
QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, int lineHeight) {
|
||||||
const auto factor = style::DevicePixelRatio();
|
const auto factor = style::DevicePixelRatio();
|
||||||
const auto size = Emoji::GetSizeNormal();
|
const auto size = Emoji::GetSizeNormal();
|
||||||
const auto width = size + st::emojiPadding * factor * 2;
|
const auto width = size + st::emojiPadding * factor * 2;
|
||||||
const auto height = std::max(QFontMetrics(font).height() * factor, size);
|
const auto height = std::max(lineHeight * factor, size);
|
||||||
auto result = QTextImageFormat();
|
auto result = QTextImageFormat();
|
||||||
result.setWidth(width / factor);
|
result.setWidth(width / factor);
|
||||||
result.setHeight(height / factor);
|
result.setHeight(height / factor);
|
||||||
result.setName(emoji->toUrl());
|
result.setName(emoji->toUrl());
|
||||||
result.setVerticalAlignment(QTextCharFormat::AlignBottom);
|
result.setVerticalAlignment(QTextCharFormat::AlignTop);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -747,6 +747,7 @@ QTextCharFormat PrepareTagFormat(
|
||||||
result.setProperty(
|
result.setProperty(
|
||||||
kCustomEmojiId,
|
kCustomEmojiId,
|
||||||
CustomEmojiIdFromLink(replaceWith));
|
CustomEmojiIdFromLink(replaceWith));
|
||||||
|
result.setVerticalAlignment(QTextCharFormat::AlignTop);
|
||||||
} else if (IsValidMarkdownLink(tag)) {
|
} else if (IsValidMarkdownLink(tag)) {
|
||||||
color = st::defaultTextPalette.linkFg;
|
color = st::defaultTextPalette.linkFg;
|
||||||
} else if (tag == kTagBold) {
|
} else if (tag == kTagBold) {
|
||||||
|
|
@ -919,6 +920,7 @@ struct FormattingAction {
|
||||||
RemoveTag,
|
RemoveTag,
|
||||||
RemoveNewline,
|
RemoveNewline,
|
||||||
ClearInstantReplace,
|
ClearInstantReplace,
|
||||||
|
FixLineHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
Type type = Type::Invalid;
|
Type type = Type::Invalid;
|
||||||
|
|
@ -1018,7 +1020,12 @@ private:
|
||||||
|
|
||||||
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji) {
|
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji) {
|
||||||
const auto currentFormat = cursor.charFormat();
|
const auto currentFormat = cursor.charFormat();
|
||||||
auto format = PrepareEmojiFormat(emoji, currentFormat.font());
|
const auto blockFormat = cursor.blockFormat();
|
||||||
|
const auto type = blockFormat.lineHeightType();
|
||||||
|
const auto height = (type == QTextBlockFormat::FixedHeight)
|
||||||
|
? blockFormat.lineHeight()
|
||||||
|
: QFontMetrics(cursor.charFormat().font()).height();
|
||||||
|
auto format = PrepareEmojiFormat(emoji, height);
|
||||||
ApplyTagFormat(format, currentFormat);
|
ApplyTagFormat(format, currentFormat);
|
||||||
cursor.insertText(kObjectReplacement, format);
|
cursor.insertText(kObjectReplacement, format);
|
||||||
}
|
}
|
||||||
|
|
@ -1035,7 +1042,7 @@ void InsertCustomEmojiAtCursor(
|
||||||
format.setProperty(kCustomEmojiText, text);
|
format.setProperty(kCustomEmojiText, text);
|
||||||
format.setProperty(kCustomEmojiLink, unique);
|
format.setProperty(kCustomEmojiLink, unique);
|
||||||
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
|
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
|
||||||
format.setVerticalAlignment(QTextCharFormat::AlignBottom);
|
format.setVerticalAlignment(QTextCharFormat::AlignTop);
|
||||||
format.setFont(field->st().font);
|
format.setFont(field->st().font);
|
||||||
format.setForeground(field->st().textFg);
|
format.setForeground(field->st().textFg);
|
||||||
format.setBackground(QBrush());
|
format.setBackground(QBrush());
|
||||||
|
|
@ -1097,8 +1104,12 @@ const InstantReplaces &InstantReplaces::TextOnly() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomEmojiObject::CustomEmojiObject(Factory factory, Fn<bool()> paused)
|
CustomEmojiObject::CustomEmojiObject(
|
||||||
: _factory(std::move(factory))
|
const style::font &font,
|
||||||
|
Factory factory,
|
||||||
|
Fn<bool()> paused)
|
||||||
|
: _font(font)
|
||||||
|
, _factory(std::move(factory))
|
||||||
, _paused(std::move(paused))
|
, _paused(std::move(paused))
|
||||||
, _now(crl::now()) {
|
, _now(crl::now()) {
|
||||||
}
|
}
|
||||||
|
|
@ -1118,8 +1129,7 @@ QSizeF CustomEmojiObject::intrinsicSize(
|
||||||
const QTextFormat &format) {
|
const QTextFormat &format) {
|
||||||
const auto size = st::emojiSize * 1.;
|
const auto size = st::emojiSize * 1.;
|
||||||
const auto width = size + st::emojiPadding * 2.;
|
const auto width = size + st::emojiPadding * 2.;
|
||||||
const auto font = format.toCharFormat().font();
|
const auto height = std::max(_font->height * 1., size);
|
||||||
const auto height = std::min(QFontMetrics(font).height() * 1., size);
|
|
||||||
if (!_skip) {
|
if (!_skip) {
|
||||||
const auto emoji = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
|
const auto emoji = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
|
||||||
_skip = (st::emojiSize - emoji) / 2;
|
_skip = (st::emojiSize - emoji) / 2;
|
||||||
|
|
@ -1211,6 +1221,30 @@ InputField::InputField(
|
||||||
_inner->setAcceptRichText(false);
|
_inner->setAcceptRichText(false);
|
||||||
resize(_st.width, _minHeight);
|
resize(_st.width, _minHeight);
|
||||||
|
|
||||||
|
{ // In case of default fonts all those should be zero.
|
||||||
|
const auto metrics = QFontMetricsF(_st.font->f);
|
||||||
|
const auto realAscent = int(base::SafeRound(metrics.ascent()));
|
||||||
|
const auto ascentAdd = _st.font->ascent - realAscent;
|
||||||
|
//const auto realHeight = int(base::SafeRound(metrics.height()));
|
||||||
|
//const auto heightAdd = _st.font->height - realHeight - ascentAdd;
|
||||||
|
//_customFontMargins = QMargins(0, ascentAdd, 0, heightAdd);
|
||||||
|
_customFontMargins = QMargins(0, ascentAdd, 0, -ascentAdd);
|
||||||
|
// We move _inner down by ascentAdd for the first line to look
|
||||||
|
// at the same vertical position as in the default font.
|
||||||
|
//
|
||||||
|
// But we don't want to get vertical scroll in case the field
|
||||||
|
// fits pixel-perfect with the default font, so we allow the
|
||||||
|
// bottom margin to be the same shift, but negative.
|
||||||
|
|
||||||
|
if (_mode != Mode::SingleLine) {
|
||||||
|
const auto metrics = QFontMetricsF(_st.font->f);
|
||||||
|
const auto leading = qMax(metrics.leading(), qreal(0.0));
|
||||||
|
const auto adjustment = (metrics.ascent() + leading)
|
||||||
|
- ((_st.font->height * 4) / 5);
|
||||||
|
_placeholderCustomFontSkip = int(base::SafeRound(-adjustment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_st.textBg->c.alphaF() >= 1.
|
if (_st.textBg->c.alphaF() >= 1.
|
||||||
&& !_st.borderRadius) {
|
&& !_st.borderRadius) {
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
@ -1231,11 +1265,21 @@ InputField::InputField(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
updatePalette();
|
updatePalette();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
{
|
||||||
|
auto cursor = _inner->textCursor();
|
||||||
|
|
||||||
_defaultCharFormat = _inner->textCursor().charFormat();
|
_defaultCharFormat = cursor.charFormat();
|
||||||
updatePalette();
|
updatePalette();
|
||||||
_inner->textCursor().setCharFormat(_defaultCharFormat);
|
cursor.setCharFormat(_defaultCharFormat);
|
||||||
|
|
||||||
|
_defaultBlockFormat = cursor.blockFormat();
|
||||||
|
_defaultBlockFormat.setLineHeight(
|
||||||
|
_st.font->height,
|
||||||
|
QTextBlockFormat::FixedHeight);
|
||||||
|
cursor.setBlockFormat(_defaultBlockFormat);
|
||||||
|
|
||||||
|
_inner->setTextCursor(cursor);
|
||||||
|
}
|
||||||
_inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
_inner->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
_inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
_inner->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
|
||||||
|
|
@ -1277,6 +1321,7 @@ InputField::InputField(
|
||||||
auto cursor = textCursor();
|
auto cursor = textCursor();
|
||||||
if (!cursor.hasSelection() && !cursor.position()) {
|
if (!cursor.hasSelection() && !cursor.position()) {
|
||||||
cursor.setCharFormat(_defaultCharFormat);
|
cursor.setCharFormat(_defaultCharFormat);
|
||||||
|
cursor.setBlockFormat(_defaultBlockFormat);
|
||||||
setTextCursor(cursor);
|
setTextCursor(cursor);
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
@ -1435,7 +1480,7 @@ void InputField::setTagMimeProcessor(Fn<QString(QStringView)> processor) {
|
||||||
void InputField::setCustomEmojiFactory(
|
void InputField::setCustomEmojiFactory(
|
||||||
CustomEmojiFactory factory,
|
CustomEmojiFactory factory,
|
||||||
Fn<bool()> paused) {
|
Fn<bool()> paused) {
|
||||||
_customEmojiObject = std::make_unique<CustomEmojiObject>([=](
|
_customEmojiObject = std::make_unique<CustomEmojiObject>(_st.font, [=](
|
||||||
QStringView data) {
|
QStringView data) {
|
||||||
return factory(data, [=] { customEmojiRepaint(); });
|
return factory(data, [=] { customEmojiRepaint(); });
|
||||||
}, std::move(paused));
|
}, std::move(paused));
|
||||||
|
|
@ -1740,6 +1785,13 @@ void InputField::paintEvent(QPaintEvent *e) {
|
||||||
const auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
|
const auto focusedDegree = _a_focused.value(_focused ? 1. : 0.);
|
||||||
paintSurrounding(p, r, errorDegree, focusedDegree);
|
paintSurrounding(p, r, errorDegree, focusedDegree);
|
||||||
|
|
||||||
|
const auto skip = int(base::SafeRound(_inner->document()->documentMargin()));
|
||||||
|
const auto margins = _st.textMargins
|
||||||
|
+ _st.placeholderMargins
|
||||||
|
+ QMargins(skip, skip + _placeholderCustomFontSkip, skip, 0)
|
||||||
|
+ _additionalMargins
|
||||||
|
+ _customFontMargins;
|
||||||
|
|
||||||
if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) {
|
if (_st.placeholderScale > 0. && !_placeholderPath.isEmpty()) {
|
||||||
auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
|
auto placeholderShiftDegree = _a_placeholderShifted.value(_placeholderShifted ? 1. : 0.);
|
||||||
p.save();
|
p.save();
|
||||||
|
|
@ -1747,7 +1799,7 @@ void InputField::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree);
|
auto placeholderTop = anim::interpolate(0, _st.placeholderShift, placeholderShiftDegree);
|
||||||
|
|
||||||
QRect r(rect().marginsRemoved(_st.textMargins + _st.placeholderMargins));
|
QRect r(rect().marginsRemoved(margins));
|
||||||
r.moveTop(r.top() + placeholderTop);
|
r.moveTop(r.top() + placeholderTop);
|
||||||
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
||||||
|
|
||||||
|
|
@ -1774,15 +1826,14 @@ void InputField::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
p.setFont(_st.placeholderFont);
|
p.setFont(_st.placeholderFont);
|
||||||
p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
|
p.setPen(anim::pen(_st.placeholderFg, _st.placeholderFgActive, focusedDegree));
|
||||||
|
|
||||||
if (_st.placeholderAlign == style::al_topleft && _placeholderAfterSymbols > 0) {
|
if (_st.placeholderAlign == style::al_topleft && _placeholderAfterSymbols > 0) {
|
||||||
const auto skipWidth = placeholderSkipWidth();
|
const auto skipWidth = placeholderSkipWidth();
|
||||||
p.drawText(
|
p.drawText(
|
||||||
_st.textMargins.left() + _st.placeholderMargins.left() + skipWidth,
|
margins.left() + skipWidth,
|
||||||
_st.textMargins.top() + _st.placeholderMargins.top() + _st.placeholderFont->ascent,
|
margins.top() + _st.placeholderFont->ascent,
|
||||||
_placeholder);
|
_placeholder);
|
||||||
} else {
|
} else {
|
||||||
auto r = rect().marginsRemoved(_st.textMargins + _st.placeholderMargins);
|
auto r = rect().marginsRemoved(margins);
|
||||||
r.moveLeft(r.left() + placeholderLeft);
|
r.moveLeft(r.left() + placeholderLeft);
|
||||||
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
if (style::RightToLeft()) r.moveLeft(width() - r.left() - r.width());
|
||||||
p.drawText(r, _placeholder, _st.placeholderAlign);
|
p.drawText(r, _placeholder, _st.placeholderAlign);
|
||||||
|
|
@ -2117,6 +2168,10 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
|
||||||
if (tillBlock.isValid()) tillBlock = tillBlock.next();
|
if (tillBlock.isValid()) tillBlock = tillBlock.next();
|
||||||
|
|
||||||
for (auto block = fromBlock; block != tillBlock; block = block.next()) {
|
for (auto block = fromBlock; block != tillBlock; block = block.next()) {
|
||||||
|
if (block.blockFormat().lineHeightType() != QTextBlockFormat::FixedHeight) {
|
||||||
|
action.type = ActionType::FixLineHeight;
|
||||||
|
break;
|
||||||
|
}
|
||||||
for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) {
|
for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) {
|
||||||
auto fragment = fragmentIt.fragment();
|
auto fragment = fragmentIt.fragment();
|
||||||
Assert(fragment.isValid());
|
Assert(fragment.isValid());
|
||||||
|
|
@ -2298,6 +2353,11 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
|
||||||
PrepareFormattingOptimization(document);
|
PrepareFormattingOptimization(document);
|
||||||
|
|
||||||
auto cursor = QTextCursor(document);
|
auto cursor = QTextCursor(document);
|
||||||
|
if (action.type == ActionType::FixLineHeight) {
|
||||||
|
cursor.select(QTextCursor::Document);
|
||||||
|
cursor.setBlockFormat(_defaultBlockFormat);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
cursor.setPosition(action.intervalStart);
|
cursor.setPosition(action.intervalStart);
|
||||||
cursor.setPosition(action.intervalEnd, QTextCursor::KeepAnchor);
|
cursor.setPosition(action.intervalEnd, QTextCursor::KeepAnchor);
|
||||||
if (action.type == ActionType::InsertEmoji
|
if (action.type == ActionType::InsertEmoji
|
||||||
|
|
@ -2627,6 +2687,7 @@ void InputField::setTextWithTags(
|
||||||
_realCharsAdded = textWithTags.text.size();
|
_realCharsAdded = textWithTags.text.size();
|
||||||
const auto document = _inner->document();
|
const auto document = _inner->document();
|
||||||
auto cursor = QTextCursor(document);
|
auto cursor = QTextCursor(document);
|
||||||
|
cursor.setBlockFormat(_defaultBlockFormat);
|
||||||
if (historyAction == HistoryAction::Clear) {
|
if (historyAction == HistoryAction::Clear) {
|
||||||
document->setUndoRedoEnabled(false);
|
document->setUndoRedoEnabled(false);
|
||||||
cursor.beginEditBlock();
|
cursor.beginEditBlock();
|
||||||
|
|
@ -3303,7 +3364,7 @@ void InputField::commitInstantReplacement(
|
||||||
result.setProperty(kCustomEmojiText, with);
|
result.setProperty(kCustomEmojiText, with);
|
||||||
result.setProperty(kCustomEmojiLink, unique);
|
result.setProperty(kCustomEmojiLink, unique);
|
||||||
result.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
|
result.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
|
||||||
result.setVerticalAlignment(QTextCharFormat::AlignBottom);
|
result.setVerticalAlignment(QTextCharFormat::AlignTop);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
const auto use = Integration::Instance().defaultEmojiVariant(
|
const auto use = Integration::Instance().defaultEmojiVariant(
|
||||||
|
|
@ -3567,6 +3628,7 @@ void InputField::commitMarkdownLinkEdit(
|
||||||
|
|
||||||
_reverseMarkdownReplacement = false;
|
_reverseMarkdownReplacement = false;
|
||||||
cursor.setCharFormat(_defaultCharFormat);
|
cursor.setCharFormat(_defaultCharFormat);
|
||||||
|
cursor.setBlockFormat(_defaultBlockFormat);
|
||||||
_inner->setTextCursor(cursor);
|
_inner->setTextCursor(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3897,14 +3959,18 @@ void InputField::insertFromMimeDataInner(const QMimeData *source) {
|
||||||
void InputField::resizeEvent(QResizeEvent *e) {
|
void InputField::resizeEvent(QResizeEvent *e) {
|
||||||
refreshPlaceholder(_placeholderFull.current());
|
refreshPlaceholder(_placeholderFull.current());
|
||||||
_inner->setGeometry(rect().marginsRemoved(
|
_inner->setGeometry(rect().marginsRemoved(
|
||||||
_st.textMargins + _additionalMargins));
|
_st.textMargins + _additionalMargins + _customFontMargins));
|
||||||
_borderAnimationStart = width() / 2;
|
_borderAnimationStart = width() / 2;
|
||||||
RpWidget::resizeEvent(e);
|
RpWidget::resizeEvent(e);
|
||||||
checkContentHeight();
|
checkContentHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::refreshPlaceholder(const QString &text) {
|
void InputField::refreshPlaceholder(const QString &text) {
|
||||||
const auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right();
|
const auto margins = _st.textMargins
|
||||||
|
+ _st.placeholderMargins
|
||||||
|
+ _additionalMargins
|
||||||
|
+ _customFontMargins;
|
||||||
|
const auto availableWidth = rect().marginsRemoved(margins).width();
|
||||||
if (_st.placeholderScale > 0.) {
|
if (_st.placeholderScale > 0.) {
|
||||||
auto placeholderFont = _st.placeholderFont->f;
|
auto placeholderFont = _st.placeholderFont->f;
|
||||||
placeholderFont.setStyleStrategy(QFont::PreferMatch);
|
placeholderFont.setStyleStrategy(QFont::PreferMatch);
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,10 @@ class CustomEmojiObject : public QObject, public QTextObjectInterface {
|
||||||
public:
|
public:
|
||||||
using Factory = Fn<std::unique_ptr<Text::CustomEmoji>(QStringView)>;
|
using Factory = Fn<std::unique_ptr<Text::CustomEmoji>(QStringView)>;
|
||||||
|
|
||||||
CustomEmojiObject(Factory factory, Fn<bool()> paused);
|
CustomEmojiObject(
|
||||||
|
const style::font &font,
|
||||||
|
Factory factory,
|
||||||
|
Fn<bool()> paused);
|
||||||
~CustomEmojiObject();
|
~CustomEmojiObject();
|
||||||
|
|
||||||
void *qt_metacast(const char *iid) override;
|
void *qt_metacast(const char *iid) override;
|
||||||
|
|
@ -97,6 +100,7 @@ public:
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const style::font _font;
|
||||||
Factory _factory;
|
Factory _factory;
|
||||||
Fn<bool()> _paused;
|
Fn<bool()> _paused;
|
||||||
base::flat_map<uint64, std::unique_ptr<Text::CustomEmoji>> _emoji;
|
base::flat_map<uint64, std::unique_ptr<Text::CustomEmoji>> _emoji;
|
||||||
|
|
@ -495,6 +499,8 @@ private:
|
||||||
std::optional<QString> _inputMethodCommit;
|
std::optional<QString> _inputMethodCommit;
|
||||||
|
|
||||||
QMargins _additionalMargins;
|
QMargins _additionalMargins;
|
||||||
|
QMargins _customFontMargins;
|
||||||
|
int _placeholderCustomFontSkip = 0;
|
||||||
|
|
||||||
bool _forcePlaceholderHidden = false;
|
bool _forcePlaceholderHidden = false;
|
||||||
bool _reverseMarkdownReplacement = false;
|
bool _reverseMarkdownReplacement = false;
|
||||||
|
|
@ -557,6 +563,7 @@ private:
|
||||||
base::unique_qptr<PopupMenu> _contextMenu;
|
base::unique_qptr<PopupMenu> _contextMenu;
|
||||||
|
|
||||||
QTextCharFormat _defaultCharFormat;
|
QTextCharFormat _defaultCharFormat;
|
||||||
|
QTextBlockFormat _defaultBlockFormat;
|
||||||
|
|
||||||
rpl::variable<int> _scrollTop;
|
rpl::variable<int> _scrollTop;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue