From ee2a1b47d92cd5862479c57cf9914b81d397252f Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Jan 2022 13:55:01 +0300 Subject: [PATCH 01/46] Fallthrough wheel events from ScrollBar to ScrollArea. --- ui/widgets/scroll_area.cpp | 4 ++++ ui/widgets/scroll_area.h | 1 + 2 files changed, 5 insertions(+) diff --git a/ui/widgets/scroll_area.cpp b/ui/widgets/scroll_area.cpp index b4b59e6..f7075dd 100644 --- a/ui/widgets/scroll_area.cpp +++ b/ui/widgets/scroll_area.cpp @@ -284,6 +284,10 @@ void ScrollBar::resizeEvent(QResizeEvent *e) { updateBar(); } +void ScrollBar::wheelEvent(QWheelEvent *e) { + static_cast(parentWidget())->viewportEvent(e); +} + auto ScrollBar::shadowVisibilityChanged() const -> rpl::producer { return _shadowVisibilityChanged.events(); diff --git a/ui/widgets/scroll_area.h b/ui/widgets/scroll_area.h index e7143d9..3ab7db1 100644 --- a/ui/widgets/scroll_area.h +++ b/ui/widgets/scroll_area.h @@ -88,6 +88,7 @@ protected: void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; + void wheelEvent(QWheelEvent *e) override; private: ScrollArea *area(); From c9a97514723d945c71dc73918f4fa8aaf002723f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 28 Dec 2021 08:18:57 +0300 Subject: [PATCH 02/46] Added utilities to mid and filter TextWithEntities. --- ui/text/text_utilities.cpp | 43 ++++++++++++++++++++++++++++++++++++++ ui/text/text_utilities.h | 8 +++++++ 2 files changed, 51 insertions(+) diff --git a/ui/text/text_utilities.cpp b/ui/text/text_utilities.cpp index 37172fd..715c8ed 100644 --- a/ui/text/text_utilities.cpp +++ b/ui/text/text_utilities.cpp @@ -74,5 +74,48 @@ TextWithEntities RichLangValue(const QString &text) { return result; } +TextWithEntities Mid(const TextWithEntities &text, int position, int n) { + if (n == -1) { + n = int(text.text.size()) - position; + } + const auto midEnd = (position + n); + auto entities = ranges::views::all( + text.entities + ) | ranges::views::filter([&](const EntityInText &entity) { + // Intersects of ranges. + const auto l1 = entity.offset(); + const auto r1 = entity.offset() + entity.length() - 1; + const auto l2 = position; + const auto r2 = midEnd - 1; + return !(l1 > r2 || l2 > r1); + }) | ranges::views::transform([&](const EntityInText &entity) { + if ((entity.offset() == position) && (entity.length() == n)) { + return entity; + } + const auto start = std::max(entity.offset(), position); + const auto end = std::min(entity.offset() + entity.length(), midEnd); + return EntityInText( + entity.type(), + start - position, + end - start, + entity.data()); + }) | ranges::to(); + return { + .text = text.text.mid(position, n), + .entities = std::move(entities), + }; +} + +TextWithEntities Filtered( + const TextWithEntities &text, + const std::vector &types) { + auto result = ranges::views::all( + text.entities + ) | ranges::views::filter([&](const EntityInText &entity) { + return ranges::contains(types, entity.type()); + }) | ranges::to(); + return { .text = text.text, .entities = std::move(result) }; +} + } // namespace Text } // namespace Ui diff --git a/ui/text/text_utilities.h b/ui/text/text_utilities.h index c77c567..b07d0e7 100644 --- a/ui/text/text_utilities.h +++ b/ui/text/text_utilities.h @@ -65,5 +65,13 @@ inline constexpr auto Upper = details::ToUpperType{}; return rpl::map(WithEntities); } +[[nodiscard]] TextWithEntities Mid( + const TextWithEntities &text, + int position, + int n = -1); +[[nodiscard]] TextWithEntities Filtered( + const TextWithEntities &result, + const std::vector &types); + } // namespace Text } // namespace Ui From 8e999fe8fb7f821887446f8008e7f5615d072e5d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 6 Jan 2022 19:38:41 +0300 Subject: [PATCH 03/46] Moved out all block implementations from header file. --- ui/text/text_block.cpp | 278 +++++++++++++++++++++++++++++++++++++++++ ui/text/text_block.h | 225 +++++---------------------------- 2 files changed, 306 insertions(+), 197 deletions(-) diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index 1e344e0..f862dfb 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -446,6 +446,10 @@ TextBlock::TextBlock( } } +QFixed TextBlock::real_f_rbearing() const { + return _words.isEmpty() ? 0 : _words.back().f_rbearing(); +} + EmojiBlock::EmojiBlock( const style::font &font, const QString &str, @@ -470,5 +474,279 @@ EmojiBlock::EmojiBlock( } } +NewlineBlock::NewlineBlock( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex, + uint16 spoilerIndex) +: AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { + _flags |= ((TextBlockTNewline & 0x0F) << 8); +} + +Qt::LayoutDirection NewlineBlock::nextDirection() const { + return _nextDir; +} + +SkipBlock::SkipBlock( + const style::font &font, + const QString &str, + uint16 from, + int32 w, + int32 h, + uint16 lnkIndex, + uint16 spoilerIndex) +: AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex) +, _height(h) { + _flags |= ((TextBlockTSkip & 0x0F) << 8); + _width = w; +} + +int SkipBlock::height() const { + return _height; +} + + +TextWord::TextWord( + uint16 from, + QFixed width, + QFixed rbearing, + QFixed rpadding) +: _from(from) +, _rbearing((rbearing.value() > 0x7FFF) + ? 0x7FFF + : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) +, _width(width) +, _rpadding(rpadding) { +} + +uint16 TextWord::from() const { + return _from; +} + +QFixed TextWord::f_rbearing() const { + return QFixed::fromFixed(_rbearing); +} + +QFixed TextWord::f_width() const { + return _width; +} + +QFixed TextWord::f_rpadding() const { + return _rpadding; +} + +void TextWord::add_rpadding(QFixed padding) { + _rpadding += padding; +} + +Block::Block() { + Unexpected("Should not be called."); +} + +Block::Block(const Block &other) { + switch (other->type()) { + case TextBlockTNewline: + emplace(other.unsafe()); + break; + case TextBlockTText: + emplace(other.unsafe()); + break; + case TextBlockTEmoji: + emplace(other.unsafe()); + break; + case TextBlockTSkip: + emplace(other.unsafe()); + break; + default: + Unexpected("Bad text block type in Block(const Block&)."); + } +} + +Block::Block(Block &&other) { + switch (other->type()) { + case TextBlockTNewline: + emplace(std::move(other.unsafe())); + break; + case TextBlockTText: + emplace(std::move(other.unsafe())); + break; + case TextBlockTEmoji: + emplace(std::move(other.unsafe())); + break; + case TextBlockTSkip: + emplace(std::move(other.unsafe())); + break; + default: + Unexpected("Bad text block type in Block(Block&&)."); + } +} + +Block &Block::operator=(const Block &other) { + if (&other == this) { + return *this; + } + destroy(); + switch (other->type()) { + case TextBlockTNewline: + emplace(other.unsafe()); + break; + case TextBlockTText: + emplace(other.unsafe()); + break; + case TextBlockTEmoji: + emplace(other.unsafe()); + break; + case TextBlockTSkip: + emplace(other.unsafe()); + break; + default: + Unexpected("Bad text block type in operator=(const Block&)."); + } + return *this; +} + +Block &Block::operator=(Block &&other) { + if (&other == this) { + return *this; + } + destroy(); + switch (other->type()) { + case TextBlockTNewline: + emplace(std::move(other.unsafe())); + break; + case TextBlockTText: + emplace(std::move(other.unsafe())); + break; + case TextBlockTEmoji: + emplace(std::move(other.unsafe())); + break; + case TextBlockTSkip: + emplace(std::move(other.unsafe())); + break; + default: + Unexpected("Bad text block type in operator=(Block&&)."); + } + return *this; +} + +Block::~Block() { + destroy(); +} + +Block Block::Newline( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex, + uint16 spoilerIndex) { + return New( + font, + str, + from, + length, + flags, + lnkIndex, + spoilerIndex); +} + +Block Block::Text( + const style::font &font, + const QString &str, + QFixed minResizeWidth, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex, + uint16 spoilerIndex) { + return New( + font, + str, + minResizeWidth, + from, + length, + flags, + lnkIndex, + spoilerIndex); +} + +Block Block::Emoji( + const style::font &font, + const QString &str, + uint16 from, + uint16 length, + uchar flags, + uint16 lnkIndex, + uint16 spoilerIndex, + EmojiPtr emoji) { + return New( + font, + str, + from, + length, + flags, + lnkIndex, + spoilerIndex, + emoji); +} + +Block Block::Skip( + const style::font &font, + const QString &str, + uint16 from, + int32 w, + int32 h, + uint16 lnkIndex, + uint16 spoilerIndex) { + return New(font, str, from, w, h, lnkIndex, spoilerIndex); +} + +AbstractBlock *Block::get() { + return &unsafe(); +} + +const AbstractBlock *Block::get() const { + return &unsafe(); +} + +AbstractBlock *Block::operator->() { + return get(); +} + +const AbstractBlock *Block::operator->() const { + return get(); +} + +AbstractBlock &Block::operator*() { + return *get(); +} + +const AbstractBlock &Block::operator*() const { + return *get(); +} + +void Block::destroy() { + switch (get()->type()) { + case TextBlockTNewline: + unsafe().~NewlineBlock(); + break; + case TextBlockTText: + unsafe().~TextBlock(); + break; + case TextBlockTEmoji: + unsafe().~EmojiBlock(); + break; + case TextBlockTSkip: + unsafe().~SkipBlock(); + break; + default: + Unexpected("Bad text block type in Block(Block&&)."); + } +} + } // namespace Text } // namespace Ui diff --git a/ui/text/text_block.h b/ui/text/text_block.h index e1a6534..b57075b 100644 --- a/ui/text/text_block.h +++ b/ui/text/text_block.h @@ -88,14 +88,9 @@ public: uint16 length, uchar flags, uint16 lnkIndex, - uint16 spoilerIndex) - : AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { - _flags |= ((TextBlockTNewline & 0x0F) << 8); - } + uint16 spoilerIndex); - Qt::LayoutDirection nextDirection() const { - return _nextDir; - } + Qt::LayoutDirection nextDirection() const; private: Qt::LayoutDirection _nextDir = Qt::LayoutDirectionAuto; @@ -109,29 +104,12 @@ private: class TextWord final { public: TextWord() = default; - TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) - : _from(from) - , _rbearing((rbearing.value() > 0x7FFF) - ? 0x7FFF - : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())) - , _width(width) - , _rpadding(rpadding) { - } - uint16 from() const { - return _from; - } - QFixed f_rbearing() const { - return QFixed::fromFixed(_rbearing); - } - QFixed f_width() const { - return _width; - } - QFixed f_rpadding() const { - return _rpadding; - } - void add_rpadding(QFixed padding) { - _rpadding += padding; - } + TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0); + uint16 from() const; + QFixed f_rbearing() const; + QFixed f_width() const; + QFixed f_rpadding() const; + void add_rpadding(QFixed padding); private: uint16 _from = 0; @@ -153,9 +131,7 @@ public: uint16 spoilerIndex); private: - QFixed real_f_rbearing() const { - return _words.isEmpty() ? 0 : _words.back().f_rbearing(); - } + QFixed real_f_rbearing() const; QVector _words; @@ -197,16 +173,9 @@ public: int32 w, int32 h, uint16 lnkIndex, - uint16 spoilerIndex) - : AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex) - , _height(h) { - _flags |= ((TextBlockTSkip & 0x0F) << 8); - _width = w; - } + uint16 spoilerIndex); - int height() const { - return _height; - } + int height() const; private: int _height = 0; @@ -219,94 +188,12 @@ private: class Block final { public: - Block() { - Unexpected("Should not be called."); - } - Block(const Block &other) { - switch (other->type()) { - case TextBlockTNewline: - emplace(other.unsafe()); - break; - case TextBlockTText: - emplace(other.unsafe()); - break; - case TextBlockTEmoji: - emplace(other.unsafe()); - break; - case TextBlockTSkip: - emplace(other.unsafe()); - break; - default: - Unexpected("Bad text block type in Block(const Block&)."); - } - } - Block(Block &&other) { - switch (other->type()) { - case TextBlockTNewline: - emplace(std::move(other.unsafe())); - break; - case TextBlockTText: - emplace(std::move(other.unsafe())); - break; - case TextBlockTEmoji: - emplace(std::move(other.unsafe())); - break; - case TextBlockTSkip: - emplace(std::move(other.unsafe())); - break; - default: - Unexpected("Bad text block type in Block(Block&&)."); - } - } - Block &operator=(const Block &other) { - if (&other == this) { - return *this; - } - destroy(); - switch (other->type()) { - case TextBlockTNewline: - emplace(other.unsafe()); - break; - case TextBlockTText: - emplace(other.unsafe()); - break; - case TextBlockTEmoji: - emplace(other.unsafe()); - break; - case TextBlockTSkip: - emplace(other.unsafe()); - break; - default: - Unexpected("Bad text block type in operator=(const Block&)."); - } - return *this; - } - Block &operator=(Block &&other) { - if (&other == this) { - return *this; - } - destroy(); - switch (other->type()) { - case TextBlockTNewline: - emplace(std::move(other.unsafe())); - break; - case TextBlockTText: - emplace(std::move(other.unsafe())); - break; - case TextBlockTEmoji: - emplace(std::move(other.unsafe())); - break; - case TextBlockTSkip: - emplace(std::move(other.unsafe())); - break; - default: - Unexpected("Bad text block type in operator=(Block&&)."); - } - return *this; - } - ~Block() { - destroy(); - } + Block(); + Block(const Block &other); + Block(Block &&other); + Block &operator=(const Block &other); + Block &operator=(Block &&other); + ~Block(); [[nodiscard]] static Block Newline( const style::font &font, @@ -315,9 +202,7 @@ public: uint16 length, uchar flags, uint16 lnkIndex, - uint16 spoilerIndex) { - return New(font, str, from, length, flags, lnkIndex, spoilerIndex); - } + uint16 spoilerIndex); [[nodiscard]] static Block Text( const style::font &font, @@ -327,17 +212,7 @@ public: uint16 length, uchar flags, uint16 lnkIndex, - uint16 spoilerIndex) { - return New( - font, - str, - minResizeWidth, - from, - length, - flags, - lnkIndex, - spoilerIndex); - } + uint16 spoilerIndex); [[nodiscard]] static Block Emoji( const style::font &font, @@ -347,17 +222,7 @@ public: uchar flags, uint16 lnkIndex, uint16 spoilerIndex, - EmojiPtr emoji) { - return New( - font, - str, - from, - length, - flags, - lnkIndex, - spoilerIndex, - emoji); - } + EmojiPtr emoji); [[nodiscard]] static Block Skip( const style::font &font, @@ -366,9 +231,7 @@ public: int32 w, int32 h, uint16 lnkIndex, - uint16 spoilerIndex) { - return New(font, str, from, w, h, lnkIndex, spoilerIndex); - } + uint16 spoilerIndex); template [[nodiscard]] FinalBlock &unsafe() { @@ -380,29 +243,14 @@ public: return *reinterpret_cast(&_data); } - [[nodiscard]] AbstractBlock *get() { - return &unsafe(); - } + [[nodiscard]] AbstractBlock *get(); + [[nodiscard]] const AbstractBlock *get() const; - [[nodiscard]] const AbstractBlock *get() const { - return &unsafe(); - } + [[nodiscard]] AbstractBlock *operator->(); + [[nodiscard]] const AbstractBlock *operator->() const; - [[nodiscard]] AbstractBlock *operator->() { - return get(); - } - - [[nodiscard]] const AbstractBlock *operator->() const { - return get(); - } - - [[nodiscard]] AbstractBlock &operator*() { - return *get(); - } - - [[nodiscard]] const AbstractBlock &operator*() const { - return *get(); - } + [[nodiscard]] AbstractBlock &operator*(); + [[nodiscard]] const AbstractBlock &operator*() const; private: struct Tag { @@ -423,24 +271,7 @@ private: new (&_data) FinalType(std::forward(args)...); } - void destroy() { - switch (get()->type()) { - case TextBlockTNewline: - unsafe().~NewlineBlock(); - break; - case TextBlockTText: - unsafe().~TextBlock(); - break; - case TextBlockTEmoji: - unsafe().~EmojiBlock(); - break; - case TextBlockTSkip: - unsafe().~SkipBlock(); - break; - default: - Unexpected("Bad text block type in Block(Block&&)."); - } - } + void destroy(); static_assert(sizeof(NewlineBlock) <= sizeof(TextBlock)); static_assert(alignof(NewlineBlock) <= alignof(void*)); From 745ce34dd5545737dd7c4dfc3f22a4e3d52d13a9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 29 Dec 2021 18:27:23 +0300 Subject: [PATCH 04/46] Added new entity type for plain non-interactive links. Increased size of the block flags by two bits. Added a text utility to wrap a simple text with plain links. Added a new flag to specify parsing of plain links. --- ui/text/text.cpp | 15 +++++++++++++-- ui/text/text_block.cpp | 32 ++++++++++++++++---------------- ui/text/text_block.h | 17 +++++++++-------- ui/text/text_entity.h | 2 ++ ui/text/text_utilities.cpp | 4 ++++ ui/text/text_utilities.h | 1 + 6 files changed, 45 insertions(+), 26 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 8b54eb6..e24a385 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -77,7 +77,9 @@ TextWithEntities PrepareRichFromRich( const TextParseOptions &options) { auto result = text; const auto &preparsed = text.entities; - if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) { + const bool parseLinks = (options.flags & TextParseLinks); + const bool parsePlainLinks = (options.flags & TextParsePlainLinks); + if (!preparsed.isEmpty() && (parseLinks || parsePlainLinks)) { bool parseMentions = (options.flags & TextParseMentions); bool parseHashtags = (options.flags & TextParseHashtags); bool parseBotCommands = (options.flags & TextParseBotCommands); @@ -91,6 +93,12 @@ TextWithEntities PrepareRichFromRich( if (((type == EntityType::Mention || type == EntityType::MentionName) && !parseMentions) || (type == EntityType::Hashtag && !parseHashtags) || (type == EntityType::Cashtag && !parseHashtags) || + (type == EntityType::PlainLink + && !parsePlainLinks + && !parseMarkdown) || + (!parseLinks + && (type == EntityType::Url + || type == EntityType::CustomUrl)) || (type == EntityType::BotCommand && !parseBotCommands) || // #TODO entities (!parseMarkdown && (type == EntityType::Bold || type == EntityType::Semibold @@ -601,6 +609,8 @@ bool Parser::checkEntities() { flags = TextBlockFItalic; } else if (entityType == EntityType::Underline) { flags = TextBlockFUnderline; + } else if (entityType == EntityType::PlainLink) { + flags = TextBlockFPlainLink; } else if (entityType == EntityType::StrikeOut) { flags = TextBlockFStrikeOut; } else if (entityType == EntityType::Code) { // #TODO entities @@ -2880,7 +2890,7 @@ private: } else { _background = {}; } - if (block->lnkIndex()) { + if (block->lnkIndex() || (block->flags() & TextBlockFPlainLink)) { _currentPen = &_textPalette->linkFg->p; _currentPenSelected = &_textPalette->selectLinkFg->p; } else if ((block->flags() & TextBlockFCode) || (block->flags() & TextBlockFPre)) { @@ -3576,6 +3586,7 @@ TextForMimeData String::toText( { TextBlockFBold, EntityType::Bold }, { TextBlockFSemibold, EntityType::Semibold }, { TextBlockFUnderline, EntityType::Underline }, + { TextBlockFPlainLink, EntityType::PlainLink }, { TextBlockFStrikeOut, EntityType::StrikeOut }, { TextBlockFCode, EntityType::Code }, // #TODO entities { TextBlockFPre, EntityType::Pre }, diff --git a/ui/text/text_block.cpp b/ui/text/text_block.cpp index f862dfb..a110c15 100644 --- a/ui/text/text_block.cpp +++ b/ui/text/text_block.cpp @@ -345,11 +345,11 @@ AbstractBlock::AbstractBlock( const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex) : _from(from) -, _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12)) +, _flags((flags & 0b1111111111) | ((lnkIndex & 0xFFFF) << 14)) , _spoilerIndex(spoilerIndex) { } @@ -374,11 +374,11 @@ QFixed AbstractBlock::f_rpadding() const { } uint16 AbstractBlock::lnkIndex() const { - return (_flags >> 12) & 0xFFFF; + return (_flags >> 14) & 0xFFFF; } void AbstractBlock::setLnkIndex(uint16 lnkIndex) { - _flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12); + _flags = (_flags & ~(0xFFFF << 14)) | (lnkIndex << 14); } uint16 AbstractBlock::spoilerIndex() const { @@ -390,11 +390,11 @@ void AbstractBlock::setSpoilerIndex(uint16 spoilerIndex) { } TextBlockType AbstractBlock::type() const { - return TextBlockType((_flags >> 8) & 0x0F); + return TextBlockType((_flags >> 10) & 0x0F); } int32 AbstractBlock::flags() const { - return (_flags & 0xFFF); + return (_flags & 0b1111111111); } QFixed AbstractBlock::f_rbearing() const { @@ -409,11 +409,11 @@ TextBlock::TextBlock( QFixed minResizeWidth, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { - _flags |= ((TextBlockTText & 0x0F) << 8); + _flags |= ((TextBlockTText & 0x0F) << 10); if (length) { style::font blockFont = font; if (!flags && lnkIndex) { @@ -455,13 +455,13 @@ EmojiBlock::EmojiBlock( const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex, EmojiPtr emoji) : AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) , _emoji(emoji) { - _flags |= ((TextBlockTEmoji & 0x0F) << 8); + _flags |= ((TextBlockTEmoji & 0x0F) << 10); _width = int(st::emojiSize + 2 * st::emojiPadding); _rpadding = 0; for (auto i = length; i != 0;) { @@ -479,11 +479,11 @@ NewlineBlock::NewlineBlock( const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex) : AbstractBlock(font, str, from, length, flags, lnkIndex, spoilerIndex) { - _flags |= ((TextBlockTNewline & 0x0F) << 8); + _flags |= ((TextBlockTNewline & 0x0F) << 10); } Qt::LayoutDirection NewlineBlock::nextDirection() const { @@ -500,7 +500,7 @@ SkipBlock::SkipBlock( uint16 spoilerIndex) : AbstractBlock(font, str, from, 1, 0, lnkIndex, spoilerIndex) , _height(h) { - _flags |= ((TextBlockTSkip & 0x0F) << 8); + _flags |= ((TextBlockTSkip & 0x0F) << 10); _width = w; } @@ -641,7 +641,7 @@ Block Block::Newline( const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex) { return New( @@ -660,7 +660,7 @@ Block Block::Text( QFixed minResizeWidth, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex) { return New( @@ -679,7 +679,7 @@ Block Block::Emoji( const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex, EmojiPtr emoji) { diff --git a/ui/text/text_block.h b/ui/text/text_block.h index b57075b..c02d68c 100644 --- a/ui/text/text_block.h +++ b/ui/text/text_block.h @@ -30,6 +30,7 @@ enum TextBlockFlags { TextBlockFSemibold = 0x20, TextBlockFCode = 0x40, TextBlockFPre = 0x80, + TextBlockFPlainLink = 0x100, }; class AbstractBlock { @@ -58,13 +59,13 @@ protected: const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex); uint16 _from = 0; - uint32 _flags = 0; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags + uint32 _flags = 0; // 2 bits empty, 16 bits lnkIndex, 4 bits type, 10 bits flags uint16 _spoilerIndex = 0; @@ -86,7 +87,7 @@ public: const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex); @@ -126,7 +127,7 @@ public: QFixed minResizeWidth, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex); @@ -150,7 +151,7 @@ public: const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex, EmojiPtr emoji); @@ -200,7 +201,7 @@ public: const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex); @@ -210,7 +211,7 @@ public: QFixed minResizeWidth, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex); @@ -219,7 +220,7 @@ public: const QString &str, uint16 from, uint16 length, - uchar flags, + uint16 flags, uint16 lnkIndex, uint16 spoilerIndex, EmojiPtr emoji); diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index 823932d..cbc1168 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -25,6 +25,7 @@ enum class EntityType : uchar { MentionName, BotCommand, MediaTimestamp, + PlainLink, // Senders in chat list, attachements in chat list, etc. Bold, Semibold, @@ -241,6 +242,7 @@ enum { TextParseHashtags = 0x010, TextParseBotCommands = 0x020, TextParseMarkdown = 0x040, + TextParsePlainLinks = 0x080, }; struct TextWithTags { diff --git a/ui/text/text_utilities.cpp b/ui/text/text_utilities.cpp index 715c8ed..dcc8d4f 100644 --- a/ui/text/text_utilities.cpp +++ b/ui/text/text_utilities.cpp @@ -42,6 +42,10 @@ TextWithEntities Link(const QString &text, const QString &url) { return WithSingleEntity(text, EntityType::CustomUrl, url); } +TextWithEntities PlainLink(const QString &text) { + return WithSingleEntity(text, EntityType::PlainLink); +} + TextWithEntities RichLangValue(const QString &text) { static const auto kStart = QRegularExpression("(\\*\\*|__)"); diff --git a/ui/text/text_utilities.h b/ui/text/text_utilities.h index b07d0e7..3ebe974 100644 --- a/ui/text/text_utilities.h +++ b/ui/text/text_utilities.h @@ -30,6 +30,7 @@ inline constexpr auto Upper = details::ToUpperType{}; [[nodiscard]] TextWithEntities Link( const QString &text, const QString &url = "internal:action"); +[[nodiscard]] TextWithEntities PlainLink(const QString &text); [[nodiscard]] TextWithEntities RichLangValue(const QString &text); [[nodiscard]] inline TextWithEntities WithEntities(const QString &text) { return { text }; From 09b56b019be5c4824d075efb044a2ad54cedda6b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 6 Jan 2022 21:37:50 +0300 Subject: [PATCH 05/46] Added utilities to wrap TextWithEntities. --- ui/text/text_utilities.cpp | 7 +++++++ ui/text/text_utilities.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/ui/text/text_utilities.cpp b/ui/text/text_utilities.cpp index dcc8d4f..5a2d468 100644 --- a/ui/text/text_utilities.cpp +++ b/ui/text/text_utilities.cpp @@ -46,6 +46,13 @@ TextWithEntities PlainLink(const QString &text) { return WithSingleEntity(text, EntityType::PlainLink); } +TextWithEntities Wrapped(TextWithEntities text, EntityType type) { + text.entities.insert( + text.entities.begin(), + { type, 0, int(text.text.size()), {} }); + return text; +} + TextWithEntities RichLangValue(const QString &text) { static const auto kStart = QRegularExpression("(\\*\\*|__)"); diff --git a/ui/text/text_utilities.h b/ui/text/text_utilities.h index 3ebe974..3b2eddf 100644 --- a/ui/text/text_utilities.h +++ b/ui/text/text_utilities.h @@ -31,6 +31,9 @@ inline constexpr auto Upper = details::ToUpperType{}; const QString &text, const QString &url = "internal:action"); [[nodiscard]] TextWithEntities PlainLink(const QString &text); +[[nodiscard]] TextWithEntities Wrapped( + TextWithEntities text, + EntityType type); [[nodiscard]] TextWithEntities RichLangValue(const QString &text); [[nodiscard]] inline TextWithEntities WithEntities(const QString &text) { return { text }; From 7e279bd83ac97e10a747143e0634683f773d787b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 7 Jan 2022 02:02:41 +0300 Subject: [PATCH 06/46] Removed text commands. --- ui/text/text.cpp | 305 +++------------------------------------- ui/text/text.h | 38 +---- ui/text/text_entity.cpp | 161 ++------------------- ui/text/text_entity.h | 9 -- 4 files changed, 29 insertions(+), 484 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index e24a385..a244a6a 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -156,112 +156,6 @@ bool IsBad(QChar ch) { } // namespace Text } // namespace Ui -QString textcmdSkipBlock(ushort w, ushort h) { - static QString cmd(5, TextCommand); - cmd[1] = QChar(TextCommandSkipBlock); - cmd[2] = QChar(w); - cmd[3] = QChar(h); - return cmd; -} - -QString textcmdStartLink(ushort lnkIndex) { - static QString cmd(4, TextCommand); - cmd[1] = QChar(TextCommandLinkIndex); - cmd[2] = QChar(lnkIndex); - return cmd; -} - -QString textcmdStartLink(const QString &url) { - if (url.size() >= 4096) return QString(); - - QString result; - result.reserve(url.size() + 4); - return result.append(TextCommand).append(QChar(TextCommandLinkText)).append(QChar(int(url.size()))).append(url).append(TextCommand); -} - -QString textcmdStopLink() { - return textcmdStartLink(0); -} - -QString textcmdLink(ushort lnkIndex, const QString &text) { - QString result; - result.reserve(4 + text.size() + 4); - return result.append(textcmdStartLink(lnkIndex)).append(text).append(textcmdStopLink()); -} - -QString textcmdLink(const QString &url, const QString &text) { - QString result; - result.reserve(4 + url.size() + text.size() + 4); - return result.append(textcmdStartLink(url)).append(text).append(textcmdStopLink()); -} - -QString textcmdStartSemibold() { - QString result; - result.reserve(3); - return result.append(TextCommand).append(QChar(TextCommandSemibold)).append(TextCommand); -} - -QString textcmdStopSemibold() { - QString result; - result.reserve(3); - 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; - - ushort cmd = result->unicode(); - ++result; - if (result >= end) return from; - - switch (cmd) { - case TextCommandBold: - case TextCommandNoBold: - case TextCommandSemibold: - case TextCommandNoSemibold: - case TextCommandItalic: - case TextCommandNoItalic: - case TextCommandUnderline: - case TextCommandNoUnderline: - case TextCommandSpoiler: - case TextCommandNoSpoiler: - break; - - case TextCommandLinkIndex: - if (result->unicode() > 0x7FFF) return from; - ++result; - break; - - case TextCommandLinkText: { - ushort len = result->unicode(); - if (len >= 4096 || !canLink) return from; - result += len + 1; - } break; - - case TextCommandSkipBlock: - result += 2; - break; - - case TextCommandLangTag: - result += 1; - break; - } - return (result < end && *result == TextCommand) ? (result + 1) : from; -} - const TextParseOptions _defaultOptions = { TextParseLinks | TextParseMultiline, // flags 0, // maxw @@ -328,15 +222,11 @@ private: void createBlock(int32 skipBack = 0); void createSkipBlock(int32 w, int32 h); void createNewlineBlock(); - bool checkCommand(); // Returns true if at least one entity was parsed in the current position. bool checkEntities(); - bool readSkipBlockCommand(); - bool readCommand(); void parseCurrentChar(); void parseEmojiFromCurrent(); - void checkForElidedSkipBlock(); void finalize(const TextParseOptions &options); void finishEntities(); @@ -528,17 +418,6 @@ void Parser::createNewlineBlock() { createBlock(); } -bool Parser::checkCommand() { - bool result = false; - for (QChar c = ((_ptr < _end) ? *_ptr : 0); c == TextCommand; c = ((_ptr < _end) ? *_ptr : 0)) { - if (!readCommand()) { - break; - } - result = true; - } - return result; -} - void Parser::finishEntities() { while (!_startedEntities.empty() && (_ptr >= _startedEntities.begin()->first || _ptr >= _end)) { @@ -691,149 +570,6 @@ void Parser::skipBadEntities() { } } -bool Parser::readSkipBlockCommand() { - const QChar *afterCmd = textSkipCommand(_ptr, _end, _links.size() < 0x7FFF); - if (afterCmd == _ptr) { - return false; - } - - ushort cmd = (++_ptr)->unicode(); - ++_ptr; - - switch (cmd) { - case TextCommandSkipBlock: - createSkipBlock(_ptr->unicode(), (_ptr + 1)->unicode()); - break; - } - - _ptr = afterCmd; - return true; -} - -bool Parser::readCommand() { - const QChar *afterCmd = textSkipCommand(_ptr, _end, _links.size() < 0x7FFF); - if (afterCmd == _ptr) { - return false; - } - - ushort cmd = (++_ptr)->unicode(); - ++_ptr; - - switch (cmd) { - case TextCommandBold: - if (!(_flags & TextBlockFBold)) { - createBlock(); - _flags |= TextBlockFBold; - } - break; - - case TextCommandNoBold: - if (_flags & TextBlockFBold) { - createBlock(); - _flags &= ~TextBlockFBold; - } - break; - - case TextCommandSemibold: - if (!(_flags & TextBlockFSemibold)) { - createBlock(); - _flags |= TextBlockFSemibold; - } - break; - - case TextCommandNoSemibold: - if (_flags & TextBlockFSemibold) { - createBlock(); - _flags &= ~TextBlockFSemibold; - } - break; - - case TextCommandItalic: - if (!(_flags & TextBlockFItalic)) { - createBlock(); - _flags |= TextBlockFItalic; - } - break; - - case TextCommandNoItalic: - if (_flags & TextBlockFItalic) { - createBlock(); - _flags &= ~TextBlockFItalic; - } - break; - - case TextCommandUnderline: - if (!(_flags & TextBlockFUnderline)) { - createBlock(); - _flags |= TextBlockFUnderline; - } - break; - - case TextCommandNoUnderline: - if (_flags & TextBlockFUnderline) { - createBlock(); - _flags &= ~TextBlockFUnderline; - } - break; - - case TextCommandStrikeOut: - if (!(_flags & TextBlockFStrikeOut)) { - createBlock(); - _flags |= TextBlockFStrikeOut; - } - break; - - case TextCommandNoStrikeOut: - if (_flags & TextBlockFStrikeOut) { - createBlock(); - _flags &= ~TextBlockFStrikeOut; - } - break; - - case TextCommandLinkIndex: - if (_ptr->unicode() != _lnkIndex) { - createBlock(); - _lnkIndex = _ptr->unicode(); - } - break; - - case TextCommandLinkText: { - createBlock(); - int32 len = _ptr->unicode(); - _links.push_back(EntityLinkData{ - .data = QString(++_ptr, len), - .type = EntityType::CustomUrl - }); - _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; - } - - _ptr = afterCmd; - return true; -} - void Parser::parseCurrentChar() { _ch = ((_ptr < _end) ? *_ptr : 0); _emojiLookback = 0; @@ -964,7 +700,7 @@ void Parser::parse(const TextParseOptions &options) { _t->_text.reserve(_end - _ptr); for (; _ptr <= _end; ++_ptr) { - while (checkEntities() || (_rich && checkCommand())) { + while (checkEntities()) { } parseCurrentChar(); parseEmojiFromCurrent(); @@ -974,7 +710,6 @@ void Parser::parse(const TextParseOptions &options) { } } createBlock(); - checkForElidedSkipBlock(); finalize(options); } @@ -983,25 +718,25 @@ void Parser::trimSourceRange() { _source.entities, _end - _start); - while (_ptr != _end && IsTrimmed(*_ptr, _rich) && _ptr != _start + firstMonospaceOffset) { + while (_ptr != _end && IsTrimmed(*_ptr) && _ptr != _start + firstMonospaceOffset) { ++_ptr; } - while (_ptr != _end && IsTrimmed(*(_end - 1), _rich)) { + while (_ptr != _end && IsTrimmed(*(_end - 1))) { --_end; } } -void Parser::checkForElidedSkipBlock() { - if (!_sumFinished || !_rich) { - return; - } - // We could've skipped the final skip block command. - for (; _ptr < _end; ++_ptr) { - if (*_ptr == TextCommand && readSkipBlockCommand()) { - break; - } - } -} +// void Parser::checkForElidedSkipBlock() { +// if (!_sumFinished || !_rich) { +// return; +// } +// // We could've skipped the final skip block command. +// for (; _ptr < _end; ++_ptr) { +// if (*_ptr == TextCommand && readSkipBlockCommand()) { +// break; +// } +// } +// } void Parser::finalize(const TextParseOptions &options) { _t->_links.resize(_maxLnkIndex); @@ -3795,8 +3530,7 @@ bool IsAlmostLinkEnd(QChar ch) { } bool IsLinkEnd(QChar ch) { - return (ch == TextCommand) - || IsBad(ch) + return IsBad(ch) || IsSpace(ch) || IsNewline(ch) || ch.isLowSurrogate() @@ -3808,9 +3542,9 @@ bool IsNewline(QChar ch) { || (ch == 156); } -bool IsSpace(QChar ch, bool rich) { +bool IsSpace(QChar ch) { return ch.isSpace() - || (ch < 32 && !(rich && ch == TextCommand)) + || (ch < 32) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) @@ -3840,9 +3574,8 @@ bool IsReplacedBySpace(QChar ch) { || (ch >= 8232 && ch <= 8237); } -bool IsTrimmed(QChar ch, bool rich) { - return (!rich || ch != TextCommand) - && (IsSpace(ch) || IsBad(ch)); +bool IsTrimmed(QChar ch) { + return (IsSpace(ch) || IsBad(ch)); } } // namespace Text diff --git a/ui/text/text.h b/ui/text/text.h index 02612c6..e59438e 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -22,25 +22,6 @@ static const auto kQEllipsis = QStringLiteral("..."); } // namespace Ui static const QChar TextCommand(0x0010); -enum TextCommands { - TextCommandBold = 0x01, - TextCommandNoBold = 0x02, - TextCommandItalic = 0x03, - TextCommandNoItalic = 0x04, - TextCommandUnderline = 0x05, - TextCommandNoUnderline = 0x06, - TextCommandStrikeOut = 0x07, - TextCommandNoStrikeOut = 0x08, - TextCommandSemibold = 0x09, - TextCommandNoSemibold = 0x0A, - TextCommandLinkIndex = 0x0B, // 0 - NoLink - TextCommandLinkText = 0x0C, - TextCommandSkipBlock = 0x0D, - TextCommandSpoiler = 0x0E, - TextCommandNoSpoiler = 0x0F, - - TextCommandLangTag = 0x20, -}; struct TextParseOptions { int32 flags; @@ -253,10 +234,10 @@ private: [[nodiscard]] bool IsAlmostLinkEnd(QChar ch); [[nodiscard]] bool IsLinkEnd(QChar ch); [[nodiscard]] bool IsNewline(QChar ch); -[[nodiscard]] bool IsSpace(QChar ch, bool rich = false); +[[nodiscard]] bool IsSpace(QChar ch); [[nodiscard]] bool IsDiac(QChar ch); [[nodiscard]] bool IsReplacedBySpace(QChar ch); -[[nodiscard]] bool IsTrimmed(QChar ch, bool rich = false); +[[nodiscard]] bool IsTrimmed(QChar ch); } // namespace Text } // namespace Ui @@ -276,18 +257,3 @@ inline TextSelection shiftSelection(TextSelection selection, const Ui::Text::Str inline TextSelection unshiftSelection(TextSelection selection, const Ui::Text::String &byText) { return unshiftSelection(selection, byText.length()); } - -// textcmd -QString textcmdSkipBlock(ushort w, ushort h); -QString textcmdStartLink(ushort lnkIndex); -QString textcmdStartLink(const QString &url); -QString textcmdStopLink(); -QString textcmdLink(ushort lnkIndex, const QString &text); -QString textcmdLink(const QString &url, const QString &text); -QString textcmdStartSemibold(); -QString textcmdStopSemibold(); - -QString textcmdStartSpoiler(); -QString textcmdStopSpoiler(); - -const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true); diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 5c59c1a..36cb1e1 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -1293,35 +1293,17 @@ bool IsValidTopDomain(const QString &protocol) { return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar))); } -QString Clean(const QString &text, bool keepSpoilers) { - auto result = text; - for (auto s = text.unicode(), ch = s, e = text.unicode() + text.size(); ch != e; ++ch) { - if (keepSpoilers && (*ch == TextCommand)) { - if ((*(ch + 1) == TextCommandSpoiler) - || (*(ch - 1) == TextCommandSpoiler) - || (*(ch + 1) == TextCommandNoSpoiler) - || (*(ch - 1) == TextCommandNoSpoiler)) { - continue; - } - } - if (*ch == TextCommand) { - result[int(ch - s)] = QChar::Space; - } - } - return result; -} - QString EscapeForRichParsing(const QString &text) { QString result; result.reserve(text.size()); auto s = text.constData(), ch = s; for (const QChar *e = s + text.size(); ch != e; ++ch) { - if (*ch == TextCommand) { - if (ch > s) result.append(s, ch - s); - result.append(QChar::Space); - s = ch + 1; - continue; - } + // if (*ch == TextCommand) { + // if (ch > s) result.append(s, ch - s); + // result.append(QChar::Space); + // s = ch + 1; + // continue; + // } if (ch->unicode() == '\\' || ch->unicode() == '[') { if (ch > s) result.append(s, ch - s); result.append('\\'); @@ -1349,7 +1331,7 @@ QString SingleLine(const QString &text) { } for (auto ch = s; ch != e; ++ch) { - if (IsNewline(*ch) || *ch == TextCommand) { + if (IsNewline(*ch)/* || *ch == TextCommand*/) { result[int(ch - s)] = QChar::Space; } } @@ -1545,47 +1527,6 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) { return true; } -bool textcmdStartsLink(const QChar *start, int32 len, int32 commandOffset) { - if (commandOffset + 2 < len) { - if (*(start + commandOffset + 1) == TextCommandLinkIndex) { - return (*(start + commandOffset + 2) != 0); - } - return (*(start + commandOffset + 1) != TextCommandLinkText); - } - return false; -} - -bool checkTagStartInCommand(const QChar *start, int32 len, int32 tagStart, int32 &commandOffset, bool &commandIsLink, bool &inLink) { - bool inCommand = false; - const QChar *commandEnd = start + commandOffset; - while (commandOffset < len && tagStart > commandOffset) { // skip commands, evaluating are we in link or not - commandEnd = textSkipCommand(start + commandOffset, start + len); - if (commandEnd > start + commandOffset) { - if (tagStart < (commandEnd - start)) { - inCommand = true; - break; - } - for (commandOffset = commandEnd - start; commandOffset < len; ++commandOffset) { - if (*(start + commandOffset) == TextCommand) { - inLink = commandIsLink; - commandIsLink = textcmdStartsLink(start, len, commandOffset); - break; - } - } - if (commandOffset >= len) { - inLink = commandIsLink; - commandIsLink = false; - } - } else { - break; - } - } - if (inCommand) { - commandOffset = commandEnd - start; - } - return inCommand; -} - TextWithEntities ParseEntities(const QString &text, int32 flags) { const auto rich = ((flags & TextParseRichText) != 0); auto result = TextWithEntities{ text, EntitiesInText() }; @@ -1605,20 +1546,10 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) { int existingEntityIndex = 0, existingEntitiesCount = result.entities.size(); int existingEntityEnd = 0; - int32 len = result.text.size(), commandOffset = rich ? 0 : len; - bool inLink = false, commandIsLink = false; + int32 len = result.text.size(); const auto start = result.text.constData(); const auto end = start + result.text.size(); for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) { - if (commandOffset <= offset) { - for (commandOffset = offset; commandOffset < len; ++commandOffset) { - if (*(start + commandOffset) == TextCommand) { - inLink = commandIsLink; - commandIsLink = textcmdStartsLink(start, len, commandOffset); - break; - } - } - } auto mDomain = qthelp::RegExpDomain().match(result.text, matchOffset); auto mExplicitDomain = qthelp::RegExpDomainExplicit().match(result.text, matchOffset); auto mHashtag = withHashtags ? RegExpHashtag().match(result.text, matchOffset) : QRegularExpressionMatch(); @@ -1706,17 +1637,6 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) { offset = matchOffset = mentionEnd; continue; } - const auto inCommand = checkTagStartInCommand( - start, - len, - mentionStart, - commandOffset, - commandIsLink, - inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } lnkType = EntityType::Mention; lnkStart = mentionStart; @@ -1727,50 +1647,15 @@ void ParseEntities(TextWithEntities &result, int32 flags, bool rich) { offset = matchOffset = hashtagEnd; continue; } - const auto inCommand = checkTagStartInCommand( - start, - len, - hashtagStart, - commandOffset, - commandIsLink, - inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } lnkType = EntityType::Hashtag; lnkStart = hashtagStart; lnkLength = hashtagEnd - hashtagStart; } else if (botCommandStart < domainStart) { - const auto inCommand = checkTagStartInCommand( - start, - len, - botCommandStart, - commandOffset, - commandIsLink, - inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } - lnkType = EntityType::BotCommand; lnkStart = botCommandStart; lnkLength = botCommandEnd - botCommandStart; } else { - const auto inCommand = checkTagStartInCommand( - start, - len, - domainStart, - commandOffset, - commandIsLink, - inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } - auto protocol = mDomain.captured(1).toLower(); auto topDomain = mDomain.captured(3).toLower(); auto isProtocolValid = protocol.isEmpty() || IsValidProtocol(protocol); @@ -2332,36 +2217,6 @@ void SetClipboardText( } } -QString TextWithSpoilerCommands(const TextWithEntities &textWithEntities) { - auto text = textWithEntities.text; - auto offset = 0; - const auto start = textcmdStartSpoiler(); - const auto stop = textcmdStopSpoiler(); - for (const auto &e : textWithEntities.entities) { - if (e.type() == EntityType::Spoiler) { - text.insert(e.offset() + offset, start); - offset += start.size(); - text.insert(e.offset() + e.length() + offset, stop); - offset += stop.size(); - } - } - return text; -} - -QString CutTextWithCommands( - QString text, - int length, - const QString &start, - const QString &stop) { - text = text.mid(0, length); - const auto lastStart = text.lastIndexOf(start); - const auto lastStop = text.lastIndexOf(stop); - const auto additional = ((lastStart == -1) || (lastStart < lastStop)) - ? QString() - : stop; - return text + additional + Ui::kQEllipsis; -} - } // namespace TextUtilities EntityInText::EntityInText( diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index cbc1168..dc68218 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -297,7 +297,6 @@ QString MarkdownSpoilerGoodBefore(); QString MarkdownSpoilerBadAfter(); // Text preprocess. -QString Clean(const QString &text, bool keepSpoilers = false); QString EscapeForRichParsing(const QString &text); QString SingleLine(const QString &text); TextWithEntities SingleLine(const TextWithEntities &text); @@ -383,12 +382,4 @@ void SetClipboardText( const TextForMimeData &text, QClipboard::Mode mode = QClipboard::Clipboard); -[[nodiscard]] QString TextWithSpoilerCommands( - const TextWithEntities &textWithEntities); -[[nodiscard]] QString CutTextWithCommands( - QString text, - int length, - const QString &start, - const QString &stop); - } // namespace TextUtilities From b81b9371e6b422348c9c77d4ec6ac755bdbf5a06 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 7 Jan 2022 06:50:33 +0300 Subject: [PATCH 07/46] Removed TextParseRichText. --- ui/text/text.cpp | 48 +++------------------------------ ui/text/text.h | 4 +-- ui/text/text_entity.cpp | 5 ++-- ui/text/text_entity.h | 13 +++++---- ui/widgets/checkbox.cpp | 2 +- ui/widgets/labels.cpp | 7 +---- ui/widgets/labels.h | 1 - ui/widgets/menu/menu_action.cpp | 2 +- ui/widgets/tooltip.cpp | 2 +- 9 files changed, 16 insertions(+), 68 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index a244a6a..e195320 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -59,19 +59,6 @@ Qt::LayoutDirection StringDirection( return Qt::LayoutDirectionAuto; } -TextWithEntities PrepareRichFromPlain( - const QString &text, - const TextParseOptions &options) { - auto result = TextWithEntities{ text }; - if (options.flags & TextParseLinks) { - TextUtilities::ParseEntities( - result, - options.flags, - (options.flags & TextParseRichText)); - } - return result; -} - TextWithEntities PrepareRichFromRich( const TextWithEntities &text, const TextParseOptions &options) { @@ -175,11 +162,6 @@ namespace Text { class Parser { public: - Parser( - not_null string, - const QString &text, - const TextParseOptions &options, - const std::any &context); Parser( not_null string, const TextWithEntities &textWithEntities, @@ -250,7 +232,6 @@ private: const QChar *_ptr = nullptr; const EntitiesInText::const_iterator _entitiesEnd; EntitiesInText::const_iterator _waitingEntity; - const bool _rich = false; const bool _multiline = false; const QFixed _stopAfterWidth; // summary width of all added words @@ -318,19 +299,6 @@ std::optional Parser::StartedEntity::spoilerIndex() const { return std::nullopt; } -Parser::Parser( - not_null string, - const QString &text, - const TextParseOptions &options, - const std::any &context) -: Parser( - string, - PrepareRichFromPlain(text, options), - options, - context, - ReadyToken()) { -} - Parser::Parser( not_null string, const TextWithEntities &textWithEntities, @@ -358,7 +326,6 @@ Parser::Parser( , _ptr(_start) , _entitiesEnd(_source.entities.end()) , _waitingEntity(_source.entities.begin()) -, _rich(options.flags & TextParseRichText) , _multiline(options.flags & TextParseMultiline) , _stopAfterWidth(ComputeStopAfter(options, *_t->_st)) , _checkTilde(ComputeCheckTilde(*_t->_st)) { @@ -2700,20 +2667,16 @@ private: String::String(int32 minResizeWidth) : _minResizeWidth(minResizeWidth) { } -String::String(const style::TextStyle &st, const QString &text, const TextParseOptions &options, int32 minResizeWidth, bool richText) +String::String(const style::TextStyle &st, const QString &text, const TextParseOptions &options, int32 minResizeWidth) : _minResizeWidth(minResizeWidth) { - if (richText) { - setRichText(st, text, options); - } else { - setText(st, text, options); - } + setText(st, text, options); } void String::setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options) { _st = &st; clear(); { - Parser parser(this, text, options, {}); + Parser parser(this, { text }, options, {}); } recountNaturalSize(true, options.dir); } @@ -2877,11 +2840,6 @@ void String::setMarkedText(const style::TextStyle &st, const TextWithEntities &t recountNaturalSize(true, options.dir); } -void String::setRichText(const style::TextStyle &st, const QString &text, TextParseOptions options) { - options.flags |= TextParseRichText; - setText(st, text, options); -} - void String::setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk) { if (!lnkIndex || lnkIndex > _links.size()) return; _links[lnkIndex - 1] = lnk; diff --git a/ui/text/text.h b/ui/text/text.h index e59438e..b27fd80 100644 --- a/ui/text/text.h +++ b/ui/text/text.h @@ -103,8 +103,7 @@ public: const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions, - int32 minResizeWidth = QFIXED_MAX, - bool richText = false); + int32 minResizeWidth = QFIXED_MAX); String(const String &other) = default; String(String &&other) = default; String &operator=(const String &other) = default; @@ -115,7 +114,6 @@ public: int countHeight(int width, bool breakEverywhere = false) const; void countLineWidths(int width, QVector *lineWidths, bool breakEverywhere = false) const; void setText(const style::TextStyle &st, const QString &text, const TextParseOptions &options = _defaultOptions); - void setRichText(const style::TextStyle &st, const QString &text, TextParseOptions options = _defaultOptions); void setMarkedText(const style::TextStyle &st, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions, const std::any &context = {}); void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk); diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 36cb1e1..702b8f2 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -1528,14 +1528,13 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) { } TextWithEntities ParseEntities(const QString &text, int32 flags) { - const auto rich = ((flags & TextParseRichText) != 0); auto result = TextWithEntities{ text, EntitiesInText() }; - ParseEntities(result, flags, rich); + ParseEntities(result, flags); return result; } // Some code is duplicated in message_field.cpp! -void ParseEntities(TextWithEntities &result, int32 flags, bool rich) { +void ParseEntities(TextWithEntities &result, int32 flags) { constexpr auto kNotFound = std::numeric_limits::max(); auto newEntities = EntitiesInText(); diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index dc68218..7c794ec 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -237,12 +237,11 @@ struct TextForMimeData { enum { TextParseMultiline = 0x001, TextParseLinks = 0x002, - TextParseRichText = 0x004, - TextParseMentions = 0x008, - TextParseHashtags = 0x010, - TextParseBotCommands = 0x020, - TextParseMarkdown = 0x040, - TextParsePlainLinks = 0x080, + TextParseMentions = 0x004, + TextParseHashtags = 0x008, + TextParseBotCommands = 0x010, + TextParseMarkdown = 0x020, + TextParsePlainLinks = 0x040, }; struct TextWithTags { @@ -335,7 +334,7 @@ inline QString MentionNameDataFromFields(const MentionNameFields &fields) { // New entities are added to the ones that are already in result. // Changes text if (flags & TextParseMarkdown). TextWithEntities ParseEntities(const QString &text, int32 flags); -void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false); +void ParseEntities(TextWithEntities &result, int32 flags); void PrepareForSending(TextWithEntities &result, int32 flags); void Trim(TextWithEntities &result); diff --git a/ui/widgets/checkbox.cpp b/ui/widgets/checkbox.cpp index 97afa5b..9469dc0 100644 --- a/ui/widgets/checkbox.cpp +++ b/ui/widgets/checkbox.cpp @@ -22,7 +22,7 @@ TextParseOptions _checkboxOptions = { }; TextParseOptions _checkboxRichOptions = { - TextParseMultiline | TextParseRichText, // flags + TextParseMultiline, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir diff --git a/ui/widgets/labels.cpp b/ui/widgets/labels.cpp index 1200bbf..d7093cd 100644 --- a/ui/widgets/labels.cpp +++ b/ui/widgets/labels.cpp @@ -32,7 +32,7 @@ TextParseOptions _labelOptions = { }; TextParseOptions _labelMarkedOptions = { - TextParseMultiline | TextParseRichText | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags + TextParseMultiline | TextParseLinks | TextParseHashtags | TextParseMentions | TextParseBotCommands | TextParseMarkdown, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir @@ -257,11 +257,6 @@ void FlatLabel::setText(const QString &text) { textUpdated(); } -void FlatLabel::setRichText(const QString &text) { - _text.setRichText(_st.style, text, _labelOptions); - textUpdated(); -} - void FlatLabel::setMarkedText(const TextWithEntities &textWithEntities) { _text.setMarkedText(_st.style, textWithEntities, _labelMarkedOptions); textUpdated(); diff --git a/ui/widgets/labels.h b/ui/widgets/labels.h index d14a4db..2821f66 100644 --- a/ui/widgets/labels.h +++ b/ui/widgets/labels.h @@ -116,7 +116,6 @@ public: void setTextColorOverride(std::optional color); void setText(const QString &text); - void setRichText(const QString &text); void setMarkedText(const TextWithEntities &textWithEntities); void setSelectable(bool selectable); void setDoubleClickSelectsParagraph(bool doubleClickSelectsParagraph); diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index db4a610..d448cbb 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -40,7 +40,7 @@ namespace { } TextParseOptions MenuTextOptions = { - TextParseLinks | TextParseRichText, // flags + TextParseLinks, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir diff --git a/ui/widgets/tooltip.cpp b/ui/widgets/tooltip.cpp index 83c3ebd..acf7bf4 100644 --- a/ui/widgets/tooltip.cpp +++ b/ui/widgets/tooltip.cpp @@ -85,7 +85,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip * _point = m; _st = st; - _text = Text::String(_st->textStyle, text, _textPlainOptions, _st->widthMax, true); + _text = Text::String(_st->textStyle, text, _textPlainOptions, _st->widthMax); _useTransparency = Platform::TranslucentWindowsSupported(_point); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); From 4e4e54dcc59eb6c0bc49c09d6334a110bcec793c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 8 Jan 2022 07:27:35 +0300 Subject: [PATCH 08/46] Made monospace text clickable. --- ui/text/text.cpp | 83 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index e195320..fb73481 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -30,6 +30,10 @@ constexpr auto kStringLinkIndexShift = uint16(0x8000); constexpr auto kMaxDiacAfterSymbol = 2; constexpr auto kSelectedSpoilerOpacity = 0.5; +inline bool IsMono(int32 flags) { + return (flags & TextBlockFPre) || (flags & TextBlockFCode); +} + Qt::LayoutDirection StringDirection( const QString &str, int from, @@ -239,11 +243,13 @@ private: std::vector _links; std::vector _spoilers; + std::vector _monos; base::flat_map< const QChar*, std::vector> _startedEntities; uint16 _maxLnkIndex = 0; + uint16 _maxShiftedLnkIndex = 0; uint16 _maxSpoilerIndex = 0; // current state @@ -343,6 +349,11 @@ void Parser::createBlock(int32 skipBack) { if (_lnkIndex < kStringLinkIndexShift && _lnkIndex > _maxLnkIndex) { _maxLnkIndex = _lnkIndex; } + if (_lnkIndex > kStringLinkIndexShift) { + _maxShiftedLnkIndex = std::max( + uint16(_lnkIndex - kStringLinkIndexShift), + _maxShiftedLnkIndex); + } if (_spoilerIndex > _maxSpoilerIndex) { _maxSpoilerIndex = _spoilerIndex; } @@ -459,14 +470,24 @@ bool Parser::checkEntities() { flags = TextBlockFPlainLink; } else if (entityType == EntityType::StrikeOut) { flags = TextBlockFStrikeOut; - } else if (entityType == EntityType::Code) { // #TODO entities - flags = TextBlockFCode; - } else if (entityType == EntityType::Pre) { - flags = TextBlockFPre; - createBlock(); - if (!_t->_blocks.empty() && _t->_blocks.back()->type() != TextBlockTNewline) { - createNewlineBlock(); + } else if ((entityType == EntityType::Code) // #TODO entities + || (entityType == EntityType::Pre)) { + if (entityType == EntityType::Code) { + flags = TextBlockFCode; + } else { + flags = TextBlockFPre; + createBlock(); + if (!_t->_blocks.empty() + && _t->_blocks.back()->type() != TextBlockTNewline) { + createNewlineBlock(); + } } + const auto end = _waitingEntity->offset() + entityLength; + _monos.push_back({ + .text = QString(entityBegin, entityLength), + .data = QString(QChar(end)), + .type = entityType, + }); } else if (entityType == EntityType::Url || entityType == EntityType::Email || entityType == EntityType::Mention @@ -706,8 +727,13 @@ void Parser::trimSourceRange() { // } void Parser::finalize(const TextParseOptions &options) { - _t->_links.resize(_maxLnkIndex); + _t->_links.resize(_maxLnkIndex + _maxShiftedLnkIndex); _t->_spoilers.resize(_maxSpoilerIndex); + auto monoLnk = uint16(1); + struct { + uint16 mono = 0; + uint16 lnk = 0; + } lastHandlerIndex; for (auto &block : _t->_blocks) { const auto spoilerIndex = block->spoilerIndex(); if (spoilerIndex) { @@ -719,22 +745,46 @@ void Parser::finalize(const TextParseOptions &options) { } const auto shiftedIndex = block->lnkIndex(); if (shiftedIndex <= kStringLinkIndexShift) { + if (IsMono(block->flags())) { + const auto entityEnd = int( + _monos[monoLnk - 1].data.constData()->unicode()); + if (block->from() >= entityEnd) { + monoLnk++; + } + const auto monoIndex = _maxLnkIndex + + _maxShiftedLnkIndex + + monoLnk; + block->setLnkIndex(monoIndex); + + if (lastHandlerIndex.mono == monoIndex) { + continue; // Optimization. + } + const auto handler = Integration::Instance().createLinkHandler( + _monos[monoLnk - 1], + _context); + _t->_links.resize(monoIndex); + if (handler) { + _t->setLink(monoIndex, handler); + } + lastHandlerIndex.mono = monoIndex; + } continue; } const auto realIndex = (shiftedIndex - kStringLinkIndexShift); const auto index = _maxLnkIndex + realIndex; block->setLnkIndex(index); - if (_t->_links.size() >= index) { - continue; + if (lastHandlerIndex.lnk == index) { + continue; // Optimization. } - _t->_links.resize(index); + // _t->_links.resize(index); const auto handler = Integration::Instance().createLinkHandler( _links[realIndex - 1], _context); if (handler) { _t->setLink(index, handler); } + lastHandlerIndex.lnk = index; } _t->_links.squeeze(); _t->_spoilers.squeeze(); @@ -1953,7 +2003,7 @@ private: return f; } auto result = f; - if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) { + if (IsMono(flags)) { result = result->monospace(); } else { if (flags & TextBlockFBold) { @@ -2592,12 +2642,13 @@ private: } else { _background = {}; } - if (block->lnkIndex() || (block->flags() & TextBlockFPlainLink)) { - _currentPen = &_textPalette->linkFg->p; - _currentPenSelected = &_textPalette->selectLinkFg->p; - } else if ((block->flags() & TextBlockFCode) || (block->flags() & TextBlockFPre)) { + if (IsMono(block->flags())) { _currentPen = &_textPalette->monoFg->p; _currentPenSelected = &_textPalette->selectMonoFg->p; + } else if (block->lnkIndex() + || (block->flags() & TextBlockFPlainLink)) { + _currentPen = &_textPalette->linkFg->p; + _currentPenSelected = &_textPalette->selectLinkFg->p; } else { _currentPen = &_originalPen; _currentPenSelected = &_originalPenSelected; From b1f71ccab3c1383f0a0b419e765cb546dfbc4603 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 8 Jan 2022 08:15:26 +0300 Subject: [PATCH 09/46] Added selection of monospace text on press. --- ui/text/text.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index fb73481..550da72 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -1510,7 +1510,9 @@ private: QFixed from; QFixed width; } fillSpoiler; - if (_localFrom + si.position < _selection.to) { + if (_background.selectActiveBlock) { + fillSelect = { x, x + si.width }; + } else 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 @@ -1652,7 +1654,9 @@ private: auto hasSelected = false; auto hasNotSelected = true; auto selectedRect = QRect(); - if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { + if (_background.selectActiveBlock) { + fillSelectRange(x, x + itemWidth); + } else if (_localFrom + itemStart < _selection.to && _localFrom + itemEnd > _selection.from) { hasSelected = true; auto selX = x; auto selWidth = itemWidth; @@ -2615,6 +2619,7 @@ private: void applyBlockProperties(const AbstractBlock *block) { eSetFont(block); if (_p) { + const auto isMono = IsMono(block->flags()); if (block->spoilerIndex()) { const auto handler = _t->_spoilers.at(block->spoilerIndex() - 1); @@ -2639,10 +2644,15 @@ private: *_background.color); mutableCache.color = (*_background.color)->c; } + } else if (isMono && block->lnkIndex()) { + _background = { + .selectActiveBlock = ClickHandler::showAsPressed( + _t->_links.at(block->lnkIndex() - 1)), + }; } else { _background = {}; } - if (IsMono(block->flags())) { + if (isMono) { _currentPen = &_textPalette->monoFg->p; _currentPenSelected = &_textPalette->selectMonoFg->p; } else if (block->lnkIndex() @@ -2672,6 +2682,8 @@ private: bool inFront = false; crl::time startMs = 0; uint16 spoilerIndex = 0; + + bool selectActiveBlock = false; // For monospace. } _background; int _yFrom = 0; int _yTo = 0; From 20b908ea70770cb3fea0adc8285a64b7178a11bf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 8 Jan 2022 08:20:07 +0300 Subject: [PATCH 10/46] Slightly optimized spoilers to avoid recreating of same click handlers. --- ui/text/text.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 550da72..c9deba4 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -250,7 +250,6 @@ private: uint16 _maxLnkIndex = 0; uint16 _maxShiftedLnkIndex = 0; - uint16 _maxSpoilerIndex = 0; // current state int32 _flags = 0; @@ -354,9 +353,6 @@ void Parser::createBlock(int32 skipBack) { uint16(_lnkIndex - kStringLinkIndexShift), _maxShiftedLnkIndex); } - if (_spoilerIndex > _maxSpoilerIndex) { - _maxSpoilerIndex = _spoilerIndex; - } int32 len = int32(_t->_text.size()) + skipBack - _blockStart; if (len > 0) { @@ -728,7 +724,6 @@ void Parser::trimSourceRange() { void Parser::finalize(const TextParseOptions &options) { _t->_links.resize(_maxLnkIndex + _maxShiftedLnkIndex); - _t->_spoilers.resize(_maxSpoilerIndex); auto monoLnk = uint16(1); struct { uint16 mono = 0; @@ -736,7 +731,7 @@ void Parser::finalize(const TextParseOptions &options) { } lastHandlerIndex; for (auto &block : _t->_blocks) { const auto spoilerIndex = block->spoilerIndex(); - if (spoilerIndex) { + if (spoilerIndex && (_t->_spoilers.size() < spoilerIndex)) { _t->_spoilers.resize(spoilerIndex); const auto handler = (options.flags & TextParseLinks) ? std::make_shared() From 087c82e1d5b5731610019df04d44ae1c44fb941d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 8 Jan 2022 09:12:24 +0300 Subject: [PATCH 11/46] Fixed display of ellipsis under spoiler. --- ui/text/text.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index c9deba4..e3a34df 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -1700,8 +1700,10 @@ private: ? fillSpoilerOpacity() : 0.; const auto opacity = _p->opacity(); - if (spoilerOpacity < 1.) { - if (hasSpoiler) { + const auto isElidedBlock = (!rtl) + && (_indexOfElidedBlock == blockIndex); + if ((spoilerOpacity < 1.) || isElidedBlock) { + if (hasSpoiler && !isElidedBlock) { _p->setOpacity(opacity * (1. - spoilerOpacity)); } if (Q_UNLIKELY(hasSelected)) { @@ -1807,7 +1809,9 @@ private: : (blockIndex > 0) ? _t->_blocks[blockIndex - 1]->spoilerIndex() : 0; - const auto will = (positionTill < blockEnd) + const auto will = elideOffset + ? 0 + : (positionTill < blockEnd) ? now : nextBlock ? nextBlock->spoilerIndex() From c2fcbdc883ad8fb0387106e24688f36c3f19e935 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 9 Jan 2022 06:30:46 +0300 Subject: [PATCH 12/46] Limited monospace text selection on click only for single lines. --- ui/text/text.cpp | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index e3a34df..91fd640 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -478,10 +478,23 @@ bool Parser::checkEntities() { createNewlineBlock(); } } - const auto end = _waitingEntity->offset() + entityLength; + const auto text = QString(entityBegin, entityLength); + auto data = QString(2, QChar(0)); + + // End of an entity. + data[0] = QChar(_waitingEntity->offset() + entityLength); + + { + // It is better to trim the text to identify "Sample\n" as inline. + const auto trimmed = text.trimmed(); + const auto isSingleLine = !trimmed.isEmpty() + && ranges::none_of(trimmed, IsNewline); + data[1] = QChar(isSingleLine ? 1 : 2); + } + _monos.push_back({ - .text = QString(entityBegin, entityLength), - .data = QString(QChar(end)), + .text = text, + .data = std::move(data), .type = entityType, }); } else if (entityType == EntityType::Url @@ -740,11 +753,18 @@ void Parser::finalize(const TextParseOptions &options) { } const auto shiftedIndex = block->lnkIndex(); if (shiftedIndex <= kStringLinkIndexShift) { - if (IsMono(block->flags())) { - const auto entityEnd = int( - _monos[monoLnk - 1].data.constData()->unicode()); - if (block->from() >= entityEnd) { - monoLnk++; + if (IsMono(block->flags()) && (monoLnk <= _monos.size())) { + { + const auto ptr = _monos[monoLnk - 1].data.constData(); + const auto entityEnd = int(ptr->unicode()); + const auto singleLine = (int((ptr + 1)->unicode()) == 1); + if (!singleLine) { + monoLnk++; + continue; + } + if (block->from() >= entityEnd) { + monoLnk++; + } } const auto monoIndex = _maxLnkIndex + _maxShiftedLnkIndex From 5e8b8d412938f18704bfd4fb724fb1ead921d4ab Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 9 Jan 2022 06:35:15 +0300 Subject: [PATCH 13/46] Changed behavior to process paragraph selection as full for monospace. --- ui/text/text.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 91fd640..62b4b86 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -3182,6 +3182,36 @@ TextSelection String::adjustSelection(TextSelection selection, TextSelectType se if (from < _text.size() && from <= to) { if (to > _text.size()) to = _text.size(); if (selectType == TextSelectType::Paragraphs) { + + // Full selection of monospace entity. + for (const auto &b : _blocks) { + if (b->from() < from) { + continue; + } + if (!IsMono(b->flags())) { + break; + } + const auto &entities = toTextWithEntities().entities; + const auto eIt = ranges::find_if(entities, [&]( + const EntityInText &e) { + return (e.type() == EntityType::Pre + || e.type() == EntityType::Code) + && (from >= e.offset()) + && ((e.offset() + e.length()) >= to); + }); + if (eIt != entities.end()) { + from = eIt->offset(); + to = eIt->offset() + eIt->length(); + while (to > 0 && IsSpace(_text.at(to - 1))) { + --to; + } + if (to >= from) { + return { from, to }; + } + } + break; + } + if (!IsParagraphSeparator(_text.at(from))) { while (from > 0 && !IsParagraphSeparator(_text.at(from - 1))) { --from; From 7d3f27b7caf8f0c73c13a3286d5825edea6a3c87 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 10 Jan 2022 08:59:14 +0300 Subject: [PATCH 14/46] Decomposed painting of SettingsButton. --- ui/widgets/buttons.cpp | 26 ++++++++++++++++++++++---- ui/widgets/buttons.h | 7 +++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ui/widgets/buttons.cpp b/ui/widgets/buttons.cpp index bc9d890..4519357 100644 --- a/ui/widgets/buttons.cpp +++ b/ui/widgets/buttons.cpp @@ -741,19 +741,35 @@ void SettingsButton::setColorOverride(std::optional textColorOverride) { update(); } +const style::SettingsButton &SettingsButton::st() const { + return _st; +} + void SettingsButton::paintEvent(QPaintEvent *e) { Painter p(this); - auto paintOver = (isOver() || isDown()) && !isDisabled(); - p.fillRect(e->rect(), paintOver ? _st.textBgOver : _st.textBg); + const auto paintOver = (isOver() || isDown()) && !isDisabled(); + paintBg(p, e->rect(), paintOver); paintRipple(p, 0, 0); - auto outerw = width(); + const auto outerw = width(); + paintText(p, paintOver, outerw); + + if (_toggle) { + paintToggle(p, outerw); + } +} + +void SettingsButton::paintBg(Painter &p, const QRect &rect, bool over) const { + p.fillRect(rect, over ? _st.textBgOver : _st.textBg); +} + +void SettingsButton::paintText(Painter &p, bool over, int outerw) const { p.setFont(_st.font); p.setPen(_textColorOverride ? QPen(*_textColorOverride) - : paintOver + : over ? _st.textFgOver : _st.textFg); p.drawTextLeft( @@ -762,7 +778,9 @@ void SettingsButton::paintEvent(QPaintEvent *e) { outerw, _text, _textWidth); +} +void SettingsButton::paintToggle(Painter &p, int outerw) const { if (_toggle) { auto rect = toggleRect(); _toggle->paint(p, rect.left(), rect.top(), outerw); diff --git a/ui/widgets/buttons.h b/ui/widgets/buttons.h index 0d25b34..0e0c835 100644 --- a/ui/widgets/buttons.h +++ b/ui/widgets/buttons.h @@ -13,6 +13,8 @@ #include +class Painter; + namespace Ui { class RippleAnimation; @@ -273,6 +275,11 @@ protected: void paintEvent(QPaintEvent *e) override; + const style::SettingsButton &st() const; + void paintBg(Painter &p, const QRect &rect, bool over) const; + void paintText(Painter &p, bool over, int outerw) const; + void paintToggle(Painter &p, int outerw) const; + private: void setText(QString &&text); QRect toggleRect() const; From 4fbd2260670f40538944f97fcd7509aecf2ea875 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Jan 2022 13:10:53 +0300 Subject: [PATCH 15/46] Fix selection of monospace text on press. --- ui/text/text.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 62b4b86..db534d0 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -2639,6 +2639,7 @@ private: eSetFont(block); if (_p) { const auto isMono = IsMono(block->flags()); + _background = {}; if (block->spoilerIndex()) { const auto handler = _t->_spoilers.at(block->spoilerIndex() - 1); @@ -2663,14 +2664,12 @@ private: *_background.color); mutableCache.color = (*_background.color)->c; } - } else if (isMono && block->lnkIndex()) { - _background = { - .selectActiveBlock = ClickHandler::showAsPressed( - _t->_links.at(block->lnkIndex() - 1)), - }; - } else { - _background = {}; } + if (isMono && block->lnkIndex() && !_background.inFront) { + _background.selectActiveBlock = ClickHandler::showAsPressed( + _t->_links.at(block->lnkIndex() - 1)); + } + if (isMono) { _currentPen = &_textPalette->monoFg->p; _currentPenSelected = &_textPalette->selectMonoFg->p; From ba27a017f84495f97be1a277418ea38c8ae981fb Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 Jan 2022 13:18:02 +0300 Subject: [PATCH 16/46] Increase menu minimum width. --- 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 6be58d0..785c41c 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -857,7 +857,7 @@ defaultMenu: Menu { arrow: defaultMenuArrow; - widthMin: 140px; + widthMin: 156px; widthMax: 300px; ripple: defaultRippleAnimation; From d7b3a6835138ed4c487246db841a470371548394 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 11 Jan 2022 13:16:31 +0300 Subject: [PATCH 17/46] Allow slowing down Animations::Simple globally. --- ui/effects/animation_value.cpp | 11 +++++++++++ ui/effects/animation_value.h | 2 ++ ui/effects/animations.h | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ui/effects/animation_value.cpp b/ui/effects/animation_value.cpp index f56c851..3370a8f 100644 --- a/ui/effects/animation_value.cpp +++ b/ui/effects/animation_value.cpp @@ -14,6 +14,7 @@ namespace anim { namespace { rpl::variable AnimationsDisabled = false; +int SlowMultiplierMinusOne/* = 0*/; } // namespace @@ -76,6 +77,16 @@ void SetDisabled(bool disabled) { AnimationsDisabled = disabled; } +int SlowMultiplier() { + return (SlowMultiplierMinusOne + 1); +} + +void SetSlowMultiplier(int multiplier) { + Expects(multiplier > 0); + + SlowMultiplierMinusOne = multiplier - 1; +} + void DrawStaticLoading( QPainter &p, QRectF rect, diff --git a/ui/effects/animation_value.h b/ui/effects/animation_value.h index 8244227..1b9694b 100644 --- a/ui/effects/animation_value.h +++ b/ui/effects/animation_value.h @@ -351,6 +351,8 @@ QPainterPath path(QPointF (&from)[N]) { rpl::producer Disables(); bool Disabled(); void SetDisabled(bool disabled); +int SlowMultiplier(); +void SetSlowMultiplier(int multiplier); // 1 - default, 10 - slow x10. void DrawStaticLoading( QPainter &p, diff --git a/ui/effects/animations.h b/ui/effects/animations.h index c7eabf6..57d52d3 100644 --- a/ui/effects/animations.h +++ b/ui/effects/animations.h @@ -410,7 +410,7 @@ inline void Simple::startPrepared( anim::transition transition) { _data->from = _data->value; _data->delta = to - _data->from; - _data->duration = duration; + _data->duration = duration * anim::SlowMultiplier(); _data->transition = transition; _data->animation.start(); } From 2b87a251cd75a5c99b98b843be76adb032c8406a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 15 Jan 2022 12:51:49 +0300 Subject: [PATCH 18/46] Fixed enumerating of Text::String with clickable monospace. --- ui/text/text.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index db534d0..77ca9ea 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -3276,6 +3276,9 @@ void String::enumerateText(TextSelection selection, AppendPartCallback appendPar uint16 blockFrom = (i == e) ? _text.size() : (*i)->from(); int32 blockFlags = (i == e) ? 0 : (*i)->flags(); + if (IsMono(blockFlags)) { + blockLnkIndex = 0; + } if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links blockLnkIndex = 0; } From d1509436b66d0f53dd0e9bf25e0e048759ef6f01 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 16 Jan 2022 10:18:19 +0300 Subject: [PATCH 19/46] Fixed possible crash in Text::String with clickable monospace. --- ui/text/text.cpp | 80 ++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 77ca9ea..24332b7 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -255,6 +255,7 @@ private: int32 _flags = 0; uint16 _lnkIndex = 0; uint16 _spoilerIndex = 0; + uint16 _monoIndex = 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 @@ -365,14 +366,15 @@ void Parser::createBlock(int32 skipBack) { } } _lastSkipped = false; + const auto lnkIndex = _monoIndex ? _monoIndex : _lnkIndex; if (_emoji) { - _t->_blocks.push_back(Block::Emoji(_t->_st->font, _t->_text, _blockStart, len, _flags, _lnkIndex, _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, _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, _spoilerIndex)); + _t->_blocks.push_back(Block::Text(_t->_st->font, _t->_text, _t->_minResizeWidth, _blockStart, len, _flags, lnkIndex, _spoilerIndex)); } _blockStart += len; blockCreated(); @@ -382,7 +384,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, _spoilerIndex)); + _t->_blocks.push_back(Block::Skip(_t->_st->font, _t->_text, _blockStart++, w, h, _monoIndex ? _monoIndex : _lnkIndex, _spoilerIndex)); blockCreated(); } @@ -408,6 +410,9 @@ void Parser::finishEntities() { && _t->_blocks.back()->type() != TextBlockTNewline) { _newlineAwaited = true; } + if (IsMono(*flags)) { + _monoIndex = 0; + } } } else if (const auto lnkIndex = list.back().lnkIndex()) { if (_lnkIndex == *lnkIndex) { @@ -479,24 +484,16 @@ bool Parser::checkEntities() { } } const auto text = QString(entityBegin, entityLength); - auto data = QString(2, QChar(0)); - // End of an entity. - data[0] = QChar(_waitingEntity->offset() + entityLength); + // It is better to trim the text to identify "Sample\n" as inline. + const auto trimmed = text.trimmed(); + const auto isSingleLine = !trimmed.isEmpty() + && ranges::none_of(trimmed, IsNewline); - { - // It is better to trim the text to identify "Sample\n" as inline. - const auto trimmed = text.trimmed(); - const auto isSingleLine = !trimmed.isEmpty() - && ranges::none_of(trimmed, IsNewline); - data[1] = QChar(isSingleLine ? 1 : 2); + if (isSingleLine) { + _monos.push_back({ .text = text, .type = entityType }); + _monoIndex = _monos.size(); } - - _monos.push_back({ - .text = text, - .data = std::move(data), - .type = entityType, - }); } else if (entityType == EntityType::Url || entityType == EntityType::Email || entityType == EntityType::Mention @@ -737,7 +734,7 @@ void Parser::trimSourceRange() { void Parser::finalize(const TextParseOptions &options) { _t->_links.resize(_maxLnkIndex + _maxShiftedLnkIndex); - auto monoLnk = uint16(1); + auto currentIndex = uint16(0); // Current the latest index of _t->_links. struct { uint16 mono = 0; uint16 lnk = 0; @@ -753,53 +750,44 @@ void Parser::finalize(const TextParseOptions &options) { } const auto shiftedIndex = block->lnkIndex(); if (shiftedIndex <= kStringLinkIndexShift) { - if (IsMono(block->flags()) && (monoLnk <= _monos.size())) { - { - const auto ptr = _monos[monoLnk - 1].data.constData(); - const auto entityEnd = int(ptr->unicode()); - const auto singleLine = (int((ptr + 1)->unicode()) == 1); - if (!singleLine) { - monoLnk++; - continue; - } - if (block->from() >= entityEnd) { - monoLnk++; - } - } - const auto monoIndex = _maxLnkIndex - + _maxShiftedLnkIndex - + monoLnk; - block->setLnkIndex(monoIndex); + if (IsMono(block->flags()) && shiftedIndex) { + const auto monoIndex = shiftedIndex; if (lastHandlerIndex.mono == monoIndex) { + block->setLnkIndex(currentIndex); continue; // Optimization. + } else { + currentIndex++; } + block->setLnkIndex(currentIndex); const auto handler = Integration::Instance().createLinkHandler( - _monos[monoLnk - 1], + _monos[monoIndex - 1], _context); - _t->_links.resize(monoIndex); + _t->_links.resize(currentIndex); if (handler) { - _t->setLink(monoIndex, handler); + _t->setLink(currentIndex, handler); } lastHandlerIndex.mono = monoIndex; } continue; } const auto realIndex = (shiftedIndex - kStringLinkIndexShift); - const auto index = _maxLnkIndex + realIndex; - block->setLnkIndex(index); - if (lastHandlerIndex.lnk == index) { + if (lastHandlerIndex.lnk == realIndex) { + block->setLnkIndex(currentIndex); continue; // Optimization. + } else { + currentIndex++; } + block->setLnkIndex(currentIndex); - // _t->_links.resize(index); + _t->_links.resize(currentIndex); const auto handler = Integration::Instance().createLinkHandler( _links[realIndex - 1], _context); if (handler) { - _t->setLink(index, handler); + _t->setLink(currentIndex, handler); } - lastHandlerIndex.lnk = index; + lastHandlerIndex.lnk = realIndex; } _t->_links.squeeze(); _t->_spoilers.squeeze(); From 19de16ba6f74b02352af96fe4c15f38bd2e04f32 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Jan 2022 10:10:14 +0300 Subject: [PATCH 20/46] Fixed processing of started entities for monospace. --- ui/text/text.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/text/text.cpp b/ui/text/text.cpp index 24332b7..bec2761 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -441,6 +441,7 @@ bool Parser::checkEntities() { auto flags = TextBlockFlags(); auto link = EntityLinkData(); + auto monoIndex = 0; const auto entityType = _waitingEntity->type(); const auto entityLength = _waitingEntity->length(); const auto entityBegin = _start + _waitingEntity->offset(); @@ -492,7 +493,7 @@ bool Parser::checkEntities() { if (isSingleLine) { _monos.push_back({ .text = text, .type = entityType }); - _monoIndex = _monos.size(); + monoIndex = _monos.size(); } } else if (entityType == EntityType::Url || entityType == EntityType::Email @@ -525,6 +526,7 @@ bool Parser::checkEntities() { createBlock(); _flags |= flags; _startedEntities[entityEnd].emplace_back(flags); + _monoIndex = monoIndex; } } else if (entityType == EntityType::Spoiler) { createBlock(); From 9b5c939fd0aa49ac15f3f68947db840593aa427b Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Jan 2022 11:17:36 +0300 Subject: [PATCH 21/46] Update window title controls. --- icons/title_button_close.png | Bin 164 -> 262 bytes icons/title_button_close@2x.png | Bin 472 -> 416 bytes icons/title_button_close@3x.png | Bin 631 -> 527 bytes icons/title_button_maximize.png | Bin 119 -> 277 bytes icons/title_button_maximize@2x.png | Bin 144 -> 332 bytes icons/title_button_maximize@3x.png | Bin 184 -> 498 bytes icons/title_button_minimize.png | Bin 100 -> 211 bytes icons/title_button_minimize@2x.png | Bin 122 -> 242 bytes icons/title_button_minimize@3x.png | Bin 149 -> 340 bytes icons/title_button_restore.png | Bin 136 -> 380 bytes icons/title_button_restore@2x.png | Bin 160 -> 507 bytes icons/title_button_restore@3x.png | Bin 209 -> 779 bytes ui/widgets/widgets.style | 75 +++++++++++++++-------------- 13 files changed, 39 insertions(+), 36 deletions(-) diff --git a/icons/title_button_close.png b/icons/title_button_close.png index 22cfe520c4a6e3b0da93e628ac6a63dbdfbd3ff8..d2f10dbc61e41d5047cf33601a79c4b6b5e8757f 100644 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^GC(ZB!2~4J{&8IeQjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPE3<$Z&XxcvoM;=A1f&W)T^vI! z1d|gaSQjfgY!~?O@ndIa=fN#}8XQv&iKu(AtoV6VRCwgTe~DWM4fa(F?q diff --git a/icons/title_button_close@2x.png b/icons/title_button_close@2x.png index b281004394857af8156b7831ce53babf80b4003e..89bfa1bd9fa3db866be31321de5b1b4f02a479e9 100644 GIT binary patch delta 355 zcmcb?yntD;Gr-TCmrII^fq{Y7)59eQNLv800S6P1JiT2{Wul@-y~P4%1{NTn5hMe| z47TdZlYm?delL7d?|KzCC`n_-o-@%Q&#q~%k1pDp^*!|Ld!K{l&v#tB`KkKne)(F@&O%euprzX~lT5|025~bp zP1vwiU`1qO6$K8D11C!{an^LB{Ts5y=#q_ delta 411 zcmV;M0c8H51K0y0iBL{Q4GJ0x0000DNk~Le0000U0000Q2nGNE0HC4lw2>i5e;%L% z00aO40096102%-Q002M1k;MQ20YXVcK~zW$&6Z6Lgdhxs!FWPl=mEUGTURQ>BQjBF zDfl*YN4}2xV9!G5$1~W~Q8OPByO%sh% zKIm)P*7?8}F}QiQKZe?D9n@?#f6ue&x-J#OjrDzRFxMO4h($3-u$9VX?E~N|!9yr~ zpcZ0fgE0~E?z2z9DJXn}EsCY~8A4tO&OtM_h6f{o6#N1d@~6QZd|4KQwm0b<1S}cM z!7o7bR_rr{?BOZ_M_t93OHDx^KU@^lai8AQUyW04 z*H24}2`Q)#Z~;z(amz?mOM(}d%rln+wesRi;_S5;!i%p(NO<-k0C}-e=^=oM;L4Z% zMuz9hx~{H8gNY72ebDR~VP_@5tk(?#MJ4kF-Z#Y0z#pIu>>%74bP@mn002ovPDHLk FV1nEssA&KI diff --git a/icons/title_button_close@3x.png b/icons/title_button_close@3x.png index c4bf8197947da4888a2393423b43ad4e3a26aeb9..9db41de94bbc4ce17d63d47f1e23f0468b30204c 100644 GIT binary patch delta 467 zcmey)($AvU8Q|y6%O%Cdz`(%k>ERLtq(gw%gM$f3K9_YZo2ckfAF_a%fd$BC1jzs~ zgHXlx!$5AOr;B4q1>@V9XS-Sq1lZbZ_5J77)Za<}xb1e2Li5BMKcn98g!SF34^2}@ ziPINjGiYGsNnl`>IKY6;v^jFXfM@@E)4pVuo0qOzg&71rzZNt5$NRn0QfDr!_k5k( zIc?vyjZIM#Th{R2bG!b-{^Z&dHs?7dqP%afyX`Lgqx?yj){oB@HNLD7Ixw%|)XC0y zkG>z}kZ?LxYqNIltb|!j(ds8V%atq-7fC3^`XBUTR=WGSSi53s_|M{a(}Rjn9v)t8 z^(}O{ORo(3ucXgDn;*yfuRr8?;_~X_GbY8?TQeQMS@%eF#RJoTx^Jb=(`#$q-}$&h zb|>TQsw3z3sccto=YKry_o{%#HG5ulJI;P4^4`SZ_3ZgiS&G}9e*U4J;grSo?fe8` z%>x{omN{S78MV!cnE!m=_Wpeb_WXJEOZ)xXw+!jGBW@SX|NVdVySMUx%cp)@dGZiE gkTfv@tnust`{mj)8Aex?kAmXE)78&qol`;+04fj7vj6}9 delta 572 zcmV-C0>k}}1os3XiBL{Q4GJ0x0000DNk~Le0000j0000d2nGNE0Golq{E;C@e=VQ` z00aO40096104D$d000aa9q|AF0pUqRK~z`??U-w-gD?<<(@RUK(4XzTbtx5ePi{bw zO0Gu?6d%c%B$iq_AeK0D=IdmPBnPFG?yp_h->zs!*dO$}X6^ecog|4m9*&r)jFsf9JC`#-)k)EXxKx=sq3~jXbWn@wpusP5_=-S(e`5 z04|DRDmj2hf$pbnsPHU9KY+y2imw4f|M`4s*BKZ+G4_%c`Qt(K? z;m`obOXuogJcl3rBV1u3%u8u{`y(nX0geprS7{&Mh~RQ)2H4f(fvqBgW`F_oF<(R$ z7=G+>=vi^D(o$fvtog>7fB2QV8#**`X#4`W8ydT2n=w<&fJfW8>~fFz?%nw1a$!F6 z?)Jbz%G8*aSm;ITs)9UgF_UpOG){gXh;xaUCrs#J=s_3yx7&?^K8*jMzqt)(Zf)DD zK8f3z^uSr(>2y+cU6TN^5+IfWVg?501&j>LK$;OGwtxvPE3<$Z&XxcvoM;=A1f=RcT^vI! z1m8~D$a_FRfH_d)AdAlds|!Z_vcelUZyPWP2dK&&@E2SX@_*_1Iz{_qFEW__n+P~b zB-*&&RJ1x`S&pqa)X%N8janga#b>9BB9!Gxs wm96`}wrAqAhJ*zf>ho&;-Hl7Vb)cOsZud_A`;0G7O#wN{)78&qol`;+0H@w QK$93eUHx3vIVCg!0O)}s5dZ)H diff --git a/icons/title_button_maximize@2x.png b/icons/title_button_maximize@2x.png index 00daaae190fcb9c1850a3310d52ce1d8b17e11ca..708654fb09537879214cbed56d4e8d387dedbf62 100644 GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^7C>yk!2~2vZ`V@+QjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPYq5YC&Ncukv{hH01f;fkx;TbJ zaK62Mkn@OwKpUnAvc%moA z!Rq@_#pimj{EsUBwPZ?~wC9sE=h&A1+dQFC<&e|Xn}V(((GI)i(?tSv8Q<2;3Cz71 s9{*Wxr^Wi3h5LVsM&6W>gxf32#D2j*Y+YQhRw>9Qp00i_>zopr0Q_xhVgLXD literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Ey!3HF+&5pANQsJI1jv*eMZ>Je@9WdZw_Fwq_ z|0e5HPSXmGj1?xgocSUx!~!I?&go}oc<@pxw0yDR%guS^3Jkeyex=55OwLZY+sMG8 rz`!Vxf9Ur9$J2nkj+mqM>=le|%G@S}fEakt z!T5HjVONWTfNOi{!3#cnn$tfp-8**RfN+Jha{eP(kuzsZCfsdmJmh(O(Tbz#SNML^ zef=$5xrZ7z1!}82ROnW z6n>wddUKPGyz=aQ95G5f-~Z>HShZ@EMwj^qoAafyz7Hzrse3AEG?OOYZHG+TudjSmnH=r^Tnw-k*PL=7DE(TiZJq|1=1- z;CK3b?$F^Q7i(r{&0$|ynf@^GQHGt5=(*+#dp3VCC^V5@#`WAW<6dON42#px`X67Y zm7Hf7A9$VP?61#I=j@h$5=kH5>*@Bawf(;9mF}mV o;!}^+s}!Jl8y*-37W4jLI-cpjac8%^KPbFCUHx3vIVCg!074GGc>n+a literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^20*ON!3HEfFP>fkr0P9g977^n-`))5YB1nox#;`x zpL?EdW7M5P0ynzYWF z(XpBTET6wmU(0Y(exvo`Prkpkr!Yh@WpFM~1*(Yp%}_nFc)H2_zhL fik~kGq~fkvK3~v*@74E!PGRtL^>bP0l+XkK=VnL= diff --git a/icons/title_button_minimize.png b/icons/title_button_minimize.png index 85934ddde82b3cd1145635cb7d27990c40a2d6ca..93c120ee1b7c1ce1d05c185179b15ed6d4521a1d 100644 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^GC(ZB!2~4J{&8IeQjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPE3<$Z&XxcvoM;=A1f*;|T^vI! z1d|gaSQjfANHGD4M@|ib{r$zo#jUNa8?`Eae|!7nyk!2~2vZ`V@+QjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPYq5YC&Ncukv{hH01f*g-T^vIq zINx5_$a}zngXQ4yzuTGL-n>$9(Od4*)rFSF1r;0`7#NvYI0TRw57-*t)h)hl@O%HJ wocU)Ctz9+gqWbOGGj)1gH(QEA6+c+dB=79z=*y^C;R>?P)78&qol`;+03$y-OaK4? literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Ey!3HF+&5pANQjVT3jv*eMZ?A3SWia4iK3G2G zZ@3zFi=L3{JRz?6D`XZ(%rTYJb7){-WMbhEVA!zR`ReUOQ=aUS|KHG%p#4v_fWb0@ V)mTvD=VG8?44$rjF6*2UngCEZBTxVU diff --git a/icons/title_button_minimize@3x.png b/icons/title_button_minimize@3x.png index 93e36c271dd9953cc321759af5337f39141de495..64d3d5a5997b8444068249d257c051d4ab884acf 100644 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^AwcZG!2~3q%es~UDaPU;cPEB*=VV?2IWDOYo@u_m z3|c@o2Loe!CIbsd2@p#GF#`kh0!9XAAk7F8TfhXD4OzepXM2DY3RP@B45apXx;TbZ zFuuLGk@tuK59@^|vv>~q`5aYQBVx#5QtokG%{OI<$a&Ri``;Yjrpd&@A)w&Uz`#f> zV~6;S_tE^;noH+=E|lSazW!L%`>ofm-3tBeoBzDBPW(y_?|!3y&!0bh82yj=yKTj7 z{~wEzdp?|z+@WOl??;f=k444>$JQB4^~7dnV>#C!29<@yS6NC=X@Gp>>FVdQ&MBb@ E07D90)&Kwi literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^20*ON!3HEfFP>fkq+&c>977^n-`+IjI$*%Va&Y&9 z|Mih|PE49&2mRI_>bBbHsn%`#h54URLmIN^5+IfWVg?501&j>LK$;OGwtxvPE3<$Z&XxcvoM;=A1f-sMx;Tbd z2)+$64z+4`I%cb=^QqAGW&Vux7zusH!b>NjyPzUbSd5czOz?g(z2H( zz6**4oAuS0w&hxT3SBnYcur#VDlVb!MF*^ol`qV_E!Adt-q@^S9(&3C^4RMe`##35 zcx+K9bG@{eH}?9@b=Ex0Vyr$a|F~);s{TBG#m?`%Fwg5{=!@9px3{r6Dp=^Aj=EkN zIc-^Hi@N5l=axSnUio;y^WaL)pI0YkJy^?D$CDJP+})sk66AAFS3j3^P6yk!2~2vZ`V@+QjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPYq5YC&Ncukv{hH01XPyl>Eaj? z!TENYVOFbyKwG?zz>!N{8#jF5ii(Kpa{t62=ODnZ$k8-)YH#n_rE^~}@?w~if3qO^epQ~i4t|P5((n}+7Di^c0QMN%7CXWQRnohorcRYgJlPQTEVJsudk^?kK#paZ8{?DfAt zmpqR%@Xl}z+!D=g;^H&qjEm&;7wh()X3hALwZx;OS>vYr$ztbCa{nUu|3{i|F!^47 zz*fBN@Ljj=MFzf~*LYv}KijnG`^h5*KCKjaw)x-6bv!4}Y*%lKo6!Uc0$6wy%sXIi W<(<>faK^kA6!4y|elF{r5}E)uFTFPa literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Ey!3HF+&5pANQt6&9jv*eMZ>I-x9WdZ=Dt+@W z{MqIyVf(F!t!yGE!*kUIn2XrsKm>;9&`;gwlW3Q+Npm;amC7jp^PvS%t^16s-8>FVdQ I&MBb@0A_(UdjJ3c diff --git a/icons/title_button_restore@3x.png b/icons/title_button_restore@3x.png index 44b93232b2bf02340aee9fd69ea25cba61f75308..98a55e609752bfa4124a240aa4843d218cff8641 100644 GIT binary patch literal 779 zcmeAS@N?(olHy`uVBq!ia0vp^AwcZG!2~3q%es~UDaPU;cPEB*=VV?2IWDOYo@u_m z3|c@o2Loe!CIbsd2@p#GF#`kh0!9XAAk7F8TfhXD4OzepXM2DY3RP@B%)r1@>gnPb zQo;Ck#`#y7fg;E2^@4;_7kHeE@bQt5@KB3um~uV9ZP_Vj55c8-7?-gvIdOn_!3={B z=DW)8`mHVb#k(ze_s#vkO{0F#*?TSS{`p(K=OowGK4fQ7V8Dswy$*co|LnE=^2(4? zNgFxVRqwt#?R4t?`}5B~Z_<;u>Yei6>{qtg?7Z#2om)GXgz8Q2&HDd(d(>K{#)cD4 z^DWecI%W9U!@qp5vYqUCD{!iqhrqV`@B5EGew2PMZ~NuMlWCh}?B}1~9;GYyuV!)3 z$`5rfORVhUKP*0!U~uA*`s9<3*M{d@3_rIydq>PV7bQWy_FS{u!TY|1Nb|K{e);8M z_U^l%Ch#%co}ZCoH1liK*{wziCQ`i1FQ2?6!^h4ZsUueN_i;~vMv2{g%kaZ&FVk~R zb-XXz-NyP_=H=BDFY6~){7knK>&}$C|NeW>$}RWb?`%+V)0^(CHu;th*HY_0N`gn8 zpAdilwQB2A^Vz;zqeT5%(>GPjKYv{Oe&U}h_khqOr#)IzC(TvY(x1IT>tjG=sFtd= zzo}OJ-nf^OJulZ;%}sF(aI4vU_nOJ|*QJqgTe~DWM4fNApFj literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^20*ON!3HEfFP>fkq-J=!IEF;DzMUD!bwGi~Ir`?` z{x!P~v}|2^dehTduBQHJZWqtyNE$NNHfViea!>snb-r}|?)Y_=!yTA1R2r6OA4uAHT)bD?nlN{9PSLJrD|CV9V<4?F1>Fh#x+iJtuVtu4@v44$rjF6*2U FngGSRQknn& diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 785c41c..0a85f1f 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1433,90 +1433,93 @@ shakeShift: 4px; // Windows specific title +windowTitleButtonWidth: 28px; +windowTitleHeight: 24px; windowTitleButton: IconButton { - width: 24px; - height: 21px; + width: windowTitleButtonWidth; + height: windowTitleHeight; iconPosition: point(0px, 0px); } windowTitleButtonClose: IconButton(windowTitleButton) { - width: 25px; + width: windowTitleButtonWidth; } +windowTitleButtonSize: size(windowTitleButtonWidth, windowTitleHeight); defaultWindowTitle: WindowTitle { - height: 21px; + height: windowTitleHeight; bg: titleBg; bgActive: titleBgActive; fg: titleFg; fgActive: titleFgActive; minimize: IconButton(windowTitleButton) { icon: icon { - { size(24px, 21px), titleButtonBg }, - { "title_button_minimize", titleButtonFg, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBg }, + { "title_button_minimize", titleButtonFg }, }; iconOver: icon { - { size(24px, 21px), titleButtonBgOver }, - { "title_button_minimize", titleButtonFgOver, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgOver }, + { "title_button_minimize", titleButtonFgOver }, }; } minimizeIconActive: icon { - { size(24px, 21px), titleButtonBgActive }, - { "title_button_minimize", titleButtonFgActive, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgActive }, + { "title_button_minimize", titleButtonFgActive }, }; minimizeIconActiveOver: icon { - { size(24px, 21px), titleButtonBgActiveOver }, - { "title_button_minimize", titleButtonFgActiveOver, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgActiveOver }, + { "title_button_minimize", titleButtonFgActiveOver }, }; maximize: IconButton(windowTitleButton) { icon: icon { - { size(24px, 21px), titleButtonBg }, - { "title_button_maximize", titleButtonFg, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBg }, + { "title_button_maximize", titleButtonFg }, }; iconOver: icon { - { size(24px, 21px), titleButtonBgOver }, - { "title_button_maximize", titleButtonFgOver, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgOver }, + { "title_button_maximize", titleButtonFgOver }, }; } maximizeIconActive: icon { - { size(24px, 21px), titleButtonBgActive }, - { "title_button_maximize", titleButtonFgActive, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgActive }, + { "title_button_maximize", titleButtonFgActive }, }; maximizeIconActiveOver: icon { - { size(24px, 21px), titleButtonBgActiveOver }, - { "title_button_maximize", titleButtonFgActiveOver, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgActiveOver }, + { "title_button_maximize", titleButtonFgActiveOver }, }; restoreIcon: icon { - { size(24px, 21px), titleButtonBg }, - { "title_button_restore", titleButtonFg, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBg }, + { "title_button_restore", titleButtonFg }, }; restoreIconOver: icon { - { size(24px, 21px), titleButtonBgOver }, - { "title_button_restore", titleButtonFgOver, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgOver }, + { "title_button_restore", titleButtonFgOver }, }; restoreIconActive: icon { - { size(24px, 21px), titleButtonBgActive }, - { "title_button_restore", titleButtonFgActive, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgActive }, + { "title_button_restore", titleButtonFgActive }, }; restoreIconActiveOver: icon { - { size(24px, 21px), titleButtonBgActiveOver }, - { "title_button_restore", titleButtonFgActiveOver, point(4px, 4px) }, + { windowTitleButtonSize, titleButtonBgActiveOver }, + { "title_button_restore", titleButtonFgActiveOver }, }; close: IconButton(windowTitleButtonClose) { icon: icon { - { size(25px, 21px), titleButtonCloseBg }, - { "title_button_close", titleButtonCloseFg, point(5px, 4px) }, + { windowTitleButtonSize, titleButtonCloseBg }, + { "title_button_close", titleButtonCloseFg }, }; iconOver: icon { - { size(25px, 21px), titleButtonCloseBgOver }, - { "title_button_close", titleButtonCloseFgOver, point(5px, 4px) }, + { windowTitleButtonSize, titleButtonCloseBgOver }, + { "title_button_close", titleButtonCloseFgOver }, }; } closeIconActive: icon { - { size(25px, 21px), titleButtonCloseBgActive }, - { "title_button_close", titleButtonCloseFgActive, point(5px, 4px) }, + { windowTitleButtonSize, titleButtonCloseBgActive }, + { "title_button_close", titleButtonCloseFgActive }, }; closeIconActiveOver: icon { - { size(25px, 21px), titleButtonCloseBgActiveOver }, - { "title_button_close", titleButtonCloseFgActiveOver, point(5px, 4px) }, + { windowTitleButtonSize, titleButtonCloseBgActiveOver }, + { "title_button_close", titleButtonCloseFgActiveOver }, }; } From 220d6835283c51c6e529ad5ef2f100703098e270 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Jan 2022 13:37:14 +0300 Subject: [PATCH 22/46] Fix Windows 11 title controls by additional padding. --- ui/platform/win/ui_window_title_win.cpp | 154 +++++++++++++++++++++++- ui/platform/win/ui_window_title_win.h | 20 +++ ui/platform/win/ui_window_win.cpp | 18 ++- 3 files changed, 182 insertions(+), 10 deletions(-) diff --git a/ui/platform/win/ui_window_title_win.cpp b/ui/platform/win/ui_window_title_win.cpp index 1e3e63d..414adcd 100644 --- a/ui/platform/win/ui_window_title_win.cpp +++ b/ui/platform/win/ui_window_title_win.cpp @@ -6,9 +6,13 @@ // #include "ui/platform/win/ui_window_title_win.h" +#include "ui/platform/win/ui_window_win.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/ui_utility.h" +#include "base/platform/base_platform_info.h" +#include "base/platform/win/base_windows_safe_library.h" +#include "base/debug_log.h" #include "styles/style_widgets.h" #include "styles/palette.h" @@ -16,28 +20,75 @@ #include #include +#include +#include + namespace Ui { namespace Platform { +namespace { + +HRESULT(__stdcall *GetScaleFactorForMonitor)( + _In_ HMONITOR hMon, + _Out_ DEVICE_SCALE_FACTOR *pScale); + +[[nodiscard]] bool ScaleQuerySupported() { + static const auto Result = [&] { +#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name) + const auto shcore = base::Platform::SafeLoadLibrary(L"Shcore.dll"); + return LOAD_SYMBOL(shcore, GetScaleFactorForMonitor); +#undef LOAD_SYMBOL + }(); + return Result; +} + +} // namespace + +struct TitleWidget::PaddingHelper { + explicit PaddingHelper(QWidget *parent) : controlsParent(parent) { + } + + RpWidget controlsParent; + int padding = 0; +}; TitleWidget::TitleWidget(not_null parent) : RpWidget(parent) -, _controls(this, st::defaultWindowTitle) +, _paddingHelper(CheckTitlePaddingRequired() + ? std::make_unique(this) + : nullptr) +, _controls( + _paddingHelper ? &_paddingHelper->controlsParent : this, + st::defaultWindowTitle) , _shadow(this, st::titleShadow) { setAttribute(Qt::WA_OpaquePaintEvent); parent->widthValue( ) | rpl::start_with_next([=](int width) { - setGeometry(0, 0, width, _controls.st()->height); + refreshGeometryWithWidth(width); }, lifetime()); } +TitleWidget::~TitleWidget() = default; + void TitleWidget::setText(const QString &text) { window()->setWindowTitle(text); } void TitleWidget::setStyle(const style::WindowTitle &st) { _controls.setStyle(st); - setGeometry(0, 0, window()->width(), _controls.st()->height); + refreshGeometryWithWidth(window()->width()); +} + +void TitleWidget::refreshGeometryWithWidth(int width) { + const auto add = _paddingHelper ? _paddingHelper->padding : 0; + setGeometry(0, 0, width, _controls.st()->height + add); + if (_paddingHelper) { + _paddingHelper->controlsParent.setGeometry( + add, + add, + width - 2 * add, + _controls.st()->height); + } update(); } @@ -57,7 +108,8 @@ void TitleWidget::paintEvent(QPaintEvent *e) { } void TitleWidget::resizeEvent(QResizeEvent *e) { - _shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth); + const auto thickness = st::lineWidth; + _shadow->setGeometry(0, height() - thickness, width(), thickness); } HitTestResult TitleWidget::hitTest(QPoint point) const { @@ -69,5 +121,99 @@ HitTestResult TitleWidget::hitTest(QPoint point) const { return HitTestResult::None; } +bool TitleWidget::additionalPaddingRequired() const { + return _paddingHelper && !isHidden(); +} + +void TitleWidget::refreshAdditionalPaddings() { + if (!additionalPaddingRequired()) { + return; + } + const auto handle = GetWindowHandle(this); + if (!handle) { + LOG(("System Error: GetWindowHandle failed.")); + return; + } + refreshAdditionalPaddings(handle); +} + +void TitleWidget::refreshAdditionalPaddings(HWND handle) { + if (!additionalPaddingRequired()) { + return; + } + auto placement = WINDOWPLACEMENT{ + .length = sizeof(WINDOWPLACEMENT), + }; + if (!GetWindowPlacement(handle, &placement)) { + LOG(("System Error: GetWindowPlacement failed.")); + return; + } + refreshAdditionalPaddings(handle, placement); +} + +void TitleWidget::refreshAdditionalPaddings( + HWND handle, + const WINDOWPLACEMENT &placement) { + auto geometry = RECT(); + if (!GetWindowRect(handle, &geometry)) { + LOG(("System Error: GetWindowRect failed.")); + return; + } + const auto normal = placement.rcNormalPosition; + const auto rounded = (normal.left == geometry.left) + && (normal.right == geometry.right) + && (normal.top == geometry.top) + && (normal.bottom == geometry.bottom); + const auto padding = [&] { + if (!rounded) { + return 0; + } + const auto monitor = MonitorFromWindow( + handle, + MONITOR_DEFAULTTONEAREST); + if (!monitor) { + LOG(("System Error: MonitorFromWindow failed.")); + return -1; + } + auto factor = DEVICE_SCALE_FACTOR(); + if (!SUCCEEDED(GetScaleFactorForMonitor(monitor, &factor))) { + LOG(("System Error: GetScaleFactorForMonitor failed.")); + return -1; + } else if (factor < 100 || factor > 500) { + LOG(("System Error: Bad scale factor %1.").arg(int(factor))); + return -1; + } + const auto pixels = (factor + 50) / 100; + return int(base::SafeRound(pixels / window()->devicePixelRatioF())); + }(); + if (padding < 0) { + return; + } + setAdditionalPadding(padding); +} + +void TitleWidget::setAdditionalPadding(int padding) { + Expects(_paddingHelper != nullptr); + + if (_paddingHelper->padding == padding) { + return; + } + _paddingHelper->padding = padding; + refreshGeometryWithWidth(window()->width()); +} + +void TitleWidget::setVisibleHook(bool visible) { + RpWidget::setVisibleHook(visible); + if (additionalPaddingRequired()) { + PostponeCall(this, [=] { + refreshAdditionalPaddings(); + }); + } +} + +bool CheckTitlePaddingRequired() { + return ::Platform::IsWindows11OrGreater() && ScaleQuerySupported(); +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/win/ui_window_title_win.h b/ui/platform/win/ui_window_title_win.h index c6e7d0e..78cb665 100644 --- a/ui/platform/win/ui_window_title_win.h +++ b/ui/platform/win/ui_window_title_win.h @@ -13,6 +13,8 @@ #include #include +#include // HWND, WINDOWPLACEMENT + namespace style { struct WindowTitle; } // namespace style @@ -42,6 +44,7 @@ enum class HitTestResult { class TitleWidget : public RpWidget { public: explicit TitleWidget(not_null parent); + ~TitleWidget(); void setText(const QString &text); void setStyle(const style::WindowTitle &st); @@ -49,15 +52,32 @@ public: [[nodiscard]] HitTestResult hitTest(QPoint point) const; void setResizeEnabled(bool enabled); + void refreshAdditionalPaddings(); + void refreshAdditionalPaddings(HWND handle); + void refreshAdditionalPaddings( + HWND handle, + const WINDOWPLACEMENT &placement); + protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; + void setVisibleHook(bool visible) override; + private: + struct PaddingHelper; + + [[nodiscard]] bool additionalPaddingRequired() const; + void refreshGeometryWithWidth(int width); + void setAdditionalPadding(int padding); + + std::unique_ptr _paddingHelper; TitleControls _controls; object_ptr _shadow; }; +[[nodiscard]] bool CheckTitlePaddingRequired(); + } // namespace Platform } // namespace Ui diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp index c0d78dd..f888f11 100644 --- a/ui/platform/win/ui_window_win.cpp +++ b/ui/platform/win/ui_window_win.cpp @@ -9,7 +9,6 @@ #include "ui/inactive_press.h" #include "ui/platform/win/ui_window_title_win.h" #include "base/platform/base_platform_info.h" -#include "base/platform/win/base_windows_safe_library.h" #include "base/integration.h" #include "base/debug_log.h" #include "styles/palette.h" @@ -409,11 +408,16 @@ bool WindowHelper::handleNativeEvent( case WM_WINDOWPOSCHANGING: case WM_WINDOWPOSCHANGED: { if (_shadow) { - WINDOWPLACEMENT wp; - wp.length = sizeof(WINDOWPLACEMENT); - if (GetWindowPlacement(_handle, &wp) - && (wp.showCmd == SW_SHOWMAXIMIZED - || wp.showCmd == SW_SHOWMINIMIZED)) { + auto placement = WINDOWPLACEMENT{ + .length = sizeof(WINDOWPLACEMENT), + }; + if (!GetWindowPlacement(_handle, &placement)) { + LOG(("System Error: GetWindowPlacement failed.")); + return false; + } + _title->refreshAdditionalPaddings(_handle, placement); + if (placement.showCmd == SW_SHOWMAXIMIZED + || placement.showCmd == SW_SHOWMINIMIZED) { _shadow->update(WindowShadow::Change::Hidden); } else { _shadow->update( @@ -439,6 +443,7 @@ bool WindowHelper::handleNativeEvent( } updateMargins(); if (_shadow) { + _title->refreshAdditionalPaddings(_handle); const auto changes = (wParam == SIZE_MINIMIZED || wParam == SIZE_MAXIMIZED) ? WindowShadow::Change::Hidden @@ -462,6 +467,7 @@ bool WindowHelper::handleNativeEvent( case WM_MOVE: { if (_shadow) { + _title->refreshAdditionalPaddings(_handle); _shadow->update(WindowShadow::Change::Moved); } } return false; From 79d2052a8323b374b9c60ef3f4baaad9084aff3b Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Jan 2022 15:04:28 +0300 Subject: [PATCH 23/46] Use default font in window title on macOS. --- ui/platform/mac/ui_window_title_mac.mm | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ui/platform/mac/ui_window_title_mac.mm b/ui/platform/mac/ui_window_title_mac.mm index d768122..c660934 100644 --- a/ui/platform/mac/ui_window_title_mac.mm +++ b/ui/platform/mac/ui_window_title_mac.mm @@ -9,6 +9,7 @@ #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/ui_utility.h" +#include "base/debug_log.h" #include "styles/style_widgets.h" #include "styles/palette.h" @@ -59,20 +60,29 @@ void TitleWidget::init(int height) { }, lifetime()); const auto families = QStringList{ - QString(".SF NS Text"), - QString("Helvetica Neue") + u".AppleSystemUIFont"_q, + u".SF NS Text"_q, + u"Helvetica Neue"_q, }; for (auto family : families) { _font.setFamily(family); if (QFontInfo(_font).family() == _font.family()) { + static const auto logged = [&] { + LOG(("Title Font: %1").arg(family)); + return true; + }(); break; } } - if (QFontInfo(_font).family() == _font.family()) { - _font.setPixelSize((height * 15) / 24); + if (QFontInfo(_font).family() != _font.family()) { + _font = st::semiboldFont; + _font.setPixelSize(13); + } else if (_font.family() == u".AppleSystemUIFont"_q) { + _font.setBold(true); + _font.setPixelSize(13); } else { - _font = st::normalFont; + _font.setPixelSize((height * 15) / 24); } } From 45893ec22315ff728f6d3e05f6caa9092ae030c6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 17 Jan 2022 13:09:57 +0300 Subject: [PATCH 24/46] Excluded EntityType::Pre from clickable types. --- 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 bec2761..44a1196 100644 --- a/ui/text/text.cpp +++ b/ui/text/text.cpp @@ -491,7 +491,8 @@ bool Parser::checkEntities() { const auto isSingleLine = !trimmed.isEmpty() && ranges::none_of(trimmed, IsNewline); - if (isSingleLine) { + // TODO: remove trimming. + if (isSingleLine && (entityType == EntityType::Code)) { _monos.push_back({ .text = text, .type = entityType }); monoIndex = _monos.size(); } From e8f4f0213119295ac0ba5885d4e53dca7f71c6ac Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 Jan 2022 19:00:51 +0300 Subject: [PATCH 25/46] Make title bar buttons a bit wider. --- icons/title_button_close.png | Bin 262 -> 269 bytes icons/title_button_close@2x.png | Bin 416 -> 421 bytes icons/title_button_close@3x.png | Bin 527 -> 542 bytes icons/title_button_maximize.png | Bin 277 -> 290 bytes icons/title_button_maximize@2x.png | Bin 332 -> 350 bytes icons/title_button_maximize@3x.png | Bin 498 -> 522 bytes icons/title_button_minimize.png | Bin 211 -> 217 bytes icons/title_button_minimize@2x.png | Bin 242 -> 262 bytes icons/title_button_minimize@3x.png | Bin 340 -> 359 bytes icons/title_button_restore.png | Bin 380 -> 387 bytes icons/title_button_restore@2x.png | Bin 507 -> 526 bytes icons/title_button_restore@3x.png | Bin 779 -> 813 bytes icons/title_shadow_close.png | Bin 0 -> 470 bytes icons/title_shadow_close@2x.png | Bin 0 -> 890 bytes icons/title_shadow_close@3x.png | Bin 0 -> 1407 bytes icons/title_shadow_maximize.png | Bin 0 -> 457 bytes icons/title_shadow_maximize@2x.png | Bin 0 -> 798 bytes icons/title_shadow_maximize@3x.png | Bin 0 -> 1204 bytes icons/title_shadow_minimize.png | Bin 0 -> 321 bytes icons/title_shadow_minimize@2x.png | Bin 0 -> 486 bytes icons/title_shadow_minimize@3x.png | Bin 0 -> 730 bytes icons/title_shadow_restore.png | Bin 0 -> 528 bytes icons/title_shadow_restore@2x.png | Bin 0 -> 1006 bytes icons/title_shadow_restore@3x.png | Bin 0 -> 1583 bytes ui/widgets/widgets.style | 2 +- 25 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 icons/title_shadow_close.png create mode 100644 icons/title_shadow_close@2x.png create mode 100644 icons/title_shadow_close@3x.png create mode 100644 icons/title_shadow_maximize.png create mode 100644 icons/title_shadow_maximize@2x.png create mode 100644 icons/title_shadow_maximize@3x.png create mode 100644 icons/title_shadow_minimize.png create mode 100644 icons/title_shadow_minimize@2x.png create mode 100644 icons/title_shadow_minimize@3x.png create mode 100644 icons/title_shadow_restore.png create mode 100644 icons/title_shadow_restore@2x.png create mode 100644 icons/title_shadow_restore@3x.png diff --git a/icons/title_button_close.png b/icons/title_button_close.png index d2f10dbc61e41d5047cf33601a79c4b6b5e8757f..7f3b14eadff3217d5693883126a380c1aed322db 100644 GIT binary patch delta 207 zcmZo;>Sa>w4DfU3<&xrJU|`_&^l%9R(kehK!NCM1WpAC3oT%tgud;xdfd$BC1jzs~ zgWvsa)j+D;)5S5wgEu)rf_1T?ffQ4P(}Cm1)z#G4f*At@G+H(NCURW3Q2+nm(KKtgewP}`Ohtw yYJEY+;c?LAuwANZwY$=?vf3`Khy+^0;l#j@FC4gCd18(_$St0(elF{r5}E+XMM2B} delta 200 zcmeBWYGYFD4DfU3<&xrJU|`_&^l%9R(lS6S!NCM1)BbT?ov7$hFSCG|fd$BC1jzs~ z!$jMlBp_Ab>Eak-A()&X!Ma${VY|SGj~_cbI}dK*)8LqLNJQO>WyR0?`|B+&El;d! zj9H)%c6!3phsn-84YRwC9bGQGU*?X21@r!@q*TRYiEONTe|>hSWN7n!H=X0SvNnTl tVc|)`xoVzWYMpZPW$yRBlKiO1z#wuzaC@4_3r~`vz?7Wf#m7x=d#Wzp$Pz=w~rtI delta 355 zcmZ3=yntD;Gr-TCmrII^fq{Y7)59eQNLv800S6P1JiT2{Wul@-y~P4%1{NTn5hMe| z47TdZlYm?delL7d?|KzCC`n_-o-@%Q&#q~%k1pDp^*!|Ld!K{l&v#tB`KkKne)(F@&O%euprzX~lT5|025~bp zP1vwiU`1qO6$K8D11C!{an^LB{Ts5cCw9S diff --git a/icons/title_button_close@3x.png b/icons/title_button_close@3x.png index 9db41de94bbc4ce17d63d47f1e23f0468b30204c..1781b22e8f465a72712fe998af417fa1127b6133 100644 GIT binary patch literal 542 zcmeAS@N?(olHy`uVBq!ia0vp^IY8{e!2~4dShnv1QjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPo3nr!&h`K)JUc7+7EoD-r;B4q z1>@UWj(p7u0)8$r+67tO&)v&UmPlT(N>T|(iRhl?Y2tZsZ3dTylh@fqtfA4biRkqn|J-sRZx^7+hvwW3Xdlb0|1zHhT4Wn#?OooB1Iw&?7JYD@< J);T3K0RWhJyZrzF delta 467 zcmbQo($AvU8Q|y6%O%Cdz`(%k>ERLtq(gw%gM$f3K9_YZo2ckfAF_a%fd$BC1jzs~ zgHXlx!$5AOr;B4q1>@V9XS-Sq1lZbZ_5J77)Za<}xb1e2Li5BMKcn98g!SF34^2}@ ziPINjGiYGsNnl`>IKY6;v^jFXfM@@E)4pVuo0qOzg&71rzZNt5$NRn0QfDr!_k5k( zIc?vyjZIM#Th{R2bG!b-{^Z&dHs?7dqP%afyX`Lgqx?yj){oB@HNLD7Ixw%|)XC0y zkG>z}kZ?LxYqNIltb|!j(ds8V%atq-7fC3^`XBUTR=WGSSi53s_|M{a(}Rjn9v)t8 z^(}O{ORo(3ucXgDn;*yfuRr8?;_~X_GbY8?TQeQMS@%eF#RJoTx^Jb=(`#$q-}$&h zb|>TQsw3z3sccto=YKry_o{%#HG5ulJI;P4^4`SZ_3ZgiS&G}9e*U4J;grSo?fe8` z%>x{omN{S78MV!cnE!m=_Wpeb_WXJEOZ)xXw+!jGBW@SX|NVdVySMUx%cp)@dGZiE gkTfv@tnust`{mj)8Aex?kAmXE)78&qol`;+0EeZ`S^xk5 diff --git a/icons/title_button_maximize.png b/icons/title_button_maximize.png index 5d2b886f815b73027497537223d0ce12e6e6b1c5..9b0689d092e7122c7d774de721ce67dab3367233 100644 GIT binary patch delta 228 zcmbQrw1`QuGr-TCmrII^fq{Y7)59eQNUH#`1P2q4l)ZIAa-yO~y~+Y+1{NTn5hMe| z41V{wRRgIWPZ!4!58mVi3D(7m22xBroE#z}e|~#=`+p(B0+9ely}c(M>70;>d9z`I zfti_Ee0=;Gu4R66xk4`7yVn;g7CAxV^o^T0o3OMwaxgI5 WO7p*eook*H$bp`&elF{r5}E)twNefM delta 215 zcmZ3)G?huQGr-TCmrII^fq{Y7)59eQNXr1R1P2q4O#8=mb)uq2z03k;1{NTn5hMe| z3=?gGl7Ljbr;B5Vh2Yys8+i{X2rvhV9Axo1V0FQWUsiYn=WPQf;Q&>+1O9?bLjEs3 zU#Dn)>_rCie-i;GiDa8Y3GskaWav&HS+>3^T`<*6wkH+j1H KxvX5UtkYW?8JIW}8eoJBs}sl4DTc?6TP$&vTKd;w@#OT#Ifd@=PcPLybc+)|r~1M0 z*uCoPva=?+=PF~bTc&%Ryz*oDqr3aBW&NGE<;~t}$0q;!esXv8{-osc?*1blXF3_& zFM0~RN}Vuc)+tZLPD@Uwy*}&7< delta 270 zcmcb|bcRW>Gr-TCmrII^fq{Y7)59eQNLv800S6P1JiT2{Wul@-y~P4%1{NTn5hMe| z47TdZlYrDVPZ!6K2+p_H4{{z+5NLgv!vAeS#KfsLn|dQAW=PI%Y?v^6m++ys`;%KL zh5tS}m?O>1$Rgmt03sg5F(&);Y`pesu8h#c1xMb;#=qCopB?jXY0RU&SAV3)>%X1K zl)P4V>61CX52sA@KQl>(wd+dBxM!&t;uc GLK6Vf!e(Ls diff --git a/icons/title_button_maximize@3x.png b/icons/title_button_maximize@3x.png index 6ee5b8e7a10532c4030f12f30a52dc18ce34f125..bdda763b679a4334378b4b7ceff099449e3581bb 100644 GIT binary patch literal 522 zcmeAS@N?(olHy`uVBq!ia0vp^IY8{e!2~4dShnv1QjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPo3nr!&h`K)JUc7+7EoD@UWhJCI9BF8>X|0uN7@6AD}oCA3xT-qC?r_Q~>-0A18XskZ-$&uG8`wZWI4390U zeSUBL<=B39aV8Fh1_s>3c0rHJTQXybS3*CJ$KY85ToO@;4k%KqpuAkq#qd0QDDX+`H?@WR& zjSUJ-ek_3s3LOiC9Yr`elwP<9urf7qSmBZS$SgQL^X0pSP>^Dr@2olJ8ERLtq(gw%gM$f3K9_YZo2ckfAF_a%fd$BC1jzs~ zgHXlx!$59~r;B4q1>@V9hFvWV0SX-@yZbnn=K1Hu*3%K49EMb4ZtnQ*tM z@sQ{BMJtY`U*Y>v_w~1M>Hatyi31F51`Uin2@K3=Ou6I*I;RsQo>l%ctYT4hs6Xfy z9&!EazV~js{~h26dr}oTu)o zq|xYo#G+3vBH815>8+C6+rmm5HN%wFY&i7OC}nBpsx7&R4@)PmpX^SMKZk6f&op*4rSeqm+$!^B4!c0Qu#nlJ3x{K25mM1C39 zbH|K(krgv6PCx5^e4$oyo?(38b&j*YJ|Ft@qH31f+{N#IFU$P$^_)}Ry04$JTmDHT zeSEK{+q2g8`>t2IpLU8*Jyx$$faY;{a2#07`-kaxrvJvB-S+-Gpdj~j^>bP0l+XkK D6|%bY diff --git a/icons/title_button_minimize.png b/icons/title_button_minimize.png index 93c120ee1b7c1ce1d05c185179b15ed6d4521a1d..ba19ab5c2a3fd599f7a83129353fd88119ee3533 100644 GIT binary patch delta 154 zcmcc2c#~1FGr-TCmrII^fq{Y7)59eQNUH#`1P2q4l)ZIAa-yO~fyx4A1{NTn5hMe| z41V{wRRbw!PZ!4!58mVi3D(7m22xB=!eO#Ng36^UckaX_CMK@wy1%d1y7E)XnxGUe qCf`5(%nVCUn3ROv&fw^oAi%(|@u+fzE1T6qkg=YwelF{r5}E+@yDH@X delta 148 zcmcb~c$rbLGr-TCmrII^fq{Y7)59eQNXr1R1P2q4O#8=mb)uq2mdpZX1{NTn5hMe| z3=?gGl7N)0r;B5VgFVdQ&MBb@0PLA3a{vGU diff --git a/icons/title_button_minimize@2x.png b/icons/title_button_minimize@2x.png index dd49d093581a623b7f5ff1457ea881131ad44198..4d643aaeb6a288cfa931619fb2cc7e265a232e42 100644 GIT binary patch delta 200 zcmeyw*v6#T8Q|y6%O%Cdz`(%k>ERLtq&W3b5hOz^plvU7^_ejiW!-7lM80@f@C(cL(oqsBERLtq%DBhfP)E0p5CsfGEvc^-eLhW0}GJP2$BI} z23vLINkA&b)5S3)g7fW#jl2gOI9LuI|GS;}?aeC%7ro^^U0rB-Tu{NGfq{{Ug+l;| z@qn%IUESi_2EX@j%9(%W(Arg#E~?+2JyWO0b+e@?RPlrLO!Cfdj=qeV6|M{nK*xEy L`njxgN@xNAiikA7 diff --git a/icons/title_button_minimize@3x.png b/icons/title_button_minimize@3x.png index 64d3d5a5997b8444068249d257c051d4ab884acf..416c3916af702eff4ae6697ca8e07ea1b122756e 100644 GIT binary patch delta 219 zcmcb@^qfhtGr-TCmrII^fq{Y7)59eQNap~t2L}_7oMYL(YoeluOU?pj1{NTn5hMe| z3}JzX3_Dj45hI>>uiL4d_!GJop@;n1Bz+gmhP>^MblOiFc`a;DZ&_f1Ex z@x+626VD1wJj?I?UtM5Yh@a-uImtcz_wGem_btBmE8$tV`_!O&!f8kJR_=YK&Ue5w z*E!5O1+#@6LAf3g|_)76rU$><^ PAa{AX`njxgN@xNAj0;rG delta 235 zcmaFPbcIQ=Gr-TCmrII^fq{Y7)59eQNQVHi2L}_7d@k!+Hc`>THDm!Z0}GJP2$BI} z2BC`Whk?{SPZ!6K3dXk=H}W1);9H+Y`MYh!ZT}yOl6yX!k=&tV_U}iK*N;WU1;^GIO!aJFU}R$95KwSHVl{b5j9 VSbUYG^ppn3ou00KF6*2UngIUdR)+up diff --git a/icons/title_button_restore.png b/icons/title_button_restore.png index 12fa62453a03f359d1164cfb956a40cb08418dae..0b1912a0568384c00b3a5b48deccab0bc1da5f68 100644 GIT binary patch delta 326 zcmeyv)Xc2d8Q|y6%O%Cdz`(%k>ERLtq*Z`ef`bW2%HBF5IZ@H0US$C@0}GJP2$BI} z2EY5;s)5v7PZ!4!58k)IhJ3CL0$;E5s~0eBk#SIuTI=*s;N1fzDf0p*q4o<}E=xtG z3UStd>OSkTX_Hg>@~Y2jzwf;})2GrYAX|~jZWkUuH+AOgU0<&3d+&SsNa3CDwyKd2 zHqCrqxh*%?(#fM<#PzTNgTJTPk4;?cao4{paD;>&t+`%WIj=c**0LGtiE*1(trBsS zJTAx`IgRz@u63JqlIOKnp5;0Gp?1Fpmrls_t(miOZzp|XnLPQ{Q^M=t_ zB3+`*CT)*#^t7XebD9?|%lvHPe>r5@(}gA4zLQu=5?c!oTyLMcW2uZbUpL>Lh0ON- Wiz1bImF-(WLE!1?=d#Wzp$Pzmn}Iw4 delta 319 zcmZo>{==l$8Q|y6%O%Cdz`(%k>ERLtq-B6uf`bW2rv2l(I#JQ1USl=1CM~OfY2v$}Sg=`NjcHr1wWrW!la1#jRRxof>R9>0+}l!ZhUbmV zD(115+%J#4zOnCP?25-0g)-MmdwFB8?_6ii!z{+?!}5=-R-)?9^H=Qr&I|LrUWUGi zU4DBTtD}O2?&+xOrIFK?Wwxkm&U$Y7ERLtq&jIDPrKf9B8q4`1$%$q-;va$xCT5L94tY2W~IK1i|L z`?hW2>s_yRO{!Npt15MTqt0oIzV1acF3%~n<7irM&$EI3pCIoZ6rg@?QBqVc)I$ztaD0e0ssERLtq%DBhfP)E0p5CsfGEvc^-eLhW0}GJP2$BI} z23vLINkDF@r;B4q1n1jnhFPr+0&Vd=0!J=+ZQSsID=H$Y%l#9BoPz+rB1hBIslB~x zm(G2`$dj>9g_&Eib=|J>C%DguE!zD5%%3`)-M=dyI52rIa4ImWG_Xuy5VG)l@OH

9 zVCdHO)vkdKoNlq#|NdO^JkG#7!!>YAG`ES1&y+JRlGk6X+kcuh<4e{OkCJAMo9-uz zoj1w-i{SqsX~Mzed-(xd@wUTv-MSYU_ERLtq;r7SgM$f3&arIYHBr%{K4$?l0}GJP2$BI} zhO@JRZ!s`1P4{$h45?szd((flVW15Ahu4nVRZclgoV4}EjSU+n-PGS$98^}DbISA{dM-t;=1&6$7d$b`TVhwpGARz zk;8$3NuYrN%&bRZLzyK48(v&^wpz`2rjOO!Z8vk)bJfM~zyE&q)hq)E9Wm~MT5S4m zP4Q9oYr{@I{wN{iv7~ycm+R_>`>S4mwUXfJGD&%2bTQ*gh0VYIoHuBprEK zvTEO#cM)GqAIW}yShjoe$rKf#pMU=R39S3|Rif8z^G%=F?CrNdD%w7fk+wXMwt4p1 zw&Q(m3vTA@iqSJGvYI<5WoOK%%EN3A3w{*sESPV~{^W$)GUqp6t7bi%r*%nt;-eMH zi|mS%Z0hC0tNvBkoO=4{VZj4NRiP)Je}-r+ow6jvfAV4vww-cr;GraG84GZQ0`R@~20B z)`Z1YR%gta=+QCP&z&*bL@IxJu<3&H_F`9gPw;dfP1+k*fB*gW$tPXX?uGBQ}m!mnvlXkDILY+}NGqu)7v4;cr>Ufs!~d(*`j01atmj&CUPPt?}Z`CQy3y MboFyt=akR{0Ff*3G8a8Q|y6%O%Cdz`(%k>ERLtq(gw%gM$f3K9_YZo2ckfAF_a%fd$BC1jzs~ zgHXlx!wd{erJgR1Ar*{oXPkeP87OkRUN1-}b%Dpp2p=B_2@kcnhAGzr+?Jhk_7GgU zhjAI(k`o7*7tApDV7{yTuHV{{U%cCrci-Iq+cfI;oW0lL?w`N)drop~?L&4Z1qM`9 zkHnYvI`F0cv)A&=D??5tZRA*2z5DL8)2aLK&p-dXNl)IYcglaWU)g4}^S1wXZtYwW zsyDqi>;LQRQEQnR8%{XQw@?@Al;LX+|MI=ccCzQKz^P&$0^9Dt??3+dQTn~S?Uxfz zrfrt7pMQRPl&;*rn#DmYKh(V}v8uO^|FHN_g29PL>XT1CUK^fsG5p-->>V-dT$BX) z+H=ir2k-k5BF)!+`Q?|3*}Lz4n!v|&dwxcW(af(^XSW(9m`L$1zkKqR3?Dmtq>fn4 z-^V=x8YOn~EyEA9y-d$N)$zV;cN^<#nU_~rysV#G@iW~{tUFWk{`>DiE4SQ#zq3Kf ztzK`sx7y@eK3q$!|0oF_d459t{nx6kPt9lhZjBQ4Z%yA+G5`E=@%xE?s@wxYlbrTw zO`SAXT}yxV3ayU;m7!Xy*8Zki^?T!9PWHT9XEis)F~F^6_uXqI*I$=LK6+aJ@Z>2U z@#O&;SF*O=%`=zlFWt_=KUL8Gf8~nC^oZx%`?7NCf9D6+|6Nq`@k_y_ON*v;U(KpD zbIoGhc=~Bk;&0iuKe_g^&n~*C@zl#`#orPex%sDVKmPckhU@4yDYuOgSH7zB@U9g6 zwuAeMK)%^*_U1;zgKu1W4YF8PDMn0JeYyNsznikvmNo7d7QE9nJ+wk_MR%tnM*{;A iQb*=6aQq2p`N!~n{g>`Yk?hH!)adEz=d#Wzp$Pzh%t8SG diff --git a/icons/title_shadow_close.png b/icons/title_shadow_close.png new file mode 100644 index 0000000000000000000000000000000000000000..84fa43c13ebf3307da3f73a5241e495d3c3c3fc6 GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^DnKm3!2~2_Z=H|?QjEnx?oJHr&dIz4a$Hg)JkxxA z8MJ_G4hF{dOa>N^5+IfWVg?501&j>LK$;OGwtxvPtFnL@&Xxcv^t->U8mP>{)5S5w zgZJ%}n|#d%JS?YU*Zuz=_-^srhSt!=uHa7hKLV<1A5+Zp3CljIhM_< zHo@arr5p?6&dPbd_lh*9KlpWkp&|43G-aLcMGE@f)0MUvtzuBPVzOj`sBeWuU-D&> z+WU*oJh43Zfqk*W#gBKIej4zEhAzyRrFPkQ-6NLd`3Ekrc>iHxSh+6y$j%8Gk7nP} zzw=;;&#ss%{_huDPN-hH7sDoAXyy~e927aNq-9U=_0j_wl5eXkma&`hL>j!XwVLpJ zV)>%KEBH7U8k~s#X?@sy7oTI#6t;$ZmcNUp%{ukwzgy0eUlwj_pRV1eH+_%R0#TQvOkVD@*i4u*|xm*aUv*8Jzf1=);T3K0RTXmwo?EA literal 0 HcmV?d00001 diff --git a/icons/title_shadow_close@2x.png b/icons/title_shadow_close@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4577d49aca0a62f237aabdb2e58f0d11a9abcf4c GIT binary patch literal 890 zcmeAS@N?(olHy`uVBq!ia0vp^9zbls!2~32|E=Bwq!^2X+?^QKos)S9C!Q{j zAsMW1Z`=-2Hso;)WNP6&{Qtk!L!$|iCnZDHF$A}S#c8}g_(Q2Y`}K*u2al3&ojHIC z5Am!Kd$<1W>Z|_(GmUrNep_;BT6C%3*Y&;eZ@x6Bo;rQnTXt>v{(bwLF1^~HZhiHC zx547uUo9doiNC&j^=j4LYgOk?pFTamIdQYeCef>351#t}^XJUlFLtWTlQY;T#C0`o z^Uj?+4Mm+oL zOP$#*?(nkcI2cYCz#)2!<7Y1LkV4o=}7P14HT4XcGhwSuSaO5o`f z)+%b%D!p;^8Z+B7W3!MWg}+tvJm#GDeJX0ZG(zhlZ{nFHimPHKWh%c3Wc`*kHQ|=X z>BkDHC9`($)RcuT*umLqVArN~Ehw+MIH2<4K7+a?rrK_LnO$~S=WQqIm!@nK`C8O^ zLHGM^PwAC+XFNJ*92W8@tnsVpG0l?JaPeI;E&f~gwad?ZnUu}^yXVwJr>ENzZY+}6 zAkkyI@oU51_EmXh@rmMYtN278Y~HlBF|l|>$O8Xsckj;ayWpS-MN4?R*Qj=!J8{gH_14m4<>#Md7XNYkfBfI?wd_7_ wF9ULg=G>`Yc3|bd`vSRnGbLxBz=&y$`^!Sk_7oIx)q&Ezr>mdKI;Vst0C)eF82|tP literal 0 HcmV?d00001 diff --git a/icons/title_shadow_close@3x.png b/icons/title_shadow_close@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..da9e12f83517233d87f6d051f6fd7e29a540f8c0 GIT binary patch literal 1407 zcmZ`(eK^wz9N%m-GGjK=uctTjR@lo|3;J5TbGu4qknCTC}zVP3A) zdeZfjI;AMoYBKVYb6#7GQYiCUBGJur&(lBm+#jFs=kxh~KYxCoPwuV&I^4j*00aWT z8U8@9mbqH^!*sN>+&$->mcS{&bRSSvhvm4|00;Lm;#e$@y;j3O;G9GdbTy)dg%%(X zL<$B$vbv~MRNmFeC$)JWy3w60!+;c^m zLaz`rRrhv7Ne*8r5-M!Pz061ktQP}ThlU~&ulSO@#A0$@)0C0*@St5z8;k}-d|#7V03AtKR>hFfoo!>j;A!Y*EE z<{1cpCE9r$xe2H+9l2^?TBC9Fy#!}uTj>`?6AnKi3w+raB;RbtxtxhFEpkUnOrh8( zczNVqWzd6+YuWznHh4`6A6tNnC^6^?Ay8=Ku1=m_`QG#xczXyAWrot5Q|Ze0Rj3V_ z55qSqk&7ROI$OLucVC+eM*HosxoVR!EO4TaAP=>Jv6u9 z?=gCEL)a)F*nM|u;IaJ)hbzY<0(*4xmJO6Cy&oQ8bGmBrFHW9((7s@K zyRg!XZ{cFcq{QKiKE{{;wJL?l&Up;bk6sqcaiMh$WIy;%c}~+3lwD1bm=Cs= zTVd>7$$56o&al~N@L6eQPf4KAa{JRp+VnBJQlo*8Qc(E>CzIy|qS&4BBnd`eII4O# zJp1_8=jyM0AJelF;s=ttf16fAOq8j;QP7t2c={g=l+ufALUw(Dqv*^1{w+NeIOJkr z3kEIJ4^mZ=98=PHwE+l0fh*q2R<~J+oN$tQzW6D|Rk4mgqjtnR(&g8mtQk}EQyvZ$ z$Ji;nw|X^E3oP|#H__(GF!Fw(Y&*lNmkQj;nF-;io?lpC@wFc?A$|>=&UIT)trriy z@p5jKLFbaKG2!mvM~hPqd^+f%n0xy%t<59Wt!X}LGmHZ2s+R{vHa419JR+9G=0a7h z3bH`yjdpVa63#;85UP=ri06h!T81G>G8*(bYCo|N4NP;kzDYuO;?1T^P1j%O&qZB8 zE!bWhju%BNr6FFs#jiO&Z=`&MT5`($9ozhx(ZPdMXBpPTE##?k42&hcRLi8kNt^&u zF`QPeUmBBgP{`frZ|sw=NATSS8>Ubp?rcUG;YZou)aAPp*!;qPvnE4-KTLT z=LR+bnR$jM?p_XC6U6cNJNJ3rjV&H@)3>cHZI&<05Hg`(NrwX$Rrf`mA$wPMO=)&Z z#2e(&W}nF8Jw*Ez{zU%Q#EJ_OdoQOU_E^Wb8N~hAFgv6q5-{Y3M&TlR1JmZ`pN^5+IfWVg?501&j>LK$;OGwtxvPtFnL@&Xxcv^t->U8mP?J)5S5w zgZJ%}lYPw&JTAi97~B5;4-VV-q;t}muw%~Ud^;aHC*8@LzB;GECU3Xsj(`W1h07K{ z&y1Y*@x7Y-@s01T_s^J}aPZKc|1t@6ey2aj9GIrrkb8SsX4Ev+<9#veOm1H{>D2o( z_Xs8ae#FJ*e&p!+)<6R$x1d0g(%A4&*T7rL7fo$8DO{5&b>i&j9QSp??2Bu=F8wgz zslDGG<;Za^_Ij+3iHk4O^vSukEmis7y1HK<9wn@~Z_&4< z|DbY;f^Fqb5033umbzZ}f2Z~4AqNMURoo7o49OWYe(daO=6sMOe&DiogK$~p@9j+T aS|6B-ShpWJQ*EIM3Pw*?KbLh*2~7au7^+wR literal 0 HcmV?d00001 diff --git a/icons/title_shadow_maximize@2x.png b/icons/title_shadow_maximize@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..30da5ba07951e8a9f3a2ffa53c7d85c3a40ea334 GIT binary patch literal 798 zcmeAS@N?(olHy`uVBq!ia0vp^9zbls!2~32|E=Bwq!^2X+?^QKos)S94o?@y zkPOzhGv4MMGT?D#Z$4P@KmBR4!41Lih_IJ;wtQ7nylJnczIAf-oGrY2_D&6DUBwjI zxXK}PfmVR%kM&$Sm(-(lmi1KbzFRcaNATks2ZNZsw(VcPH9YB+n6-D}Vk_YT4T-0o z7XAEZP*b|igYj|I-a31p`D=dWte&=$Jy{`gZJ2y{^!n=zTsp@lSFLlHd;aqQieYbBZ^VUF_iCk*jF9kLw z#T-wvIewmXveC2-*%MI<`*T;WJ=(|gzS8Tp;`D^v*taRSk1g7Ig7aLA!LliKHw

a6DW9e@1b@*?%(-FNfMq?Z;io$RPKp(Lp2zyu{z9YLFbx8JMfX8$l{VD9cw__)H& zEX4iAjWnhi%S;Rs5-w}&)kQr}6gRuQMJh$dIlo3~M(?`oY*7ol!h1v%kM=L>wK;W? zJ-JH%%I~X~p6^=yIFRZ6_plU$1?Jfs zjOBS2N-XE9t!HK}Yf!S|m|dpL>oZ?j@uf@mf*;Ilm8YINo8ZJ~ z{D50@ne8!M`{4Eb(ri8F&i-*+@M_DGZ+sbZiw{b02(H+8|J$1b2d}sJ>L0%(>5?$F zf%B}7!7}%0f4P>O%wn0fMQ2s_-#N+g9_#DwB$U2Y0S^m8_t?b{}q{WY^T;5VCq@H6#`96MXYrU>Fbx= TN^5+IfWVg?501&j>LK$;OGwtxvPo3nr!&h`K)JUc7+76Su|oTrOp zNCo5DnRmNO3;GTe$KGHKJb%7_YuTK>)O+{upS@LRb;@k1Pm{(DiNZrS^y)4zJDqUT zaGD!KyWzBt%76adz5d?K&GGH&EJNp&c72XsP4?m33qs76)oquZs4r_NtNE$v-rKU` zBL}_uWK%skf3Z;Uds2N8&3uHuUBIVFx&WoPma&9DxhQiYDVD> zpKo_hEKQho@m+V*#d*cQ3N2i&I&sC7J-mMO!_0$B?ynE8Uc#?)=GwHC#(^~}jF}g) z1a_?L{m~`Qk~@cQZpbl@B_dZosGjKcskPh|H@h#_r>VoHAZD{l3rmrU$mS3gd~N@#x%2;CE2LtJ3T0++H!tOE9L(8ccGWz>zgMV{} zP^8ZKfA5_C?%(dasD1k4mHHO^Nq?^L)^xPr{k!zRuc!&)TvMaP_wM{rl+yF1Xy*)> zCuf>>2nw)Ns&iO7dGv_Gx>3K%~RD%PT2eI^&2pfe>T*r#e%-4*bZ!NRPYXXZ-$){2iliv<0a;xuuRGXAFOF*^b zeAkTSW}4IG`PwC{-6q-fHR-o|M4tJzF(xNr&gJ_|U#mA=oYuCR|KGg4dHu((TsN8I zcDQoOqfbW~WW|i<N^5+IfWVg?501&j>LK$;OGwtxvPtFnL@&Xxcv^t->U8c417ba4#v z;C(y6n~Padz$JZA-y_{U|EJotXbaYRK5XESI-}I>uuDAo;UUR$itVDxGbTEOy8 zy|H-rtHq+vWu9M($v$iOxI0R!YI6BkzlMcVE=6%Go(sGCGiPehr+e)J`DwE3%9WKUxUp~uCFjJwlVi1M+1Xpj q&s>q#bNSA%$!DMKU-9`h=N<+zNv{*%56N+YobT!C=d#Wzp$P!R9cWkp literal 0 HcmV?d00001 diff --git a/icons/title_shadow_minimize@2x.png b/icons/title_shadow_minimize@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7f22fbcd8493108212fa1dbba120308a1ed6b077 GIT binary patch literal 486 zcmeAS@N?(olHy`uVBq!ia0vp^9zbls!2~32|E=Bwq!^2X+?^QKos)S9Z=r{&(y%Q8}6#*gYx zIy~GoW6_VZ!c9F}e($@zLLfzj(^=*KPuugqH_ttC{PZ=)t#s~Mu8wdfk5#Kyy`H7s z8y=P9B$3fSf7?3k{d?5^11rQlE}Lu&c$9lvN!#zw69zVB z6%not``*u9<~is0joip-i4q#(EgNF>Z~dKNYi+h|lg{TAGp0B4*oVIQkyU#@ASasR z0z?l?2Y8iFOSvP4W1cnOa4&!=sF*x8q50HFa10x8o=nl bezOm}(}nk^eS2iv2MS|PS3j3^P6N^5+IfWVg?501&j>LK$;OGwtxvPo3nr!&h`K)JUc7+76Sv5i>HfY zNCo5DYX|cV83?!rE;zsx_UFI8(#9rU)~c!PZh{LB9!y)Z>O6P(q#KVv9&9|9mM6BM zfmJ7gkvrl569vqF-3M%P7k~a)yEgH{lD6A#m)RU%;Q4Q&if2bt_dJK(C7V|EZ|FW2 z^XSF$WwWg8m<9DuO}dq|QDH&&g5Sa2brSM%*Vh%EjBhjw@|Rz>WpB(j-|x}e1pd5| zYWkD==!W>77`+b{gV`8sn-o^7C~lhcVA|eevQqsSR%^RX|2BBl_u%X9?1^5pp5DAC zH?wg?vd8(ChYR+`tq;>~`j=Q!&Fs?>#2&!;xX*-nS6b)Cosu0P2kJ`RZE!Bwemi%2 zbZ*g}eV>*m$o3yU{7@lanzDz$l0(05hs3?ER{pr?v*P z{}tc<9rLX2?_rCVDxacUFTXa^S^BT9Zf>$-)2}(Il9TocYqsik_RqiYw{HKF8=cQ=bHEWvDb3kP)Yr-+Uz~NOwb)od z)AhGXgO6c!Zk^Cq#fz5>QWrY4o=Uvgabu&c)GVhpcN&(xZeA!pf0~E$nIGy3aS=>W zG7d=`SFQ(eb;t2KvObKSyeB1(O{ugcL|!j%n#ivro`Rm2-wS^hN^5+IfWVg?501&j>LK$;OGwtxvPtFnL@&Xxcv^t->U8mO$w)5S5w zgZJ%}(|OGX0&~wW9|gBY#y_u4blMf8mngBxX-b^JvdpS*!}G@W*Lh+KOSesJ zNbJ7)*~TL^lFd=U)skt=s#Uwz1@6!}9VY*3cS7Ie1;@Vs)#$Qt4sPt^)0_DG=i`qO z$3N~jka6~!mg+fa$2#sCQ(Vh$++kkwt;~B)tmNIo^%`7ND;0m#i9XzTG`Z7GJ9Bz( z$&+gtuYUil+wWc8b^W#IT7v+!AfKc5U7NKpTNSK%z3cYdHj#M~t{o@|){P3kc2aMK z%wk!anfWXexfxP79o@w?V`}_?4Sk-149B`GJTh``mr1aFNPAtH*Zwk^+2eA^8fVGI z$sD<}+P0^>`}%mTVbI3IJM^6omK-VUG1@#)ty5=p@3-@!?;c8AK0m)R=5WT@?alv< zR9?4x`Q1#cXiZM&SpM*sxxt#2eMctB@cp^ksK2Q0+nH=AT B)z1I` literal 0 HcmV?d00001 diff --git a/icons/title_shadow_restore@2x.png b/icons/title_shadow_restore@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..954e309eb7ad5669ca97555d09126fe764a41251 GIT binary patch literal 1006 zcmeAS@N?(olHy`uVBq!ia0vp^9zbls!2~32|E=Bwq!^2X+?^QKos)S9yQFW&m}oKgqs29my#o3yeC?ag`Fm}uExEEb zd;t=C5Fh%W;$3TB|8l*LA3t6UICCZRd#sVovG{D`+71E#H-G=mRW^RSPdo4F{5)Ip zN*?A#m$%NHGv|Z*0foIluy3E#PZODQX512~1|R0kpC2C=C-u`g!Q)2O)?a(g`1TyW z`HabXx1OHfJ0N)fzLEDW>)DH|pFe;8wCLxTFD>#4+n8rx6VkCY+|)34?p(&*yLL$} z@{;*~_H6G(m%FFip2#1%#@53jbeMVN3K_$@3rsnkD=Mq2tF>>2oyjxF)~NTYDQNeZ z8DlxcP$KGy%SzkgT|G^e+A}9>MjcEzV&LK3WE*+#*B^NcUMsP?Q=YD3U_QUNIQ2<( z!p5mC2^9tfDMc#Abx*59fBv@OJ)kpl?{e4c*RS*Q^UF*W)O>g*jXQDD(eA92UsJ+) z4=i%q_?1g^n9S8f6=%tNV2!(^!EV3xHd(C7v!>4aVU%oj_)wVTPYur5 z6X*Xtdtk+xj|-SX_vMx*pZ)PQxhX00$5$~S`-va^@FZk}8E^Qacsk?U>C@f$(aG!B z3mq+`RNW-^Xq@s}(f)u0nU8;@{;d8Lc6T;+PlizB+>Hq;Njj^}FL`*}>-P1T+-K@;?~zv07F=%@ zDzc5H`17BJgT6ysIT%4;o!&b7>BPV?Q4EZ1HF5g)y%-D7*jZt}G zRr#(%*Vpa{`zPA|+;Eo44x5d-V%*7km#n5a?Q9T)54| z{PW>-i-g&w5Q~dN%Ru8o|5P@utkd&#v6d zKf2*OI`{sxz)0CW#?rEom94Ib`w@-?lQk*5*)<6IZ0^^XOLaW@wS;e zf+UJmSvt?REn`2-(a-v_p5?dJrCBL0&#j-!c-#+vIWevg8CGh4VCQx1o?q+Tp#jQk Mp00i_>zopr09ppVpa1{> literal 0 HcmV?d00001 diff --git a/icons/title_shadow_restore@3x.png b/icons/title_shadow_restore@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..cb97cf5862e66ea263a20f58fd5323b3eb2e2712 GIT binary patch literal 1583 zcmZ{kTR_qW6vqEt6ujZzTN!h55hs$)d-GBzMsZUVsHRK7RAe<;rsdLd8Ij^N9A26^ zb!4{G%%zso!jY+2W)8J=HA7u#c{el2OQV-PZ4cl1ednC-={%h5a2D0f7;OvyfEk@e z;pi#Si-v^iYpGw>H9a879BMF7*K7Am-%tb*^n}n*fTZ_G0Ft!_fPakWh1LrIUrhxvt6)9l#U%Qgo>Ow2rnO)6WZnH)KXw z!lS5v{7qB<%kRF#ZA29)LJkF%pf6``CbqpxccN0MB(zEyO`rWG7>RHm_Ej+7+!ejC zYs#XXl!71LJ11UQuc{@12HJNl9-rSr*kGsXY0PV11S8Y2{h2Y!=4M|%KL~!&<7%5l z{N@yBAdD@wnJXqNX+=Oi#Ub!#*)(iKr_)VMO)2wvJl^!o%t>NkfLwkz)vt?^jv2jD zXnq@PsJ<;@A6?MQ&DHPoosn%k(b(vf7QR~DSNvw(>)rhz)sSJy zi;kAV#pU^dmv;ozTXuk^zH@%!u;ZU;>@k>@coN$&^uQxNc{EOaD&<$qu(WR(aP@Mj zdzfLn#Blj4i=U%NcKt0AT% z^O8B84K*C|*tAgb(e5ZdKdv;IH+1vFx|?i)TId{SCavyj4e?4!cs4jV83}U@aeB|b z=epR_6sBF?R-E=_7|~CEDwY{$;X*aNILmvS+ZJ)o%DLUY-D5xStM=;4zSA(&d44J_ z)#U~YGg)p#T&eOujRv_YgPi{P!>#!5ZM-TFRj4SmeM+fehyi|)Jbq|4^(Qpg%FS-5 z1rv|jCSUP5Z8s-psRvY`ci0&Svy^$nr`5jWPX@FUeEg6&Q>L4EZX%&g+_`^xQfMW3 z&THe?#HzBZ!1%;Ut0M2XADoQEi&!|@e`#%&%(4DZVGW32T2`#koSx)5TH6k9sfGWA z;O-L)+T)d=8mpj$UzQ*zHMUE=D{*Un@AqT-6iwk6WbYTY#_osO6E<+!@9jnhe_`C` zbYmbE+iu6uSpvHSqu+XBou8)elRe#Hify_CHR(p}w4OX*QcavE(T(aiLPnIC4Vx%! zbE`Xuo}mOPZk}_CESwrax)qL%3BNCj8_Ua9PQWaf*^h1*bk%^>m3&i5$Jp;C#f&c! zEOL8D;}K#&7^7n$M%vq9{>Ba|gggQ+XZ;Qws*WdKEinXr9MX)L(&m*8?dmzl>I|eC z;gVCpsf3BmEfTki`zOA3%q2y(M&CMbEa<|4OqqHzNK%4%ol&4Lt11_@jE*qOq|Pu$ zJo3gJRtQeIi*^>9<_VR1U}vo>ROsR2>70>JtA=I{44>0vdNH>0{<2T*G29K$>dTg~ zkK|qfPb5Qy2RQTVdf#FhKh~Z73bh&(-{)9J_xZ6=GQQeLY}Vv>I_nOPF+RX{Y?_&#K5YNu`1rXW-aOGN`~DEbx2V`LT_WE!I%0Pv!opQ`M}4LNg3~T6 zQ0#I2nJe~_@VbMMdjcBMNR=#h5RD@+WcsKcJOHL Mx3Va8!7<{007^-zlmGw# literal 0 HcmV?d00001 diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 0a85f1f..3ec601c 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1433,7 +1433,7 @@ shakeShift: 4px; // Windows specific title -windowTitleButtonWidth: 28px; +windowTitleButtonWidth: 36px; windowTitleHeight: 24px; windowTitleButton: IconButton { width: windowTitleButtonWidth; From d7e47aa8a63da03092cd875b4e7c7c019eefe22c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 18 Jan 2022 12:59:37 +0300 Subject: [PATCH 26/46] Support Windows 11 snap layouts in RpWindow title controls. --- ui/abstract_button.cpp | 69 ++++++---- ui/abstract_button.h | 5 + ui/platform/ui_platform_window_title.cpp | 58 ++++++++- ui/platform/ui_platform_window_title.h | 32 ++++- ui/platform/win/ui_window_title_win.cpp | 13 +- ui/platform/win/ui_window_title_win.h | 18 +-- ui/platform/win/ui_window_win.cpp | 154 +++++++++++++++++++---- ui/platform/win/ui_window_win.h | 9 ++ 8 files changed, 288 insertions(+), 70 deletions(-) diff --git a/ui/abstract_button.cpp b/ui/abstract_button.cpp index ec0dee0..61eac90 100644 --- a/ui/abstract_button.cpp +++ b/ui/abstract_button.cpp @@ -26,7 +26,9 @@ AbstractButton::AbstractButton(QWidget *parent) : RpWidget(parent) { } void AbstractButton::leaveEventHook(QEvent *e) { - if (_state & StateFlag::Down) return; + if (_state & StateFlag::Down) { + return; + } setOver(false, StateChangeSource::ByHover); return TWidget::leaveEventHook(e); @@ -48,12 +50,13 @@ void AbstractButton::checkIfOver(QPoint localPos) { void AbstractButton::mousePressEvent(QMouseEvent *e) { checkIfOver(e->pos()); - if (_acceptBoth || (e->buttons() & Qt::LeftButton)) { - if ((_state & StateFlag::Over) && !(_state & StateFlag::Down)) { - auto was = _state; - _state |= StateFlag::Down; - onStateChanged(was, StateChangeSource::ByPress); - + if (_state & StateFlag::Over) { + const auto set = setDown( + true, + StateChangeSource::ByPress, + e->modifiers(), + e->button()); + if (set) { e->accept(); } } @@ -68,21 +71,13 @@ void AbstractButton::mouseMoveEvent(QMouseEvent *e) { } void AbstractButton::mouseReleaseEvent(QMouseEvent *e) { - if (_state & StateFlag::Down) { - const auto was = _state; - _state &= ~State(StateFlag::Down); - - const auto weak = MakeWeak(this); - onStateChanged(was, StateChangeSource::ByPress); - if (!weak) { - return; - } - - if (was & StateFlag::Over) { - clicked(e->modifiers(), e->button()); - } else { - setOver(false, StateChangeSource::ByHover); - } + const auto set = setDown( + false, + StateChangeSource::ByPress, + e->modifiers(), + e->button()); + if (set) { + e->accept(); } } @@ -123,6 +118,36 @@ void AbstractButton::setOver(bool over, StateChangeSource source) { updateCursor(); } +bool AbstractButton::setDown( + bool down, + StateChangeSource source, + Qt::KeyboardModifiers modifiers, + Qt::MouseButton button) { + if (down + && !(_state & StateFlag::Down) + && (_acceptBoth || button == Qt::LeftButton)) { + auto was = _state; + _state |= StateFlag::Down; + onStateChanged(was, source); + return true; + } else if (!down && (_state & StateFlag::Down)) { + const auto was = _state; + _state &= ~State(StateFlag::Down); + + const auto weak = MakeWeak(this); + onStateChanged(was, source); + if (weak) { + if (was & StateFlag::Over) { + clicked(modifiers, button); + } else { + setOver(false, source); + } + } + return true; + } + return false; +} + void AbstractButton::updateCursor() { auto pointerCursor = _enablePointerCursor && (_state & StateFlag::Over); setCursor(pointerCursor ? style::cur_pointer : style::cur_default); diff --git a/ui/abstract_button.h b/ui/abstract_button.h index 820224a..2710de9 100644 --- a/ui/abstract_button.h +++ b/ui/abstract_button.h @@ -80,6 +80,11 @@ protected: ByHover = 0x02, }; void setOver(bool over, StateChangeSource source = StateChangeSource::ByUser); + bool setDown( + bool down, + StateChangeSource source, + Qt::KeyboardModifiers modifiers, + Qt::MouseButton button); virtual void onStateChanged(State was, StateChangeSource source) { } diff --git a/ui/platform/ui_platform_window_title.cpp b/ui/platform/ui_platform_window_title.cpp index 7da20ec..9add5af 100644 --- a/ui/platform/ui_platform_window_title.cpp +++ b/ui/platform/ui_platform_window_title.cpp @@ -35,6 +35,22 @@ void RemoveDuplicates(std::vector &v) { } // namespace +class TitleControls::Button final : public IconButton { +public: + using IconButton::IconButton; + + void setOver(bool over) { + IconButton::setOver(over, StateChangeSource::ByPress); + } + void setDown(bool down) { + IconButton::setDown( + down, + StateChangeSource::ByPress, + {}, + Qt::LeftButton); + } +}; + TitleControls::TitleControls( not_null parent, const style::WindowTitle &st, @@ -172,7 +188,47 @@ void TitleControls::raise() { _close->raise(); } -Ui::IconButton *TitleControls::controlWidget(Control control) const { +HitTestResult TitleControls::hitTest(QPoint point) const { + const auto test = [&](const object_ptr