From a6eae0aa27bcb93e3a3ce02d566c5753017fefcb Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Dec 2021 21:54:51 +0400 Subject: [PATCH 01/35] Improve color in the default popup menu. --- ui/colors.palette | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/colors.palette b/ui/colors.palette index 4491872..d78cf94 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -55,7 +55,7 @@ menuBgOver: windowBgOver; // default popup menu item background with mouse over menuBgRipple: windowBgRipple; // default popup menu item ripple effect menuIconFg: #a8a8a8; // default popup menu item icon (like main menu) menuIconFgOver: #999999; // default popup menu item icon with mouse over -menuSubmenuArrowFg: #373737; // default popup menu submenu arrow icon (like in message field context menu in case of RTL system language) +menuSubmenuArrowFg: #666B72; // default popup menu submenu arrow icon (like in message field context menu in case of RTL system language) menuFgDisabled: #cccccc; // default popup menu item disabled text (like unavailable items in message field context menu) menuSeparatorFg: #f1f1f1; // default popup menu separator (like in message field context menu) From 7efa1f9b2b11b3b2134cebf701fb1bcabe073195 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Dec 2021 21:55:19 +0400 Subject: [PATCH 02/35] Use base::unique_qptr for PopupMenu-s. --- ui/widgets/input_fields.cpp | 8 +++++--- ui/widgets/input_fields.h | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index 64c7cf2..5c23eaf 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -1160,8 +1160,9 @@ void FlatInput::refreshPlaceholder(const QString &text) { } void FlatInput::contextMenuEvent(QContextMenuEvent *e) { - if (auto menu = createStandardContextMenu()) { - (new PopupMenu(this, menu))->popup(e->globalPos()); + if (const auto menu = createStandardContextMenu()) { + _contextMenu = base::make_unique_q(this, menu); + _contextMenu->popup(e->globalPos()); } } @@ -4024,7 +4025,8 @@ void MaskedInputField::setPlaceholder(rpl::producer placeholder) { void MaskedInputField::contextMenuEvent(QContextMenuEvent *e) { if (const auto menu = createStandardContextMenu()) { - (new PopupMenu(this, menu))->popup(e->globalPos()); + _contextMenu = base::make_unique_q(this, menu); + _contextMenu->popup(e->globalPos()); } } diff --git a/ui/widgets/input_fields.h b/ui/widgets/input_fields.h index 1426c07..5638c59 100644 --- a/ui/widgets/input_fields.h +++ b/ui/widgets/input_fields.h @@ -137,6 +137,9 @@ private: QTimer _touchTimer; bool _touchPress, _touchRightButton, _touchMove; QPoint _touchStart; + + base::unique_qptr _contextMenu; + }; class InputField : public RpWidget { @@ -696,6 +699,9 @@ private: bool _touchRightButton = false; bool _touchMove = false; QPoint _touchStart; + + base::unique_qptr _contextMenu; + }; class PasswordInput : public MaskedInputField { From 6ac846adbb5e8478bae200244147b5f39fd902e8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 9 Dec 2021 21:55:35 +0400 Subject: [PATCH 03/35] Allow adding submenu actions with icons. --- ui/widgets/menu/menu.cpp | 6 ++++-- ui/widgets/menu/menu.h | 4 +++- ui/widgets/popup_menu.cpp | 16 +++++++++++++--- ui/widgets/popup_menu.h | 12 ++++++++++-- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 8b7a90e..1460114 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -68,10 +68,12 @@ not_null Menu::addAction( not_null Menu::addAction( const QString &text, - std::unique_ptr submenu) { + std::unique_ptr submenu, + const style::icon *icon, + const style::icon *iconOver) { const auto action = new QAction(text, this); action->setMenu(submenu.release()); - return addAction(action, nullptr, nullptr); + return addAction(action, icon, iconOver); } not_null Menu::addAction( diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index 0af9898..3aa0b05 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -40,7 +40,9 @@ public: const style::icon *iconOver = nullptr); not_null addAction( const QString &text, - std::unique_ptr submenu); + std::unique_ptr submenu, + const style::icon *icon = nullptr, + const style::icon *iconOver = nullptr); not_null addSeparator(); void clearActions(); void finishAnimating(); diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index 37faaaa..308fed7 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -357,14 +357,24 @@ not_null PopupMenu::addAction( return _menu->addAction(std::move(widget)); } -not_null PopupMenu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { +not_null PopupMenu::addAction( + const QString &text, + Fn callback, + const style::icon *icon, + const style::icon *iconOver) { return _menu->addAction(text, std::move(callback), icon, iconOver); } not_null PopupMenu::addAction( const QString &text, - std::unique_ptr submenu) { - const auto action = _menu->addAction(text, std::make_unique()); + std::unique_ptr submenu, + const style::icon *icon, + const style::icon *iconOver) { + const auto action = _menu->addAction( + text, + std::make_unique(), + icon, + iconOver); const auto saved = _submenus.emplace( action, base::unique_qptr(submenu.release()) diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index 510015e..26937dd 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -29,8 +29,16 @@ public: } not_null addAction(base::unique_qptr widget); - not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); - not_null addAction(const QString &text, std::unique_ptr submenu); + not_null addAction( + const QString &text, + Fn callback, + const style::icon *icon = nullptr, + const style::icon *iconOver = nullptr); + not_null addAction( + const QString &text, + std::unique_ptr submenu, + const style::icon *icon = nullptr, + const style::icon *iconOver = nullptr); not_null addSeparator(); void clearActions(); From 9c7fe328303043309fb1c3de017714945148b15f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 8 Dec 2021 05:23:40 +0400 Subject: [PATCH 04/35] Use winId to get window handle on Windows There's no need to call QPlatformNativeInterface for that --- ui/platform/win/ui_window_win.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp index b48948e..e4c57e7 100644 --- a/ui/platform/win/ui_window_win.cpp +++ b/ui/platform/win/ui_window_win.cpp @@ -740,16 +740,7 @@ HWND GetWindowHandle(not_null widget) { } HWND GetWindowHandle(not_null window) { - if (!window->winId()) { - window->create(); - } - - const auto native = QGuiApplication::platformNativeInterface(); - Assert(native != nullptr); - - return static_cast(native->nativeResourceForWindow( - QByteArrayLiteral("handle"), - window)); + return static_cast(window->winId()); } void SendWMPaintForce(not_null widget) { From 692fd7a3a28b539f39d553c926bba9d4b67dbbc1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 8 Dec 2021 05:25:04 +0400 Subject: [PATCH 05/35] Use generic XCB::GetRootWindow instead of QPlatformNativeInterface-based GetRootWindowFromQt --- ui/platform/linux/ui_utility_linux.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index 2a7fb2d..240fc0c 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -132,7 +132,7 @@ std::optional XCBCurrentWorkspace() { return std::nullopt; } - const auto root = base::Platform::XCB::GetRootWindowFromQt(); + const auto root = base::Platform::XCB::GetRootWindow(connection); if (!root.has_value()) { return std::nullopt; } @@ -220,7 +220,7 @@ std::optional XCBIsOverlapped( return std::nullopt; } - const auto root = base::Platform::XCB::GetRootWindowFromQt(); + const auto root = base::Platform::XCB::GetRootWindow(connection); if (!root.has_value()) { return std::nullopt; } @@ -345,7 +345,7 @@ bool ShowXCBWindowMenu(QWindow *window) { return false; } - const auto root = base::Platform::XCB::GetRootWindowFromQt(); + const auto root = base::Platform::XCB::GetRootWindow(connection); if (!root.has_value()) { return false; } From 5195b7b45f0df6fefcfbc699bf3bd5aa10493f80 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 8 Dec 2021 05:26:28 +0400 Subject: [PATCH 06/35] Use XCB instead of QPlatformNativeInterface to get state of translucent windows support --- ui/platform/linux/ui_utility_linux.cpp | 45 +++++++++++++++----------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index 240fc0c..f55ed32 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -7,10 +7,8 @@ #include "ui/platform/linux/ui_utility_linux.h" #include "base/platform/base_platform_info.h" -#include "base/debug_log.h" #include "ui/platform/linux/ui_linux_wayland_integration.h" #include "base/const_string.h" -#include "base/flat_set.h" #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION #include "base/platform/linux/base_linux_glibmm_helper.h" @@ -23,10 +21,8 @@ #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION #include -#include #include #include -#include namespace Ui { namespace Platform { @@ -431,23 +427,36 @@ bool TranslucentWindowsSupported(QPoint globalPosition) { return true; } +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION if (::Platform::IsX11()) { - if (const auto native = QGuiApplication::platformNativeInterface()) { - if (const auto screen = QGuiApplication::screenAt(globalPosition)) { - if (native->nativeResourceForScreen(QByteArray("compositingEnabled"), screen)) { - return true; - } - const auto index = QGuiApplication::screens().indexOf(screen); - static auto WarnedAbout = base::flat_set(); - if (!WarnedAbout.contains(index)) { - WarnedAbout.emplace(index); - LOG(("WARNING: Compositing is disabled for screen index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y())); - } - } else { - LOG(("WARNING: Could not get screen for position %1,%2").arg(globalPosition.x()).arg(globalPosition.y())); - } + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return false; } + + const auto atom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_CM_S0"); + + if (!atom) { + return false; + } + + const auto cookie = xcb_get_selection_owner(connection, *atom); + + const auto result = base::Platform::XCB::MakeReplyPointer( + xcb_get_selection_owner_reply( + connection, + cookie, + nullptr)); + + if (!result) { + return false; + } + + return result->owner; } +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION return false; } From 26a8f46634a56fe7397d7e15b717a727dc0df41f Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 16 Dec 2021 04:54:49 +0400 Subject: [PATCH 07/35] Fix global scale reset with custom platformthemes At least Kvantum makes QGuiApplication constructor to call QGuiApplication::devicePixelRatio, so the value is cached and won't change. Resetting the cache fixes the issue. --- ui/ui_utility.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/ui_utility.cpp b/ui/ui_utility.cpp index 5fef2af..e66055d 100644 --- a/ui/ui_utility.cpp +++ b/ui/ui_utility.cpp @@ -13,6 +13,8 @@ #include #include #include + +#include #include #include @@ -214,6 +216,7 @@ bool IsContentVisible( void DisableCustomScaling() { QHighDpiScaling::setGlobalFactor(1); + QGuiApplicationPrivate::resetCachedDevicePixelRatio(); } int WheelDirection(not_null e) { From eaea768ca03cc3c12e880c3f3f3aa15736925d56 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 22 Dec 2021 10:22:50 +0000 Subject: [PATCH 08/35] Fix HWND retrieval on Windows. --- ui/platform/win/ui_window_win.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp index e4c57e7..c0d78dd 100644 --- a/ui/platform/win/ui_window_win.cpp +++ b/ui/platform/win/ui_window_win.cpp @@ -740,7 +740,7 @@ HWND GetWindowHandle(not_null widget) { } HWND GetWindowHandle(not_null window) { - return static_cast(window->winId()); + return reinterpret_cast(window->winId()); } void SendWMPaintForce(not_null widget) { From c06f0435c40dd6c987413337f108e468cfe8bfc9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 19 Dec 2021 12:59:05 +0300 Subject: [PATCH 09/35] Moved splitting of tags to separated method. --- ui/text/text_entity.cpp | 14 ++++++++++---- ui/text/text_entity.h | 1 + ui/widgets/input_fields.cpp | 10 +++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 614d967..00cfcf3 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -22,6 +22,8 @@ namespace TextUtilities { namespace { +constexpr auto kTagSeparator = '|'; + using namespace Ui::Text; QString ExpressionMailNameAtEnd() { @@ -2041,17 +2043,21 @@ QString JoinTag(const QList &list) { result.append(list.front()); for (auto i = 1, count = int(list.size()); i != count; ++i) { if (!IsSeparateTag(list[i])) { - result.append('|').append(list[i]); + result.append(kTagSeparator).append(list[i]); } } return result; } +QList SplitTags(const QString &tag) { + return QStringView(tag).split(kTagSeparator); +} + QString TagWithRemoved(const QString &tag, const QString &removed) { if (tag == removed) { return QString(); } - auto list = QStringView(tag).split('|'); + auto list = SplitTags(tag); list.erase(ranges::remove(list, QStringView(removed)), list.end()); return JoinTag(list); } @@ -2060,7 +2066,7 @@ QString TagWithAdded(const QString &tag, const QString &added) { if (tag.isEmpty() || tag == added) { return added; } - auto list = QStringView(tag).split('|'); + auto list = SplitTags(tag); const auto ref = QStringView(added); if (list.contains(ref)) { return tag; @@ -2173,7 +2179,7 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { }; const auto stateForTag = [&](const QString &tag) { auto result = State(); - const auto list = QStringView(tag).split('|'); + const auto list = SplitTags(tag); for (const auto &single : list) { if (single == Ui::InputField::kTagBold) { result.set(EntityType::Bold); diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index cee3a7d..db5b598 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -363,6 +363,7 @@ inline const auto kMentionTagStart = qstr("mention://user."); [[nodiscard]] bool IsMentionLink(QStringView link); [[nodiscard]] bool IsSeparateTag(QStringView tag); [[nodiscard]] QString JoinTag(const QList &list); +[[nodiscard]] QList SplitTags(const QString &tag); [[nodiscard]] QString TagWithRemoved( const QString &tag, const QString &removed); diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index 5c23eaf..98cb7f1 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -125,7 +125,7 @@ bool IsNewline(QChar ch) { return QString(); } auto found = false; - for (const auto &single : QStringView(existing.id).split('|')) { + for (const auto &single : TextUtilities::SplitTags(existing.id)) { const auto normalized = (single == QStringView(kTagPre)) ? QStringView(kTagCode) : single; @@ -718,7 +718,7 @@ QTextCharFormat PrepareTagFormat( font = font->monospace(); } }; - for (const auto &tag : QStringView(tag).split('|')) { + for (const auto &tag : TextUtilities::SplitTags(tag)) { applyOne(tag); } result.setFont(font); @@ -2903,8 +2903,8 @@ auto InputField::selectionEditLinkData(EditLinkSelection selection) const }; const auto stateTagHasLink = [&](const State &state) { const auto tag = stateTag(state); - return (tag == link) || QStringView(tag).split('|').contains( - QStringView(link)); + return (tag == link) + || TextUtilities::SplitTags(tag).contains(QStringView(link)); }; const auto stateStart = [&](const State &state) { return state.i.fragment().position(); @@ -3112,7 +3112,7 @@ void InputField::commitInstantReplacement( const auto currentTag = cursor.charFormat().property( kTagProperty ).toString(); - const auto currentTags = QStringView(currentTag).split('|'); + const auto currentTags = TextUtilities::SplitTags(currentTag); if (currentTags.contains(QStringView(kTagPre)) || currentTags.contains(QStringView(kTagCode))) { return; From 8aeb6def8871c2a9887d35a13b82653dd6b08f08 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 18 Dec 2021 15:13:49 +0300 Subject: [PATCH 10/35] Moved out AbstractBlock implementation from header file. --- ui/text/text_block.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++ ui/text/text_block.h | 41 +++++++++--------------------------- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index bc34603..ced3aca 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -340,6 +340,53 @@ bool BlockParser::isLineBreak( return lineBreak; } +AbstractBlock::AbstractBlock( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex) +: _from(from) +, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) { +} + +uint16 AbstractBlock::from() const { + return _from; +} + +int AbstractBlock::width() const { + return _width.toInt(); +} + +int AbstractBlock::rpadding() const { + return _rpadding.toInt(); +} + +QFixed AbstractBlock::f_width() const { + return _width; +} + +QFixed AbstractBlock::f_rpadding() const { + return _rpadding; +} + +uint16 AbstractBlock::lnkIndex() const { + return (_flags >> 12) & 0xFFFF; +} + +void AbstractBlock::setLnkIndex(uint16 lnkIndex) { + _flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12); +} + +TextBlockType AbstractBlock::type() const { + return TextBlockType((_flags >> 8) & 0x0F); +} + +int32 AbstractBlock::flags() const { + return (_flags & 0xFF); +} + QFixed AbstractBlock::f_rbearing() const { return (type() == TextBlockTText) ? static_cast(this)->real_f_rbearing() diff --git a/ui/text/text_block.h b/ui/text/text_block.h index 5f99108..a8b8bde 100644 --- a/ui/text/text_block.h +++ b/ui/text/text_block.h @@ -34,38 +34,20 @@ enum TextBlockFlags { class AbstractBlock { public: - uint16 from() const { - return _from; - } - int width() const { - return _width.toInt(); - } - int rpadding() const { - return _rpadding.toInt(); - } - QFixed f_width() const { - return _width; - } - QFixed f_rpadding() const { - return _rpadding; - } + uint16 from() const; + int width() const; + int rpadding() const; + QFixed f_width() const; + QFixed f_rpadding() const; // Should be virtual, but optimized throught type() call. QFixed f_rbearing() const; - uint16 lnkIndex() const { - return (_flags >> 12) & 0xFFFF; - } - void setLnkIndex(uint16 lnkIndex) { - _flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12); - } + uint16 lnkIndex() const; + void setLnkIndex(uint16 lnkIndex); - TextBlockType type() const { - return TextBlockType((_flags >> 8) & 0x0F); - } - int32 flags() const { - return (_flags & 0xFF); - } + TextBlockType type() const; + int32 flags() const; protected: AbstractBlock( @@ -74,10 +56,7 @@ protected: uint16 from, uint16 length, uchar flags, - uint16 lnkIndex) - : _from(from) - , _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) { - } + uint16 lnkIndex); uint16 _from = 0; From 0524252197c93f083638c470c6542b1fd4d1ff89 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 18 Dec 2021 13:18:50 +0300 Subject: [PATCH 11/35] Added new colors to text palette for spoilers. --- ui/basic.style | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/basic.style b/ui/basic.style index 5a79615..aff4d1b 100644 --- a/ui/basic.style +++ b/ui/basic.style @@ -15,6 +15,8 @@ TextPalette { selectMonoFg: color; selectOverlay: color; linkAlwaysActive: int; + spoilerBg: color; + spoilerActiveBg: color; } TextStyle { @@ -44,6 +46,8 @@ defaultTextPalette: TextPalette { selectLinkFg: historyLinkInFgSelected; selectMonoFg: msgInMonoFgSelected; selectOverlay: msgSelectOverlay; + spoilerBg: msgInDateFg; + spoilerActiveBg: msgInDateFg; } defaultTextStyle: TextStyle { font: normalFont; @@ -139,6 +143,8 @@ inTextPalette: TextPalette(defaultTextPalette) { selectLinkFg: historyLinkInFgSelected; selectMonoFg: msgInMonoFgSelected; selectOverlay: msgSelectOverlay; + spoilerBg: msgInDateFg; + spoilerActiveBg: msgInDateFg; } inTextPaletteSelected: TextPalette(inTextPalette) { linkFg: historyLinkInFgSelected; @@ -152,6 +158,8 @@ outTextPalette: TextPalette(defaultTextPalette) { selectLinkFg: historyLinkOutFgSelected; selectMonoFg: msgOutMonoFgSelected; selectOverlay: msgSelectOverlay; + spoilerBg: msgOutDateFg; + spoilerActiveBg: msgOutDateFg; } outTextPaletteSelected: TextPalette(outTextPalette) { linkFg: historyLinkOutFgSelected; From e19a4c6544eb3f429e28a7e049c6efde43b43a45 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 18 Dec 2021 15:34:53 +0300 Subject: [PATCH 12/35] Added index for spoiler to AbstractBox. --- ui/text/text.cpp | 11 ++++++----- ui/text/text_block.cpp | 24 ++++++++++++++++++------ ui/text/text_block.h | 40 ++++++++++++++++++++++++++++------------ 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index c24712e..0a3b747 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -450,13 +450,13 @@ void Parser::createBlock(int32 skipBack) { } _lastSkipped = false; if (_emoji) { - _t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _emoji)); + _t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, 0/*spoilerIndex*/, _emoji)); _emoji = nullptr; _lastSkipped = true; } else if (newline) { - _t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex)); + _t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, 0/*spoilerIndex*/)); } else { - _t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex)); + _t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex, 0/*spoilerIndex*/)); } _blockStart += len; blockCreated(); @@ -466,7 +466,7 @@ void Parser::createBlock(int32 skipBack) { void Parser::createSkipBlock(int32 w, int32 h) { createBlock(); _t->_text.push_back('_'); - _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex)); + _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex, 0/*spoilerIndex*/)); blockCreated(); } @@ -1843,7 +1843,7 @@ private: _elideSavedIndex = blockIndex; auto mutableText = const_cast(_t); _elideSavedBlock = std::move(mutableText->_blocks[blockIndex]); - mutableText->_blocks[blockIndex] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex()); + mutableText->_blocks[blockIndex] = Block::Text(_t->_st->font, _t->_text, QFIXED_MAX, elideStart, 0, (*_elideSavedBlock)->flags(), (*_elideSavedBlock)->lnkIndex(), (*_elideSavedBlock)->spoilerIndex()); _blocksSize = blockIndex + 1; _endBlock = (blockIndex + 1 < _t->_blocks.size() ? _t->_blocks[blockIndex + 1].get() : nullptr); } @@ -2889,6 +2889,7 @@ bool String::updateSkipBlock(int width, int height) { _text.size() - 1, width, height, + 0, 0)); recountNaturalSize(false); return true; diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index ced3aca..1e344e0 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -346,9 +346,11 @@ AbstractBlock::AbstractBlock( uint16 from, uint16 length, uchar flags, - uint16 lnkIndex) + uint16 lnkIndex, + uint16 spoilerIndex) : _from(from) -, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) { +, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) +, _spoilerIndex(spoilerIndex) { } uint16 AbstractBlock::from() const { @@ -379,12 +381,20 @@ void AbstractBlock::setLnkIndex(uint16 lnkIndex) { _flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12); } +uint16 AbstractBlock::spoilerIndex() const { + return _spoilerIndex; +} + +void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) { + _spoilerIndex = spoilerIndex; +} + TextBlockType AbstractBlock::type() const { return TextBlockType((_flags >> 8) & 0x0F); } int32 AbstractBlock::flags() const { - return (_flags & 0xFF); + return (_flags & 0xFFF); } QFixed AbstractBlock::f_rbearing() const { @@ -400,8 +410,9 @@ TextBlock::TextBlock( uint16 from, uint16 length, uchar flags, - uint16 lnkIndex) -: AbstractBlock(font, str, from, length, flags, lnkIndex) { + uint16 lnkIndex, + uint16 spoilerIndex) +: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { _flags |= ((TextBlockTText & 0x0F) << 8); if (length) { style::font blockFont = font; @@ -442,8 +453,9 @@ EmojiBlock::EmojiBlock( uint16 length, uchar flags, uint16 lnkIndex, + uint16 spoilerIndex, EmojiPtr emoji) -: AbstractBlock(font, str, from, length, flags, lnkIndex) +: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) , _emoji(emoji) { _flags |= ((TextBlockTEmoji & 0x0F) << 8); _width = int(st::emojiSize + 2 * st::emojiPadding); diff --git a/ui/text/text_block.h b/ui/text/text_block.h index a8b8bde..e1a6534 100644 --- a/ui/text/text_block.h +++ b/ui/text/text_block.h @@ -46,6 +46,9 @@ public: uint16 lnkIndex() const; void setLnkIndex(uint16 lnkIndex); + uint16 spoilerIndex() const; + void setSpoilerIndex(uint16 spoilerIndex); + TextBlockType type() const; int32 flags() const; @@ -56,12 +59,15 @@ protected: uint16 from, uint16 length, uchar flags, - uint16 lnkIndex); + uint16 lnkIndex, + uint16 spoilerIndex); uint16 _from = 0; uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags + uint16 _spoilerIndex = 0; + QFixed _width = 0; // Right padding: spaces after the last content of the block (like a word). @@ -81,8 +87,9 @@ public: uint16 from, uint16 length, uchar flags, - uint16 lnkIndex) - : AbstractBlock(font, str, from, length, flags, lnkIndex) { + uint16 lnkIndex, + uint16 spoilerIndex) + : AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { _flags |= ((TextBlockTNewline & 0x0F) << 8); } @@ -142,7 +149,8 @@ public: uint16 from, uint16 length, uchar flags, - uint16 lnkIndex); + uint16 lnkIndex, + uint16 spoilerIndex); private: QFixed real_f_rbearing() const { @@ -168,6 +176,7 @@ public: uint16 length, uchar flags, uint16 lnkIndex, + uint16 spoilerIndex, EmojiPtr emoji); private: @@ -187,8 +196,9 @@ public: uint16 from, int32 w, int32 h, - uint16 lnkIndex) - : AbstractBlock(font, str, from, 1, 0, lnkIndex) + uint16 lnkIndex, + uint16 spoilerIndex) + : AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex) , _height(h) { _flags |= ((TextBlockTSkip & 0x0F) << 8); _width = w; @@ -304,8 +314,9 @@ public: uint16 from, uint16 length, uchar flags, - uint16 lnkIndex) { - return New(font, str, from, length, flags, lnkIndex); + uint16 lnkIndex, + uint16 spoilerIndex) { + return New(font, str, from, length, flags, lnkIndex, spoilerIndex); } [[nodiscard]] static Block Text( @@ -315,7 +326,8 @@ public: uint16 from, uint16 length, uchar flags, - uint16 lnkIndex) { + uint16 lnkIndex, + uint16 spoilerIndex) { return New( font, str, @@ -323,7 +335,8 @@ public: from, length, flags, - lnkIndex); + lnkIndex, + spoilerIndex); } [[nodiscard]] static Block Emoji( @@ -333,6 +346,7 @@ public: uint16 length, uchar flags, uint16 lnkIndex, + uint16 spoilerIndex, EmojiPtr emoji) { return New( font, @@ -341,6 +355,7 @@ public: length, flags, lnkIndex, + spoilerIndex, emoji); } @@ -350,8 +365,9 @@ public: uint16 from, int32 w, int32 h, - uint16 lnkIndex) { - return New(font, str, from, w, h, lnkIndex); + uint16 lnkIndex, + uint16 spoilerIndex) { + return New(font, str, from, w, h, lnkIndex, spoilerIndex); } template From 1819e6e3a5f89ec652943e290944b36f00064901 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 16 Dec 2021 20:05:13 +0300 Subject: [PATCH 13/35] Added initial spoiler support to Ui::Text::String. --- ui/text/text.cpp | 208 +++++++++++++++++++++++++++++++++++++----- ui/text/text.h | 5 + ui/text/text_entity.h | 1 + 3 files changed, 192 insertions(+), 22 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 0a3b747..3c02a3c 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -253,6 +253,30 @@ const TextParseOptions _textPlainOptions = { namespace Ui { namespace Text { +class String::SpoilerClickHandler final : public ClickHandler { +public: + SpoilerClickHandler() = default; + + TextEntity getTextEntity() const override { + return { EntityType::Spoiler }; + } + + void onClick(ClickContext context) const override { + if (!_shown) { + const auto nonconst = const_cast(this); + nonconst->_shown = true; + } + } + + [[nodiscard]] bool shown() const { + return _shown; + } + +private: + bool _shown = false; + +}; + class Parser { public: Parser( @@ -273,13 +297,20 @@ private: class StartedEntity { public: explicit StartedEntity(TextBlockFlags flags); - explicit StartedEntity(uint16 lnkIndex); + explicit StartedEntity(uint16 index, bool isLnk = true); std::optional flags() const; std::optional lnkIndex() const; + std::optional spoilerIndex() const; private: - int _value = 0; + enum class Type { + Flags, + Link, + Spoiler, + }; + const int _value = 0; + const Type _type; }; @@ -334,15 +365,18 @@ private: const bool _checkTilde = false; // do we need a special text block for tilde symbol std::vector _links; + std::vector _spoilers; base::flat_map< const QChar*, std::vector> _startedEntities; uint16 _maxLnkIndex = 0; + uint16 _maxSpoilerIndex = 0; // current state int32 _flags = 0; uint16 _lnkIndex = 0; + uint16 _spoilerIndex = 0; EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero int32 _blockStart = 0; // offset in result, from which current parsed block is started int32 _diacs = 0; // diac chars skipped without good char @@ -357,23 +391,36 @@ private: }; -Parser::StartedEntity::StartedEntity(TextBlockFlags flags) : _value(flags) { +Parser::StartedEntity::StartedEntity(TextBlockFlags flags) +: _value(flags) +, _type(Type::Flags) { Expects(_value >= 0 && _value < int(kStringLinkIndexShift)); } -Parser::StartedEntity::StartedEntity(uint16 lnkIndex) : _value(lnkIndex) { - Expects(_value >= kStringLinkIndexShift); +Parser::StartedEntity::StartedEntity(uint16 index, bool isLnk) +: _value(index) +, _type(isLnk ? Type::Link : Type::Spoiler) { + Expects((_type == Type::Link) + ? (_value >= kStringLinkIndexShift) + : (_value < kStringLinkIndexShift)); } std::optional Parser::StartedEntity::flags() const { - if (_value < int(kStringLinkIndexShift)) { + if (_value < int(kStringLinkIndexShift) && (_type == Type::Flags)) { return TextBlockFlags(_value); } return std::nullopt; } std::optional Parser::StartedEntity::lnkIndex() const { - if (_value >= int(kStringLinkIndexShift)) { + if (_value >= int(kStringLinkIndexShift) && (_type == Type::Link)) { + return uint16(_value); + } + return std::nullopt; +} + +std::optional Parser::StartedEntity::spoilerIndex() const { + if (_value < int(kStringLinkIndexShift) && (_type == Type::Spoiler)) { return uint16(_value); } return std::nullopt; @@ -437,6 +484,9 @@ void Parser::createBlock(int32 skipBack) { if (_lnkIndex < kStringLinkIndexShift && _lnkIndex > _maxLnkIndex) { _maxLnkIndex = _lnkIndex; } + if (_spoilerIndex > _maxSpoilerIndex) { + _maxSpoilerIndex = _spoilerIndex; + } int32 len = int32(_t->_text.size()) + skipBack - _blockStart; if (len > 0) { @@ -450,13 +500,13 @@ void Parser::createBlock(int32 skipBack) { } _lastSkipped = false; if (_emoji) { - _t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, 0/*spoilerIndex*/, _emoji)); + _t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _spoilerIndex, _emoji)); _emoji = nullptr; _lastSkipped = true; } else if (newline) { - _t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, 0/*spoilerIndex*/)); + _t->_blocks.push_back(Block::Newline(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _spoilerIndex)); } else { - _t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex, 0/*spoilerIndex*/)); + _t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, _lnkIndex, _spoilerIndex)); } _blockStart += len; blockCreated(); @@ -466,7 +516,7 @@ void Parser::createBlock(int32 skipBack) { void Parser::createSkipBlock(int32 w, int32 h) { createBlock(); _t->_text.push_back('_'); - _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex, 0/*spoilerIndex*/)); + _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _lnkIndex, _spoilerIndex)); blockCreated(); } @@ -509,6 +559,11 @@ void Parser::finishEntities() { createBlock(); _lnkIndex = 0; } + } else if (const auto spoilerIndex = list.back().spoilerIndex()) { + if (_spoilerIndex == *spoilerIndex && (_spoilerIndex != 0)) { + createBlock(); + _spoilerIndex = 0; + } } list.pop_back(); } @@ -594,6 +649,16 @@ bool Parser::checkEntities() { _flags |= flags; _startedEntities[entityEnd].emplace_back(flags); } + } else if (entityType == EntityType::Spoiler) { + createBlock(); + + _spoilers.push_back(EntityLinkData{ + .data = QString::number(_spoilers.size() + 1), + .type = entityType, + }); + _spoilerIndex = _spoilers.size(); + + _startedEntities[entityEnd].emplace_back(_spoilerIndex, false); } ++_waitingEntity; @@ -918,7 +983,16 @@ void Parser::checkForElidedSkipBlock() { void Parser::finalize(const TextParseOptions &options) { _t->_links.resize(_maxLnkIndex); + _t->_spoilers.resize(_maxSpoilerIndex); for (auto &block : _t->_blocks) { + const auto spoilerIndex = block->spoilerIndex(); + if (spoilerIndex) { + _t->_spoilers.resize(spoilerIndex); + const auto handler = (options.flags & TextParseLinks) + ? std::make_shared() + : nullptr; + _t->_spoilers[spoilerIndex - 1] = std::move(handler); + } const auto shiftedIndex = block->lnkIndex(); if (shiftedIndex <= kStringLinkIndexShift) { continue; @@ -939,6 +1013,7 @@ void Parser::finalize(const TextParseOptions &options) { } } _t->_links.squeeze(); + _t->_spoilers.squeeze(); _t->_blocks.squeeze(); _t->_text.squeeze(); } @@ -1591,8 +1666,14 @@ private: TextBlockType _type = currentBlock->type(); if (!_p && _lookupX >= x && _lookupX < x + si.width) { // _lookupRequest if (_lookupLink) { - if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { - _lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1); + if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { + const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex()); + const auto resultLink = (spoilerLink || !currentBlock->lnkIndex()) + ? spoilerLink + : _t->_links.at(currentBlock->lnkIndex() - 1); + if (resultLink) { + _lookupResult.link = resultLink; + } } } if (_type != TextBlockTSkip) { @@ -1664,12 +1745,35 @@ private: } } } - Emoji::Draw( - *_p, - static_cast(currentBlock)->_emoji, - Emoji::GetSizeNormal(), - (glyphX + st::emojiPadding).toInt(), - _y + _yDelta + emojiY); + if (_background.brush) { + const auto from = currentBlock->from(); + const auto to = currentBlock->from() + currentBlock->width(); + if (_localFrom + si.position < to) { + auto chFrom = _str + currentBlock->from(); + auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); + if (_localFrom + si.position >= from) { // could be without space + if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || to >= (chTo - _str)) { + fillSpoilerRange(x, si.width, blockIndex); + } else { // or with space + fillSpoilerRange(glyphX, currentBlock->f_width(), blockIndex); + } + } else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= from) { + if (rtl) { // rtl space only + fillSpoilerRange(x, glyphX - x, blockIndex); + } else { // ltr space only + fillSpoilerRange(x + currentBlock->f_width(), si.width, blockIndex); + } + } + } + } + if (!_background.inFront) { + Emoji::Draw( + *_p, + static_cast(currentBlock)->_emoji, + Emoji::GetSizeNormal(), + (glyphX + st::emojiPadding).toInt(), + _y + _yDelta + emojiY); + } // } else if (_p && currentBlock->type() == TextBlockSkip) { // debug // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast(currentBlock)->height()), QColor(0, 0, 0, 32)); } @@ -1697,8 +1801,14 @@ private: if (!_p && _lookupX >= x && _lookupX < x + itemWidth) { // _lookupRequest if (_lookupLink) { - if (currentBlock->lnkIndex() && _lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { - _lookupResult.link = _t->_links.at(currentBlock->lnkIndex() - 1); + if (_lookupY >= _y + _yDelta && _lookupY < _y + _yDelta + _fontHeight) { + const auto spoilerLink = _t->spoilerLink(currentBlock->spoilerIndex()); + const auto resultLink = (spoilerLink || !currentBlock->lnkIndex()) + ? spoilerLink + : _t->_links.at(currentBlock->lnkIndex() - 1); + if (resultLink) { + _lookupResult.link = resultLink; + } } } _lookupResult.uponSymbol = true; @@ -1752,6 +1862,10 @@ private: gf.justified = false; gf.initWithScriptItem(si); + if (!_background.inFront) { + fillSpoilerRange(x, si.width, blockIndex); + } + auto hasSelected = false; auto hasNotSelected = true; auto selectedRect = QRect(); @@ -1823,6 +1937,10 @@ private: _p->setPen(*_currentPen); _p->drawTextItem(QPointF(x.toReal(), textY), gf); } + + if (_background.inFront) { + fillSpoilerRange(x, si.width, blockIndex); + } } x += itemWidth; @@ -1835,6 +1953,22 @@ private: _p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg); } + void fillSpoilerRange(QFixed x, QFixed width, int currentBlockIndex) { + if (_background.brush) { + const auto elideOffset = + (_indexOfElidedBlock == currentBlockIndex) + ? (_elideRemoveFromEnd + _f->elidew) + : 0; + + _p->fillRect( + x.toInt(), + _y + _yDelta, + width.toInt() - elideOffset, + _fontHeight, + *_background.brush); + } + } + void elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) { if (_elideSavedBlock) { restoreAfterElided(); @@ -1900,6 +2034,7 @@ private: lineText = lineText.mid(0, currentBlock->from() - _localFrom) + _Elide; lineLength = currentBlock->from() + _Elide.size() - _lineStart; _selection.to = qMin(_selection.to, currentBlock->from()); + _indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0); setElideBidi(currentBlock->from(), _Elide.size()); elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth); return; @@ -1932,6 +2067,7 @@ private: lineText += _Elide; lineLength = _localFrom + pos + _Elide.size() - _lineStart; _selection.to = qMin(_selection.to, uint16(_localFrom + pos)); + _indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0); setElideBidi(_localFrom + pos, _Elide.size()); _blocksSize = blockIndex; _endBlock = nextBlock; @@ -1952,6 +2088,7 @@ private: int32 elideStart = _localFrom + lineText.size(); _selection.to = qMin(_selection.to, uint16(elideStart)); + _indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0); setElideBidi(elideStart, _Elide.size()); lineText += _Elide; @@ -2611,6 +2748,18 @@ private: void applyBlockProperties(const AbstractBlock *block) { eSetFont(block); if (_p) { + const auto handler = !block->spoilerIndex() + ? nullptr + : _t->_spoilers.at(block->spoilerIndex() - 1); + if (!block->spoilerIndex()) { + _background = {}; + } else { + const auto inBack = (handler && handler->shown()); + _background.inFront = !inBack; + _background.brush = inBack + ? &_textPalette->spoilerActiveBg->b + : &_textPalette->spoilerBg->b; + } if (block->lnkIndex()) { _currentPen = &_textPalette->linkFg->p; _currentPenSelected = &_textPalette->selectLinkFg->p; @@ -2635,6 +2784,10 @@ private: QPen _originalPenSelected; const QPen *_currentPen = nullptr; const QPen *_currentPenSelected = nullptr; + struct { + const QBrush *brush = nullptr; + bool inFront = false; + } _background; int _yFrom = 0; int _yTo = 0; int _yToElide = 0; @@ -2642,6 +2795,8 @@ private: bool _fullWidthSelection = true; const QChar *_str = nullptr; + int _indexOfElidedBlock = -1; // For spoilers. + // current paragraph data String::TextBlocks::const_iterator _parStartBlock; Qt::LayoutDirection _parDirection; @@ -3248,7 +3403,7 @@ TextForMimeData String::toText( { TextBlockFUnderline, EntityType::Underline }, { TextBlockFStrikeOut, EntityType::StrikeOut }, { TextBlockFCode, EntityType::Code }, // #TODO entities - { TextBlockFPre, EntityType::Pre } + { TextBlockFPre, EntityType::Pre }, } : std::vector(); const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) { if (!composeEntities) { @@ -3368,10 +3523,19 @@ void String::clear() { void String::clearFields() { _blocks.clear(); _links.clear(); + _spoilers.clear(); _maxWidth = _minHeight = 0; _startDir = Qt::LayoutDirectionAuto; } +ClickHandlerPtr String::spoilerLink(uint16 spoilerIndex) const { + if (spoilerIndex) { + const auto &handler = _spoilers.at(spoilerIndex - 1); + return (handler && !handler->shown()) ? handler : nullptr; + } + return nullptr; +} + bool IsWordSeparator(QChar ch) { switch (ch.unicode()) { case QChar::Space: diff --git a/ui/text/text.h b/ui/text/text.h index 75bc9c0..973b531 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -205,6 +205,8 @@ private: // it is also called from move constructor / assignment operator void clearFields(); + ClickHandlerPtr spoilerLink(uint16 spoilerIndex) const; + TextForMimeData toText( TextSelection selection, bool composeExpanded, @@ -220,6 +222,9 @@ private: TextBlocks _blocks; TextLinks _links; + class SpoilerClickHandler; + QVector> _spoilers; + Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto; friend class Parser; diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index db5b598..52ee5ce 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -33,6 +33,7 @@ enum class EntityType : uchar { StrikeOut, Code, // inline Pre, // block + Spoiler, }; enum class EntityLinkShown : uchar { From 6b905f77546898fde55b37cf79bffc7683c7291c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 18 Dec 2021 16:35:38 +0300 Subject: [PATCH 14/35] Added text command for spoilers. --- ui/text/text.cpp | 32 ++++++++++++++++++++++++++++++++ ui/text/text.h | 6 ++++++ 2 files changed, 38 insertions(+) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 3c02a3c..894cd86 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -195,6 +195,18 @@ QString textcmdStopSemibold() { return result.append(TextCommand).append(QChar(TextCommandNoSemibold)).append(TextCommand); } +QString textcmdStartSpoiler() { + QString result; + result.reserve(3); + return result.append(TextCommand).append(QChar(TextCommandSpoiler)).append(TextCommand); +} + +QString textcmdStopSpoiler() { + QString result; + result.reserve(3); + return result.append(TextCommand).append(QChar(TextCommandNoSpoiler)).append(TextCommand); +} + const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) { const QChar *result = from + 1; if (*from != TextCommand || result >= end) return from; @@ -212,6 +224,8 @@ const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) case TextCommandNoItalic: case TextCommandUnderline: case TextCommandNoUnderline: + case TextCommandSpoiler: + case TextCommandNoSpoiler: break; case TextCommandLinkIndex: @@ -803,6 +817,24 @@ bool Parser::readCommand() { _lnkIndex = kStringLinkIndexShift + _links.size(); } break; + case TextCommandSpoiler: { + if (!_spoilerIndex) { + createBlock(); + _spoilers.push_back(EntityLinkData{ + .data = QString::number(_spoilers.size() + 1), + .type = EntityType::Spoiler, + }); + _spoilerIndex = _spoilers.size(); + } + } break; + + case TextCommandNoSpoiler: + if (_spoilerIndex == _spoilers.size()) { + createBlock(); + _spoilerIndex = 0; + } + break; + case TextCommandSkipBlock: createSkipBlock(_ptr->unicode(), (_ptr + 1)->unicode()); break; diff --git a/ui/text/text.h b/ui/text/text.h index 973b531..21530a3 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -30,6 +30,8 @@ enum TextCommands { TextCommandLinkIndex = 0x0B, // 0 - NoLink TextCommandLinkText = 0x0C, TextCommandSkipBlock = 0x0D, + TextCommandSpoiler = 0x0E, + TextCommandNoSpoiler = 0x0F, TextCommandLangTag = 0x20, }; @@ -269,4 +271,8 @@ QString textcmdLink(ushort lnkIndex, const QString &text); QString textcmdLink(const QString &url, const QString &text); QString textcmdStartSemibold(); QString textcmdStopSemibold(); + +QString textcmdStartSpoiler(); +QString textcmdStopSpoiler(); + const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true); From bcf16c6c8049fdc8a1e2b3a5dcefb2b737e916e3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 18 Dec 2021 14:29:44 +0300 Subject: [PATCH 15/35] Added spoiler support to conversion from Ui::Text::String to text. --- ui/text/text.cpp | 65 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 894cd86..4f6a212 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -3339,9 +3339,14 @@ void String::enumerateText(TextSelection selection, AppendPartCallback appendPar int lnkIndex = 0; uint16 lnkFrom = 0; + + int spoilerIndex = 0; + uint16 spoilerFrom = 0; + int32 flags = 0; for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) { int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex(); + int blockSpoilerIndex = (i == e) ? 0 : (*i)->spoilerIndex(); uint16 blockFrom = (i == e) ? _text.size() : (*i)->from(); int32 blockFlags = (i == e) ? 0 : (*i)->flags(); @@ -3354,18 +3359,43 @@ void String::enumerateText(TextSelection selection, AppendPartCallback appendPar auto rangeTo = qMin(selection.to, blockFrom); if (rangeTo > rangeFrom) { // handle click handler const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom); - if (lnkFrom != rangeFrom || blockFrom != rangeTo) { - // Ignore links that are partially copied. - clickHandlerFinishCallback(r, nullptr); - } else { - clickHandlerFinishCallback(r, _links.at(lnkIndex - 1)); - } + // Ignore links that are partially copied. + const auto handler = (lnkFrom != rangeFrom || blockFrom != rangeTo) + ? nullptr + : _links.at(lnkIndex - 1); + const auto type = handler + ? handler->getTextEntity().type + : EntityType::Invalid; + clickHandlerFinishCallback(r, handler, type); } } lnkIndex = blockLnkIndex; if (lnkIndex) { lnkFrom = blockFrom; - clickHandlerStartCallback(); + const auto handler = _links.at(lnkIndex - 1); + clickHandlerStartCallback(handler + ? handler->getTextEntity().type + : EntityType::Invalid); + } + } + if (blockSpoilerIndex != spoilerIndex) { + if (spoilerIndex) { + auto rangeFrom = qMax(selection.from, spoilerFrom); + auto rangeTo = qMin(selection.to, blockFrom); + if (rangeTo > rangeFrom) { // handle click handler + const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom); + // Ignore links that are partially copied. + const auto handler = (spoilerFrom != rangeFrom || blockFrom != rangeTo) + ? nullptr + : _spoilers.at(spoilerIndex - 1); + const auto type = EntityType::Spoiler; + clickHandlerFinishCallback(r, handler, type); + } + } + spoilerIndex = blockSpoilerIndex; + if (spoilerIndex) { + spoilerFrom = blockFrom; + clickHandlerStartCallback(EntityType::Spoiler); } } @@ -3427,6 +3457,7 @@ TextForMimeData String::toText( result.rich.entities.insert(i, std::move(entity)); }; auto linkStart = 0; + auto spoilerStart = 0; auto markdownTrackers = composeEntities ? std::vector{ { TextBlockFItalic, EntityType::Italic }, @@ -3453,12 +3484,26 @@ TextForMimeData String::toText( } } }; - const auto clickHandlerStartCallback = [&] { - linkStart = result.rich.text.size(); + const auto clickHandlerStartCallback = [&](EntityType type) { + if (type == EntityType::Spoiler) { + spoilerStart = result.rich.text.size(); + } else { + linkStart = result.rich.text.size(); + } }; const auto clickHandlerFinishCallback = [&]( QStringView inText, - const ClickHandlerPtr &handler) { + const ClickHandlerPtr &handler, + EntityType type) { + if (type == EntityType::Spoiler) { + insertEntity({ + type, + spoilerStart, + int(result.rich.text.size() - spoilerStart), + QString(), + }); + return; + } if (!handler || (!composeExpanded && !composeEntities)) { return; } From ee4a94c12256c4b1a5244d6b1495990c2be1da61 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 19 Dec 2021 17:06:21 +0300 Subject: [PATCH 16/35] Added spoiler support to input field. --- ui/integration.cpp | 4 ++++ ui/integration.h | 1 + ui/text/text_entity.cpp | 16 ++++++++++++++++ ui/text/text_entity.h | 2 ++ ui/widgets/input_fields.cpp | 20 ++++++++++++++++++++ ui/widgets/input_fields.h | 2 ++ 6 files changed, 45 insertions(+) diff --git a/ui/integration.cpp b/ui/integration.cpp index 999140b..28ffb7c 100644 --- a/ui/integration.cpp +++ b/ui/integration.cpp @@ -139,4 +139,8 @@ QString Integration::phraseFormattingMonospace() { return "Monospace"; } +QString Integration::phraseFormattingSpoiler() { + return "Spoiler"; +} + } // namespace Ui diff --git a/ui/integration.h b/ui/integration.h index cd7704b..c981689 100644 --- a/ui/integration.h +++ b/ui/integration.h @@ -70,6 +70,7 @@ public: [[nodiscard]] virtual QString phraseFormattingUnderline(); [[nodiscard]] virtual QString phraseFormattingStrikeOut(); [[nodiscard]] virtual QString phraseFormattingMonospace(); + [[nodiscard]] virtual QString phraseFormattingSpoiler(); }; diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 00cfcf3..edb97f4 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -70,6 +70,10 @@ QString SeparatorsMono() { return Separators(QString::fromUtf8("*~/")); } +QString SeparatorsSpoiler() { + return Separators(QString::fromUtf8("|*~/")); +} + QString ExpressionHashtag() { return QString::fromUtf8("(^|[") + ExpressionSeparators(QString::fromUtf8("`\\*/")) + QString::fromUtf8("])#[\\w]{2,64}([\\W]|$)"); } @@ -1271,6 +1275,14 @@ QString MarkdownPreBadAfter() { return QString::fromLatin1("`"); } +QString MarkdownSpoilerGoodBefore() { + return SeparatorsSpoiler(); +} + +QString MarkdownSpoilerBadAfter() { + return QString::fromLatin1("|"); +} + bool IsValidProtocol(const QString &protocol) { static const auto list = CreateValidProtocols(); return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar))); @@ -2087,6 +2099,7 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { EntityType::Italic, EntityType::Underline, EntityType::StrikeOut, + EntityType::Spoiler, EntityType::Code, EntityType::Pre, }; @@ -2193,6 +2206,8 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { result.set(EntityType::Code); } else if (single == Ui::InputField::kTagPre) { result.set(EntityType::Pre); + } else if (single == Ui::InputField::kTagSpoiler) { + result.set(EntityType::Spoiler); } else { result.link = single.toString(); } @@ -2281,6 +2296,7 @@ TextWithTags::Tags ConvertEntitiesToTextTags( break; case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities case EntityType::Pre: push(Ui::InputField::kTagPre); break; + case EntityType::Spoiler: push(Ui::InputField::kTagSpoiler); break; } } if (!toRemove.empty()) { diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index 52ee5ce..915e62e 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -291,6 +291,8 @@ QString MarkdownCodeGoodBefore(); QString MarkdownCodeBadAfter(); QString MarkdownPreGoodBefore(); QString MarkdownPreBadAfter(); +QString MarkdownSpoilerGoodBefore(); +QString MarkdownSpoilerBadAfter(); // Text preprocess. QString Clean(const QString &text); diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index 98cb7f1..24f2eab 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -44,6 +44,7 @@ const auto &kTagUnderline = InputField::kTagUnderline; const auto &kTagStrikeOut = InputField::kTagStrikeOut; const auto &kTagCode = InputField::kTagCode; const auto &kTagPre = InputField::kTagPre; +const auto &kTagSpoiler = InputField::kTagSpoiler; const auto kTagCheckLinkMeta = QString("^:/:/:^"); const auto kNewlineChars = QString("\r\n") + QChar(0xfdd0) // QTextBeginningOfFrame @@ -230,6 +231,7 @@ constexpr auto kTagItalicIndex = 1; constexpr auto kTagStrikeOutIndex = 2; constexpr auto kTagCodeIndex = 3; constexpr auto kTagPreIndex = 4; +constexpr auto kTagSpoilerIndex = 5; constexpr auto kInvalidPosition = std::numeric_limits::max() / 2; class TagSearchItem { @@ -373,6 +375,13 @@ const std::vector &TagStartExpressions() { TextUtilities::MarkdownPreBadAfter(), TextUtilities::MarkdownPreGoodBefore() }, + { + kTagSpoiler, + TextUtilities::MarkdownSpoilerGoodBefore(), + TextUtilities::MarkdownSpoilerBadAfter(), + TextUtilities::MarkdownSpoilerBadAfter(), + TextUtilities::MarkdownSpoilerGoodBefore() + }, }; return cached; } @@ -385,6 +394,7 @@ const std::map &TagIndices() { { kTagStrikeOut, kTagStrikeOutIndex }, { kTagCode, kTagCodeIndex }, { kTagPre, kTagPreIndex }, + { kTagSpoiler, kTagSpoilerIndex }, }; return cached; } @@ -702,6 +712,7 @@ QTextCharFormat PrepareTagFormat( auto result = QTextCharFormat(); auto font = st.font; auto color = std::optional(); + auto bg = std::optional(); const auto applyOne = [&](QStringView tag) { if (IsValidMarkdownLink(tag)) { color = st::defaultTextPalette.linkFg; @@ -716,6 +727,8 @@ QTextCharFormat PrepareTagFormat( } else if (tag == kTagCode || tag == kTagPre) { color = st::defaultTextPalette.monoFg; font = font->monospace(); + } else if (tag == kTagSpoiler) { + bg = st::defaultTextPalette.spoilerActiveBg; } }; for (const auto &tag : TextUtilities::SplitTags(tag)) { @@ -724,6 +737,9 @@ QTextCharFormat PrepareTagFormat( result.setFont(font); result.setForeground(color.value_or(st.textFg)); result.setProperty(kTagProperty, tag); + if (bg) { + result.setBackground(*bg); + } return result; } @@ -842,6 +858,7 @@ const QString InputField::kTagUnderline = QStringLiteral("^^"); const QString InputField::kTagStrikeOut = QStringLiteral("~~"); const QString InputField::kTagCode = QStringLiteral("`"); const QString InputField::kTagPre = QStringLiteral("```"); +const QString InputField::kTagSpoiler = QStringLiteral("||"); class InputField::Inner final : public QTextEdit { public: @@ -2819,6 +2836,8 @@ bool InputField::handleMarkdownKey(QKeyEvent *e) { toggleSelectionMarkdown(kTagStrikeOut); } else if (matches(kMonospaceSequence)) { toggleSelectionMarkdown(kTagCode); + } else if (matches(kSpoilerSequence)) { + toggleSelectionMarkdown(kTagSpoiler); } else if (matches(kClearFormatSequence)) { clearSelectionMarkdown(); } else if (matches(kEditLinkSequence) && _editLinkCallback) { @@ -3578,6 +3597,7 @@ void InputField::addMarkdownActions( addtag(integration.phraseFormattingUnderline(), QKeySequence::Underline, kTagUnderline); addtag(integration.phraseFormattingStrikeOut(), kStrikeOutSequence, kTagStrikeOut); addtag(integration.phraseFormattingMonospace(), kMonospaceSequence, kTagCode); + addtag(integration.phraseFormattingSpoiler(), kSpoilerSequence, kTagSpoiler); if (_editLinkCallback) { submenu->addSeparator(); diff --git a/ui/widgets/input_fields.h b/ui/widgets/input_fields.h index 5638c59..846f18b 100644 --- a/ui/widgets/input_fields.h +++ b/ui/widgets/input_fields.h @@ -26,6 +26,7 @@ const auto kClearFormatSequence = QKeySequence("ctrl+shift+n"); const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x"); const auto kMonospaceSequence = QKeySequence("ctrl+shift+m"); const auto kEditLinkSequence = QKeySequence("ctrl+k"); +const auto kSpoilerSequence = QKeySequence("ctrl+shift+p"); class PopupMenu; @@ -171,6 +172,7 @@ public: static const QString kTagStrikeOut; static const QString kTagCode; static const QString kTagPre; + static const QString kTagSpoiler; InputField( QWidget *parent, From 8331322c20d35e3853dee4b6bc9ca72e43e03043 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 19 Dec 2021 17:04:04 +0300 Subject: [PATCH 17/35] Replaced tag separator to avoid conflicts with new spoiler tag. --- ui/text/text_entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index edb97f4..5d427a9 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -22,7 +22,7 @@ namespace TextUtilities { namespace { -constexpr auto kTagSeparator = '|'; +constexpr auto kTagSeparator = '$'; using namespace Ui::Text; From fc6c3841ee0b0d775b8d3a74868297d3f93f9b9c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 Dec 2021 17:47:03 +0300 Subject: [PATCH 18/35] Improved utility to clean text from text commands except spoilers. --- ui/text/text_entity.cpp | 10 +++++++++- ui/text/text_entity.h | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 5d427a9..df78daf 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -1293,9 +1293,17 @@ bool IsValidTopDomain(const QString &protocol) { return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar))); } -QString Clean(const QString &text) { +QString Clean(const QString &text, bool keepSpoilers) { auto result = text; for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch) { + if (keepSpoilers && (*ch == TextCommand)) { + if ((*(ch + 1) == TextCommandSpoiler) + || (*(ch - 1) == TextCommandSpoiler) + || (*(ch + 1) == TextCommandNoSpoiler) + || (*(ch - 1) == TextCommandNoSpoiler)) { + continue; + } + } if (*ch == TextCommand) { result[int(ch - s)] = QChar::Space; } diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index 915e62e..27a7847 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -295,7 +295,7 @@ QString MarkdownSpoilerGoodBefore(); QString MarkdownSpoilerBadAfter(); // Text preprocess. -QString Clean(const QString &text); +QString Clean(const QString &text, bool keepSpoilers = false); QString EscapeForRichParsing(const QString &text); QString SingleLine(const QString &text); TextWithEntities SingleLine(const TextWithEntities &text); From 9c1572564b2cab8922aff670d2c6fb903efbcee4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 21 Dec 2021 17:48:06 +0300 Subject: [PATCH 19/35] Added utility to replace spoiler entities with text commands. --- ui/text/text_entity.cpp | 16 ++++++++++++++++ ui/text/text_entity.h | 3 +++ 2 files changed, 19 insertions(+) diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index df78daf..69585fe 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -2332,6 +2332,22 @@ void SetClipboardText( } } +QString TextWithSpoilerCommands(const TextWithEntities &textWithEntities) { + auto text = textWithEntities.text; + auto offset = 0; + const auto start = textcmdStartSpoiler(); + const auto stop = textcmdStopSpoiler(); + for (const auto &e : textWithEntities.entities) { + if (e.type() == EntityType::Spoiler) { + text.insert(e.offset() + offset, start); + offset += start.size(); + text.insert(e.offset() + e.length() + offset, stop); + offset += stop.size(); + } + } + return text; +} + } // namespace TextUtilities EntityInText::EntityInText( diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index 27a7847..0cf60e2 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -381,4 +381,7 @@ void SetClipboardText( const TextForMimeData &text, QClipboard::Mode mode = QClipboard::Clipboard); +[[nodiscard]] QString TextWithSpoilerCommands( + const TextWithEntities &textWithEntities); + } // namespace TextUtilities From 6c9d832ad8192bbee9e9033e18d8fbc81022e743 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 Dec 2021 20:55:47 +0300 Subject: [PATCH 20/35] Added nice corners for spoilers. --- ui/text/text.cpp | 100 ++++++++++++++++++++++++++++++++++++----------- ui/text/text.h | 5 +++ 2 files changed, 82 insertions(+), 23 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 4f6a212..b53cb4f 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -11,6 +11,8 @@ #include "ui/text/text_isolated_emoji.h" #include "ui/emoji_config.h" #include "ui/integration.h" +#include "ui/round_rect.h" +#include "ui/image/image_prepare.h" #include "base/platform/base_platform_info.h" #include "base/qt_adapters.h" @@ -1777,7 +1779,7 @@ private: } } } - if (_background.brush) { + if (_background.color) { const auto from = currentBlock->from(); const auto to = currentBlock->from() + currentBlock->width(); if (_localFrom + si.position < to) { @@ -1986,19 +1988,58 @@ private: } void fillSpoilerRange(QFixed x, QFixed width, int currentBlockIndex) { - if (_background.brush) { - const auto elideOffset = - (_indexOfElidedBlock == currentBlockIndex) - ? (_elideRemoveFromEnd + _f->elidew) - : 0; - - _p->fillRect( - x.toInt(), - _y + _yDelta, - width.toInt() - elideOffset, - _fontHeight, - *_background.brush); + if (!_background.color) { + return; } + const auto elideOffset = (_indexOfElidedBlock == currentBlockIndex) + ? (_elideRemoveFromEnd + _f->elidew) + : 0; + const auto rect = QRect( + x.toInt(), + _y + _yDelta, + width.toInt() - elideOffset, + _fontHeight); + if (!rect.isValid()) { + return; + } + + const auto parts = [&] { + const auto blockIndex = currentBlockIndex - 1; + const auto now = _t->_blocks[blockIndex]->spoilerIndex(); + const auto was = (blockIndex > 0) + ? _t->_blocks[blockIndex - 1]->spoilerIndex() + : 0; + const auto will = (blockIndex < _t->_blocks.size() - 1) + ? _t->_blocks[blockIndex + 1]->spoilerIndex() + : 0; + return RectPart::None + | ((now != was) ? (RectPart::FullLeft) : RectPart::None) + | ((now != will) ? (RectPart::FullRight) : RectPart::None); + }(); + + const auto &cache = _background.inFront + ? _t->_spoilerCache + : _t->_spoilerShownCache; + if (parts != RectPart::None) { + DrawRoundedRect( + *_p, + rect, + *_background.color, + cache.corners, + parts); + } + const auto hasLeft = (parts & RectPart::Left) != 0; + const auto hasRight = (parts & RectPart::Right) != 0; + const auto cornerWidth = cache.corners[0].width() + / style::DevicePixelRatio(); + _p->fillRect( + rect.left() + (hasLeft ? cornerWidth : 0), + rect.top(), + rect.width() + - (hasRight ? cornerWidth : 0) + - (hasLeft ? cornerWidth : 0), + rect.height(), + *_background.color); } void elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock, int32 elideStart, int32 elideWidth) { @@ -2780,17 +2821,30 @@ private: void applyBlockProperties(const AbstractBlock *block) { eSetFont(block); if (_p) { - const auto handler = !block->spoilerIndex() - ? nullptr - : _t->_spoilers.at(block->spoilerIndex() - 1); - if (!block->spoilerIndex()) { - _background = {}; - } else { + if (block->spoilerIndex()) { + const auto handler + = _t->_spoilers.at(block->spoilerIndex() - 1); const auto inBack = (handler && handler->shown()); _background.inFront = !inBack; - _background.brush = inBack - ? &_textPalette->spoilerActiveBg->b - : &_textPalette->spoilerBg->b; + _background.color = inBack + ? &_textPalette->spoilerActiveBg + : &_textPalette->spoilerBg; + + const auto &cache = _background.inFront + ? _t->_spoilerCache + : _t->_spoilerShownCache; + if (cache.color != (*_background.color)->c) { + auto mutableText = const_cast(_t); + auto &mutableCache = _background.inFront + ? mutableText->_spoilerCache + : mutableText->_spoilerShownCache; + mutableCache.corners = Images::PrepareCorners( + ImageRoundRadius::Small, + *_background.color); + mutableCache.color = (*_background.color)->c; + } + } else { + _background = {}; } if (block->lnkIndex()) { _currentPen = &_textPalette->linkFg->p; @@ -2817,7 +2871,7 @@ private: const QPen *_currentPen = nullptr; const QPen *_currentPenSelected = nullptr; struct { - const QBrush *brush = nullptr; + const style::color *color = nullptr; bool inFront = false; } _background; int _yFrom = 0; diff --git a/ui/text/text.h b/ui/text/text.h index 21530a3..d6125e1 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -229,6 +229,11 @@ private: Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto; + struct { + std::array corners; + QColor color; + } _spoilerCache, _spoilerShownCache; + friend class Parser; friend class Renderer; From af2cfca307c928903498f716be8ff42d10d946bf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 22 Dec 2021 23:36:25 +0300 Subject: [PATCH 21/35] Improved style of selected text with spoilers. --- ui/basic.style | 4 ++++ ui/text/text.cpp | 56 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/ui/basic.style b/ui/basic.style index aff4d1b..57d56d0 100644 --- a/ui/basic.style +++ b/ui/basic.style @@ -17,6 +17,7 @@ TextPalette { linkAlwaysActive: int; spoilerBg: color; spoilerActiveBg: color; + spoilerActiveFg: color; } TextStyle { @@ -48,6 +49,7 @@ defaultTextPalette: TextPalette { selectOverlay: msgSelectOverlay; spoilerBg: msgInDateFg; spoilerActiveBg: msgInDateFg; + spoilerActiveFg: msgInBg; } defaultTextStyle: TextStyle { font: normalFont; @@ -145,6 +147,7 @@ inTextPalette: TextPalette(defaultTextPalette) { selectOverlay: msgSelectOverlay; spoilerBg: msgInDateFg; spoilerActiveBg: msgInDateFg; + spoilerActiveFg: msgInBg; } inTextPaletteSelected: TextPalette(inTextPalette) { linkFg: historyLinkInFgSelected; @@ -160,6 +163,7 @@ outTextPalette: TextPalette(defaultTextPalette) { selectOverlay: msgSelectOverlay; spoilerBg: msgOutDateFg; spoilerActiveBg: msgOutDateFg; + spoilerActiveFg: msgOutBg; } outTextPaletteSelected: TextPalette(outTextPalette) { linkFg: historyLinkOutFgSelected; diff --git a/ui/text/text.cpp b/ui/text/text.cpp index b53cb4f..439203b 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -27,6 +27,7 @@ namespace { constexpr auto kStringLinkIndexShift = uint16(0x8000); constexpr auto kMaxDiacAfterSymbol = 2; +constexpr auto kSelectedSpoilerOpacity = 0.5; Qt::LayoutDirection StringDirection( const QString &str, @@ -1762,20 +1763,28 @@ private: if (rtl) { glyphX += spacesWidth; } + struct { + QFixed from; + QFixed to; + } fillSelect; + struct { + QFixed from; + QFixed width; + } fillSpoiler; if (_localFrom + si.position < _selection.to) { auto chFrom = _str + currentBlock->from(); auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); if (_localFrom + si.position >= _selection.from) { // could be without space if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || _selection.to >= (chTo - _str)) { - fillSelectRange(x, x + si.width); + fillSelect = { x, x + si.width }; } else { // or with space - fillSelectRange(glyphX, glyphX + currentBlock->f_width()); + fillSelect = { glyphX, glyphX + currentBlock->f_width() }; } } else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= _selection.from) { if (rtl) { // rtl space only - fillSelectRange(x, glyphX); + fillSelect = { x, glyphX }; } else { // ltr space only - fillSelectRange(x + currentBlock->f_width(), x + si.width); + fillSelect = { x + currentBlock->f_width(), x + si.width }; } } } @@ -1787,19 +1796,32 @@ private: auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); if (_localFrom + si.position >= from) { // could be without space if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || to >= (chTo - _str)) { - fillSpoilerRange(x, si.width, blockIndex); + fillSpoiler = { x, si.width }; } else { // or with space - fillSpoilerRange(glyphX, currentBlock->f_width(), blockIndex); + fillSpoiler = { glyphX, currentBlock->f_width() }; } } else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= from) { if (rtl) { // rtl space only - fillSpoilerRange(x, glyphX - x, blockIndex); + fillSpoiler = { x, glyphX - x }; } else { // ltr space only - fillSpoilerRange(x + currentBlock->f_width(), si.width, blockIndex); + fillSpoiler = { x + currentBlock->f_width(), si.width }; } } } } + const auto hasSpoiler = fillSpoiler.width != QFixed(); + const auto hasSelect = fillSelect.to != QFixed(); + if (hasSpoiler && !_background.inFront) { + fillSpoilerRange(fillSpoiler.from, fillSpoiler.width, blockIndex); + } + if (hasSelect) { + const auto opacity = _p->opacity(); + if (hasSpoiler && !_background.inFront) { + _p->setOpacity(kSelectedSpoilerOpacity); + } + fillSelectRange(fillSelect.from, fillSelect.to); + _p->setOpacity(opacity); + } if (!_background.inFront) { Emoji::Draw( *_p, @@ -1808,6 +1830,9 @@ private: (glyphX + st::emojiPadding).toInt(), _y + _yDelta + emojiY); } + if (hasSpoiler && _background.inFront) { + fillSpoilerRange(fillSpoiler.from, fillSpoiler.width, blockIndex); + } // } else if (_p && currentBlock->type() == TextBlockSkip) { // debug // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast(currentBlock)->height()), QColor(0, 0, 0, 32)); } @@ -1896,7 +1921,9 @@ private: gf.justified = false; gf.initWithScriptItem(si); - if (!_background.inFront) { + const auto hasBackground = !_background.inFront + && _background.color; + if (hasBackground) { fillSpoilerRange(x, si.width, blockIndex); } @@ -1904,6 +1931,11 @@ private: auto hasNotSelected = true; auto selectedRect = QRect(); if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { + const auto opacity = _p->opacity(); + if (hasBackground) { + _p->setOpacity(kSelectedSpoilerOpacity); + } + hasSelected = true; auto selX = x; auto selWidth = itemWidth; @@ -1946,6 +1978,7 @@ private: if (rtl) selX = x + itemWidth - (selX - x) - selWidth; selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight); fillSelectRange(selX, selX + selWidth); + _p->setOpacity(opacity); } if (Q_UNLIKELY(hasSelected)) { if (Q_UNLIKELY(hasNotSelected)) { @@ -2843,6 +2876,11 @@ private: *_background.color); mutableCache.color = (*_background.color)->c; } + if (inBack) { + _currentPen = &_textPalette->spoilerActiveFg->p; + _currentPenSelected = &_textPalette->spoilerActiveFg->p; + return; + } } else { _background = {}; } From 2c1e6b458d4a4c7a0119248d7cd436c1e3ba433d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 Dec 2021 15:04:51 +0300 Subject: [PATCH 22/35] Moved SpoilerClickHandler to separated file. --- CMakeLists.txt | 2 ++ ui/spoiler_click_handler.cpp | 32 ++++++++++++++++++++++++++++++++ ui/spoiler_click_handler.h | 29 +++++++++++++++++++++++++++++ ui/text/text.cpp | 29 +++-------------------------- ui/text/text.h | 4 +++- 5 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 ui/spoiler_click_handler.cpp create mode 100644 ui/spoiler_click_handler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c4970a7..8fac166 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,6 +230,8 @@ PRIVATE ui/rect_part.h ui/round_rect.cpp ui/round_rect.h + ui/spoiler_click_handler.cpp + ui/spoiler_click_handler.h ui/rp_widget.cpp ui/rp_widget.h ui/ui_utility.cpp diff --git a/ui/spoiler_click_handler.cpp b/ui/spoiler_click_handler.cpp new file mode 100644 index 0000000..a982eb8 --- /dev/null +++ b/ui/spoiler_click_handler.cpp @@ -0,0 +1,32 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/spoiler_click_handler.h" + +#include "ui/text/text_entity.h" + +ClickHandler::TextEntity SpoilerClickHandler::getTextEntity() const { + return { EntityType::Spoiler }; +} + +void SpoilerClickHandler::onClick(ClickContext context) const { + if (!_shown) { + const auto nonconst = const_cast(this); + nonconst->_shown = true; + } +} + +bool SpoilerClickHandler::shown() const { + return _shown; +} + +crl::time SpoilerClickHandler::startMs() const { + return _startMs; +} + +void SpoilerClickHandler::setStartMs(crl::time value) { + _startMs = value; +} diff --git a/ui/spoiler_click_handler.h b/ui/spoiler_click_handler.h new file mode 100644 index 0000000..8655856 --- /dev/null +++ b/ui/spoiler_click_handler.h @@ -0,0 +1,29 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/click_handler.h" + +enum class EntityType : uchar; + +class SpoilerClickHandler : public ClickHandler { +public: + SpoilerClickHandler() = default; + + TextEntity getTextEntity() const override; + + void onClick(ClickContext context) const override; + + [[nodiscard]] bool shown() const; + [[nodiscard]] crl::time startMs() const; + void setStartMs(crl::time value); + +private: + bool _shown = false; + crl::time _startMs = 0; + +}; diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 439203b..0e40f89 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -13,6 +13,7 @@ #include "ui/integration.h" #include "ui/round_rect.h" #include "ui/image/image_prepare.h" +#include "ui/spoiler_click_handler.h" #include "base/platform/base_platform_info.h" #include "base/qt_adapters.h" @@ -270,30 +271,6 @@ const TextParseOptions _textPlainOptions = { namespace Ui { namespace Text { -class String::SpoilerClickHandler final : public ClickHandler { -public: - SpoilerClickHandler() = default; - - TextEntity getTextEntity() const override { - return { EntityType::Spoiler }; - } - - void onClick(ClickContext context) const override { - if (!_shown) { - const auto nonconst = const_cast(this); - nonconst->_shown = true; - } - } - - [[nodiscard]] bool shown() const { - return _shown; - } - -private: - bool _shown = false; - -}; - class Parser { public: Parser( @@ -1024,9 +1001,9 @@ void Parser::finalize(const TextParseOptions &options) { if (spoilerIndex) { _t->_spoilers.resize(spoilerIndex); const auto handler = (options.flags & TextParseLinks) - ? std::make_shared() + ? std::make_shared() : nullptr; - _t->_spoilers[spoilerIndex - 1] = std::move(handler); + _t->setSpoiler(spoilerIndex, std::move(handler)); } const auto shiftedIndex = block->lnkIndex(); if (shiftedIndex <= kStringLinkIndexShift) { diff --git a/ui/text/text.h b/ui/text/text.h index d6125e1..99edc80 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -15,6 +15,8 @@ #include #include +class SpoilerClickHandler; + static const QChar TextCommand(0x0010); enum TextCommands { TextCommandBold = 0x01, @@ -133,6 +135,7 @@ public: void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); bool hasLinks() const; + void setSpoiler(uint16 lnkIndex, const std::shared_ptr &lnk); bool hasSkipBlock() const; bool updateSkipBlock(int width, int height); @@ -224,7 +227,6 @@ private: TextBlocks _blocks; TextLinks _links; - class SpoilerClickHandler; QVector> _spoilers; Qt::LayoutDirection _startDir = Qt::LayoutDirectionAuto; From db871a87f5a2895b6ffdca106ecdff6f710e2b02 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 24 Dec 2021 15:05:14 +0300 Subject: [PATCH 23/35] Added animation for spoilers. --- ui/text/text.cpp | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 0e40f89..27ae6e5 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -1807,8 +1807,15 @@ private: (glyphX + st::emojiPadding).toInt(), _y + _yDelta + emojiY); } - if (hasSpoiler && _background.inFront) { + if ((hasSpoiler && _background.inFront) + || _background.startMs) { + const auto opacity = _p->opacity(); + const auto fillOpacity = fillSpoilerOpacity(); + if (fillOpacity != opacity) { + _p->setOpacity(fillOpacity); + } fillSpoilerRange(fillSpoiler.from, fillSpoiler.width, blockIndex); + _p->setOpacity(opacity); } // } else if (_p && currentBlock->type() == TextBlockSkip) { // debug // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast(currentBlock)->height()), QColor(0, 0, 0, 32)); @@ -1982,8 +1989,14 @@ private: _p->drawTextItem(QPointF(x.toReal(), textY), gf); } - if (_background.inFront) { + if (_background.inFront || _background.startMs) { + const auto opacity = _p->opacity(); + const auto fillOpacity = fillSpoilerOpacity(); + if (fillOpacity != opacity) { + _p->setOpacity(fillOpacity); + } fillSpoilerRange(x, si.width, blockIndex); + _p->setOpacity(opacity); } } @@ -1997,6 +2010,21 @@ private: _p->fillRect(left, _y + _yDelta, width, _fontHeight, _textPalette->selectBg); } + float fillSpoilerOpacity() { + if (!_background.startMs) { + return 1.; + } + const auto progress = float64(crl::now() - _background.startMs) + / st::fadeWrapDuration; + if ((progress > 1.) && _background.spoilerIndex) { + const auto link = _t->_spoilers.at(_background.spoilerIndex - 1); + if (link) { + link->setStartMs(0); + } + } + return (1. - std::min(progress, 1.)); + } + void fillSpoilerRange(QFixed x, QFixed width, int currentBlockIndex) { if (!_background.color) { return; @@ -2839,6 +2867,8 @@ private: _background.color = inBack ? &_textPalette->spoilerActiveBg : &_textPalette->spoilerBg; + _background.startMs = handler ? handler->startMs() : 0; + _background.spoilerIndex = block->spoilerIndex(); const auto &cache = _background.inFront ? _t->_spoilerCache @@ -2888,6 +2918,8 @@ private: struct { const style::color *color = nullptr; bool inFront = false; + crl::time startMs = 0; + uint16 spoilerIndex = 0; } _background; int _yFrom = 0; int _yTo = 0; @@ -3121,6 +3153,13 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { _links[lnkIndex - 1] = lnk; } +void String::setSpoiler( + uint16 lnkIndex, + const std::shared_ptr &lnk) { + if (!lnkIndex || lnkIndex > _spoilers.size()) return; + _spoilers[lnkIndex - 1] = lnk; +} + bool String::hasLinks() const { return !_links.isEmpty(); } From 44853e933400a7f1536b9847fd0adc2b7d575bff Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 Dec 2021 16:53:00 +0300 Subject: [PATCH 24/35] Added ability to show or hide spoilers from outside. --- ui/spoiler_click_handler.cpp | 4 ++++ ui/spoiler_click_handler.h | 1 + ui/text/text.cpp | 17 ++++++++++++++++- ui/text/text.h | 6 +++++- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ui/spoiler_click_handler.cpp b/ui/spoiler_click_handler.cpp index a982eb8..dbd25e9 100644 --- a/ui/spoiler_click_handler.cpp +++ b/ui/spoiler_click_handler.cpp @@ -30,3 +30,7 @@ crl::time SpoilerClickHandler::startMs() const { void SpoilerClickHandler::setStartMs(crl::time value) { _startMs = value; } + +void SpoilerClickHandler::setShown(bool value) { + _shown = value; +} diff --git a/ui/spoiler_click_handler.h b/ui/spoiler_click_handler.h index 8655856..bacaf68 100644 --- a/ui/spoiler_click_handler.h +++ b/ui/spoiler_click_handler.h @@ -19,6 +19,7 @@ public: void onClick(ClickContext context) const override; [[nodiscard]] bool shown() const; + void setShown(bool value); [[nodiscard]] crl::time startMs() const; void setStartMs(crl::time value); diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 27ae6e5..9b17993 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -3156,14 +3156,29 @@ void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { void String::setSpoiler( uint16 lnkIndex, const std::shared_ptr &lnk) { - if (!lnkIndex || lnkIndex > _spoilers.size()) return; + if (!lnkIndex || lnkIndex > _spoilers.size()) { + return; + } _spoilers[lnkIndex - 1] = lnk; } +void String::setSpoilerShown(uint16 lnkIndex, bool shown) { + if (!lnkIndex + || (lnkIndex > _spoilers.size()) + || !_spoilers[lnkIndex - 1]) { + return; + } + _spoilers[lnkIndex - 1]->setShown(shown); +} + bool String::hasLinks() const { return !_links.isEmpty(); } +int String::spoilersCount() const { + return _spoilers.size(); +} + bool String::hasSkipBlock() const { return _blocks.empty() ? false : _blocks.back()->type() == TextBlockTSkip; } diff --git a/ui/text/text.h b/ui/text/text.h index 99edc80..442e9cb 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -135,7 +135,11 @@ public: void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); bool hasLinks() const; - void setSpoiler(uint16 lnkIndex, const std::shared_ptr &lnk); + void setSpoiler( + uint16 lnkIndex, + const std::shared_ptr &lnk); + void setSpoilerShown(uint16 lnkIndex, bool shown); + int spoilersCount() const; bool hasSkipBlock() const; bool updateSkipBlock(int width, int height); From 774da18a386e9f0eda71331db22bc8e9b24bf44e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 Dec 2021 17:31:10 +0300 Subject: [PATCH 25/35] Added utility to correctly cut text with commands. --- ui/text/text_entity.cpp | 14 ++++++++++++++ ui/text/text_entity.h | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 69585fe..7c047c8 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -2348,6 +2348,20 @@ QString TextWithSpoilerCommands(const TextWithEntities &textWithEntities) { return text; } +QString CutTextWithCommands( + QString text, + int length, + const QString &start, + const QString &stop) { + text = text.mid(0, length); + const auto lastStart = text.lastIndexOf(start); + const auto lastStop = text.lastIndexOf(stop); + const auto additional = ((lastStart == -1) || (lastStart < lastStop)) + ? QString() + : stop; + return text + additional + qstr("..."); +} + } // namespace TextUtilities EntityInText::EntityInText( diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index 0cf60e2..823932d 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -383,5 +383,10 @@ void SetClipboardText( [[nodiscard]] QString TextWithSpoilerCommands( const TextWithEntities &textWithEntities); +[[nodiscard]] QString CutTextWithCommands( + QString text, + int length, + const QString &start, + const QString &stop); } // namespace TextUtilities From 3461c18cda421654a9215cced21cb82fc7554090 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 Dec 2021 18:24:09 +0300 Subject: [PATCH 26/35] Extracted ellipsis string from String::Text to global access. --- ui/text/text.cpp | 20 +++++++++----------- ui/text/text.h | 4 ++++ ui/text/text_entity.cpp | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 9b17993..5b19d44 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -2104,8 +2104,6 @@ private: } void prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, const AbstractBlock *&_endBlock, int repeat = 0) { - static const auto _Elide = QString::fromLatin1("..."); - _f = _t->_st->font; QStackTextEngine engine(lineText, _f->f); engine.option.setTextDirection(_parDirection); @@ -2142,11 +2140,11 @@ private: } if (_type == TextBlockTEmoji || _type == TextBlockTSkip || _type == TextBlockTNewline) { if (_wLeft < si.width) { - lineText = lineText.mid(0, currentBlock->from() - _localFrom) + _Elide; - lineLength = currentBlock->from() + _Elide.size() - _lineStart; + lineText = lineText.mid(0, currentBlock->from() - _localFrom) + kQEllipsis; + lineLength = currentBlock->from() + kQEllipsis.size() - _lineStart; _selection.to = qMin(_selection.to, currentBlock->from()); _indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0); - setElideBidi(currentBlock->from(), _Elide.size()); + setElideBidi(currentBlock->from(), kQEllipsis.size()); elideSaveBlock(blockIndex - 1, _endBlock, currentBlock->from(), elideWidth); return; } @@ -2175,11 +2173,11 @@ private: } if (lineText.size() <= pos || repeat > 3) { - lineText += _Elide; - lineLength = _localFrom + pos + _Elide.size() - _lineStart; + lineText += kQEllipsis; + lineLength = _localFrom + pos + kQEllipsis.size() - _lineStart; _selection.to = qMin(_selection.to, uint16(_localFrom + pos)); _indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0); - setElideBidi(_localFrom + pos, _Elide.size()); + setElideBidi(_localFrom + pos, kQEllipsis.size()); _blocksSize = blockIndex; _endBlock = nextBlock; } else { @@ -2200,10 +2198,10 @@ private: int32 elideStart = _localFrom + lineText.size(); _selection.to = qMin(_selection.to, uint16(elideStart)); _indexOfElidedBlock = blockIndex + (nextBlock ? 1 : 0); - setElideBidi(elideStart, _Elide.size()); + setElideBidi(elideStart, kQEllipsis.size()); - lineText += _Elide; - lineLength += _Elide.size(); + lineText += kQEllipsis; + lineLength += kQEllipsis.size(); if (!repeat) { for (; blockIndex < _blocksSize && _t->_blocks[blockIndex].get() != _endBlock && _t->_blocks[blockIndex]->from() < elideStart; ++blockIndex) { diff --git a/ui/text/text.h b/ui/text/text.h index 442e9cb..02612c6 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -17,6 +17,10 @@ class SpoilerClickHandler; +namespace Ui { +static const auto kQEllipsis = QStringLiteral("..."); +} // namespace Ui + static const QChar TextCommand(0x0010); enum TextCommands { TextCommandBold = 0x01, diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 7c047c8..5c59c1a 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -2359,7 +2359,7 @@ QString CutTextWithCommands( const auto additional = ((lastStart == -1) || (lastStart < lastStop)) ? QString() : stop; - return text + additional + qstr("..."); + return text + additional + Ui::kQEllipsis; } } // namespace TextUtilities From 0a0e5e12589d7918a31d60b100dc32de68ef2e8a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 25 Dec 2021 20:36:45 +0300 Subject: [PATCH 27/35] Added ability to pass TextWithEntities to Checkbox. --- ui/widgets/checkbox.cpp | 40 ++++++++++++++++++++++++++++++++-------- ui/widgets/checkbox.h | 8 +++++++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ui/widgets/checkbox.cpp b/ui/widgets/checkbox.cpp index e75482b..97afa5b 100644 --- a/ui/widgets/checkbox.cpp +++ b/ui/widgets/checkbox.cpp @@ -380,7 +380,22 @@ Checkbox::Checkbox( const style::Check &checkSt) : Checkbox( parent, - text, + rpl::single(text) | rpl::map(TextWithEntities::Simple), + st, + std::make_unique( + checkSt, + checked)) { +} + +Checkbox::Checkbox( + QWidget *parent, + const TextWithEntities &text, + bool checked, + const style::Checkbox &st, + const style::Check &checkSt) +: Checkbox( + parent, + rpl::single(text), st, std::make_unique( checkSt, @@ -395,7 +410,7 @@ Checkbox::Checkbox( const style::Toggle &toggleSt) : Checkbox( parent, - rpl::single(text), + rpl::single(text) | rpl::map(TextWithEntities::Simple), st, std::make_unique( toggleSt, @@ -410,7 +425,7 @@ Checkbox::Checkbox( const style::Check &checkSt) : Checkbox( parent, - std::move(text), + std::move(text) | rpl::map(TextWithEntities::Simple), st, std::make_unique( checkSt, @@ -425,7 +440,7 @@ Checkbox::Checkbox( const style::Toggle &toggleSt) : Checkbox( parent, - std::move(text), + std::move(text) | rpl::map(TextWithEntities::Simple), st, std::make_unique( toggleSt, @@ -439,14 +454,14 @@ Checkbox::Checkbox( std::unique_ptr check) : Checkbox( parent, - rpl::single(text), + rpl::single(text) | rpl::map(TextWithEntities::Simple), st, std::move(check)) { } Checkbox::Checkbox( QWidget *parent, - rpl::producer &&text, + rpl::producer &&text, const style::Checkbox &st, std::unique_ptr check) : RippleButton(parent, st.ripple) @@ -462,8 +477,17 @@ Checkbox::Checkbox( setCursor(style::cur_pointer); std::move( text - ) | rpl::start_with_next([=](QString &&value) { - setText(std::move(value)); + ) | rpl::start_with_next([=](TextWithEntities &&value) { + if (value.entities.empty()) { + setText(base::take(value.text)); + } else { + _text.setMarkedText( + _st.style, + std::move(value), + _checkboxRichOptions); + resizeToText(); + update(); + } }, lifetime()); } diff --git a/ui/widgets/checkbox.h b/ui/widgets/checkbox.h index 7c0d7de..eeb5bf5 100644 --- a/ui/widgets/checkbox.h +++ b/ui/widgets/checkbox.h @@ -140,6 +140,12 @@ public: bool checked = false, const style::Checkbox &st = st::defaultCheckbox, const style::Check &checkSt = st::defaultCheck); + Checkbox( + QWidget *parent, + const TextWithEntities &text, + bool checked = false, + const style::Checkbox &st = st::defaultCheckbox, + const style::Check &checkSt = st::defaultCheck); Checkbox( QWidget *parent, const QString &text, @@ -165,7 +171,7 @@ public: std::unique_ptr check); Checkbox( QWidget *parent, - rpl::producer &&text, + rpl::producer &&text, const style::Checkbox &st, std::unique_ptr check); From a3b3745d7c7ff94cfeac78daca2b0d89cb6c865e Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 Dec 2021 17:52:40 +0300 Subject: [PATCH 28/35] Allow click on a submenu-creating item. --- ui/widgets/menu/menu.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 1460114..23c6bd0 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -367,8 +367,19 @@ void Menu::mouseReleaseEvent(QMouseEvent *e) { void Menu::handleMousePress(QPoint globalPosition) { handleMouseMove(globalPosition); - if (_mousePressDelegate) { - _mousePressDelegate(globalPosition); + const auto margins = style::margins(0, _st.skip, 0, _st.skip); + const auto inner = rect().marginsRemoved(margins); + const auto localPosition = mapFromGlobal(globalPosition); + const auto pressed = (inner.contains(localPosition) + && _lastSelectedByMouse) + ? findSelectedAction() + : nullptr; + if (pressed) { + pressed->setClicked(); + } else { + if (_mousePressDelegate) { + _mousePressDelegate(globalPosition); + } } } From e961883914705fbd519a293fbabc718b70d763fd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 30 Dec 2021 03:23:39 +0300 Subject: [PATCH 29/35] Fixed spoiler width for thin characters. --- ui/text/text.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 5b19d44..b7e363a 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -2032,10 +2032,15 @@ private: const auto elideOffset = (_indexOfElidedBlock == currentBlockIndex) ? (_elideRemoveFromEnd + _f->elidew) : 0; + const auto &cache = _background.inFront + ? _t->_spoilerCache + : _t->_spoilerShownCache; + const auto cornerWidth = cache.corners[0].width() + / style::DevicePixelRatio(); const auto rect = QRect( x.toInt(), _y + _yDelta, - width.toInt() - elideOffset, + std::max(width.toInt() - elideOffset, cornerWidth * 2), _fontHeight); if (!rect.isValid()) { return; @@ -2055,9 +2060,6 @@ private: | ((now != will) ? (RectPart::FullRight) : RectPart::None); }(); - const auto &cache = _background.inFront - ? _t->_spoilerCache - : _t->_spoilerShownCache; if (parts != RectPart::None) { DrawRoundedRect( *_p, @@ -2068,8 +2070,6 @@ private: } const auto hasLeft = (parts & RectPart::Left) != 0; const auto hasRight = (parts & RectPart::Right) != 0; - const auto cornerWidth = cache.corners[0].width() - / style::DevicePixelRatio(); _p->fillRect( rect.left() + (hasLeft ? cornerWidth : 0), rect.top(), From 8b0ff16b66bd0535779f4cf68f032031ac78cd73 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 30 Dec 2021 03:24:11 +0300 Subject: [PATCH 30/35] Fixed isolated emoji under spoilers. --- ui/text/text.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index b7e363a..198a153 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -3696,7 +3696,8 @@ IsolatedEmoji String::toIsolatedEmoji() const { auto result = IsolatedEmoji(); const auto skip = (_blocks.empty() || _blocks.back()->type() != TextBlockTSkip) ? 0 : 1; - if (_blocks.size() > kIsolatedEmojiLimit + skip) { + if ((_blocks.size() > kIsolatedEmojiLimit + skip) + || !_spoilers.empty()) { return IsolatedEmoji(); } auto index = 0; From 5f94fa24a0c808ce4ddbadc7d7884786f9c0a15f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 Dec 2021 15:19:45 +0300 Subject: [PATCH 31/35] Some fixes in spoiler geometry. --- ui/text/text.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 198a153..22fde20 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -1767,7 +1767,7 @@ private: } if (_background.color) { const auto from = currentBlock->from(); - const auto to = currentBlock->from() + currentBlock->width(); + const auto to = (nextBlock ? nextBlock->from() : _t->_text.size()); if (_localFrom + si.position < to) { auto chFrom = _str + currentBlock->from(); auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); @@ -1781,7 +1781,7 @@ private: if (rtl) { // rtl space only fillSpoiler = { x, glyphX - x }; } else { // ltr space only - fillSpoiler = { x + currentBlock->f_width(), si.width }; + fillSpoiler = { x + currentBlock->f_width(), si.width - currentBlock->f_width() }; } } } @@ -1995,7 +1995,7 @@ private: if (fillOpacity != opacity) { _p->setOpacity(fillOpacity); } - fillSpoilerRange(x, si.width, blockIndex); + fillSpoilerRange(x, itemWidth, blockIndex); _p->setOpacity(opacity); } } From 541c8d258b8bc3f60b71b9ea3c912d3476fb3f62 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 Dec 2021 15:20:43 +0300 Subject: [PATCH 32/35] Display shown spoilers as plain text. --- ui/text/text.cpp | 63 +++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 43 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 22fde20..a1d204d 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -1788,16 +1788,8 @@ private: } const auto hasSpoiler = fillSpoiler.width != QFixed(); const auto hasSelect = fillSelect.to != QFixed(); - if (hasSpoiler && !_background.inFront) { - fillSpoilerRange(fillSpoiler.from, fillSpoiler.width, blockIndex); - } if (hasSelect) { - const auto opacity = _p->opacity(); - if (hasSpoiler && !_background.inFront) { - _p->setOpacity(kSelectedSpoilerOpacity); - } fillSelectRange(fillSelect.from, fillSelect.to); - _p->setOpacity(opacity); } if (!_background.inFront) { Emoji::Draw( @@ -1905,21 +1897,10 @@ private: gf.justified = false; gf.initWithScriptItem(si); - const auto hasBackground = !_background.inFront - && _background.color; - if (hasBackground) { - fillSpoilerRange(x, si.width, blockIndex); - } - auto hasSelected = false; auto hasNotSelected = true; auto selectedRect = QRect(); if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { - const auto opacity = _p->opacity(); - if (hasBackground) { - _p->setOpacity(kSelectedSpoilerOpacity); - } - hasSelected = true; auto selX = x; auto selWidth = itemWidth; @@ -1962,31 +1943,32 @@ private: if (rtl) selX = x + itemWidth - (selX - x) - selWidth; selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight); fillSelectRange(selX, selX + selWidth); - _p->setOpacity(opacity); } - if (Q_UNLIKELY(hasSelected)) { - if (Q_UNLIKELY(hasNotSelected)) { - auto clippingEnabled = _p->hasClipping(); - auto clippingRegion = _p->clipRegion(); - _p->setClipRect(selectedRect, Qt::IntersectClip); - _p->setPen(*_currentPenSelected); - _p->drawTextItem(QPointF(x.toReal(), textY), gf); - auto externalClipping = clippingEnabled ? clippingRegion : QRegion(QRect((_x - _w).toInt(), _y - _lineHeight, (_x + 2 * _w).toInt(), _y + 2 * _lineHeight)); - _p->setClipRegion(externalClipping - selectedRect); - _p->setPen(*_currentPen); - _p->drawTextItem(QPointF(x.toReal(), textY), gf); - if (clippingEnabled) { - _p->setClipRegion(clippingRegion); + if (!_background.inFront || _background.startMs) { + if (Q_UNLIKELY(hasSelected)) { + if (Q_UNLIKELY(hasNotSelected)) { + auto clippingEnabled = _p->hasClipping(); + auto clippingRegion = _p->clipRegion(); + _p->setClipRect(selectedRect, Qt::IntersectClip); + _p->setPen(*_currentPenSelected); + _p->drawTextItem(QPointF(x.toReal(), textY), gf); + auto externalClipping = clippingEnabled ? clippingRegion : QRegion(QRect((_x - _w).toInt(), _y - _lineHeight, (_x + 2 * _w).toInt(), _y + 2 * _lineHeight)); + _p->setClipRegion(externalClipping - selectedRect); + _p->setPen(*_currentPen); + _p->drawTextItem(QPointF(x.toReal(), textY), gf); + if (clippingEnabled) { + _p->setClipRegion(clippingRegion); + } else { + _p->setClipping(false); + } } else { - _p->setClipping(false); + _p->setPen(*_currentPenSelected); + _p->drawTextItem(QPointF(x.toReal(), textY), gf); } } else { - _p->setPen(*_currentPenSelected); + _p->setPen(*_currentPen); _p->drawTextItem(QPointF(x.toReal(), textY), gf); } - } else { - _p->setPen(*_currentPen); - _p->drawTextItem(QPointF(x.toReal(), textY), gf); } if (_background.inFront || _background.startMs) { @@ -2881,11 +2863,6 @@ private: *_background.color); mutableCache.color = (*_background.color)->c; } - if (inBack) { - _currentPen = &_textPalette->spoilerActiveFg->p; - _currentPenSelected = &_textPalette->spoilerActiveFg->p; - return; - } } else { _background = {}; } From 9da4e1e73101ceedc85dc720f39bd8ac379bff57 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 Dec 2021 15:56:55 +0300 Subject: [PATCH 33/35] Fix spoiler rounding. --- ui/text/text.cpp | 102 +++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index a1d204d..8b54eb6 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -1765,33 +1765,23 @@ private: } } } - if (_background.color) { - const auto from = currentBlock->from(); - const auto to = (nextBlock ? nextBlock->from() : _t->_text.size()); - if (_localFrom + si.position < to) { - auto chFrom = _str + currentBlock->from(); - auto chTo = chFrom + ((nextBlock ? nextBlock->from() : _t->_text.size()) - currentBlock->from()); - if (_localFrom + si.position >= from) { // could be without space - if (chTo == chFrom || (chTo - 1)->unicode() != QChar::Space || to >= (chTo - _str)) { - fillSpoiler = { x, si.width }; - } else { // or with space - fillSpoiler = { glyphX, currentBlock->f_width() }; - } - } else if (chTo > chFrom && (chTo - 1)->unicode() == QChar::Space && (chTo - 1 - _str) >= from) { - if (rtl) { // rtl space only - fillSpoiler = { x, glyphX - x }; - } else { // ltr space only - fillSpoiler = { x + currentBlock->f_width(), si.width - currentBlock->f_width() }; - } - } - } + const auto hasSpoiler = _background.color && + (_background.inFront || _background.startMs); + if (hasSpoiler) { + fillSpoiler = { x, si.width }; } - const auto hasSpoiler = fillSpoiler.width != QFixed(); + const auto spoilerOpacity = hasSpoiler + ? fillSpoilerOpacity() + : 0.; const auto hasSelect = fillSelect.to != QFixed(); if (hasSelect) { fillSelectRange(fillSelect.from, fillSelect.to); } - if (!_background.inFront) { + const auto opacity = _p->opacity(); + if (spoilerOpacity < 1.) { + if (hasSpoiler) { + _p->setOpacity(opacity * (1. - spoilerOpacity)); + } Emoji::Draw( *_p, static_cast(currentBlock)->_emoji, @@ -1799,14 +1789,14 @@ private: (glyphX + st::emojiPadding).toInt(), _y + _yDelta + emojiY); } - if ((hasSpoiler && _background.inFront) - || _background.startMs) { - const auto opacity = _p->opacity(); - const auto fillOpacity = fillSpoilerOpacity(); - if (fillOpacity != opacity) { - _p->setOpacity(fillOpacity); - } - fillSpoilerRange(fillSpoiler.from, fillSpoiler.width, blockIndex); + if (hasSpoiler) { + _p->setOpacity(opacity * spoilerOpacity); + fillSpoilerRange( + fillSpoiler.from, + fillSpoiler.width, + blockIndex, + currentBlock->from(), + (nextBlock ? nextBlock->from() : _t->_text.size())); _p->setOpacity(opacity); } // } else if (_p && currentBlock->type() == TextBlockSkip) { // debug @@ -1944,7 +1934,15 @@ private: selectedRect = QRect(selX.toInt(), _y + _yDelta, (selX + selWidth).toInt() - selX.toInt(), _fontHeight); fillSelectRange(selX, selX + selWidth); } - if (!_background.inFront || _background.startMs) { + const auto hasSpoiler = (_background.inFront || _background.startMs); + const auto spoilerOpacity = hasSpoiler + ? fillSpoilerOpacity() + : 0.; + const auto opacity = _p->opacity(); + if (spoilerOpacity < 1.) { + if (hasSpoiler) { + _p->setOpacity(opacity * (1. - spoilerOpacity)); + } if (Q_UNLIKELY(hasSelected)) { if (Q_UNLIKELY(hasNotSelected)) { auto clippingEnabled = _p->hasClipping(); @@ -1971,13 +1969,14 @@ private: } } - if (_background.inFront || _background.startMs) { - const auto opacity = _p->opacity(); - const auto fillOpacity = fillSpoilerOpacity(); - if (fillOpacity != opacity) { - _p->setOpacity(fillOpacity); - } - fillSpoilerRange(x, itemWidth, blockIndex); + if (hasSpoiler) { + _p->setOpacity(opacity * spoilerOpacity); + fillSpoilerRange( + x, + itemWidth, + blockIndex, + _localFrom + itemStart, + _localFrom + itemEnd); _p->setOpacity(opacity); } } @@ -2007,7 +2006,12 @@ private: return (1. - std::min(progress, 1.)); } - void fillSpoilerRange(QFixed x, QFixed width, int currentBlockIndex) { + void fillSpoilerRange( + QFixed x, + QFixed width, + int currentBlockIndex, + int positionFrom, + int positionTill) { if (!_background.color) { return; } @@ -2019,10 +2023,11 @@ private: : _t->_spoilerShownCache; const auto cornerWidth = cache.corners[0].width() / style::DevicePixelRatio(); + const auto useWidth = (x + width).toInt() - x.toInt(); const auto rect = QRect( x.toInt(), _y + _yDelta, - std::max(width.toInt() - elideOffset, cornerWidth * 2), + std::max(useWidth - elideOffset, cornerWidth * 2), _fontHeight); if (!rect.isValid()) { return; @@ -2030,12 +2035,21 @@ private: const auto parts = [&] { const auto blockIndex = currentBlockIndex - 1; - const auto now = _t->_blocks[blockIndex]->spoilerIndex(); - const auto was = (blockIndex > 0) + const auto block = _t->_blocks[blockIndex].get(); + const auto nextBlock = (blockIndex + 1 < _t->_blocks.size()) + ? _t->_blocks[blockIndex + 1].get() + : nullptr; + const auto blockEnd = nextBlock ? nextBlock->from() : _t->_text.size(); + const auto now = block->spoilerIndex(); + const auto was = (positionFrom > block->from()) + ? now + : (blockIndex > 0) ? _t->_blocks[blockIndex - 1]->spoilerIndex() : 0; - const auto will = (blockIndex < _t->_blocks.size() - 1) - ? _t->_blocks[blockIndex + 1]->spoilerIndex() + const auto will = (positionTill < blockEnd) + ? now + : nextBlock + ? nextBlock->spoilerIndex() : 0; return RectPart::None | ((now != was) ? (RectPart::FullLeft) : RectPart::None) From 1f845ea91980522a52a8a4199529e5bc15a7cb87 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 30 Dec 2021 20:36:20 +0300 Subject: [PATCH 34/35] Fixed spoilers with animations disabled. --- ui/spoiler_click_handler.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/spoiler_click_handler.cpp b/ui/spoiler_click_handler.cpp index dbd25e9..ccb16eb 100644 --- a/ui/spoiler_click_handler.cpp +++ b/ui/spoiler_click_handler.cpp @@ -6,6 +6,7 @@ // #include "ui/spoiler_click_handler.h" +#include "ui/effects/animation_value.h" #include "ui/text/text_entity.h" ClickHandler::TextEntity SpoilerClickHandler::getTextEntity() const { @@ -28,6 +29,9 @@ crl::time SpoilerClickHandler::startMs() const { } void SpoilerClickHandler::setStartMs(crl::time value) { + if (anim::Disabled()) { + return; + } _startMs = value; } From 633648074a25d0d1b3524b71a63842c22894176b Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 Dec 2021 17:52:06 +0300 Subject: [PATCH 35/35] Allow smaller popup menus. --- ui/widgets/widgets.style | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 4ecc4ac..6be58d0 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -857,7 +857,7 @@ defaultMenu: Menu { arrow: defaultMenuArrow; - widthMin: 180px; + widthMin: 140px; widthMax: 300px; ripple: defaultRippleAnimation;