From bab4cfa5aaac7fb50c13809d37eb51f76538bb67 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 24 Dec 2020 13:53:19 +0300 Subject: [PATCH 01/60] Fixed crash in mute button disconnect with disabled animations. --- ui/widgets/call_mute_button.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 15e3820..6cd6958 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -553,25 +553,33 @@ void CallMuteButton::init() { }, _centerLabel->lifetime()); _centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents); - _radialInfo.rawShowProgress.value( - ) | rpl::start_with_next([=](float64 value) { + rpl::combine( + _radialInfo.rawShowProgress.value(), + anim::Disables() + ) | rpl::start_with_next([=](float64 value, bool disabled) { auto &info = _radialInfo; info.realShowProgress = (1. - value) / kRadialEndPartAnimation; - if (((value == 0.) || anim::Disabled()) && _radial) { + const auto guard = gsl::finally([&] { + _content->update(); + }); + + if (((value == 0.) || disabled) && _radial) { _radial->stop(); _radial = nullptr; return; } - if ((value > 0.) && !anim::Disabled() && !_radial) { + if ((value > 0.) && !disabled && !_radial) { _radial = std::make_unique( [=] { _content->update(); }, _radialInfo.st); _radial->start(); } if ((info.realShowProgress < 1.) && !info.isDirectionToShow) { - _radial->stop(anim::type::instant); - _radial->start(); + if (_radial) { + _radial->stop(anim::type::instant); + _radial->start(); + } info.state = std::nullopt; return; } From 17eb0f22b4df542e99648b7407ca1d139ba39b78 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 25 Dec 2020 07:35:07 +0300 Subject: [PATCH 02/60] Slightly improved connecting animation in mute button. --- ui/widgets/call_mute_button.cpp | 53 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 6cd6958..10c5062 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -328,6 +328,15 @@ BlobsWidget::BlobsWidget( void BlobsWidget::init() { setAttribute(Qt::WA_TransparentForMouseEvents); + const auto cutRect = [](Painter &p, const QRectF &r) { + p.save(); + p.setOpacity(1.); + p.setBrush(st::groupCallBg); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawEllipse(r); + p.restore(); + }; + { const auto s = _blobs.maxRadius() * 2 * kGlowPaddingFactor; resize(s, s); @@ -351,6 +360,8 @@ void BlobsWidget::init() { Painter p(this); PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + // Glow. const auto s = kGlowMinScale + (1. - kGlowMinScale) * _blobs.currentLevel(); @@ -369,37 +380,41 @@ void BlobsWidget::init() { _switchConnectingProgress / kBlobPartAnimation))) : _blobsScaleEnter; _blobs.paint(p, _blobBrush, scale); + p.translate(-_center, -_center); + + if (scale < 1.) { + cutRect(p, _circleRect); + } // Main circle. - p.translate(-_center, -_center); - p.setPen(Qt::NoPen); - p.setBrush(_blobBrush); - p.drawEllipse(_circleRect); + const auto circleProgress = + Clamp(_switchConnectingProgress - kBlobPartAnimation) + / kFillCirclePartAnimation; + const auto skipColoredCircle = (circleProgress == 1.); + + if (!skipColoredCircle) { + p.setBrush(_blobBrush); + p.drawEllipse(_circleRect); + } if (_switchConnectingProgress > 0.) { p.resetTransform(); - const auto circleProgress = - Clamp(_switchConnectingProgress - kBlobPartAnimation) - / kFillCirclePartAnimation; - const auto mF = (_circleRect.width() / 2) * (1. - circleProgress); const auto cutOutRect = _circleRect.marginsRemoved( QMarginsF(mF, mF, mF, mF)); - p.setPen(Qt::NoPen); - p.setBrush(st::callConnectingRadial.color); - p.setOpacity(circleProgress); - p.drawEllipse(_circleRect); + if (!skipColoredCircle) { + p.setBrush(st::callConnectingRadial.color); + p.setOpacity(circleProgress); + p.drawEllipse(_circleRect); + } p.setOpacity(1.); + + cutRect(p, cutOutRect); + p.setBrush(st::callIconBg); - - p.save(); - p.setCompositionMode(QPainter::CompositionMode_Source); - p.drawEllipse(cutOutRect); - p.restore(); - p.drawEllipse(cutOutRect); } }, lifetime()); @@ -729,6 +744,8 @@ void CallMuteButton::init() { const auto to = r.arcFrom - kRadialFinishArcShift; ComputeRadialFinish(r.arcFrom, radialProgress, to); ComputeRadialFinish(r.arcLength, radialProgress); + } else { + r.arcLength = RadialState::kFull; } const auto opacity = (radialProgress > kOverlapProgressRadialHide) From b5fb343d6cea52df8cde00e8e3ca1008c05e4589 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 30 Dec 2020 17:49:54 +0400 Subject: [PATCH 03/60] Allow only Ctrl modifier for Backspace on Linux. --- ui/widgets/input_fields.cpp | 38 ++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index 9d9ed90..bcb3a38 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -2630,12 +2630,18 @@ bool InputField::ShouldSubmit( } void InputField::keyPressEventInner(QKeyEvent *e) { - bool shift = e->modifiers().testFlag(Qt::ShiftModifier), alt = e->modifiers().testFlag(Qt::AltModifier); - bool macmeta = Platform::IsMac() && e->modifiers().testFlag(Qt::ControlModifier) && !e->modifiers().testFlag(Qt::MetaModifier) && !e->modifiers().testFlag(Qt::AltModifier); - bool ctrl = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier); - bool enterSubmit = (_mode != Mode::MultiLine) + const auto shift = e->modifiers().testFlag(Qt::ShiftModifier); + const auto alt = e->modifiers().testFlag(Qt::AltModifier); + const auto macmeta = Platform::IsMac() + && e->modifiers().testFlag(Qt::ControlModifier) + && !e->modifiers().testFlag(Qt::MetaModifier) + && !e->modifiers().testFlag(Qt::AltModifier); + const auto ctrl = e->modifiers().testFlag(Qt::ControlModifier) + || e->modifiers().testFlag(Qt::MetaModifier); + const auto enterSubmit = (_mode != Mode::MultiLine) || ShouldSubmit(_submitSettings, e->modifiers()); - bool enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); + const auto enter = (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return); + const auto backspace = (e->key() == Qt::Key_Backspace); if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || e->key() == Qt::Key_Up @@ -2645,12 +2651,12 @@ void InputField::keyPressEventInner(QKeyEvent *e) { _reverseMarkdownReplacement = false; } - if (macmeta && e->key() == Qt::Key_Backspace) { + if (macmeta && backspace) { QTextCursor tc(textCursor()), start(tc); start.movePosition(QTextCursor::StartOfLine); tc.setPosition(start.position(), QTextCursor::KeepAnchor); tc.removeSelectedText(); - } else if (e->key() == Qt::Key_Backspace + } else if (backspace && e->modifiers() == 0 && revertFormatReplace()) { e->accept(); @@ -2687,12 +2693,22 @@ void InputField::keyPressEventInner(QKeyEvent *e) { } else { const auto text = e->text(); const auto oldPosition = textCursor().position(); - if (enter && ctrl) { - e->setModifiers(e->modifiers() & ~Qt::ControlModifier); - } else if (enter && shift) { - e->setModifiers(e->modifiers() & ~Qt::ShiftModifier); + const auto oldModifiers = e->modifiers(); + const auto allowedModifiers = (enter && ctrl) + ? (~Qt::ControlModifier) + : (enter && shift) + ? (~Qt::ShiftModifier) + : (backspace && Platform::IsLinux()) + ? (Qt::ControlModifier) + : oldModifiers; + const auto changeModifiers = (oldModifiers & ~allowedModifiers) != 0; + if (changeModifiers) { + e->setModifiers(oldModifiers & allowedModifiers); } _inner->QTextEdit::keyPressEvent(e); + if (changeModifiers) { + e->setModifiers(oldModifiers); + } auto cursor = textCursor(); if (cursor.position() == oldPosition) { bool check = false; From 120a52c143a9efbcb41d2bf109e82728becf7d6c Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 5 Jan 2021 21:09:05 +0400 Subject: [PATCH 04/60] Add traits for customizing RpWidgetWrap instances. --- ui/rp_widget.cpp | 6 ++++-- ui/rp_widget.h | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ui/rp_widget.cpp b/ui/rp_widget.cpp index 8646c92..16f8919 100644 --- a/ui/rp_widget.cpp +++ b/ui/rp_widget.cpp @@ -160,8 +160,10 @@ bool RpWidgetMethods::handleEvent(QEvent *event) { return eventHook(event); } -RpWidgetMethods::Initer::Initer(QWidget *parent) { - parent->setGeometry(0, 0, 0, 0); +RpWidgetMethods::Initer::Initer(QWidget *parent, bool setZeroGeometry) { + if (setZeroGeometry) { + parent->setGeometry(0, 0, 0, 0); + } } void RpWidgetMethods::visibilityChangedHook(bool wasVisible, bool nowVisible) { diff --git a/ui/rp_widget.h b/ui/rp_widget.h index 8cffbe5..f6ce3c2 100644 --- a/ui/rp_widget.h +++ b/ui/rp_widget.h @@ -243,7 +243,7 @@ using RpWidgetParent = std::conditional_t< TWidget, TWidgetHelper>; -template +template class RpWidgetWrap; class RpWidgetMethods { @@ -281,7 +281,7 @@ protected: virtual bool eventHook(QEvent *event) = 0; private: - template + template friend class RpWidgetWrap; struct EventStreams { @@ -292,7 +292,7 @@ private: rpl::event_stream<> alive; }; struct Initer { - Initer(QWidget *parent); + Initer(QWidget *parent, bool setZeroGeometry); }; virtual void callSetVisible(bool visible) = 0; @@ -310,11 +310,15 @@ private: }; -template +struct RpWidgetDefaultTraits { + static constexpr bool kSetZeroGeometry = true; +}; + +template class RpWidgetWrap : public RpWidgetParent , public RpWidgetMethods { - using Self = RpWidgetWrap; + using Self = RpWidgetWrap; using Parent = RpWidgetParent; public: @@ -362,7 +366,7 @@ private: return this->isHidden(); } - Initer _initer = { this }; + Initer _initer = { this, Traits::kSetZeroGeometry }; }; From 9ebd51c8f89799d5b6125e34028528d52b0b0b8e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 2 Jan 2021 14:35:02 +0300 Subject: [PATCH 05/60] Fixed blobs paint with high update rate. --- ui/paint/blob.cpp | 11 ++++++----- ui/paint/blob.h | 4 ++-- ui/paint/blobs.cpp | 18 ++++++++++++++++-- ui/paint/blobs_linear.cpp | 16 +++++++++++++--- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index b7f783e..22ccf19 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -52,11 +52,12 @@ void Blob::generateSingleValues(int i) { + kSegmentSpeedDiff * std::abs(RandomAdditional()); } -void Blob::update(float level, float speedScale) { +void Blob::update(float level, float speedScale, float64 rate) { for (auto i = 0; i < _segmentsCount; i++) { auto &segment = segmentAt(i); - segment.progress += (segment.speed * _minSpeed) - + level * segment.speed * _maxSpeed * speedScale; + segment.progress += (_minSpeed + level * _maxSpeed * speedScale) + * segment.speed + * rate; if (segment.progress >= 1) { generateSingleValues(i); generateTwoValues(i); @@ -152,9 +153,9 @@ void RadialBlob::generateTwoValues(int i) { radius.setNext(_radiuses.min + std::abs(RandomAdditional()) * radDiff); } -void RadialBlob::update(float level, float speedScale) { +void RadialBlob::update(float level, float speedScale, float64 rate) { _scale = level; - Blob::update(level, speedScale); + Blob::update(level, speedScale, rate); } Blob::Segment &RadialBlob::segmentAt(int i) { diff --git a/ui/paint/blob.h b/ui/paint/blob.h index e48e621..805914b 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -20,7 +20,7 @@ public: Blob(int n, float minSpeed = 0, float maxSpeed = 0); virtual ~Blob() = default; - void update(float level, float speedScale); + void update(float level, float speedScale, float64 rate); void generateBlob(); void setRadiuses(Radiuses values); @@ -59,7 +59,7 @@ public: RadialBlob(int n, float minScale, float minSpeed = 0, float maxSpeed = 0); void paint(Painter &p, const QBrush &brush, float outerScale = 1.); - void update(float level, float speedScale); + void update(float level, float speedScale, float64 rate); private: struct Segment : Blob::Segment { diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index 6bce7f6..903f5d9 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -10,6 +10,13 @@ namespace Ui::Paint { +namespace { + +constexpr auto kRateLimitF = 1000. / 60.; +constexpr auto kRateLimit = int(kRateLimitF + 0.5); // Round. + +} // namespace + Blobs::Blobs( std::vector blobDatas, float levelDuration, @@ -76,7 +83,6 @@ void Blobs::resetLevel() { void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) { const auto opacity = p.opacity(); for (auto i = 0; i < _blobs.size(); i++) { - _blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale); const auto alpha = _blobDatas[i].alpha; if (alpha != 1.) { p.setOpacity(opacity * alpha); @@ -89,7 +95,15 @@ void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) { } void Blobs::updateLevel(crl::time dt) { - _levelValue.update((dt > 20) ? 17 : dt); + const auto limitedDt = (dt > 20) ? kRateLimit : dt; + _levelValue.update(limitedDt); + + for (auto i = 0; i < _blobs.size(); i++) { + _blobs[i].update( + _levelValue.current(), + _blobDatas[i].speedScale, + limitedDt / kRateLimitF); + } } float64 Blobs::currentLevel() const { diff --git a/ui/paint/blobs_linear.cpp b/ui/paint/blobs_linear.cpp index 37d92ec..1f46e1f 100644 --- a/ui/paint/blobs_linear.cpp +++ b/ui/paint/blobs_linear.cpp @@ -10,6 +10,13 @@ namespace Ui::Paint { +namespace { + +constexpr auto kRateLimitF = 1000. / 60.; +constexpr auto kRateLimit = int(kRateLimitF + 0.5); // Round. + +} // namespace + LinearBlobs::LinearBlobs( std::vector blobDatas, float levelDuration, @@ -75,7 +82,6 @@ void LinearBlobs::paint(Painter &p, const QBrush &brush, int width) { PainterHighQualityEnabler hq(p); const auto opacity = p.opacity(); for (auto i = 0; i < _blobs.size(); i++) { - _blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale); const auto alpha = _blobDatas[i].alpha; if (alpha != 1.) { p.setOpacity(opacity * alpha); @@ -88,8 +94,8 @@ void LinearBlobs::paint(Painter &p, const QBrush &brush, int width) { } void LinearBlobs::updateLevel(crl::time dt) { - const auto d = (dt > 20) ? 17 : dt; - _levelValue.update(d); + const auto limitedDt = (dt > 20) ? kRateLimit : dt; + _levelValue.update(limitedDt); const auto level = (float)currentLevel(); for (auto i = 0; i < _blobs.size(); i++) { @@ -97,6 +103,10 @@ void LinearBlobs::updateLevel(crl::time dt) { _blobs[i].setRadiuses({ data.minRadius, data.idleRadius + (data.maxRadius - data.idleRadius) * level }); + _blobs[i].update( + _levelValue.current(), + data.speedScale, + limitedDt / kRateLimitF); } } From 239cec923ff8ec4ddbd4b4db8dec19ab40aec667 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 15 Jan 2021 15:40:06 +0400 Subject: [PATCH 06/60] Make radio button closer to guidelines. --- ui/widgets/checkbox.cpp | 5 +++-- ui/widgets/widgets.style | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/widgets/checkbox.cpp b/ui/widgets/checkbox.cpp index eefca13..7113f91 100644 --- a/ui/widgets/checkbox.cpp +++ b/ui/widgets/checkbox.cpp @@ -320,7 +320,8 @@ void RadioView::paint(Painter &p, int left, int top, int outerWidth) { p.setBrush(_st->bg); //int32 skip = qCeil(_st->thickness / 2.); //p.drawEllipse(_checkRect.marginsRemoved(QMargins(skip, skip, skip, skip))); - p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth)); + const auto skip = (_st->outerSkip / 10.) + (_st->thickness / 2); + p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(skip, skip, skip, skip)), outerWidth)); if (toggled > 0) { p.setPen(Qt::NoPen); @@ -332,7 +333,7 @@ void RadioView::paint(Painter &p, int left, int top, int outerWidth) { ? anim::brush(*_untoggledOverride, _st->toggledFg, toggled) : anim::brush(_st->untoggledFg, _st->toggledFg, toggled))); - auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled; + const auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled; p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth)); //int32 fskip = qFloor(checkSkip), cskip = qCeil(checkSkip); //if (2 * fskip < _checkRect.width()) { diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index ced097c..4771072 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -135,6 +135,7 @@ Radio { toggledFg: color; diameter: pixels; thickness: pixels; + outerSkip: pixels; skip: pixels; duration: int; rippleAreaPadding: pixels; @@ -741,7 +742,8 @@ defaultRadio: Radio { toggledFg: windowBgActive; diameter: 22px; thickness: 2px; - skip: 65px; // * 0.1 + outerSkip: 10px; // * 0.1 + skip: 60px; // * 0.1 duration: 120; rippleAreaPadding: 8px; } From 55a97c01066618bb75d119f1fb389c47e7cae6b6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 15 Jan 2021 16:56:57 +0400 Subject: [PATCH 07/60] Allow different icon on RoundButton hover. --- ui/widgets/buttons.cpp | 5 ++++- ui/widgets/widgets.style | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/widgets/buttons.cpp b/ui/widgets/buttons.cpp index d770d0e..eec2bae 100644 --- a/ui/widgets/buttons.cpp +++ b/ui/widgets/buttons.cpp @@ -392,7 +392,10 @@ void RoundButton::paintEvent(QPaintEvent *e) { _numbers->paint(p, textLeft, textTop, width()); } if (!_st.icon.empty()) { - _st.icon.paint(p, QPoint(iconLeft, iconTop), width()); + const auto ¤t = ((over || down) && !_st.iconOver.empty()) + ? _st.iconOver + : _st.icon; + current.paint(p, QPoint(iconLeft, iconTop), width()); } } diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 4771072..11671a9 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -93,6 +93,7 @@ RoundButton { textTop: pixels; icon: icon; + iconOver: icon; iconPosition: point; font: font; From e6b66158e6b30aa32e5d75d2e951177a945a75dc Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 11 Jan 2021 22:45:28 +0300 Subject: [PATCH 08/60] Added color for song cover overlay. --- ui/colors.palette | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/colors.palette b/ui/colors.palette index 4fd9208..00b6fb2 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -635,3 +635,5 @@ sideBarIconFgActive: #5eb5f7; // filters side bar active item icon sideBarBadgeBg: #5eb5f7; // filters side bar badge background sideBarBadgeBgMuted: #8393a3; // filters side bar unimportant badge background sideBarBadgeFg: #ffffff; // filters side bar badge text + +songCoverOverlayFg: #00000066; // song cover overlay From 6f097fc5713a706cbdafc63be06c116d05889cf9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 11 Jan 2021 23:53:32 +0300 Subject: [PATCH 09/60] Removed obsolete ability to fill menu with slots. --- ui/widgets/dropdown_menu.cpp | 4 ---- ui/widgets/dropdown_menu.h | 1 - ui/widgets/labels.cpp | 13 +++++++++---- ui/widgets/menu.cpp | 6 ------ ui/widgets/menu.h | 1 - ui/widgets/popup_menu.cpp | 4 ---- ui/widgets/popup_menu.h | 1 - 7 files changed, 9 insertions(+), 21 deletions(-) diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index 9e69974..fd54a1a 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -50,10 +50,6 @@ void DropdownMenu::init() { hide(); } -not_null DropdownMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) { - return _menu->addAction(text, receiver, member, icon, iconOver); -} - not_null DropdownMenu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, std::move(callback), icon, iconOver); } diff --git a/ui/widgets/dropdown_menu.h b/ui/widgets/dropdown_menu.h index 27a9ded..abfbc03 100644 --- a/ui/widgets/dropdown_menu.h +++ b/ui/widgets/dropdown_menu.h @@ -18,7 +18,6 @@ class DropdownMenu : public InnerDropdown { public: DropdownMenu(QWidget *parent, const style::DropdownMenu &st = st::defaultDropdownMenu); - not_null addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addSeparator(); void clearActions(); diff --git a/ui/widgets/labels.cpp b/ui/widgets/labels.cpp index ac5c900..c97143d 100644 --- a/ui/widgets/labels.cpp +++ b/ui/widgets/labels.cpp @@ -637,12 +637,17 @@ void FlatLabel::showContextMenu(QContextMenuEvent *e, ContextMenuReason reason) _contextMenu = new PopupMenu(this); if (fullSelection && !_contextCopyText.isEmpty()) { - _contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText())); + _contextMenu->addAction( + _contextCopyText, + [=] { onCopyContextText(); }); } else if (uponSelection && !fullSelection) { - const auto text = Integration::Instance().phraseContextCopySelected(); - _contextMenu->addAction(text, this, SLOT(onCopySelectedText())); + _contextMenu->addAction( + Integration::Instance().phraseContextCopySelected(), + [=] { onCopySelectedText(); }); } else if (_selectable && !hasSelection && !_contextCopyText.isEmpty()) { - _contextMenu->addAction(_contextCopyText, this, SLOT(onCopyContextText())); + _contextMenu->addAction( + _contextCopyText, + [=] { onCopyContextText(); }); } if (const auto link = ClickHandler::getActive()) { diff --git a/ui/widgets/menu.cpp b/ui/widgets/menu.cpp index 2605a8a..37bf5b7 100644 --- a/ui/widgets/menu.cpp +++ b/ui/widgets/menu.cpp @@ -95,12 +95,6 @@ void Menu::init() { } } -not_null Menu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) { - const auto action = addAction(new QAction(text, this), icon, iconOver); - connect(action, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection); - return action; -} - not_null Menu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { const auto action = addAction(new QAction(text, this), icon, iconOver); connect(action, &QAction::triggered, action, std::move(callback), Qt::QueuedConnection); diff --git a/ui/widgets/menu.h b/ui/widgets/menu.h index 2cf7347..4b9f338 100644 --- a/ui/widgets/menu.h +++ b/ui/widgets/menu.h @@ -22,7 +22,6 @@ public: Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu); ~Menu(); - not_null addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addAction(const QString &text, std::unique_ptr submenu); not_null addSeparator(); diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index de300ec..30285b9 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -88,10 +88,6 @@ void PopupMenu::handleMenuResize() { _inner = rect().marginsRemoved(_padding); } -not_null PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) { - return _menu->addAction(text, receiver, member, icon, iconOver); -} - not_null PopupMenu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, std::move(callback), icon, iconOver); } diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index ca08f98..1a8e9f3 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -21,7 +21,6 @@ public: PopupMenu(QWidget *parent, const style::PopupMenu &st = st::defaultPopupMenu); PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu); - not_null addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addAction(const QString &text, std::unique_ptr submenu); not_null addSeparator(); From 8d5bdd6b7e5a83761171eeadd3ee593a2c668caa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 15 Jan 2021 15:38:54 +0300 Subject: [PATCH 10/60] Partially replaced code for using widgets as items in Ui::Menu. --- CMakeLists.txt | 3 + ui/widgets/dropdown_menu.cpp | 5 +- ui/widgets/menu.cpp | 626 ++++++++++++++++------------- ui/widgets/menu.h | 33 +- ui/widgets/menu/menu_common.h | 16 + ui/widgets/menu/menu_item_base.cpp | 108 +++++ ui/widgets/menu/menu_item_base.h | 65 +++ ui/widgets/popup_menu.cpp | 5 +- 8 files changed, 561 insertions(+), 300 deletions(-) create mode 100644 ui/widgets/menu/menu_common.h create mode 100644 ui/widgets/menu/menu_item_base.cpp create mode 100644 ui/widgets/menu/menu_item_base.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9860dbe..b580363 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,9 @@ PRIVATE ui/widgets/labels.h ui/widgets/menu.cpp ui/widgets/menu.h + ui/widgets/menu/menu_common.h + ui/widgets/menu/menu_item_base.cpp + ui/widgets/menu/menu_item_base.h ui/widgets/popup_menu.cpp ui/widgets/popup_menu.h ui/widgets/scroll_area.cpp diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index fd54a1a..c293ebc 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -33,7 +33,10 @@ DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : Inn void DropdownMenu::init() { InnerDropdown::setHiddenCallback([this] { hideFinish(); }); - _menu->setResizedCallback([this] { resizeToContent(); }); + _menu->sizeValue( + ) | rpl::start_with_next([=] { + resizeToContent(); + }, _menu->lifetime()); _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { handleActivated(action, actionTop, source); }); diff --git a/ui/widgets/menu.cpp b/ui/widgets/menu.cpp index 37bf5b7..b6a1b85 100644 --- a/ui/widgets/menu.cpp +++ b/ui/widgets/menu.cpp @@ -7,7 +7,9 @@ #include "ui/widgets/menu.h" #include "ui/effects/ripple_animation.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/menu/menu_item_base.h" #include "ui/text/text.h" #include @@ -49,31 +51,237 @@ TextParseOptions MenuTextOptions = { } // namespace -struct Menu::ActionData { - Text::String text; - QString shortcut; - const style::icon *icon = nullptr; - const style::icon *iconOver = nullptr; - std::unique_ptr ripple; - std::unique_ptr toggle; - int textWidth = 0; - bool hasSubmenu = false; +class Menu::Separator : public ItemBase { +public: + Separator(not_null parent, const style::Menu &st, int index) + : ItemBase(parent, st, index) + , _lineWidth(st.separatorWidth) + , _padding(st.separatorPadding) + , _fg(st.separatorFg) + , _bg(st.itemBg) + , _height(_padding.top() + _lineWidth + _padding.bottom()) { + + initResizeHook(parent->sizeValue()); + // setAttribute(Qt::WA_TransparentForMouseEvents, true); + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + + p.fillRect(0, 0, width(), _height, _bg); + p.fillRect( + _padding.left(), + _padding.top(), + width() - _padding.left() - _padding.right(), + _lineWidth, + _fg); + }, lifetime()); + + } + + QAction *action() const override { + return nullptr; + } + + bool isEnabled() const override { + return false; + } + +protected: + int contentHeight() const override { + return _height; + } + +private: + const int _lineWidth; + const style::margins &_padding; + const style::color &_fg; + const style::color &_bg; + const int _height; + +}; + +class Menu::Action : public ItemBase { +public: + Action( + not_null parent, + const style::Menu &st, + int index, + not_null action, + const style::icon *icon, + const style::icon *iconOver, + bool hasSubmenu) + : ItemBase(parent, st, index) + , _action(action) + , _st(st) + , _icon(icon) + , _iconOver(iconOver) + , _height(_st.itemPadding.top() + + _st.itemStyle.font->height + + _st.itemPadding.bottom()) { + + initResizeHook(parent->sizeValue()); + processAction(); + setHasSubmenu(hasSubmenu); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + events( + ) | rpl::filter([=](not_null e) { + return _action->isEnabled() + && ((e->type() == QEvent::Leave) + || (e->type() == QEvent::Enter)); + }) | rpl::map([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=](bool selected) { + setSelected(selected); + }, lifetime()); + + events( + ) | rpl::filter([=](not_null e) { + return _action->isEnabled() && (e->type() == QEvent::MouseMove); + }) | rpl::start_with_next([=](not_null e) { + setSelected(true); + }, lifetime()); + + connect(_action, &QAction::changed, [=] { processAction(); }); + } + +void paint(Painter &p) { + + const auto enabled = _action->isEnabled(); + const auto selected = isSelected(); + if (selected && _st.itemBgOver->c.alpha() < 255) { + p.fillRect(0, 0, width(), _height, _st.itemBg); + } + p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); + if (isEnabled()) { + paintRipple(p, 0, 0); + } + if (const auto icon = (selected ? _iconOver : _icon)) { + icon->paint(p, _st.itemIconPosition, width()); + } + p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); + _text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), _textWidth, width()); + if (hasSubmenu()) { + const auto left = width() - _st.itemPadding.right() - _st.arrow.width(); + const auto top = (_height - _st.arrow.height()) / 2; + if (enabled) { + _st.arrow.paint(p, left, top, width()); + } else { + _st.arrow.paint( + p, + left, + top, + width(), + _st.itemFgDisabled->c); + } + } else if (!_shortcut.isEmpty()) { + p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); + p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), _shortcut); + } +} + +void processAction() { + if (_action->text().isEmpty()) { + _shortcut = QString(); + _text.clear(); + return; + } + const auto actionTextParts = _action->text().split('\t'); + const auto actionText = actionTextParts.empty() + ? QString() + : actionTextParts[0]; + const auto actionShortcut = (actionTextParts.size() > 1) + ? actionTextParts[1] + : QString(); + _text.setMarkedText( + _st.itemStyle, + ParseMenuItem(actionText), + MenuTextOptions); + const auto textWidth = _text.maxWidth(); + const auto &padding = _st.itemPadding; + + const auto additionalWidth = hasSubmenu() + ? padding.right() + _st.arrow.width() + : (!actionShortcut.isEmpty()) + ? (padding.right() + _st.itemStyle.font->width(actionShortcut)) + : 0; + const auto goodWidth = padding.left() + + textWidth + + padding.right() + + additionalWidth; + // if (action->isCheckable()) { + // auto updateCallback = [this, index] { updateItem(index); }; + // if (_toggle) { + // _toggle->setUpdateCallback(updateCallback); + // _toggle->setChecked(action->isChecked(), anim::type::normal); + // } else { + // _toggle = std::make_unique(_st.itemToggle, action->isChecked(), updateCallback); + // } + // goodWidth += _st.itemPadding.right() + _toggle->getSize().width() - _st.itemToggleShift; + // } else { + // _toggle.reset(); + // } + const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); + _textWidth = w - (goodWidth - textWidth); + _shortcut = actionShortcut; + setContentWidth(w); + update(); +} + +bool isEnabled() const override { + return _action->isEnabled(); +} + +QAction *action() const override { + return _action; +} + +protected: +QPoint prepareRippleStartPosition() const override { + return mapFromGlobal(QCursor::pos()); +} + +QImage prepareRippleMask() const override { + return RippleAnimation::rectMask(size()); +} + +protected: + int contentHeight() const override { + return _height; + } + +private: + + Text::String _text; + QString _shortcut; + const not_null _action; + const style::Menu &_st; + const style::icon *_icon; + const style::icon *_iconOver; + // std::unique_ptr _ripple; + std::unique_ptr _toggle; + int _textWidth = 0; + const int _height; + + // rpl::variable _selected = false; + }; Menu::Menu(QWidget *parent, const style::Menu &st) : RpWidget(parent) -, _st(st) -, _itemHeight(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom()) -, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) { +, _st(st) { init(); } Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st) : RpWidget(parent) , _st(st) -, _wappedMenu(menu) -, _itemHeight(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom()) -, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) { +, _wappedMenu(menu) { init(); _wappedMenu->setParent(this); @@ -93,6 +301,12 @@ void Menu::init() { if (_st.itemBg->c.alpha() == 255) { setAttribute(Qt::WA_OpaquePaintEvent); } + + paintRequest( + ) | rpl::start_with_next([=](const QRect &clip) { + Painter p(this); + p.fillRect(clip, _st.itemBg); + }, lifetime()); } not_null Menu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { @@ -108,27 +322,78 @@ not_null Menu::addAction(const QString &text, std::unique_ptr s } not_null Menu::addAction(not_null action, const style::icon *icon, const style::icon *iconOver) { - connect(action, &QAction::changed, this, [=] { - actionChanged(); - }); _actions.emplace_back(action); - _actionsData.push_back([&] { - auto data = ActionData(); - data.icon = icon; - data.iconOver = iconOver ? iconOver : icon; - data.hasSubmenu = (action->menu() != nullptr); - return data; - }()); - auto newWidth = qMax(width(), _st.widthMin); - newWidth = processAction(action, _actions.size() - 1, newWidth); - auto newHeight = height() + (action->isSeparator() ? _separatorHeight : _itemHeight); - resize(_forceWidth ? _forceWidth : newWidth, newHeight); - if (_resizedCallback) { - _resizedCallback(); + const auto top = _actionWidgets.empty() + ? 0 + : _actionWidgets.back()->y() + _actionWidgets.back()->height(); + const auto index = _actionWidgets.size(); + if (action->isSeparator()) { + auto widget = base::make_unique_q(this, _st, index); + widget->moveToLeft(0, top); + widget->show(); + _actionWidgets.push_back(std::move(widget)); + } else { + auto widget = base::make_unique_q( + this, + _st, + index, + action, + icon, + iconOver ? iconOver : icon, + (action->menu() != nullptr)); + widget->moveToLeft(0, top); + widget->show(); + + widget->selects( + ) | rpl::start_with_next([=, w = widget.get()](bool selected) { + if (!selected) { + return; + } + for (auto i = 0; i < _actionWidgets.size(); i++) { + if (_actionWidgets[i].get() != w) { + _actionWidgets[i]->setSelected(false); + } + } + if (_activatedCallback) { + _activatedCallback( + w->action(), + w->y(), + w->lastTriggeredSource()); + } + }, widget->lifetime()); + + widget->clicks( + ) | rpl::start_with_next([=, w = widget.get()]() { + if (_triggeredCallback) { + _triggeredCallback(w->action(), w->y(), w->lastTriggeredSource()); + } + }, widget->lifetime()); + + widget->contentWidthValue( + ) | rpl::start_with_next([=] { + const auto newWidth = _forceWidth + ? _forceWidth + : _actionWidgets.empty() + ? _st.widthMin + : (*ranges::max_element( + _actionWidgets, + std::greater<>(), + &ItemBase::width))->contentWidth(); + resize(newWidth, height()); + }, widget->lifetime()); + + _actionWidgets.push_back(std::move(widget)); } + + + const auto newHeight = ranges::accumulate( + _actionWidgets, + 0, + ranges::plus(), + &ItemBase::height); + resize(width(), newHeight); updateSelected(QCursor::pos()); - update(); return action; } @@ -141,64 +406,16 @@ not_null Menu::addSeparator() { void Menu::clearActions() { setSelected(-1); - setPressed(-1); - _actionsData.clear(); + _actionWidgets.clear(); for (auto action : base::take(_actions)) { if (action->parent() == this) { delete action; } } resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2); - if (_resizedCallback) { - _resizedCallback(); - } } void Menu::finishAnimating() { - for (auto &data : _actionsData) { - if (data.ripple) { - data.ripple.reset(); - } - if (data.toggle) { - data.toggle->finishAnimating(); - } - } -} - -int Menu::processAction(not_null action, int index, int width) { - auto &data = _actionsData[index]; - if (action->isSeparator() || action->text().isEmpty()) { - data.shortcut = QString(); - data.text.clear(); - } else { - auto actionTextParts = action->text().split('\t'); - auto actionText = actionTextParts.empty() ? QString() : actionTextParts[0]; - auto actionShortcut = (actionTextParts.size() > 1) ? actionTextParts[1] : QString(); - data.text.setMarkedText(_st.itemStyle, ParseMenuItem(actionText), MenuTextOptions); - const auto textw = data.text.maxWidth(); - int goodw = _st.itemPadding.left() + textw + _st.itemPadding.right(); - if (data.hasSubmenu) { - goodw += _st.itemPadding.right() + _st.arrow.width(); - } else if (!actionShortcut.isEmpty()) { - goodw += _st.itemPadding.right() + _st.itemStyle.font->width(actionShortcut); - } - if (action->isCheckable()) { - auto updateCallback = [this, index] { updateItem(index); }; - if (data.toggle) { - data.toggle->setUpdateCallback(updateCallback); - data.toggle->setChecked(action->isChecked(), anim::type::normal); - } else { - data.toggle = std::make_unique(_st.itemToggle, action->isChecked(), updateCallback); - } - goodw += _st.itemPadding.right() + data.toggle->getSize().width() - _st.itemToggleShift; - } else { - data.toggle.reset(); - } - width = std::clamp(goodw, width, _st.widthMax); - data.textWidth = width - (goodw - textw); - data.shortcut = actionShortcut; - } - return width; } void Menu::setShowSource(TriggeredSource source) { @@ -215,147 +432,48 @@ void Menu::setForceWidth(int forceWidth) { resize(_forceWidth, height()); } -void Menu::actionChanged() { - auto newWidth = _st.widthMin; - auto index = 0; - for (const auto action : _actions) { - newWidth = processAction(action, index++, newWidth); - } - if (newWidth != width() && !_forceWidth) { - resize(newWidth, height()); - if (_resizedCallback) { - _resizedCallback(); - } - } - update(); -} - -void Menu::paintEvent(QPaintEvent *e) { - Painter p(this); - - auto clip = e->rect(); - - auto topskip = QRect(0, 0, width(), _st.skip); - auto bottomskip = QRect(0, height() - _st.skip, width(), _st.skip); - if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st.itemBg); - if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st.itemBg); - - int top = _st.skip; - p.translate(0, top); - p.setFont(_st.itemStyle.font); - for (int i = 0, count = int(_actions.size()); i != count; ++i) { - if (clip.top() + clip.height() <= top) break; - - const auto action = _actions[i]; - auto &data = _actionsData[i]; - auto actionHeight = action->isSeparator() ? _separatorHeight : _itemHeight; - top += actionHeight; - if (clip.top() < top) { - if (action->isSeparator()) { - p.fillRect(0, 0, width(), actionHeight, _st.itemBg); - p.fillRect(_st.separatorPadding.left(), _st.separatorPadding.top(), width() - _st.separatorPadding.left() - _st.separatorPadding.right(), _st.separatorWidth, _st.separatorFg); - } else { - auto enabled = action->isEnabled(); - auto selected = ((i == _selected || i == _pressed) && enabled); - if (selected && _st.itemBgOver->c.alpha() < 255) { - p.fillRect(0, 0, width(), actionHeight, _st.itemBg); - } - p.fillRect(0, 0, width(), actionHeight, selected ? _st.itemBgOver : _st.itemBg); - if (data.ripple) { - data.ripple->paint(p, 0, 0, width()); - if (data.ripple->empty()) { - data.ripple.reset(); - } - } - if (auto icon = (selected ? data.iconOver : data.icon)) { - icon->paint(p, _st.itemIconPosition, width()); - } - p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); - data.text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), data.textWidth, width()); - if (data.hasSubmenu) { - const auto left = width() - _st.itemPadding.right() - _st.arrow.width(); - const auto top = (_itemHeight - _st.arrow.height()) / 2; - if (enabled) { - _st.arrow.paint(p, left, top, width()); - } else { - _st.arrow.paint( - p, - left, - top, - width(), - _st.itemFgDisabled->c); - } - } else if (!data.shortcut.isEmpty()) { - p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); - p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), data.shortcut); - } else if (data.toggle) { - auto toggleSize = data.toggle->getSize(); - data.toggle->paint(p, width() - _st.itemPadding.right() - toggleSize.width() + _st.itemToggleShift, (_itemHeight - toggleSize.height()) / 2, width()); - } - } - } - p.translate(0, actionHeight); - } -} - void Menu::updateSelected(QPoint globalPosition) { - if (!_mouseSelection) return; - - auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip); - auto selected = -1, top = 0; - while (top <= p.y() && ++selected < _actions.size()) { - top += _actions[selected]->isSeparator() ? _separatorHeight : _itemHeight; + if (!_mouseSelection) { + return; + } + + const auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip); + for (const auto &widget : _actionWidgets) { + const auto widgetRect = QRect(widget->pos(), widget->size()); + if (widgetRect.contains(p)) { + widget->setSelected(true); + break; + } } - setSelected((selected >= 0 && selected < _actions.size() && _actions[selected]->isEnabled() && !_actions[selected]->isSeparator()) ? selected : -1); } void Menu::itemPressed(TriggeredSource source) { - if (source == TriggeredSource::Mouse && !_mouseSelection) { - return; - } - if (_selected >= 0 && _selected < _actions.size() && _actions[_selected]->isEnabled()) { - setPressed(_selected); - if (source == TriggeredSource::Mouse) { - if (!_actionsData[_pressed].ripple) { - auto mask = RippleAnimation::rectMask(QSize(width(), _itemHeight)); - _actionsData[_pressed].ripple = std::make_unique(_st.ripple, std::move(mask), [this, selected = _pressed] { - updateItem(selected); - }); - } - _actionsData[_pressed].ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, itemTop(_pressed))); - } else { - itemReleased(source); - } - } -} - -void Menu::itemReleased(TriggeredSource source) { - if (_pressed >= 0 && _pressed < _actions.size()) { - auto pressed = _pressed; - setPressed(-1); - if (source == TriggeredSource::Mouse && _actionsData[pressed].ripple) { - _actionsData[pressed].ripple->lastStop(); - } - if (pressed == _selected && _triggeredCallback) { - _triggeredCallback(_actions[_selected], itemTop(_selected), source); + if (const auto action = findSelectedAction()) { + if (action->lastTriggeredSource() == source) { + action->setClicked(source); } } } void Menu::keyPressEvent(QKeyEvent *e) { - auto key = e->key(); + const auto key = e->key(); if (!_keyPressDelegate || !_keyPressDelegate(key)) { handleKeyPress(key); } } +ItemBase *Menu::findSelectedAction() const { + const auto it = ranges::find_if(_actionWidgets, &ItemBase::isSelected); + return (it == end(_actionWidgets)) ? nullptr : it->get(); +} + void Menu::handleKeyPress(int key) { if (key == Qt::Key_Enter || key == Qt::Key_Return) { itemPressed(TriggeredSource::Keyboard); return; } if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) { - if (_selected >= 0 && _actionsData[_selected].hasSubmenu) { + if (_selected >= 0 && _actionWidgets[_selected]->hasSubmenu()) { itemPressed(TriggeredSource::Keyboard); return; } else if (_selected < 0 && !_actions.empty()) { @@ -367,7 +485,9 @@ void Menu::handleKeyPress(int key) { return; } - auto delta = (key == Qt::Key_Down ? 1 : -1), start = _selected; + const auto delta = (key == Qt::Key_Down ? 1 : -1); + const auto selected = findSelectedAction(); + auto start = selected ? selected->index() : -1; if (start < 0 || start >= _actions.size()) { start = (delta > 0) ? (_actions.size() - 1) : 0; } @@ -379,9 +499,9 @@ void Menu::handleKeyPress(int key) { } else if (newSelected >= _actions.size()) { newSelected -= _actions.size(); } - } while (newSelected != start && (!_actions[newSelected]->isEnabled() || _actions[newSelected]->isSeparator())); + } while (newSelected != start && (!_actionWidgets[newSelected]->isEnabled())); - if (_actions[newSelected]->isEnabled() && !_actions[newSelected]->isSeparator()) { + if (_actionWidgets[newSelected]->isEnabled()) { _mouseSelection = false; setSelected(newSelected); } @@ -398,86 +518,47 @@ void Menu::clearMouseSelection() { } } -void Menu::enterEventHook(QEvent *e) { - QPoint mouse = QCursor::pos(); - if (!rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)).contains(mapFromGlobal(mouse))) { - clearMouseSelection(); - } - return TWidget::enterEventHook(e); -} - -void Menu::leaveEventHook(QEvent *e) { - clearMouseSelection(); - return TWidget::leaveEventHook(e); -} - void Menu::setSelected(int selected) { if (selected >= _actions.size()) { selected = -1; } if (_selected != selected) { - updateSelectedItem(); - if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) { - _actionsData[_selected].toggle->setStyle(_st.itemToggle); + const auto source = _mouseSelection + ? TriggeredSource::Mouse + : TriggeredSource::Keyboard; + // updateSelectedItem(); + // if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) { + // _actionsData[_selected].toggle->setStyle(_st.itemToggle); + // } + if (_selected >= 0) { + _actionWidgets[_selected]->setSelected(false, source); } _selected = selected; - if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) { - _actionsData[_selected].toggle->setStyle(_st.itemToggleOver); - } - updateSelectedItem(); - if (_activatedCallback) { - auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard; - _activatedCallback( - (_selected >= 0) ? _actions[_selected].get() : nullptr, - itemTop(_selected), - source); + if (_selected >= 0) { + _actionWidgets[_selected]->setSelected(true, source); } + // if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) { + // _actionsData[_selected].toggle->setStyle(_st.itemToggleOver); + // } + // updateSelectedItem(); + // if (_activatedCallback) { + // auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard; + // _activatedCallback( + // (_selected >= 0) ? _actions[_selected].get() : nullptr, + // itemTop(_selected), + // source); + // } } } -void Menu::setPressed(int pressed) { - if (pressed >= _actions.size()) { - pressed = -1; - } - if (_pressed != pressed) { - if (_pressed >= 0 && _pressed != _selected && _actionsData[_pressed].toggle) { - _actionsData[_pressed].toggle->setStyle(_st.itemToggle); - } - _pressed = pressed; - if (_pressed >= 0 && _actionsData[_pressed].toggle && _actions[_pressed]->isEnabled()) { - _actionsData[_pressed].toggle->setStyle(_st.itemToggleOver); - } - } -} - -int Menu::itemTop(int index) { - if (index > _actions.size()) { - index = _actions.size(); - } - int top = _st.skip; - for (int i = 0; i < index; ++i) { - top += _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight; - } - return top; -} - -void Menu::updateItem(int index) { - if (index >= 0 && index < _actions.size()) { - update(0, itemTop(index), width(), _actions[index]->isSeparator() ? _separatorHeight : _itemHeight); - } -} - -void Menu::updateSelectedItem() { - updateItem(_selected); -} - void Menu::mouseMoveEvent(QMouseEvent *e) { handleMouseMove(e->globalPos()); } void Menu::handleMouseMove(QPoint globalPosition) { - auto inner = rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)); - auto localPosition = mapFromGlobal(globalPosition); + const auto margins = style::margins(0, _st.skip, 0, _st.skip); + const auto inner = rect().marginsRemoved(margins); + const auto localPosition = mapFromGlobal(globalPosition); if (inner.contains(localPosition)) { _mouseSelection = true; updateSelected(globalPosition); @@ -499,17 +580,14 @@ void Menu::mouseReleaseEvent(QMouseEvent *e) { void Menu::handleMousePress(QPoint globalPosition) { handleMouseMove(globalPosition); - if (rect().contains(mapFromGlobal(globalPosition))) { - itemPressed(TriggeredSource::Mouse); - } else if (_mousePressDelegate) { + if (_mousePressDelegate) { _mousePressDelegate(globalPosition); } } void Menu::handleMouseRelease(QPoint globalPosition) { - handleMouseMove(globalPosition); - itemReleased(TriggeredSource::Mouse); - if (!rect().contains(mapFromGlobal(globalPosition)) && _mouseReleaseDelegate) { + if (!rect().contains(mapFromGlobal(globalPosition)) + && _mouseReleaseDelegate) { _mouseReleaseDelegate(globalPosition); } } diff --git a/ui/widgets/menu.h b/ui/widgets/menu.h index 4b9f338..0e14241 100644 --- a/ui/widgets/menu.h +++ b/ui/widgets/menu.h @@ -6,13 +6,16 @@ // #pragma once +#include "base/unique_qptr.h" #include "ui/rp_widget.h" +#include "ui/widgets/menu/menu_common.h" #include "styles/style_widgets.h" #include namespace Ui { +class ItemBase; class ToggleView; class RippleAnimation; @@ -30,10 +33,7 @@ public: void clearSelection(); - enum class TriggeredSource { - Mouse, - Keyboard, - }; + using TriggeredSource = ContextMenu::TriggeredSource; void setChildShown(bool shown) { _childShown = shown; } @@ -42,10 +42,6 @@ public: const std::vector> &actions() const; - void setResizedCallback(Fn callback) { - _resizedCallback = std::move(callback); - } - void setActivatedCallback(Fn callback) { _activatedCallback = std::move(callback); } @@ -74,38 +70,29 @@ public: void handleMouseRelease(QPoint globalPosition); protected: - void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; - void enterEventHook(QEvent *e) override; - void leaveEventHook(QEvent *e) override; private: - struct ActionData; + class Separator; + class Action; void updateSelected(QPoint globalPosition); - void actionChanged(); void init(); - // Returns the new width. - int processAction(not_null action, int index, int width); not_null addAction(not_null action, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); void setSelected(int selected); - void setPressed(int pressed); void clearMouseSelection(); - int itemTop(int index); - void updateItem(int index); - void updateSelectedItem(); void itemPressed(TriggeredSource source); - void itemReleased(TriggeredSource source); + + ItemBase *findSelectedAction() const; const style::Menu &_st; - Fn _resizedCallback; Fn _activatedCallback; Fn _triggeredCallback; Fn _keyPressDelegate; @@ -115,15 +102,13 @@ private: QMenu *_wappedMenu = nullptr; std::vector> _actions; - std::vector _actionsData; + std::vector> _actionWidgets; int _forceWidth = 0; - int _itemHeight, _separatorHeight; bool _mouseSelection = false; int _selected = -1; - int _pressed = -1; bool _childShown = false; }; diff --git a/ui/widgets/menu/menu_common.h b/ui/widgets/menu/menu_common.h new file mode 100644 index 0000000..09cb823 --- /dev/null +++ b/ui/widgets/menu/menu_common.h @@ -0,0 +1,16 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +namespace Ui::ContextMenu { + +enum class TriggeredSource { + Mouse, + Keyboard, +}; + +} // namespace Ui::ContextMenu diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp new file mode 100644 index 0000000..d17226b --- /dev/null +++ b/ui/widgets/menu/menu_item_base.cpp @@ -0,0 +1,108 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/widgets/menu/menu_item_base.h" + +namespace Ui { + +namespace { + +using TriggeredSource = Menu::TriggeredSource; + +} // namespace + +ItemBase::ItemBase( + not_null parent, + const style::Menu &st, int index) +: RippleButton(parent, st.ripple) +, _index(index) { + init(); +} + +void ItemBase::setSelected( + bool selected, + TriggeredSource source) { + if (!isEnabled()) { + return; + } + if (_selected.current() != selected) { + _lastTriggeredSource = source; + _selected = selected; + update(); + } +} + +bool ItemBase::isSelected() const { + return _selected.current(); +} +rpl::producer ItemBase::selects() const { + return _selected.changes(); +} + +TriggeredSource ItemBase::lastTriggeredSource() const { + return _lastTriggeredSource; +} + +int ItemBase::index() const { + return _index; +} + +void ItemBase::setClicked(TriggeredSource source) { + if (isEnabled()) { + _lastTriggeredSource = source; + _clicks.fire({}); + } +} + +rpl::producer<> ItemBase::clicks() const { + return rpl::merge( + AbstractButton::clicks() | rpl::to_empty, + _clicks.events()); +} + +rpl::producer ItemBase::contentWidthValue() const { + return _contentWidth.value(); +} + +int ItemBase::contentWidth() const { + return _contentWidth.current(); +} + +bool ItemBase::hasSubmenu() const { + return _hasSubmenu; +} + +void ItemBase::setHasSubmenu(bool value) { + _hasSubmenu = value; +} + +void ItemBase::init() { + events( + ) | rpl::filter([=](not_null e) { + return isEnabled() + && isSelected() + && (e->type() == QEvent::MouseButtonRelease); + }) | rpl::to_empty | rpl::start_with_next([=] { + const auto point = mapFromGlobal(QCursor::pos()); + if (!rect().contains(point)) { + setSelected(false); + } + }, lifetime()); +} + +void ItemBase::initResizeHook(rpl::producer &&size) { + std::move( + size + ) | rpl::start_with_next([=](QSize s) { + resize(s.width(), contentHeight()); + }, lifetime()); +} + +void ItemBase::setContentWidth(int w) { + _contentWidth = w; +} + +} // namespace Ui diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h new file mode 100644 index 0000000..3d76d6f --- /dev/null +++ b/ui/widgets/menu/menu_item_base.h @@ -0,0 +1,65 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/widgets/buttons.h" +#include "ui/widgets/menu.h" +#include "ui/widgets/menu/menu_common.h" +#include "styles/style_widgets.h" + +namespace Ui { + +class ItemBase : public RippleButton { +public: + ItemBase(not_null parent, const style::Menu &st, int index); + + using TriggeredSource = ContextMenu::TriggeredSource; + TriggeredSource lastTriggeredSource() const; + + rpl::producer selects() const; + void setSelected( + bool selected, + TriggeredSource source = TriggeredSource::Mouse); + bool isSelected() const; + + int index() const; + + void setClicked(TriggeredSource source = TriggeredSource::Mouse); + + rpl::producer<> clicks() const; + + rpl::producer contentWidthValue() const; + int contentWidth() const; + void setContentWidth(int w); + + bool hasSubmenu() const; + void setHasSubmenu(bool value); + + virtual QAction *action() const = 0; + virtual bool isEnabled() const = 0; + +protected: + void init(); + void initResizeHook(rpl::producer &&size); + + virtual int contentHeight() const = 0; + +private: + const int _index; + + bool _hasSubmenu = false; + + rpl::variable _selected = false; + rpl::event_stream<> _clicks; + + rpl::variable _contentWidth = 0; + + TriggeredSource _lastTriggeredSource = TriggeredSource::Mouse; + +}; + +} // namespace Ui diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index 30285b9..41f9a22 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -52,7 +52,10 @@ void PopupMenu::init() { hideMenu(true); }, lifetime()); - _menu->setResizedCallback([this] { handleMenuResize(); }); + _menu->sizeValue( + ) | rpl::start_with_next([=] { + handleMenuResize(); + }, _menu->lifetime()); _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { handleActivated(action, actionTop, source); }); From 7e52011a06fbed3969fd1554b7d6e26bb25e7afa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 01:34:24 +0300 Subject: [PATCH 11/60] Moved menu separator to separate file. --- CMakeLists.txt | 2 ++ ui/widgets/menu.cpp | 52 ++---------------------------- ui/widgets/menu.h | 1 - ui/widgets/menu/menu_separator.cpp | 51 +++++++++++++++++++++++++++++ ui/widgets/menu/menu_separator.h | 35 ++++++++++++++++++++ 5 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 ui/widgets/menu/menu_separator.cpp create mode 100644 ui/widgets/menu/menu_separator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b580363..e319652 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,6 +151,8 @@ PRIVATE ui/widgets/menu/menu_common.h ui/widgets/menu/menu_item_base.cpp ui/widgets/menu/menu_item_base.h + ui/widgets/menu/menu_separator.cpp + ui/widgets/menu/menu_separator.h ui/widgets/popup_menu.cpp ui/widgets/popup_menu.h ui/widgets/scroll_area.cpp diff --git a/ui/widgets/menu.cpp b/ui/widgets/menu.cpp index b6a1b85..2cb18eb 100644 --- a/ui/widgets/menu.cpp +++ b/ui/widgets/menu.cpp @@ -10,6 +10,7 @@ #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/menu/menu_item_base.h" +#include "ui/widgets/menu/menu_separator.h" #include "ui/text/text.h" #include @@ -51,55 +52,6 @@ TextParseOptions MenuTextOptions = { } // namespace -class Menu::Separator : public ItemBase { -public: - Separator(not_null parent, const style::Menu &st, int index) - : ItemBase(parent, st, index) - , _lineWidth(st.separatorWidth) - , _padding(st.separatorPadding) - , _fg(st.separatorFg) - , _bg(st.itemBg) - , _height(_padding.top() + _lineWidth + _padding.bottom()) { - - initResizeHook(parent->sizeValue()); - // setAttribute(Qt::WA_TransparentForMouseEvents, true); - paintRequest( - ) | rpl::start_with_next([=] { - Painter p(this); - - p.fillRect(0, 0, width(), _height, _bg); - p.fillRect( - _padding.left(), - _padding.top(), - width() - _padding.left() - _padding.right(), - _lineWidth, - _fg); - }, lifetime()); - - } - - QAction *action() const override { - return nullptr; - } - - bool isEnabled() const override { - return false; - } - -protected: - int contentHeight() const override { - return _height; - } - -private: - const int _lineWidth; - const style::margins &_padding; - const style::color &_fg; - const style::color &_bg; - const int _height; - -}; - class Menu::Action : public ItemBase { public: Action( @@ -329,7 +281,7 @@ not_null Menu::addAction(not_null action, const style::icon : _actionWidgets.back()->y() + _actionWidgets.back()->height(); const auto index = _actionWidgets.size(); if (action->isSeparator()) { - auto widget = base::make_unique_q(this, _st, index); + auto widget = base::make_unique_q(this, _st, index); widget->moveToLeft(0, top); widget->show(); _actionWidgets.push_back(std::move(widget)); diff --git a/ui/widgets/menu.h b/ui/widgets/menu.h index 0e14241..9e2f3e5 100644 --- a/ui/widgets/menu.h +++ b/ui/widgets/menu.h @@ -76,7 +76,6 @@ protected: void mouseReleaseEvent(QMouseEvent *e) override; private: - class Separator; class Action; void updateSelected(QPoint globalPosition); diff --git a/ui/widgets/menu/menu_separator.cpp b/ui/widgets/menu/menu_separator.cpp new file mode 100644 index 0000000..c00b96d --- /dev/null +++ b/ui/widgets/menu/menu_separator.cpp @@ -0,0 +1,51 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/widgets/menu/menu_separator.h" + +#include "ui/painter.h" + +namespace Ui { + +Separator::Separator( + not_null parent, + const style::Menu &st, + int index) +: ItemBase(parent, st, index) +, _lineWidth(st.separatorWidth) +, _padding(st.separatorPadding) +, _fg(st.separatorFg) +, _bg(st.itemBg) +, _height(_padding.top() + _lineWidth + _padding.bottom()) { + + initResizeHook(parent->sizeValue()); + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + + p.fillRect(0, 0, width(), _height, _bg); + p.fillRect( + _padding.left(), + _padding.top(), + width() - _padding.left() - _padding.right(), + _lineWidth, + _fg); + }, lifetime()); +} + +QAction *Separator::action() const { + return nullptr; +} + +bool Separator::isEnabled() const { + return false; +} + +int Separator::contentHeight() const { + return _height; +} + +} // namespace Ui diff --git a/ui/widgets/menu/menu_separator.h b/ui/widgets/menu/menu_separator.h new file mode 100644 index 0000000..e2bae7f --- /dev/null +++ b/ui/widgets/menu/menu_separator.h @@ -0,0 +1,35 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/widgets/menu/menu_item_base.h" +#include "styles/style_widgets.h" + +class Painter; + +namespace Ui { + +class Separator : public ItemBase { +public: + Separator(not_null parent, const style::Menu &st, int index); + + QAction *action() const override; + bool isEnabled() const override; + +protected: + int contentHeight() const override; + +private: + const int _lineWidth; + const style::margins &_padding; + const style::color &_fg; + const style::color &_bg; + const int _height; + +}; + +} // namespace Ui From f3e5220dfb18af034d55a2a5971854de2767b722 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 01:47:06 +0300 Subject: [PATCH 12/60] Moved menu action item to separate file. --- CMakeLists.txt | 2 + ui/widgets/menu.cpp | 212 +------------------------------- ui/widgets/menu.h | 2 - ui/widgets/menu/menu_action.cpp | 205 ++++++++++++++++++++++++++++++ ui/widgets/menu/menu_action.h | 53 ++++++++ 5 files changed, 262 insertions(+), 212 deletions(-) create mode 100644 ui/widgets/menu/menu_action.cpp create mode 100644 ui/widgets/menu/menu_action.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e319652..435774b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,8 @@ PRIVATE ui/widgets/labels.h ui/widgets/menu.cpp ui/widgets/menu.h + ui/widgets/menu/menu_action.cpp + ui/widgets/menu/menu_action.h ui/widgets/menu/menu_common.h ui/widgets/menu/menu_item_base.cpp ui/widgets/menu/menu_item_base.h diff --git a/ui/widgets/menu.cpp b/ui/widgets/menu.cpp index 2cb18eb..591e74e 100644 --- a/ui/widgets/menu.cpp +++ b/ui/widgets/menu.cpp @@ -6,223 +6,15 @@ // #include "ui/widgets/menu.h" -#include "ui/effects/ripple_animation.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/menu/menu_action.h" #include "ui/widgets/menu/menu_item_base.h" #include "ui/widgets/menu/menu_separator.h" -#include "ui/text/text.h" #include namespace Ui { -namespace { - -[[nodiscard]] TextWithEntities ParseMenuItem(const QString &text) { - auto result = TextWithEntities(); - result.text.reserve(text.size()); - auto afterAmpersand = false; - for (const auto ch : text) { - if (afterAmpersand) { - afterAmpersand = false; - if (ch == '&') { - result.text.append(ch); - } else { - result.entities.append(EntityInText{ - EntityType::Underline, - result.text.size(), - 1 }); - result.text.append(ch); - } - } else if (ch == '&') { - afterAmpersand = true; - } else { - result.text.append(ch); - } - } - return result; -} - -TextParseOptions MenuTextOptions = { - TextParseLinks | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir -}; - -} // namespace - -class Menu::Action : public ItemBase { -public: - Action( - not_null parent, - const style::Menu &st, - int index, - not_null action, - const style::icon *icon, - const style::icon *iconOver, - bool hasSubmenu) - : ItemBase(parent, st, index) - , _action(action) - , _st(st) - , _icon(icon) - , _iconOver(iconOver) - , _height(_st.itemPadding.top() - + _st.itemStyle.font->height - + _st.itemPadding.bottom()) { - - initResizeHook(parent->sizeValue()); - processAction(); - setHasSubmenu(hasSubmenu); - - paintRequest( - ) | rpl::start_with_next([=] { - Painter p(this); - paint(p); - }, lifetime()); - - events( - ) | rpl::filter([=](not_null e) { - return _action->isEnabled() - && ((e->type() == QEvent::Leave) - || (e->type() == QEvent::Enter)); - }) | rpl::map([=](not_null e) { - return (e->type() == QEvent::Enter); - }) | rpl::start_with_next([=](bool selected) { - setSelected(selected); - }, lifetime()); - - events( - ) | rpl::filter([=](not_null e) { - return _action->isEnabled() && (e->type() == QEvent::MouseMove); - }) | rpl::start_with_next([=](not_null e) { - setSelected(true); - }, lifetime()); - - connect(_action, &QAction::changed, [=] { processAction(); }); - } - -void paint(Painter &p) { - - const auto enabled = _action->isEnabled(); - const auto selected = isSelected(); - if (selected && _st.itemBgOver->c.alpha() < 255) { - p.fillRect(0, 0, width(), _height, _st.itemBg); - } - p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); - if (isEnabled()) { - paintRipple(p, 0, 0); - } - if (const auto icon = (selected ? _iconOver : _icon)) { - icon->paint(p, _st.itemIconPosition, width()); - } - p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); - _text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), _textWidth, width()); - if (hasSubmenu()) { - const auto left = width() - _st.itemPadding.right() - _st.arrow.width(); - const auto top = (_height - _st.arrow.height()) / 2; - if (enabled) { - _st.arrow.paint(p, left, top, width()); - } else { - _st.arrow.paint( - p, - left, - top, - width(), - _st.itemFgDisabled->c); - } - } else if (!_shortcut.isEmpty()) { - p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); - p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), _shortcut); - } -} - -void processAction() { - if (_action->text().isEmpty()) { - _shortcut = QString(); - _text.clear(); - return; - } - const auto actionTextParts = _action->text().split('\t'); - const auto actionText = actionTextParts.empty() - ? QString() - : actionTextParts[0]; - const auto actionShortcut = (actionTextParts.size() > 1) - ? actionTextParts[1] - : QString(); - _text.setMarkedText( - _st.itemStyle, - ParseMenuItem(actionText), - MenuTextOptions); - const auto textWidth = _text.maxWidth(); - const auto &padding = _st.itemPadding; - - const auto additionalWidth = hasSubmenu() - ? padding.right() + _st.arrow.width() - : (!actionShortcut.isEmpty()) - ? (padding.right() + _st.itemStyle.font->width(actionShortcut)) - : 0; - const auto goodWidth = padding.left() - + textWidth - + padding.right() - + additionalWidth; - // if (action->isCheckable()) { - // auto updateCallback = [this, index] { updateItem(index); }; - // if (_toggle) { - // _toggle->setUpdateCallback(updateCallback); - // _toggle->setChecked(action->isChecked(), anim::type::normal); - // } else { - // _toggle = std::make_unique(_st.itemToggle, action->isChecked(), updateCallback); - // } - // goodWidth += _st.itemPadding.right() + _toggle->getSize().width() - _st.itemToggleShift; - // } else { - // _toggle.reset(); - // } - const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); - _textWidth = w - (goodWidth - textWidth); - _shortcut = actionShortcut; - setContentWidth(w); - update(); -} - -bool isEnabled() const override { - return _action->isEnabled(); -} - -QAction *action() const override { - return _action; -} - -protected: -QPoint prepareRippleStartPosition() const override { - return mapFromGlobal(QCursor::pos()); -} - -QImage prepareRippleMask() const override { - return RippleAnimation::rectMask(size()); -} - -protected: - int contentHeight() const override { - return _height; - } - -private: - - Text::String _text; - QString _shortcut; - const not_null _action; - const style::Menu &_st; - const style::icon *_icon; - const style::icon *_iconOver; - // std::unique_ptr _ripple; - std::unique_ptr _toggle; - int _textWidth = 0; - const int _height; - - // rpl::variable _selected = false; - -}; Menu::Menu(QWidget *parent, const style::Menu &st) : RpWidget(parent) @@ -286,7 +78,7 @@ not_null Menu::addAction(not_null action, const style::icon widget->show(); _actionWidgets.push_back(std::move(widget)); } else { - auto widget = base::make_unique_q( + auto widget = base::make_unique_q( this, _st, index, diff --git a/ui/widgets/menu.h b/ui/widgets/menu.h index 9e2f3e5..2bbc5f1 100644 --- a/ui/widgets/menu.h +++ b/ui/widgets/menu.h @@ -76,8 +76,6 @@ protected: void mouseReleaseEvent(QMouseEvent *e) override; private: - class Action; - void updateSelected(QPoint globalPosition); void init(); diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp new file mode 100644 index 0000000..1323e19 --- /dev/null +++ b/ui/widgets/menu/menu_action.cpp @@ -0,0 +1,205 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/widgets/menu/menu_action.h" + +#include "ui/effects/ripple_animation.h" +#include "ui/painter.h" + +namespace Ui { +namespace { + +[[nodiscard]] TextWithEntities ParseMenuItem(const QString &text) { + auto result = TextWithEntities(); + result.text.reserve(text.size()); + auto afterAmpersand = false; + for (const auto ch : text) { + if (afterAmpersand) { + afterAmpersand = false; + if (ch == '&') { + result.text.append(ch); + } else { + result.entities.append(EntityInText{ + EntityType::Underline, + result.text.size(), + 1 }); + result.text.append(ch); + } + } else if (ch == '&') { + afterAmpersand = true; + } else { + result.text.append(ch); + } + } + return result; +} + +TextParseOptions MenuTextOptions = { + TextParseLinks | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; + +} // namespace + +Action::Action( + not_null parent, + const style::Menu &st, + int index, + not_null action, + const style::icon *icon, + const style::icon *iconOver, + bool hasSubmenu) +: ItemBase(parent, st, index) +, _action(action) +, _st(st) +, _icon(icon) +, _iconOver(iconOver) +, _height(_st.itemPadding.top() + + _st.itemStyle.font->height + + _st.itemPadding.bottom()) { + + initResizeHook(parent->sizeValue()); + processAction(); + setHasSubmenu(hasSubmenu); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + events( + ) | rpl::filter([=](not_null e) { + return _action->isEnabled() + && ((e->type() == QEvent::Leave) + || (e->type() == QEvent::Enter)); + }) | rpl::map([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=](bool selected) { + setSelected(selected); + }, lifetime()); + + events( + ) | rpl::filter([=](not_null e) { + return _action->isEnabled() && (e->type() == QEvent::MouseMove); + }) | rpl::start_with_next([=](not_null e) { + setSelected(true); + }, lifetime()); + + connect(_action, &QAction::changed, [=] { processAction(); }); +} + +void Action::paint(Painter &p) { + const auto enabled = _action->isEnabled(); + const auto selected = isSelected(); + if (selected && _st.itemBgOver->c.alpha() < 255) { + p.fillRect(0, 0, width(), _height, _st.itemBg); + } + p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); + if (isEnabled()) { + paintRipple(p, 0, 0); + } + if (const auto icon = (selected ? _iconOver : _icon)) { + icon->paint(p, _st.itemIconPosition, width()); + } + p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); + _text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), _textWidth, width()); + if (hasSubmenu()) { + const auto left = width() - _st.itemPadding.right() - _st.arrow.width(); + const auto top = (_height - _st.arrow.height()) / 2; + if (enabled) { + _st.arrow.paint(p, left, top, width()); + } else { + _st.arrow.paint( + p, + left, + top, + width(), + _st.itemFgDisabled->c); + } + } else if (!_shortcut.isEmpty()) { + p.setPen(selected + ? _st.itemFgShortcutOver + : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); + p.drawTextRight( + _st.itemPadding.right(), + _st.itemPadding.top(), + width(), + _shortcut); + } +} + +void Action::processAction() { + if (_action->text().isEmpty()) { + _shortcut = QString(); + _text.clear(); + return; + } + const auto actionTextParts = _action->text().split('\t'); + const auto actionText = actionTextParts.empty() + ? QString() + : actionTextParts[0]; + const auto actionShortcut = (actionTextParts.size() > 1) + ? actionTextParts[1] + : QString(); + _text.setMarkedText( + _st.itemStyle, + ParseMenuItem(actionText), + MenuTextOptions); + const auto textWidth = _text.maxWidth(); + const auto &padding = _st.itemPadding; + + const auto additionalWidth = hasSubmenu() + ? padding.right() + _st.arrow.width() + : (!actionShortcut.isEmpty()) + ? (padding.right() + _st.itemStyle.font->width(actionShortcut)) + : 0; + const auto goodWidth = padding.left() + + textWidth + + padding.right() + + additionalWidth; + // if (action->isCheckable()) { + // auto updateCallback = [this, index] { updateItem(index); }; + // if (_toggle) { + // _toggle->setUpdateCallback(updateCallback); + // _toggle->setChecked(action->isChecked(), anim::type::normal); + // } else { + // _toggle = std::make_unique(_st.itemToggle, action->isChecked(), updateCallback); + // } + // goodWidth += _st.itemPadding.right() + _toggle->getSize().width() - _st.itemToggleShift; + // } else { + // _toggle.reset(); + // } + const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); + _textWidth = w - (goodWidth - textWidth); + _shortcut = actionShortcut; + setContentWidth(w); + update(); +} + +bool Action::isEnabled() const { + return _action->isEnabled(); +} + +QAction *Action::action() const { + return _action; +} + +QPoint Action::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()); +} + +QImage Action::prepareRippleMask() const { + return RippleAnimation::rectMask(size()); +} + +int Action::contentHeight() const { + return _height; +} + +} // namespace Ui diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h new file mode 100644 index 0000000..d2aa1cd --- /dev/null +++ b/ui/widgets/menu/menu_action.h @@ -0,0 +1,53 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/text/text.h" +#include "ui/widgets/menu/menu_item_base.h" +#include "styles/style_widgets.h" + +class Painter; + +namespace Ui { + +class Action : public ItemBase { +public: + Action( + not_null parent, + const style::Menu &st, + int index, + not_null action, + const style::icon *icon, + const style::icon *iconOver, + bool hasSubmenu); + + bool isEnabled() const override; + QAction *action() const override; + +protected: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + + int contentHeight() const override; + +private: + void processAction(); + void paint(Painter &p); + + Text::String _text; + QString _shortcut; + const not_null _action; + const style::Menu &_st; + const style::icon *_icon; + const style::icon *_iconOver; +// std::unique_ptr _toggle; + int _textWidth = 0; + const int _height; + +}; + +} // namespace Ui From 4cbe5fc464b70413df0e47e8349bdc4565e78e38 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 02:00:07 +0300 Subject: [PATCH 13/60] Moved all files related to menu to separate namespace. --- CMakeLists.txt | 4 ++-- ui/widgets/dropdown_menu.cpp | 2 +- ui/widgets/dropdown_menu.h | 4 ++-- ui/widgets/{ => menu}/menu.cpp | 10 +++++----- ui/widgets/{ => menu}/menu.h | 5 ++--- ui/widgets/menu/menu_action.cpp | 6 +++--- ui/widgets/menu/menu_action.h | 4 ++-- ui/widgets/menu/menu_common.h | 4 ++-- ui/widgets/menu/menu_item_base.cpp | 10 ++-------- ui/widgets/menu/menu_item_base.h | 7 +++---- ui/widgets/menu/menu_separator.cpp | 4 ++-- ui/widgets/menu/menu_separator.h | 4 ++-- ui/widgets/popup_menu.h | 4 ++-- 13 files changed, 30 insertions(+), 38 deletions(-) rename ui/widgets/{ => menu}/menu.cpp (97%) rename ui/widgets/{ => menu}/menu.h (97%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 435774b..0068c4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,8 +146,8 @@ PRIVATE ui/widgets/input_fields.h ui/widgets/labels.cpp ui/widgets/labels.h - ui/widgets/menu.cpp - ui/widgets/menu.h + ui/widgets/menu/menu.cpp + ui/widgets/menu/menu.h ui/widgets/menu/menu_action.cpp ui/widgets/menu/menu_action.h ui/widgets/menu/menu_common.h diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index c293ebc..5380f4a 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -12,7 +12,7 @@ namespace Ui { DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : InnerDropdown(parent, st.wrap) , _st(st) { - _menu = setOwnedWidget(object_ptr(this, _st.menu)); + _menu = setOwnedWidget(object_ptr(this, _st.menu)); init(); } diff --git a/ui/widgets/dropdown_menu.h b/ui/widgets/dropdown_menu.h index abfbc03..1c283ff 100644 --- a/ui/widgets/dropdown_menu.h +++ b/ui/widgets/dropdown_menu.h @@ -8,7 +8,7 @@ #include "styles/style_widgets.h" #include "ui/widgets/inner_dropdown.h" -#include "ui/widgets/menu.h" +#include "ui/widgets/menu/menu.h" namespace Ui { @@ -80,7 +80,7 @@ private: const style::DropdownMenu &_st; Fn _hiddenCallback; - QPointer _menu; + QPointer _menu; // Not ready with submenus yet. //using Submenus = QMap; diff --git a/ui/widgets/menu.cpp b/ui/widgets/menu/menu.cpp similarity index 97% rename from ui/widgets/menu.cpp rename to ui/widgets/menu/menu.cpp index 591e74e..bf76367 100644 --- a/ui/widgets/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -4,7 +4,7 @@ // For license and copyright information please follow this link: // https://github.com/desktop-app/legal/blob/master/LEGAL // -#include "ui/widgets/menu.h" +#include "ui/widgets/menu/menu.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" @@ -14,7 +14,7 @@ #include -namespace Ui { +namespace Ui::Menu { Menu::Menu(QWidget *parent, const style::Menu &st) : RpWidget(parent) @@ -73,12 +73,12 @@ not_null Menu::addAction(not_null action, const style::icon : _actionWidgets.back()->y() + _actionWidgets.back()->height(); const auto index = _actionWidgets.size(); if (action->isSeparator()) { - auto widget = base::make_unique_q(this, _st, index); + auto widget = base::make_unique_q(this, _st, index); widget->moveToLeft(0, top); widget->show(); _actionWidgets.push_back(std::move(widget)); } else { - auto widget = base::make_unique_q( + auto widget = base::make_unique_q( this, _st, index, @@ -336,4 +336,4 @@ void Menu::handleMouseRelease(QPoint globalPosition) { } } -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/menu.h b/ui/widgets/menu/menu.h similarity index 97% rename from ui/widgets/menu.h rename to ui/widgets/menu/menu.h index 2bbc5f1..c7aea23 100644 --- a/ui/widgets/menu.h +++ b/ui/widgets/menu/menu.h @@ -13,7 +13,7 @@ #include -namespace Ui { +namespace Ui::Menu { class ItemBase; class ToggleView; @@ -33,7 +33,6 @@ public: void clearSelection(); - using TriggeredSource = ContextMenu::TriggeredSource; void setChildShown(bool shown) { _childShown = shown; } @@ -110,4 +109,4 @@ private: }; -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 1323e19..3422c55 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -9,7 +9,7 @@ #include "ui/effects/ripple_animation.h" #include "ui/painter.h" -namespace Ui { +namespace Ui::Menu { namespace { [[nodiscard]] TextWithEntities ParseMenuItem(const QString &text) { @@ -195,11 +195,11 @@ QPoint Action::prepareRippleStartPosition() const { } QImage Action::prepareRippleMask() const { - return RippleAnimation::rectMask(size()); + return Ui::RippleAnimation::rectMask(size()); } int Action::contentHeight() const { return _height; } -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h index d2aa1cd..8714ac7 100644 --- a/ui/widgets/menu/menu_action.h +++ b/ui/widgets/menu/menu_action.h @@ -12,7 +12,7 @@ class Painter; -namespace Ui { +namespace Ui::Menu { class Action : public ItemBase { public: @@ -50,4 +50,4 @@ private: }; -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_common.h b/ui/widgets/menu/menu_common.h index 09cb823..ebebfe7 100644 --- a/ui/widgets/menu/menu_common.h +++ b/ui/widgets/menu/menu_common.h @@ -6,11 +6,11 @@ // #pragma once -namespace Ui::ContextMenu { +namespace Ui::Menu { enum class TriggeredSource { Mouse, Keyboard, }; -} // namespace Ui::ContextMenu +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index d17226b..7f09b95 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -6,13 +6,7 @@ // #include "ui/widgets/menu/menu_item_base.h" -namespace Ui { - -namespace { - -using TriggeredSource = Menu::TriggeredSource; - -} // namespace +namespace Ui::Menu { ItemBase::ItemBase( not_null parent, @@ -105,4 +99,4 @@ void ItemBase::setContentWidth(int w) { _contentWidth = w; } -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index 3d76d6f..e470bc0 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -7,17 +7,16 @@ #pragma once #include "ui/widgets/buttons.h" -#include "ui/widgets/menu.h" +#include "ui/widgets/menu/menu.h" #include "ui/widgets/menu/menu_common.h" #include "styles/style_widgets.h" -namespace Ui { +namespace Ui::Menu { class ItemBase : public RippleButton { public: ItemBase(not_null parent, const style::Menu &st, int index); - using TriggeredSource = ContextMenu::TriggeredSource; TriggeredSource lastTriggeredSource() const; rpl::producer selects() const; @@ -62,4 +61,4 @@ private: }; -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_separator.cpp b/ui/widgets/menu/menu_separator.cpp index c00b96d..bdd7f16 100644 --- a/ui/widgets/menu/menu_separator.cpp +++ b/ui/widgets/menu/menu_separator.cpp @@ -8,7 +8,7 @@ #include "ui/painter.h" -namespace Ui { +namespace Ui::Menu { Separator::Separator( not_null parent, @@ -48,4 +48,4 @@ int Separator::contentHeight() const { return _height; } -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_separator.h b/ui/widgets/menu/menu_separator.h index e2bae7f..024131a 100644 --- a/ui/widgets/menu/menu_separator.h +++ b/ui/widgets/menu/menu_separator.h @@ -11,7 +11,7 @@ class Painter; -namespace Ui { +namespace Ui::Menu { class Separator : public ItemBase { public: @@ -32,4 +32,4 @@ private: }; -} // namespace Ui +} // namespace Ui::Menu diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index 1a8e9f3..304a9ec 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -7,7 +7,7 @@ #pragma once #include "styles/style_widgets.h" -#include "ui/widgets/menu.h" +#include "ui/widgets/menu/menu.h" #include "ui/effects/animations.h" #include "ui/effects/panel_animation.h" #include "ui/round_rect.h" @@ -99,7 +99,7 @@ private: const style::PopupMenu &_st; RoundRect _roundRect; - object_ptr _menu; + object_ptr _menu; using Submenus = QMap; Submenus _submenus; From ec988db6c7398583123ae53dea9f621114f95efe Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 02:21:49 +0300 Subject: [PATCH 14/60] Replaced passing of numerous arguments with struct in menu. --- ui/widgets/dropdown_menu.cpp | 22 +++++++++++----------- ui/widgets/dropdown_menu.h | 6 +++--- ui/widgets/menu/menu.cpp | 15 ++++++--------- ui/widgets/menu/menu.h | 8 ++++---- ui/widgets/menu/menu_common.h | 8 ++++++++ ui/widgets/menu/menu_item_base.cpp | 15 +++++++++++---- ui/widgets/menu/menu_item_base.h | 4 ++-- ui/widgets/popup_menu.cpp | 26 +++++++++++++------------- ui/widgets/popup_menu.h | 6 +++--- 9 files changed, 61 insertions(+), 49 deletions(-) diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index 5380f4a..c3a3d64 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -37,11 +37,11 @@ void DropdownMenu::init() { ) | rpl::start_with_next([=] { resizeToContent(); }, _menu->lifetime()); - _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { - handleActivated(action, actionTop, source); + _menu->setActivatedCallback([this](const Menu::CallbackData &data) { + handleActivated(data); }); - _menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) { - handleTriggered(action, actionTop, source); + _menu->setTriggeredCallback([this](const Menu::CallbackData &data) { + handleTriggered(data); }); _menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); }); _menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); }); @@ -72,9 +72,9 @@ const std::vector> &DropdownMenu::actions() const { return _menu->actions(); } -void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) { - if (source == TriggeredSource::Mouse) { - if (!popupSubmenuFromAction(action, actionTop, source)) { +void DropdownMenu::handleActivated(const Menu::CallbackData &data) { + if (data.source == TriggeredSource::Mouse) { + if (!popupSubmenuFromAction(data)) { if (auto currentSubmenu = base::take(_activeSubmenu)) { currentSubmenu->hideMenu(true); } @@ -82,11 +82,11 @@ void DropdownMenu::handleActivated(QAction *action, int actionTop, TriggeredSour } } -void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) { - if (!popupSubmenuFromAction(action, actionTop, source)) { +void DropdownMenu::handleTriggered(const Menu::CallbackData &data) { + if (!popupSubmenuFromAction(data)) { hideMenu(); _triggering = true; - emit action->trigger(); + emit data.action->trigger(); _triggering = false; if (_deleteLater) { _deleteLater = false; @@ -96,7 +96,7 @@ void DropdownMenu::handleTriggered(QAction *action, int actionTop, TriggeredSour } // Not ready with submenus yet. -bool DropdownMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) { +bool DropdownMenu::popupSubmenuFromAction(const Menu::CallbackData &data) { //if (auto submenu = _submenus.value(action)) { // if (_activeSubmenu == submenu) { // submenu->hideMenu(true); diff --git a/ui/widgets/dropdown_menu.h b/ui/widgets/dropdown_menu.h index 1c283ff..0b03a06 100644 --- a/ui/widgets/dropdown_menu.h +++ b/ui/widgets/dropdown_menu.h @@ -55,8 +55,8 @@ private: void hideFinish(); using TriggeredSource = Menu::TriggeredSource; - void handleActivated(QAction *action, int actionTop, TriggeredSource source); - void handleTriggered(QAction *action, int actionTop, TriggeredSource source); + void handleActivated(const Menu::CallbackData &data); + void handleTriggered(const Menu::CallbackData &data); void forwardKeyPress(int key); bool handleKeyPress(int key); void forwardMouseMove(QPoint globalPosition) { @@ -73,7 +73,7 @@ private: void handleMouseRelease(QPoint globalPosition); using SubmenuPointer = QPointer; - bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source); + bool popupSubmenuFromAction(const Menu::CallbackData &data); void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source); void showMenu(const QPoint &p, DropdownMenu *parent, TriggeredSource source); diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index bf76367..964ff2a 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -90,27 +90,24 @@ not_null Menu::addAction(not_null action, const style::icon widget->show(); widget->selects( - ) | rpl::start_with_next([=, w = widget.get()](bool selected) { - if (!selected) { + ) | rpl::start_with_next([=](const CallbackData &data) { + if (!data.selected) { return; } for (auto i = 0; i < _actionWidgets.size(); i++) { - if (_actionWidgets[i].get() != w) { + if (i != data.index) { _actionWidgets[i]->setSelected(false); } } if (_activatedCallback) { - _activatedCallback( - w->action(), - w->y(), - w->lastTriggeredSource()); + _activatedCallback(data); } }, widget->lifetime()); widget->clicks( - ) | rpl::start_with_next([=, w = widget.get()]() { + ) | rpl::start_with_next([=](const CallbackData &data) { if (_triggeredCallback) { - _triggeredCallback(w->action(), w->y(), w->lastTriggeredSource()); + _triggeredCallback(data); } }, widget->lifetime()); diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index c7aea23..b90eda3 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -41,10 +41,10 @@ public: const std::vector> &actions() const; - void setActivatedCallback(Fn callback) { + void setActivatedCallback(Fn callback) { _activatedCallback = std::move(callback); } - void setTriggeredCallback(Fn callback) { + void setTriggeredCallback(Fn callback) { _triggeredCallback = std::move(callback); } @@ -89,8 +89,8 @@ private: const style::Menu &_st; - Fn _activatedCallback; - Fn _triggeredCallback; + Fn _activatedCallback; + Fn _triggeredCallback; Fn _keyPressDelegate; Fn _mouseMoveDelegate; Fn _mousePressDelegate; diff --git a/ui/widgets/menu/menu_common.h b/ui/widgets/menu/menu_common.h index ebebfe7..e1d19f4 100644 --- a/ui/widgets/menu/menu_common.h +++ b/ui/widgets/menu/menu_common.h @@ -13,4 +13,12 @@ enum class TriggeredSource { Keyboard, }; +struct CallbackData { + QAction *action; + int actionTop = 0; + TriggeredSource source; + int index = 0; + bool selected = false; +}; + } // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index 7f09b95..9340e6b 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -32,8 +32,12 @@ void ItemBase::setSelected( bool ItemBase::isSelected() const { return _selected.current(); } -rpl::producer ItemBase::selects() const { - return _selected.changes(); + +rpl::producer ItemBase::selects() const { + return _selected.changes( + ) | rpl::map([=](bool selected) -> CallbackData { + return { action(), y(), _lastTriggeredSource, _index, selected }; + }); } TriggeredSource ItemBase::lastTriggeredSource() const { @@ -51,10 +55,13 @@ void ItemBase::setClicked(TriggeredSource source) { } } -rpl::producer<> ItemBase::clicks() const { +rpl::producer ItemBase::clicks() const { return rpl::merge( AbstractButton::clicks() | rpl::to_empty, - _clicks.events()); + _clicks.events() + ) | rpl::map([=]() -> CallbackData { + return { action(), y(), _lastTriggeredSource, _index, true }; + });; } rpl::producer ItemBase::contentWidthValue() const { diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index e470bc0..efcc7da 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -19,7 +19,7 @@ public: TriggeredSource lastTriggeredSource() const; - rpl::producer selects() const; + rpl::producer selects() const; void setSelected( bool selected, TriggeredSource source = TriggeredSource::Mouse); @@ -29,7 +29,7 @@ public: void setClicked(TriggeredSource source = TriggeredSource::Mouse); - rpl::producer<> clicks() const; + rpl::producer clicks() const; rpl::producer contentWidthValue() const; int contentWidth() const; diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index 41f9a22..814cc06 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -56,11 +56,11 @@ void PopupMenu::init() { ) | rpl::start_with_next([=] { handleMenuResize(); }, _menu->lifetime()); - _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { - handleActivated(action, actionTop, source); + _menu->setActivatedCallback([this](const Menu::CallbackData &data) { + handleActivated(data); }); - _menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) { - handleTriggered(action, actionTop, source); + _menu->setTriggeredCallback([this](const Menu::CallbackData &data) { + handleTriggered(data); }); _menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); }); _menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); }); @@ -152,9 +152,9 @@ void PopupMenu::paintBg(QPainter &p) { } } -void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) { - if (source == TriggeredSource::Mouse) { - if (!popupSubmenuFromAction(action, actionTop, source)) { +void PopupMenu::handleActivated(const Menu::CallbackData &data) { + if (data.source == TriggeredSource::Mouse) { + if (!popupSubmenuFromAction(data)) { if (auto currentSubmenu = base::take(_activeSubmenu)) { currentSubmenu->hideMenu(true); } @@ -162,11 +162,11 @@ void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource } } -void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) { - if (!popupSubmenuFromAction(action, actionTop, source)) { +void PopupMenu::handleTriggered(const Menu::CallbackData &data) { + if (!popupSubmenuFromAction(data)) { _triggering = true; hideMenu(); - emit action->trigger(); + emit data.action->trigger(); _triggering = false; if (_deleteLater) { _deleteLater = false; @@ -175,12 +175,12 @@ void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource } } -bool PopupMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) { - if (auto submenu = _submenus.value(action)) { +bool PopupMenu::popupSubmenuFromAction(const Menu::CallbackData &data) { + if (auto submenu = _submenus.value(data.action)) { if (_activeSubmenu == submenu) { submenu->hideMenu(true); } else { - popupSubmenu(submenu, actionTop, source); + popupSubmenu(submenu, data.actionTop, data.source); } return true; } diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index 304a9ec..f7c7bd5 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -74,8 +74,8 @@ private: using TriggeredSource = Menu::TriggeredSource; void handleCompositingUpdate(); void handleMenuResize(); - void handleActivated(QAction *action, int actionTop, TriggeredSource source); - void handleTriggered(QAction *action, int actionTop, TriggeredSource source); + void handleActivated(const Menu::CallbackData &data); + void handleTriggered(const Menu::CallbackData &data); void forwardKeyPress(int key); bool handleKeyPress(int key); void forwardMouseMove(QPoint globalPosition) { @@ -92,7 +92,7 @@ private: void handleMouseRelease(QPoint globalPosition); using SubmenuPointer = QPointer; - bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source); + bool popupSubmenuFromAction(const Menu::CallbackData &data); void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source); void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source); From 11a2b0f88d74a1ba53e17235cebcae8bef691bea Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 04:10:03 +0300 Subject: [PATCH 15/60] Added ability to check if menu has no items. --- ui/widgets/dropdown_menu.cpp | 4 ++++ ui/widgets/dropdown_menu.h | 1 + ui/widgets/menu/menu.cpp | 4 ++++ ui/widgets/menu/menu.h | 2 ++ ui/widgets/popup_menu.cpp | 4 ++++ ui/widgets/popup_menu.h | 1 + 6 files changed, 16 insertions(+) diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index c3a3d64..d1dd929 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -72,6 +72,10 @@ const std::vector> &DropdownMenu::actions() const { return _menu->actions(); } +bool DropdownMenu::empty() const { + return _menu->empty(); +} + void DropdownMenu::handleActivated(const Menu::CallbackData &data) { if (data.source == TriggeredSource::Mouse) { if (!popupSubmenuFromAction(data)) { diff --git a/ui/widgets/dropdown_menu.h b/ui/widgets/dropdown_menu.h index 0b03a06..8b34f9c 100644 --- a/ui/widgets/dropdown_menu.h +++ b/ui/widgets/dropdown_menu.h @@ -27,6 +27,7 @@ public: } const std::vector> &actions() const; + bool empty() const; ~DropdownMenu(); diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 964ff2a..58d2913 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -159,6 +159,10 @@ void Menu::clearActions() { void Menu::finishAnimating() { } +bool Menu::empty() const { + return _actionWidgets.empty(); +} + void Menu::setShowSource(TriggeredSource source) { _mouseSelection = (source == TriggeredSource::Mouse); setSelected((source == TriggeredSource::Mouse || _actions.empty()) ? -1 : 0); diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index b90eda3..a172e00 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -31,6 +31,8 @@ public: void clearActions(); void finishAnimating(); + bool empty() const; + void clearSelection(); void setChildShown(bool shown) { diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index 814cc06..cb8a6ba 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -118,6 +118,10 @@ const std::vector> &PopupMenu::actions() const { return _menu->actions(); } +bool PopupMenu::empty() const { + return _menu->empty(); +} + void PopupMenu::paintEvent(QPaintEvent *e) { QPainter p(this); diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index f7c7bd5..8f3c193 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -27,6 +27,7 @@ public: void clearActions(); const std::vector> &actions() const; + bool empty() const; void deleteOnHide(bool del); void popup(const QPoint &p); From 98fa99ae42aefd520a89558667b99f92e96d52a9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 16:08:42 +0300 Subject: [PATCH 16/60] Fixed mouse clicking on disabled context menu items. --- ui/widgets/menu/menu_item_base.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index 9340e6b..3f7ca00 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -59,9 +59,11 @@ rpl::producer ItemBase::clicks() const { return rpl::merge( AbstractButton::clicks() | rpl::to_empty, _clicks.events() - ) | rpl::map([=]() -> CallbackData { + ) | rpl::filter([=] { + return isEnabled(); + }) | rpl::map([=]() -> CallbackData { return { action(), y(), _lastTriggeredSource, _index, true }; - });; + }); } rpl::producer ItemBase::contentWidthValue() const { From df0a8ff5890c6a5bf90e871ba2c983d6a3fcf642 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 03:49:42 +0300 Subject: [PATCH 17/60] Slightly refactored creating new items in menu. --- ui/widgets/menu/menu.cpp | 127 ++++++++++++++++------------- ui/widgets/menu/menu.h | 16 +++- ui/widgets/menu/menu_separator.cpp | 8 +- ui/widgets/menu/menu_separator.h | 7 +- 4 files changed, 95 insertions(+), 63 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 58d2913..85095a4 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -53,80 +53,94 @@ void Menu::init() { }, lifetime()); } -not_null Menu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { +not_null Menu::addAction( + const QString &text, + Fn callback, + const style::icon *icon, + const style::icon *iconOver) { const auto action = addAction(new QAction(text, this), icon, iconOver); - connect(action, &QAction::triggered, action, std::move(callback), Qt::QueuedConnection); + connect( + action, + &QAction::triggered, + action, + std::move(callback), + Qt::QueuedConnection); return action; } -not_null Menu::addAction(const QString &text, std::unique_ptr submenu) { +not_null Menu::addAction( + const QString &text, + std::unique_ptr submenu) { const auto action = new QAction(text, this); action->setMenu(submenu.release()); return addAction(action, nullptr, nullptr); } -not_null Menu::addAction(not_null action, const style::icon *icon, const style::icon *iconOver) { +not_null Menu::addAction( + not_null action, + const style::icon *icon, + const style::icon *iconOver) { + if (action->isSeparator()) { + return addSeparator(); + } + auto item = base::make_unique_q( + this, + _st, + 0, + std::move(action), + icon, + iconOver ? iconOver : icon, + (action->menu() != nullptr)); + return addAction(std::move(item)); +} + +not_null Menu::addAction(base::unique_qptr widget) { + const auto action = widget->action(); _actions.emplace_back(action); const auto top = _actionWidgets.empty() ? 0 : _actionWidgets.back()->y() + _actionWidgets.back()->height(); - const auto index = _actionWidgets.size(); - if (action->isSeparator()) { - auto widget = base::make_unique_q(this, _st, index); - widget->moveToLeft(0, top); - widget->show(); - _actionWidgets.push_back(std::move(widget)); - } else { - auto widget = base::make_unique_q( - this, - _st, - index, - action, - icon, - iconOver ? iconOver : icon, - (action->menu() != nullptr)); - widget->moveToLeft(0, top); - widget->show(); - widget->selects( - ) | rpl::start_with_next([=](const CallbackData &data) { - if (!data.selected) { - return; + widget->moveToLeft(0, top); + widget->show(); + + widget->selects( + ) | rpl::start_with_next([=](const CallbackData &data) { + if (!data.selected) { + return; + } + for (auto i = 0; i < _actionWidgets.size(); i++) { + if (i != data.index) { + _actionWidgets[i]->setSelected(false); } - for (auto i = 0; i < _actionWidgets.size(); i++) { - if (i != data.index) { - _actionWidgets[i]->setSelected(false); - } - } - if (_activatedCallback) { - _activatedCallback(data); - } - }, widget->lifetime()); + } + if (_activatedCallback) { + _activatedCallback(data); + } + }, widget->lifetime()); - widget->clicks( - ) | rpl::start_with_next([=](const CallbackData &data) { - if (_triggeredCallback) { - _triggeredCallback(data); - } - }, widget->lifetime()); + widget->clicks( + ) | rpl::start_with_next([=](const CallbackData &data) { + if (_triggeredCallback) { + _triggeredCallback(data); + } + }, widget->lifetime()); - widget->contentWidthValue( - ) | rpl::start_with_next([=] { - const auto newWidth = _forceWidth - ? _forceWidth - : _actionWidgets.empty() - ? _st.widthMin - : (*ranges::max_element( - _actionWidgets, - std::greater<>(), - &ItemBase::width))->contentWidth(); - resize(newWidth, height()); - }, widget->lifetime()); - - _actionWidgets.push_back(std::move(widget)); - } + widget->contentWidthValue( + ) | rpl::start_with_next([=] { + const auto newWidth = _forceWidth + ? _forceWidth + : _actionWidgets.empty() + ? _st.widthMin + : (*ranges::max_element( + _actionWidgets, + std::greater<>(), + &ItemBase::width))->contentWidth(); + resize(newWidth, height()); + }, widget->lifetime()); + _actionWidgets.push_back(std::move(widget)); const auto newHeight = ranges::accumulate( _actionWidgets, @@ -142,7 +156,8 @@ not_null Menu::addAction(not_null action, const style::icon not_null Menu::addSeparator() { const auto separator = new QAction(this); separator->setSeparator(true); - return addAction(separator); + auto item = base::make_unique_q(this, _st, 0, separator); + return addAction(std::move(item)); } void Menu::clearActions() { diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index a172e00..77161da 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -25,8 +25,14 @@ public: Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu); ~Menu(); - not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); - not_null addAction(const QString &text, std::unique_ptr submenu); + not_null addAction( + const QString &text, + Fn callback, + const style::icon *icon = nullptr, + const style::icon *iconOver = nullptr); + not_null addAction( + const QString &text, + std::unique_ptr submenu); not_null addSeparator(); void clearActions(); void finishAnimating(); @@ -80,7 +86,11 @@ private: void updateSelected(QPoint globalPosition); void init(); - not_null addAction(not_null action, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); + not_null addAction( + not_null action, + const style::icon *icon = nullptr, + const style::icon *iconOver = nullptr); + not_null addAction(base::unique_qptr widget); void setSelected(int selected); void clearMouseSelection(); diff --git a/ui/widgets/menu/menu_separator.cpp b/ui/widgets/menu/menu_separator.cpp index bdd7f16..8d2079b 100644 --- a/ui/widgets/menu/menu_separator.cpp +++ b/ui/widgets/menu/menu_separator.cpp @@ -13,13 +13,15 @@ namespace Ui::Menu { Separator::Separator( not_null parent, const style::Menu &st, - int index) + int index, + not_null action) : ItemBase(parent, st, index) , _lineWidth(st.separatorWidth) , _padding(st.separatorPadding) , _fg(st.separatorFg) , _bg(st.itemBg) -, _height(_padding.top() + _lineWidth + _padding.bottom()) { +, _height(_padding.top() + _lineWidth + _padding.bottom()) +, _action(action) { initResizeHook(parent->sizeValue()); paintRequest( @@ -37,7 +39,7 @@ Separator::Separator( } QAction *Separator::action() const { - return nullptr; + return _action; } bool Separator::isEnabled() const { diff --git a/ui/widgets/menu/menu_separator.h b/ui/widgets/menu/menu_separator.h index 024131a..04ac54a 100644 --- a/ui/widgets/menu/menu_separator.h +++ b/ui/widgets/menu/menu_separator.h @@ -15,7 +15,11 @@ namespace Ui::Menu { class Separator : public ItemBase { public: - Separator(not_null parent, const style::Menu &st, int index); + Separator( + not_null parent, + const style::Menu &st, + int index, + not_null action); QAction *action() const override; bool isEnabled() const override; @@ -29,6 +33,7 @@ private: const style::color &_fg; const style::color &_bg; const int _height; + const not_null _action; }; From 497452250a2d9638cb5e04fa7ef4f5dc764b034e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 18:04:05 +0300 Subject: [PATCH 18/60] Added ability to set index to menu item. --- ui/widgets/menu/menu.cpp | 5 +++-- ui/widgets/menu/menu_action.cpp | 3 +-- ui/widgets/menu/menu_action.h | 1 - ui/widgets/menu/menu_item_base.cpp | 9 ++++++--- ui/widgets/menu/menu_item_base.h | 5 +++-- ui/widgets/menu/menu_separator.cpp | 3 +-- ui/widgets/menu/menu_separator.h | 1 - 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 85095a4..5922274 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -86,7 +86,6 @@ not_null Menu::addAction( auto item = base::make_unique_q( this, _st, - 0, std::move(action), icon, iconOver ? iconOver : icon, @@ -105,6 +104,8 @@ not_null Menu::addAction(base::unique_qptr widget) { widget->moveToLeft(0, top); widget->show(); + widget->setIndex(_actionWidgets.size()); + widget->selects( ) | rpl::start_with_next([=](const CallbackData &data) { if (!data.selected) { @@ -156,7 +157,7 @@ not_null Menu::addAction(base::unique_qptr widget) { not_null Menu::addSeparator() { const auto separator = new QAction(this); separator->setSeparator(true); - auto item = base::make_unique_q(this, _st, 0, separator); + auto item = base::make_unique_q(this, _st, separator); return addAction(std::move(item)); } diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 3422c55..8ca196e 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -49,12 +49,11 @@ TextParseOptions MenuTextOptions = { Action::Action( not_null parent, const style::Menu &st, - int index, not_null action, const style::icon *icon, const style::icon *iconOver, bool hasSubmenu) -: ItemBase(parent, st, index) +: ItemBase(parent, st) , _action(action) , _st(st) , _icon(icon) diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h index 8714ac7..37d3d41 100644 --- a/ui/widgets/menu/menu_action.h +++ b/ui/widgets/menu/menu_action.h @@ -19,7 +19,6 @@ public: Action( not_null parent, const style::Menu &st, - int index, not_null action, const style::icon *icon, const style::icon *iconOver, diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index 3f7ca00..a798898 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -10,9 +10,8 @@ namespace Ui::Menu { ItemBase::ItemBase( not_null parent, - const style::Menu &st, int index) -: RippleButton(parent, st.ripple) -, _index(index) { + const style::Menu &st) +: RippleButton(parent, st.ripple) { init(); } @@ -48,6 +47,10 @@ int ItemBase::index() const { return _index; } +void ItemBase::setIndex(int index) { + _index = index; +} + void ItemBase::setClicked(TriggeredSource source) { if (isEnabled()) { _lastTriggeredSource = source; diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index efcc7da..9844bf6 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -15,7 +15,7 @@ namespace Ui::Menu { class ItemBase : public RippleButton { public: - ItemBase(not_null parent, const style::Menu &st, int index); + ItemBase(not_null parent, const style::Menu &st); TriggeredSource lastTriggeredSource() const; @@ -26,6 +26,7 @@ public: bool isSelected() const; int index() const; + void setIndex(int index); void setClicked(TriggeredSource source = TriggeredSource::Mouse); @@ -48,7 +49,7 @@ protected: virtual int contentHeight() const = 0; private: - const int _index; + int _index = -1; bool _hasSubmenu = false; diff --git a/ui/widgets/menu/menu_separator.cpp b/ui/widgets/menu/menu_separator.cpp index 8d2079b..cce5e45 100644 --- a/ui/widgets/menu/menu_separator.cpp +++ b/ui/widgets/menu/menu_separator.cpp @@ -13,9 +13,8 @@ namespace Ui::Menu { Separator::Separator( not_null parent, const style::Menu &st, - int index, not_null action) -: ItemBase(parent, st, index) +: ItemBase(parent, st) , _lineWidth(st.separatorWidth) , _padding(st.separatorPadding) , _fg(st.separatorFg) diff --git a/ui/widgets/menu/menu_separator.h b/ui/widgets/menu/menu_separator.h index 04ac54a..c6a3a76 100644 --- a/ui/widgets/menu/menu_separator.h +++ b/ui/widgets/menu/menu_separator.h @@ -18,7 +18,6 @@ public: Separator( not_null parent, const style::Menu &st, - int index, not_null action); QAction *action() const override; From 0c5135f6717f7566daa6c62fc8421f83dacf6e20 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 18:17:27 +0300 Subject: [PATCH 19/60] Added ability to fill menu with custom widgets. --- ui/widgets/dropdown_menu.cpp | 5 +++++ ui/widgets/dropdown_menu.h | 1 + ui/widgets/menu/menu.cpp | 2 ++ ui/widgets/menu/menu.h | 4 ++-- ui/widgets/popup_menu.cpp | 5 +++++ ui/widgets/popup_menu.h | 1 + 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index d1dd929..105a510 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -53,6 +53,11 @@ void DropdownMenu::init() { hide(); } +not_null DropdownMenu::addAction( + base::unique_qptr widget) { + return _menu->addAction(std::move(widget)); +} + not_null DropdownMenu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, std::move(callback), icon, iconOver); } diff --git a/ui/widgets/dropdown_menu.h b/ui/widgets/dropdown_menu.h index 8b34f9c..0f5a1a9 100644 --- a/ui/widgets/dropdown_menu.h +++ b/ui/widgets/dropdown_menu.h @@ -18,6 +18,7 @@ class DropdownMenu : public InnerDropdown { public: DropdownMenu(QWidget *parent, const style::DropdownMenu &st = st::defaultDropdownMenu); + not_null addAction(base::unique_qptr widget); not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addSeparator(); void clearActions(); diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 5922274..bebd046 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -97,6 +97,8 @@ not_null Menu::addAction(base::unique_qptr widget) { const auto action = widget->action(); _actions.emplace_back(action); + widget->setParent(this); + const auto top = _actionWidgets.empty() ? 0 : _actionWidgets.back()->y() + _actionWidgets.back()->height(); diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index 77161da..27adde3 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -16,7 +16,7 @@ namespace Ui::Menu { class ItemBase; -class ToggleView; +//class ToggleView; class RippleAnimation; class Menu : public RpWidget { @@ -25,6 +25,7 @@ public: Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu); ~Menu(); + not_null addAction(base::unique_qptr widget); not_null addAction( const QString &text, Fn callback, @@ -90,7 +91,6 @@ private: not_null action, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); - not_null addAction(base::unique_qptr widget); void setSelected(int selected); void clearMouseSelection(); diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index cb8a6ba..c16c079 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -91,6 +91,11 @@ void PopupMenu::handleMenuResize() { _inner = rect().marginsRemoved(_padding); } +not_null PopupMenu::addAction( + base::unique_qptr widget) { + return _menu->addAction(std::move(widget)); +} + not_null PopupMenu::addAction(const QString &text, Fn callback, const style::icon *icon, const style::icon *iconOver) { return _menu->addAction(text, std::move(callback), icon, iconOver); } diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index 8f3c193..9e05866 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -21,6 +21,7 @@ public: PopupMenu(QWidget *parent, const style::PopupMenu &st = st::defaultPopupMenu); PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu); + not_null addAction(base::unique_qptr widget); not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addAction(const QString &text, std::unique_ptr submenu); not_null addSeparator(); From 3640f693694b216346722fcb2e8d134ec3b72eb5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 18:20:08 +0300 Subject: [PATCH 20/60] Removed redundant argument from action menu item constructor. --- ui/widgets/menu/menu.cpp | 3 +-- ui/widgets/menu/menu_action.cpp | 5 ++--- ui/widgets/menu/menu_action.h | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index bebd046..246f565 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -88,8 +88,7 @@ not_null Menu::addAction( _st, std::move(action), icon, - iconOver ? iconOver : icon, - (action->menu() != nullptr)); + iconOver ? iconOver : icon); return addAction(std::move(item)); } diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 8ca196e..ba37e58 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -51,8 +51,7 @@ Action::Action( const style::Menu &st, not_null action, const style::icon *icon, - const style::icon *iconOver, - bool hasSubmenu) + const style::icon *iconOver) : ItemBase(parent, st) , _action(action) , _st(st) @@ -64,7 +63,7 @@ Action::Action( initResizeHook(parent->sizeValue()); processAction(); - setHasSubmenu(hasSubmenu); + setHasSubmenu(action->menu() != nullptr); paintRequest( ) | rpl::start_with_next([=] { diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h index 37d3d41..2b05922 100644 --- a/ui/widgets/menu/menu_action.h +++ b/ui/widgets/menu/menu_action.h @@ -21,8 +21,7 @@ public: const style::Menu &st, not_null action, const style::icon *icon, - const style::icon *iconOver, - bool hasSubmenu); + const style::icon *iconOver); bool isEnabled() const override; QAction *action() const override; From 5e37c06a6ad8f2cfe66687dd5e3577daa830cdf2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 18:58:09 +0300 Subject: [PATCH 21/60] Added action menu item with toggle view. --- CMakeLists.txt | 3 ++ ui/widgets/menu/menu.cpp | 25 +----------- ui/widgets/menu/menu.h | 1 - ui/widgets/menu/menu_action.cpp | 13 +----- ui/widgets/menu/menu_common.cpp | 27 +++++++++++++ ui/widgets/menu/menu_common.h | 5 +++ ui/widgets/menu/menu_toggle.cpp | 70 +++++++++++++++++++++++++++++++++ ui/widgets/menu/menu_toggle.h | 40 +++++++++++++++++++ 8 files changed, 148 insertions(+), 36 deletions(-) create mode 100644 ui/widgets/menu/menu_common.cpp create mode 100644 ui/widgets/menu/menu_toggle.cpp create mode 100644 ui/widgets/menu/menu_toggle.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0068c4c..56b575a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,11 +150,14 @@ PRIVATE ui/widgets/menu/menu.h ui/widgets/menu/menu_action.cpp ui/widgets/menu/menu_action.h + ui/widgets/menu/menu_common.cpp ui/widgets/menu/menu_common.h ui/widgets/menu/menu_item_base.cpp ui/widgets/menu/menu_item_base.h ui/widgets/menu/menu_separator.cpp ui/widgets/menu/menu_separator.h + ui/widgets/menu/menu_toggle.cpp + ui/widgets/menu/menu_toggle.h ui/widgets/popup_menu.cpp ui/widgets/popup_menu.h ui/widgets/scroll_area.cpp diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 246f565..95eb817 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -58,14 +58,8 @@ not_null Menu::addAction( Fn callback, const style::icon *icon, const style::icon *iconOver) { - const auto action = addAction(new QAction(text, this), icon, iconOver); - connect( - action, - &QAction::triggered, - action, - std::move(callback), - Qt::QueuedConnection); - return action; + auto action = CreateAction(this, text, std::move(callback)); + return addAction(std::move(action), icon, iconOver); } not_null Menu::addAction( @@ -288,10 +282,6 @@ void Menu::setSelected(int selected) { const auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard; - // updateSelectedItem(); - // if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) { - // _actionsData[_selected].toggle->setStyle(_st.itemToggle); - // } if (_selected >= 0) { _actionWidgets[_selected]->setSelected(false, source); } @@ -299,17 +289,6 @@ void Menu::setSelected(int selected) { if (_selected >= 0) { _actionWidgets[_selected]->setSelected(true, source); } - // if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) { - // _actionsData[_selected].toggle->setStyle(_st.itemToggleOver); - // } - // updateSelectedItem(); - // if (_activatedCallback) { - // auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard; - // _activatedCallback( - // (_selected >= 0) ? _actions[_selected].get() : nullptr, - // itemTop(_selected), - // source); - // } } } diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index 27adde3..10051ac 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -16,7 +16,6 @@ namespace Ui::Menu { class ItemBase; -//class ToggleView; class RippleAnimation; class Menu : public RpWidget { diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index ba37e58..2a9241a 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -161,18 +161,7 @@ void Action::processAction() { + textWidth + padding.right() + additionalWidth; - // if (action->isCheckable()) { - // auto updateCallback = [this, index] { updateItem(index); }; - // if (_toggle) { - // _toggle->setUpdateCallback(updateCallback); - // _toggle->setChecked(action->isChecked(), anim::type::normal); - // } else { - // _toggle = std::make_unique(_st.itemToggle, action->isChecked(), updateCallback); - // } - // goodWidth += _st.itemPadding.right() + _toggle->getSize().width() - _st.itemToggleShift; - // } else { - // _toggle.reset(); - // } + const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); _textWidth = w - (goodWidth - textWidth); _shortcut = actionShortcut; diff --git a/ui/widgets/menu/menu_common.cpp b/ui/widgets/menu/menu_common.cpp new file mode 100644 index 0000000..7e29038 --- /dev/null +++ b/ui/widgets/menu/menu_common.cpp @@ -0,0 +1,27 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/widgets/menu/menu_common.h" + +#include + +namespace Ui::Menu { + +not_null CreateAction( + QWidget *parent, + const QString &text, + Fn &&callback) { + const auto action = new QAction(text, parent); + parent->connect( + action, + &QAction::triggered, + action, + std::move(callback), + Qt::QueuedConnection); + return action; +} + +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_common.h b/ui/widgets/menu/menu_common.h index e1d19f4..6d3de70 100644 --- a/ui/widgets/menu/menu_common.h +++ b/ui/widgets/menu/menu_common.h @@ -21,4 +21,9 @@ struct CallbackData { bool selected = false; }; +not_null CreateAction( + QWidget *parent, + const QString &text, + Fn &&callback); + } // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_toggle.cpp b/ui/widgets/menu/menu_toggle.cpp new file mode 100644 index 0000000..c96eb3f --- /dev/null +++ b/ui/widgets/menu/menu_toggle.cpp @@ -0,0 +1,70 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/widgets/menu/menu_toggle.h" + +#include "ui/widgets/checkbox.h" + +namespace Ui::Menu { + +Toggle::Toggle( + not_null parent, + const style::Menu &st, + const QString &text, + Fn &&callback, + const style::icon *icon, + const style::icon *iconOver) +: Action( + parent, + st, + CreateAction(parent, text, std::move(callback)), + icon, + iconOver) +, _padding(st.itemPadding) +, _toggleShift(st.itemToggleShift) +, _itemToggle(st.itemToggle) +, _itemToggleOver(st.itemToggleOver) { + + const auto processAction = [=] { + if (!action()->isCheckable()) { + _toggle.reset(); + return; + } + if (_toggle) { + _toggle->setChecked(action()->isChecked(), anim::type::normal); + } else { + _toggle = std::make_unique( + st.itemToggle, + action()->isChecked(), + [=] { update(); }); + } + }; + processAction(); + connect(action(), &QAction::changed, [=] { processAction(); }); + + selects( + ) | rpl::start_with_next([=](const CallbackData &data) { + if (!_toggle) { + return; + } + _toggle->setStyle(data.selected ? _itemToggleOver : _itemToggle); + }, lifetime()); + +} + +void Toggle::paintEvent(QPaintEvent *e) { + Action::paintEvent(e); + if (_toggle) { + Painter p(this); + const auto toggleSize = _toggle->getSize(); + _toggle->paint( + p, + width() - _padding.right() - toggleSize.width() + _toggleShift, + (contentHeight() - toggleSize.height()) / 2, width()); + } +} + +} // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_toggle.h b/ui/widgets/menu/menu_toggle.h new file mode 100644 index 0000000..8b46d66 --- /dev/null +++ b/ui/widgets/menu/menu_toggle.h @@ -0,0 +1,40 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/widgets/menu/menu_action.h" +#include "styles/style_widgets.h" + +namespace Ui { +class ToggleView; +} // namespace Ui + +namespace Ui::Menu { + +class Toggle : public Action { +public: + Toggle( + not_null parent, + const style::Menu &st, + const QString &text, + Fn &&callback, + const style::icon *icon, + const style::icon *iconOver); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + const style::margins &_padding; + const int _toggleShift; + const style::Toggle &_itemToggle; + const style::Toggle &_itemToggleOver; + std::unique_ptr _toggle; + +}; + +} // namespace Ui::Menu From 79146e54b9e173c86bd5efc6e3e118c548c52884 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 19:41:39 +0300 Subject: [PATCH 22/60] Returned finish of animating to menu. --- ui/widgets/buttons.cpp | 4 ++++ ui/widgets/buttons.h | 2 ++ ui/widgets/menu/menu.cpp | 3 +++ ui/widgets/menu/menu_item_base.cpp | 4 ++++ ui/widgets/menu/menu_item_base.h | 2 ++ ui/widgets/menu/menu_toggle.cpp | 7 +++++++ ui/widgets/menu/menu_toggle.h | 2 ++ 7 files changed, 24 insertions(+) diff --git a/ui/widgets/buttons.cpp b/ui/widgets/buttons.cpp index eec2bae..10df939 100644 --- a/ui/widgets/buttons.cpp +++ b/ui/widgets/buttons.cpp @@ -79,6 +79,10 @@ RippleButton::RippleButton(QWidget *parent, const style::RippleAnimation &st) void RippleButton::clearState() { AbstractButton::clearState(); + finishAnimating(); +} + +void RippleButton::finishAnimating() { if (_ripple) { _ripple.reset(); update(); diff --git a/ui/widgets/buttons.h b/ui/widgets/buttons.h index d654d2f..3d21d55 100644 --- a/ui/widgets/buttons.h +++ b/ui/widgets/buttons.h @@ -60,6 +60,8 @@ public: void paintRipple(QPainter &p, int x, int y, const QColor *colorOverride = nullptr); + void finishAnimating(); + ~RippleButton(); protected: diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 95eb817..e384360 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -168,6 +168,9 @@ void Menu::clearActions() { } void Menu::finishAnimating() { + for (const auto &widget : _actionWidgets) { + widget->finishAnimating(); + } } bool Menu::empty() const { diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index a798898..222806c 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -111,4 +111,8 @@ void ItemBase::setContentWidth(int w) { _contentWidth = w; } +void ItemBase::finishAnimating() { + RippleButton::finishAnimating(); +} + } // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index 9844bf6..53d7fb8 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -42,6 +42,8 @@ public: virtual QAction *action() const = 0; virtual bool isEnabled() const = 0; + virtual void finishAnimating(); + protected: void init(); void initResizeHook(rpl::producer &&size); diff --git a/ui/widgets/menu/menu_toggle.cpp b/ui/widgets/menu/menu_toggle.cpp index c96eb3f..eedc7bc 100644 --- a/ui/widgets/menu/menu_toggle.cpp +++ b/ui/widgets/menu/menu_toggle.cpp @@ -67,4 +67,11 @@ void Toggle::paintEvent(QPaintEvent *e) { } } +void Toggle::finishAnimating() { + ItemBase::finishAnimating(); + if (_toggle) { + _toggle->finishAnimating(); + } +} + } // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_toggle.h b/ui/widgets/menu/menu_toggle.h index 8b46d66..7806858 100644 --- a/ui/widgets/menu/menu_toggle.h +++ b/ui/widgets/menu/menu_toggle.h @@ -25,6 +25,8 @@ public: const style::icon *icon, const style::icon *iconOver); + void finishAnimating() override; + protected: void paintEvent(QPaintEvent *e) override; From a6a2a8dde013ec29e74955428f1f30d4ef571c2c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 22:14:07 +0300 Subject: [PATCH 23/60] Fixed crash in dropdown menu. --- ui/widgets/dropdown_menu.cpp | 2 +- ui/widgets/menu/menu.cpp | 20 ++++++++++++++++---- ui/widgets/menu/menu.h | 6 ++++++ ui/widgets/popup_menu.cpp | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index 105a510..7ee28ed 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -33,7 +33,7 @@ DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : Inn void DropdownMenu::init() { InnerDropdown::setHiddenCallback([this] { hideFinish(); }); - _menu->sizeValue( + _menu->resizesFromInner( ) | rpl::start_with_next([=] { resizeToContent(); }, _menu->lifetime()); diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index e384360..5f2a1ea 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -133,7 +133,7 @@ not_null Menu::addAction(base::unique_qptr widget) { _actionWidgets, std::greater<>(), &ItemBase::width))->contentWidth(); - resize(newWidth, height()); + resizeFromInner(newWidth, height()); }, widget->lifetime()); _actionWidgets.push_back(std::move(widget)); @@ -143,7 +143,7 @@ not_null Menu::addAction(base::unique_qptr widget) { 0, ranges::plus(), &ItemBase::height); - resize(width(), newHeight); + resizeFromInner(width(), newHeight); updateSelected(QCursor::pos()); return action; @@ -164,7 +164,7 @@ void Menu::clearActions() { delete action; } } - resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2); + resizeFromInner(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2); } void Menu::finishAnimating() { @@ -177,6 +177,18 @@ bool Menu::empty() const { return _actionWidgets.empty(); } +void Menu::resizeFromInner(int w, int h) { + if ((w == width()) && (h == height())) { + return; + } + resize(w, h); + _resizesFromInner.fire({}); +} + +rpl::producer<> Menu::resizesFromInner() const { + return _resizesFromInner.events(); +} + void Menu::setShowSource(TriggeredSource source) { _mouseSelection = (source == TriggeredSource::Mouse); setSelected((source == TriggeredSource::Mouse || _actions.empty()) ? -1 : 0); @@ -188,7 +200,7 @@ const std::vector> &Menu::actions() const { void Menu::setForceWidth(int forceWidth) { _forceWidth = forceWidth; - resize(_forceWidth, height()); + resizeFromInner(_forceWidth, height()); } void Menu::updateSelected(QPoint globalPosition) { diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index 10051ac..f838a1b 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -76,6 +76,8 @@ public: } void handleMouseRelease(QPoint globalPosition); + rpl::producer<> resizesFromInner() const; + protected: void keyPressEvent(QKeyEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; @@ -98,6 +100,8 @@ private: ItemBase *findSelectedAction() const; + void resizeFromInner(int w, int h); + const style::Menu &_st; Fn _activatedCallback; @@ -118,6 +122,8 @@ private: int _selected = -1; bool _childShown = false; + rpl::event_stream<> _resizesFromInner; + }; } // namespace Ui::Menu diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index c16c079..394a8c4 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -52,7 +52,7 @@ void PopupMenu::init() { hideMenu(true); }, lifetime()); - _menu->sizeValue( + _menu->resizesFromInner( ) | rpl::start_with_next([=] { handleMenuResize(); }, _menu->lifetime()); From 4325eb2f1bcae2475036e4f23af599f966e143d4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 14 Jan 2021 00:07:39 +0300 Subject: [PATCH 24/60] Wrapped raw QAction pointers with not_null in menu items. --- ui/widgets/menu/menu_action.cpp | 2 +- ui/widgets/menu/menu_action.h | 2 +- ui/widgets/menu/menu_item_base.h | 2 +- ui/widgets/menu/menu_separator.cpp | 2 +- ui/widgets/menu/menu_separator.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 2a9241a..8035deb 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -173,7 +173,7 @@ bool Action::isEnabled() const { return _action->isEnabled(); } -QAction *Action::action() const { +not_null Action::action() const { return _action; } diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h index 2b05922..c274c03 100644 --- a/ui/widgets/menu/menu_action.h +++ b/ui/widgets/menu/menu_action.h @@ -24,7 +24,7 @@ public: const style::icon *iconOver); bool isEnabled() const override; - QAction *action() const override; + not_null action() const override; protected: QPoint prepareRippleStartPosition() const override; diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index 53d7fb8..d74219c 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -39,7 +39,7 @@ public: bool hasSubmenu() const; void setHasSubmenu(bool value); - virtual QAction *action() const = 0; + virtual not_null action() const = 0; virtual bool isEnabled() const = 0; virtual void finishAnimating(); diff --git a/ui/widgets/menu/menu_separator.cpp b/ui/widgets/menu/menu_separator.cpp index cce5e45..a2460b7 100644 --- a/ui/widgets/menu/menu_separator.cpp +++ b/ui/widgets/menu/menu_separator.cpp @@ -37,7 +37,7 @@ Separator::Separator( }, lifetime()); } -QAction *Separator::action() const { +not_null Separator::action() const { return _action; } diff --git a/ui/widgets/menu/menu_separator.h b/ui/widgets/menu/menu_separator.h index c6a3a76..1ff5f6f 100644 --- a/ui/widgets/menu/menu_separator.h +++ b/ui/widgets/menu/menu_separator.h @@ -20,7 +20,7 @@ public: const style::Menu &st, not_null action); - QAction *action() const override; + not_null action() const override; bool isEnabled() const override; protected: From 0d6a0d9cef2fba65ae9f71be309de6107f7ad33f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 14 Jan 2021 01:47:47 +0300 Subject: [PATCH 25/60] Moved mouse selecting of menu items to menu base item. --- ui/widgets/menu/menu_action.cpp | 18 +----------------- ui/widgets/menu/menu_item_base.cpp | 13 +++++++++++++ ui/widgets/menu/menu_item_base.h | 2 ++ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 8035deb..2442bb9 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -71,23 +71,7 @@ Action::Action( paint(p); }, lifetime()); - events( - ) | rpl::filter([=](not_null e) { - return _action->isEnabled() - && ((e->type() == QEvent::Leave) - || (e->type() == QEvent::Enter)); - }) | rpl::map([=](not_null e) { - return (e->type() == QEvent::Enter); - }) | rpl::start_with_next([=](bool selected) { - setSelected(selected); - }, lifetime()); - - events( - ) | rpl::filter([=](not_null e) { - return _action->isEnabled() && (e->type() == QEvent::MouseMove); - }) | rpl::start_with_next([=](not_null e) { - setSelected(true); - }, lifetime()); + enableMouseSelecting(); connect(_action, &QAction::changed, [=] { processAction(); }); } diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index 222806c..d9e28b5 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -115,4 +115,17 @@ void ItemBase::finishAnimating() { RippleButton::finishAnimating(); } +void ItemBase::enableMouseSelecting() { + events( + ) | rpl::filter([=](not_null e) { + return action()->isEnabled() + && ((e->type() == QEvent::Leave) + || (e->type() == QEvent::Enter)); + }) | rpl::map([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=](bool selected) { + setSelected(selected); + }, lifetime()); +} + } // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index d74219c..c52ed30 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -48,6 +48,8 @@ protected: void init(); void initResizeHook(rpl::producer &&size); + void enableMouseSelecting(); + virtual int contentHeight() const = 0; private: From 5d74463bc94776b7ebbbfbf7b845650681ae9b2f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 14 Jan 2021 01:48:17 +0300 Subject: [PATCH 26/60] Renamed contentWidth property to minWidth in menu items. --- ui/widgets/menu/menu.cpp | 4 ++-- ui/widgets/menu/menu_action.cpp | 2 +- ui/widgets/menu/menu_item_base.cpp | 12 ++++++------ ui/widgets/menu/menu_item_base.h | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 5f2a1ea..3701532 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -123,7 +123,7 @@ not_null Menu::addAction(base::unique_qptr widget) { } }, widget->lifetime()); - widget->contentWidthValue( + widget->minWidthValue( ) | rpl::start_with_next([=] { const auto newWidth = _forceWidth ? _forceWidth @@ -132,7 +132,7 @@ not_null Menu::addAction(base::unique_qptr widget) { : (*ranges::max_element( _actionWidgets, std::greater<>(), - &ItemBase::width))->contentWidth(); + &ItemBase::width))->minWidth(); resizeFromInner(newWidth, height()); }, widget->lifetime()); diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 2442bb9..955a722 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -149,7 +149,7 @@ void Action::processAction() { const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); _textWidth = w - (goodWidth - textWidth); _shortcut = actionShortcut; - setContentWidth(w); + setMinWidth(w); update(); } diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index d9e28b5..68fddb9 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -69,12 +69,12 @@ rpl::producer ItemBase::clicks() const { }); } -rpl::producer ItemBase::contentWidthValue() const { - return _contentWidth.value(); +rpl::producer ItemBase::minWidthValue() const { + return _minWidth.value(); } -int ItemBase::contentWidth() const { - return _contentWidth.current(); +int ItemBase::minWidth() const { + return _minWidth.current(); } bool ItemBase::hasSubmenu() const { @@ -107,8 +107,8 @@ void ItemBase::initResizeHook(rpl::producer &&size) { }, lifetime()); } -void ItemBase::setContentWidth(int w) { - _contentWidth = w; +void ItemBase::setMinWidth(int w) { + _minWidth = w; } void ItemBase::finishAnimating() { diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index c52ed30..4dbe242 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -32,9 +32,9 @@ public: rpl::producer clicks() const; - rpl::producer contentWidthValue() const; - int contentWidth() const; - void setContentWidth(int w); + rpl::producer minWidthValue() const; + int minWidth() const; + void setMinWidth(int w); bool hasSubmenu() const; void setHasSubmenu(bool value); @@ -60,7 +60,7 @@ private: rpl::variable _selected = false; rpl::event_stream<> _clicks; - rpl::variable _contentWidth = 0; + rpl::variable _minWidth = 0; TriggeredSource _lastTriggeredSource = TriggeredSource::Mouse; From 5bcd894429e7a85164458261965a4c9e3c1f2d2e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 17 Jan 2021 04:56:47 +0300 Subject: [PATCH 27/60] Added initial implementation of arc painter. --- CMakeLists.txt | 2 + ui/paint/arcs.cpp | 92 ++++++++++++++++++++++++++++++++++++++++ ui/paint/arcs.h | 68 +++++++++++++++++++++++++++++ ui/widgets/widgets.style | 11 +++++ 4 files changed, 173 insertions(+) create mode 100644 ui/paint/arcs.cpp create mode 100644 ui/paint/arcs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 56b575a..770ba1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,8 @@ PRIVATE ui/layers/layer_manager.h ui/layers/layer_widget.cpp ui/layers/layer_widget.h + ui/paint/arcs.cpp + ui/paint/arcs.h ui/paint/blob.cpp ui/paint/blob.h ui/paint/blobs.cpp diff --git a/ui/paint/arcs.cpp b/ui/paint/arcs.cpp new file mode 100644 index 0000000..9538b99 --- /dev/null +++ b/ui/paint/arcs.cpp @@ -0,0 +1,92 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/paint/arcs.h" + +#include "ui/painter.h" + +namespace Ui::Paint { + +ArcsAnimation::ArcsAnimation( + const style::ArcsAnimation &st, + int count, + VerticalDirection direction, + int centerX, + int startY) +: _st(st) +, _center(centerX) +, _start(startY) +, _horizontalDirection(HorizontalDirection::None) +, _verticalDirection(direction) +, _startAngle((st.deltaAngle + + ((direction == VerticalDirection::Up) ? 90 : 270)) * 16) +, _spanAngle(-st.deltaAngle * 2 * 16) { + initArcs(count); +} + +ArcsAnimation::ArcsAnimation( + const style::ArcsAnimation &st, + int count, + HorizontalDirection direction, + int startX, + int centerY) +: _st(st) +, _center(centerY) +, _start(startX) +, _horizontalDirection(direction) +, _verticalDirection(VerticalDirection::None) +, _startAngle((st.deltaAngle + + ((direction == HorizontalDirection::Left) ? 180 : 0)) * 16) +, _spanAngle(-st.deltaAngle * 2 * 16) { + initArcs(count); +} + +void ArcsAnimation::initArcs(int count) { + _arcs.reserve(count); + + for (auto i = 0; i < count; i++) { + auto arc = Arc{ .rect = computeArcRect(i) }; + _arcs.push_back(std::move(arc)); + } +} + +QRectF ArcsAnimation::computeArcRect(int index) const { + const auto w = _st.startWidth + _st.deltaWidth * index; + const auto h = _st.startHeight + _st.deltaHeight * index; + if (_horizontalDirection != HorizontalDirection::None) { + auto rect = QRectF(0, _center - h / 2.0, w, h); + if (_horizontalDirection == HorizontalDirection::Right) { + rect.moveRight(_start + index * _st.space); + } else { + rect.moveLeft(_start - index * _st.space); + } + return rect; + } else if (_verticalDirection != VerticalDirection::None) { + auto rect = QRectF(_center - w / 2.0, 0, w, h); + if (_verticalDirection == VerticalDirection::Up) { + rect.moveTop(_start - index * _st.space); + } else { + rect.moveBottom(_start + index * _st.space); + } + return rect; + } + return QRectF(); +} + +void ArcsAnimation::paint(Painter &p, std::optional colorOverride) { + PainterHighQualityEnabler hq(p); + QPen pen; + pen.setWidth(_st.stroke); + pen.setCapStyle(Qt::RoundCap); + pen.setColor(colorOverride ? (*colorOverride) : _st.fg->c); + p.setPen(pen); + for (const auto &arc : _arcs) { + p.drawArc(arc.rect, _startAngle, _spanAngle); + } +} + + +} // namespace Ui::Paint diff --git a/ui/paint/arcs.h b/ui/paint/arcs.h new file mode 100644 index 0000000..1c4988e --- /dev/null +++ b/ui/paint/arcs.h @@ -0,0 +1,68 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "styles/style_widgets.h" + +class Painter; + +namespace Ui::Paint { + +class ArcsAnimation { +public: + + enum class HorizontalDirection { + Left, + Right, + None, + }; + + enum class VerticalDirection { + Up, + Down, + None, + }; + + ArcsAnimation( + const style::ArcsAnimation &st, + int count, + VerticalDirection direction, + int centerX, + int startY); + + ArcsAnimation( + const style::ArcsAnimation &st, + int count, + HorizontalDirection direction, + int startX, + int centerY); + + void paint( + Painter &p, + std::optional colorOverride = std::nullopt); + +private: + struct Arc { + QRectF rect; + }; + + void initArcs(int count); + QRectF computeArcRect(int index) const; + + const style::ArcsAnimation &_st; + const int _center; + const int _start; + const HorizontalDirection _horizontalDirection; + const VerticalDirection _verticalDirection; + const int _startAngle; + const int _spanAngle; + + std::vector _arcs; + +}; + +} // namespace Ui::Paint diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 11671a9..36f0e4d 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -410,6 +410,17 @@ CrossLineAnimation { stroke: pixels; } +ArcsAnimation { + fg: color; + stroke: pixels; + space: pixels; + deltaAngle: int; + deltaHeight: pixels; + deltaWidth: pixels; + startHeight: pixels; + startWidth: pixels; +} + MultiSelectItem { padding: margins; maxWidth: pixels; From ddda92c777b89e84603699952c84864202690fe5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 17 Jan 2021 23:08:21 +0300 Subject: [PATCH 28/60] Added animation support to arc painter. --- ui/paint/arcs.cpp | 130 ++++++++++++++++++++++++++++++++++++--- ui/paint/arcs.h | 32 +++++++++- ui/widgets/widgets.style | 1 + 3 files changed, 150 insertions(+), 13 deletions(-) diff --git a/ui/paint/arcs.cpp b/ui/paint/arcs.cpp index 9538b99..bcc6e23 100644 --- a/ui/paint/arcs.cpp +++ b/ui/paint/arcs.cpp @@ -6,13 +6,30 @@ // #include "ui/paint/arcs.h" +#include "ui/effects/animation_value.h" #include "ui/painter.h" namespace Ui::Paint { +namespace { + +inline float64 InterpolateF(float a, float b, float64 b_ratio) { + return a + float64(b - a) * b_ratio; +}; + +QRectF InterpolatedRect(const QRectF &r1, const QRectF &r2, float64 ratio) { + return QRectF( + InterpolateF(r1.x(), r2.x(), ratio), + InterpolateF(r1.y(), r2.y(), ratio), + InterpolateF(r1.width(), r2.width(), ratio), + InterpolateF(r1.height(), r2.height(), ratio)); +} + +} // namespace ArcsAnimation::ArcsAnimation( const style::ArcsAnimation &st, - int count, + std::vector thresholds, + float64 startValue, VerticalDirection direction, int centerX, int startY) @@ -23,13 +40,16 @@ ArcsAnimation::ArcsAnimation( , _verticalDirection(direction) , _startAngle((st.deltaAngle + ((direction == VerticalDirection::Up) ? 90 : 270)) * 16) -, _spanAngle(-st.deltaAngle * 2 * 16) { - initArcs(count); +, _spanAngle(-st.deltaAngle * 2 * 16) +, _emptyRect(computeArcRect(0)) +, _currentValue(startValue) { + initArcs(std::move(thresholds)); } ArcsAnimation::ArcsAnimation( const style::ArcsAnimation &st, - int count, + std::vector thresholds, + float64 startValue, HorizontalDirection direction, int startX, int centerY) @@ -40,15 +60,24 @@ ArcsAnimation::ArcsAnimation( , _verticalDirection(VerticalDirection::None) , _startAngle((st.deltaAngle + ((direction == HorizontalDirection::Left) ? 180 : 0)) * 16) -, _spanAngle(-st.deltaAngle * 2 * 16) { - initArcs(count); +, _spanAngle(-st.deltaAngle * 2 * 16) +, _emptyRect(computeArcRect(0)) +, _currentValue(startValue) { + initArcs(std::move(thresholds)); } -void ArcsAnimation::initArcs(int count) { +void ArcsAnimation::initArcs(std::vector thresholds) { + const auto count = thresholds.size(); _arcs.reserve(count); for (auto i = 0; i < count; i++) { - auto arc = Arc{ .rect = computeArcRect(i) }; + const auto threshold = thresholds[i]; + const auto progress = (threshold > _currentValue) ? 1. : 0.; + auto arc = Arc{ + .rect = computeArcRect(i + 1), + .threshold = threshold, + .progress = progress, + }; _arcs.push_back(std::move(arc)); } } @@ -76,6 +105,77 @@ QRectF ArcsAnimation::computeArcRect(int index) const { return QRectF(); } +void ArcsAnimation::update(crl::time now) { + for (auto &arc : _arcs) { + if (!isArcFinished(arc)) { + const auto progress = std::clamp( + (now - arc.startTime) / float64(_st.duration), + 0., + 1.); + arc.progress = (arc.threshold > _currentValue) + ? progress + : (1. - progress); + } + } + if (isFinished()) { + _stopUpdateRequests.fire({}); + } +} + + +void ArcsAnimation::setValue(float64 value) { + if (_currentValue == value) { + return; + } + const auto previousValue = _currentValue; + _currentValue = value; + if (!isFinished()) { + const auto now = crl::now(); + _startUpdateRequests.fire({}); + for (auto &arc : _arcs) { + updateArcStartTime(arc, previousValue, now); + } + } +} + +void ArcsAnimation::updateArcStartTime( + Arc &arc, + float64 previousValue, + crl::time now) { + if ((arc.progress == 0.) || (arc.progress == 1.)) { + arc.startTime = isArcFinished(arc) ? 0 : now; + return; + } + const auto isPreviousToHide = (arc.threshold <= previousValue); // 0 -> 1 + const auto isCurrentToHide = (arc.threshold <= _currentValue); + if (isPreviousToHide != isCurrentToHide) { + const auto passedTime = _st.duration * arc.progress; + const auto newDelta = isCurrentToHide + ? (_st.duration - passedTime) + : passedTime; + arc.startTime = now - newDelta; + } +} + +rpl::producer<> ArcsAnimation::startUpdateRequests() { + return _startUpdateRequests.events(); +} + +rpl::producer<> ArcsAnimation::stopUpdateRequests() { + return _stopUpdateRequests.events(); +} + +bool ArcsAnimation::isFinished() const { + return ranges::all_of( + _arcs, + [=](const Arc &arc) { return isArcFinished(arc); }); +} + +bool ArcsAnimation::isArcFinished(const Arc &arc) const { + return ((arc.threshold > _currentValue) && (arc.progress == 1.)) + || ((arc.threshold <= _currentValue) && (arc.progress == 0.)); +} + void ArcsAnimation::paint(Painter &p, std::optional colorOverride) { PainterHighQualityEnabler hq(p); QPen pen; @@ -83,8 +183,18 @@ void ArcsAnimation::paint(Painter &p, std::optional colorOverride) { pen.setCapStyle(Qt::RoundCap); pen.setColor(colorOverride ? (*colorOverride) : _st.fg->c); p.setPen(pen); - for (const auto &arc : _arcs) { - p.drawArc(arc.rect, _startAngle, _spanAngle); + for (auto i = 0; i < _arcs.size(); i++) { + const auto &arc = _arcs[i]; + const auto previousRect = (!i) ? _emptyRect : _arcs[i - 1].rect; + const auto progress = arc.progress; + const auto opactity = (1. - progress); + p.setOpacity(opactity * opactity); + const auto rect = (progress == 0.) + ? arc.rect + : (progress == 1.) + ? previousRect + : InterpolatedRect(arc.rect, previousRect, progress); + p.drawArc(rect, _startAngle, _spanAngle); } } diff --git a/ui/paint/arcs.h b/ui/paint/arcs.h index 1c4988e..03f42e3 100644 --- a/ui/paint/arcs.h +++ b/ui/paint/arcs.h @@ -29,14 +29,16 @@ public: ArcsAnimation( const style::ArcsAnimation &st, - int count, + std::vector thresholds, + float64 startValue, VerticalDirection direction, int centerX, int startY); ArcsAnimation( const style::ArcsAnimation &st, - int count, + std::vector thresholds, + float64 startValue, HorizontalDirection direction, int startX, int centerY); @@ -45,14 +47,32 @@ public: Painter &p, std::optional colorOverride = std::nullopt); + void setValue(float64 value); + + rpl::producer<> startUpdateRequests(); + rpl::producer<> stopUpdateRequests(); + + void update(crl::time now); + + bool isFinished() const; + private: struct Arc { QRectF rect; + float threshold; + crl::time startTime = 0; + float64 progress = 0.; }; - void initArcs(int count); + void initArcs(std::vector thresholds); QRectF computeArcRect(int index) const; + bool isArcFinished(const Arc &arc) const; + void updateArcStartTime( + Arc &arc, + float64 previousValue, + crl::time now); + const style::ArcsAnimation &_st; const int _center; const int _start; @@ -60,6 +80,12 @@ private: const VerticalDirection _verticalDirection; const int _startAngle; const int _spanAngle; + const QRectF _emptyRect; + + float64 _currentValue = 0.; + + rpl::event_stream<> _startUpdateRequests; + rpl::event_stream<> _stopUpdateRequests; std::vector _arcs; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 36f0e4d..6da3006 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -414,6 +414,7 @@ ArcsAnimation { fg: color; stroke: pixels; space: pixels; + duration: int; deltaAngle: int; deltaHeight: pixels; deltaWidth: pixels; From 3bd07cf8372ef6d1c326de7100cf789ca22eeff6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 18 Jan 2021 00:00:13 +0300 Subject: [PATCH 29/60] Delegated responsibility for position of arc painter to parent. --- ui/paint/arcs.cpp | 38 ++++++++++++++++++++++---------------- ui/paint/arcs.h | 13 +++++-------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/ui/paint/arcs.cpp b/ui/paint/arcs.cpp index bcc6e23..5014cc9 100644 --- a/ui/paint/arcs.cpp +++ b/ui/paint/arcs.cpp @@ -30,12 +30,8 @@ ArcsAnimation::ArcsAnimation( const style::ArcsAnimation &st, std::vector thresholds, float64 startValue, - VerticalDirection direction, - int centerX, - int startY) + VerticalDirection direction) : _st(st) -, _center(centerX) -, _start(startY) , _horizontalDirection(HorizontalDirection::None) , _verticalDirection(direction) , _startAngle((st.deltaAngle @@ -50,12 +46,8 @@ ArcsAnimation::ArcsAnimation( const style::ArcsAnimation &st, std::vector thresholds, float64 startValue, - HorizontalDirection direction, - int startX, - int centerY) + HorizontalDirection direction) : _st(st) -, _center(centerY) -, _start(startX) , _horizontalDirection(direction) , _verticalDirection(VerticalDirection::None) , _startAngle((st.deltaAngle @@ -86,19 +78,19 @@ QRectF ArcsAnimation::computeArcRect(int index) const { const auto w = _st.startWidth + _st.deltaWidth * index; const auto h = _st.startHeight + _st.deltaHeight * index; if (_horizontalDirection != HorizontalDirection::None) { - auto rect = QRectF(0, _center - h / 2.0, w, h); + auto rect = QRectF(0, -h / 2.0, w, h); if (_horizontalDirection == HorizontalDirection::Right) { - rect.moveRight(_start + index * _st.space); + rect.moveRight(index * _st.space); } else { - rect.moveLeft(_start - index * _st.space); + rect.moveLeft(-index * _st.space); } return rect; } else if (_verticalDirection != VerticalDirection::None) { - auto rect = QRectF(_center - w / 2.0, 0, w, h); + auto rect = QRectF(-w / 2.0, 0, w, h); if (_verticalDirection == VerticalDirection::Up) { - rect.moveTop(_start - index * _st.space); + rect.moveTop(-index * _st.space); } else { - rect.moveBottom(_start + index * _st.space); + rect.moveBottom(index * _st.space); } return rect; } @@ -157,6 +149,20 @@ void ArcsAnimation::updateArcStartTime( } } +float ArcsAnimation::width() const { + if (_arcs.empty()) { + return 0; + } + const auto &r = _arcs.back().rect; + return r.x() + r.width(); +} + +float ArcsAnimation::height() const { + return _arcs.empty() + ? 0 + : _arcs.back().rect.height(); +} + rpl::producer<> ArcsAnimation::startUpdateRequests() { return _startUpdateRequests.events(); } diff --git a/ui/paint/arcs.h b/ui/paint/arcs.h index 03f42e3..9efef71 100644 --- a/ui/paint/arcs.h +++ b/ui/paint/arcs.h @@ -31,17 +31,13 @@ public: const style::ArcsAnimation &st, std::vector thresholds, float64 startValue, - VerticalDirection direction, - int centerX, - int startY); + VerticalDirection direction); ArcsAnimation( const style::ArcsAnimation &st, std::vector thresholds, float64 startValue, - HorizontalDirection direction, - int startX, - int centerY); + HorizontalDirection direction); void paint( Painter &p, @@ -56,6 +52,9 @@ public: bool isFinished() const; + float width() const; + float height() const; + private: struct Arc { QRectF rect; @@ -74,8 +73,6 @@ private: crl::time now); const style::ArcsAnimation &_st; - const int _center; - const int _start; const HorizontalDirection _horizontalDirection; const VerticalDirection _verticalDirection; const int _startAngle; From 43f09755e51ac22b3ae0263d950dad9303ac6576 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 19 Jan 2021 23:03:31 +0300 Subject: [PATCH 30/60] Moved key press handling from menu to menu item base. --- ui/widgets/dropdown_menu.cpp | 8 ++++---- ui/widgets/dropdown_menu.h | 2 +- ui/widgets/menu/menu.cpp | 23 +++++++---------------- ui/widgets/menu/menu.h | 2 +- ui/widgets/menu/menu_action.cpp | 19 +++++++++++++++++++ ui/widgets/menu/menu_action.h | 2 ++ ui/widgets/menu/menu_item_base.h | 3 +++ ui/widgets/popup_menu.cpp | 8 ++++---- ui/widgets/popup_menu.h | 2 +- 9 files changed, 42 insertions(+), 27 deletions(-) diff --git a/ui/widgets/dropdown_menu.cpp b/ui/widgets/dropdown_menu.cpp index 7ee28ed..a8f1960 100644 --- a/ui/widgets/dropdown_menu.cpp +++ b/ui/widgets/dropdown_menu.cpp @@ -134,9 +134,9 @@ bool DropdownMenu::popupSubmenuFromAction(const Menu::CallbackData &data) { // } //} -void DropdownMenu::forwardKeyPress(int key) { - if (!handleKeyPress(key)) { - _menu->handleKeyPress(key); +void DropdownMenu::forwardKeyPress(not_null e) { + if (!handleKeyPress(e->key())) { + _menu->handleKeyPress(e); } } @@ -193,7 +193,7 @@ void DropdownMenu::hideEvent(QHideEvent *e) { } void DropdownMenu::keyPressEvent(QKeyEvent *e) { - forwardKeyPress(e->key()); + forwardKeyPress(e); } void DropdownMenu::mouseMoveEvent(QMouseEvent *e) { diff --git a/ui/widgets/dropdown_menu.h b/ui/widgets/dropdown_menu.h index 0f5a1a9..166616f 100644 --- a/ui/widgets/dropdown_menu.h +++ b/ui/widgets/dropdown_menu.h @@ -59,7 +59,7 @@ private: using TriggeredSource = Menu::TriggeredSource; void handleActivated(const Menu::CallbackData &data); void handleTriggered(const Menu::CallbackData &data); - void forwardKeyPress(int key); + void forwardKeyPress(not_null e); bool handleKeyPress(int key); void forwardMouseMove(QPoint globalPosition) { _menu->handleMouseMove(globalPosition); diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 3701532..217e8f0 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -229,7 +229,7 @@ void Menu::itemPressed(TriggeredSource source) { void Menu::keyPressEvent(QKeyEvent *e) { const auto key = e->key(); if (!_keyPressDelegate || !_keyPressDelegate(key)) { - handleKeyPress(key); + handleKeyPress(e); } } @@ -238,26 +238,17 @@ ItemBase *Menu::findSelectedAction() const { return (it == end(_actionWidgets)) ? nullptr : it->get(); } -void Menu::handleKeyPress(int key) { - if (key == Qt::Key_Enter || key == Qt::Key_Return) { - itemPressed(TriggeredSource::Keyboard); - return; - } - if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) { - if (_selected >= 0 && _actionWidgets[_selected]->hasSubmenu()) { - itemPressed(TriggeredSource::Keyboard); - return; - } else if (_selected < 0 && !_actions.empty()) { - _mouseSelection = false; - setSelected(0); - } - } +void Menu::handleKeyPress(not_null e) { + const auto key = e->key(); + const auto selected = findSelectedAction(); if ((key != Qt::Key_Up && key != Qt::Key_Down) || _actions.empty()) { + if (selected) { + selected->handleKeyPress(e); + } return; } const auto delta = (key == Qt::Key_Down ? 1 : -1); - const auto selected = findSelectedAction(); auto start = selected ? selected->index() : -1; if (start < 0 || start >= _actions.size()) { start = (delta > 0) ? (_actions.size() - 1) : 0; diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index f838a1b..9f3d265 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -59,7 +59,7 @@ public: void setKeyPressDelegate(Fn delegate) { _keyPressDelegate = std::move(delegate); } - void handleKeyPress(int key); + void handleKeyPress(not_null e); void setMouseMoveDelegate(Fn delegate) { _mouseMoveDelegate = std::move(delegate); diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 955a722..e8f8273 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -9,6 +9,8 @@ #include "ui/effects/ripple_animation.h" #include "ui/painter.h" +#include + namespace Ui::Menu { namespace { @@ -173,4 +175,21 @@ int Action::contentHeight() const { return _height; } +void Action::handleKeyPress(not_null e) { + if (!isSelected()) { + return; + } + const auto key = e->key(); + if (key == Qt::Key_Enter || key == Qt::Key_Return) { + setClicked(TriggeredSource::Keyboard); + return; + } + if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) { + if (hasSubmenu()) { + setClicked(TriggeredSource::Keyboard); + return; + } + } +} + } // namespace Ui::Menu diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h index c274c03..448cd66 100644 --- a/ui/widgets/menu/menu_action.h +++ b/ui/widgets/menu/menu_action.h @@ -26,6 +26,8 @@ public: bool isEnabled() const override; not_null action() const override; + void handleKeyPress(not_null e) override; + protected: QPoint prepareRippleStartPosition() const override; QImage prepareRippleMask() const override; diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index 4dbe242..8dcdc63 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -39,6 +39,9 @@ public: bool hasSubmenu() const; void setHasSubmenu(bool value); + virtual void handleKeyPress(not_null e) { + } + virtual not_null action() const = 0; virtual bool isEnabled() const = 0; diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index 394a8c4..38d0acc 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -211,9 +211,9 @@ void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSou } } -void PopupMenu::forwardKeyPress(int key) { - if (!handleKeyPress(key)) { - _menu->handleKeyPress(key); +void PopupMenu::forwardKeyPress(not_null e) { + if (!handleKeyPress(e->key())) { + _menu->handleKeyPress(e); } } @@ -270,7 +270,7 @@ void PopupMenu::hideEvent(QHideEvent *e) { } void PopupMenu::keyPressEvent(QKeyEvent *e) { - forwardKeyPress(e->key()); + forwardKeyPress(e); } void PopupMenu::mouseMoveEvent(QMouseEvent *e) { diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index 9e05866..9caa56d 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -78,7 +78,7 @@ private: void handleMenuResize(); void handleActivated(const Menu::CallbackData &data); void handleTriggered(const Menu::CallbackData &data); - void forwardKeyPress(int key); + void forwardKeyPress(not_null e); bool handleKeyPress(int key); void forwardMouseMove(QPoint globalPosition) { _menu->handleMouseMove(globalPosition); From 0ce0766b5eec2b002855f15e5e7c892646d99b20 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 19 Jan 2021 23:10:10 +0300 Subject: [PATCH 31/60] Removed redundant hasSubmenu property from menu item base. --- ui/widgets/menu/menu_action.cpp | 5 ++++- ui/widgets/menu/menu_action.h | 2 ++ ui/widgets/menu/menu_item_base.cpp | 8 -------- ui/widgets/menu/menu_item_base.h | 5 ----- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index e8f8273..196f2ec 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -65,7 +65,6 @@ Action::Action( initResizeHook(parent->sizeValue()); processAction(); - setHasSubmenu(action->menu() != nullptr); paintRequest( ) | rpl::start_with_next([=] { @@ -78,6 +77,10 @@ Action::Action( connect(_action, &QAction::changed, [=] { processAction(); }); } +bool Action::hasSubmenu() const { + return _action->menu() != nullptr; +} + void Action::paint(Painter &p) { const auto enabled = _action->isEnabled(); const auto selected = isSelected(); diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h index 448cd66..2600788 100644 --- a/ui/widgets/menu/menu_action.h +++ b/ui/widgets/menu/menu_action.h @@ -38,6 +38,8 @@ private: void processAction(); void paint(Painter &p); + bool hasSubmenu() const; + Text::String _text; QString _shortcut; const not_null _action; diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index 68fddb9..f149b98 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -77,14 +77,6 @@ int ItemBase::minWidth() const { return _minWidth.current(); } -bool ItemBase::hasSubmenu() const { - return _hasSubmenu; -} - -void ItemBase::setHasSubmenu(bool value) { - _hasSubmenu = value; -} - void ItemBase::init() { events( ) | rpl::filter([=](not_null e) { diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index 8dcdc63..e233623 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -36,9 +36,6 @@ public: int minWidth() const; void setMinWidth(int w); - bool hasSubmenu() const; - void setHasSubmenu(bool value); - virtual void handleKeyPress(not_null e) { } @@ -58,8 +55,6 @@ protected: private: int _index = -1; - bool _hasSubmenu = false; - rpl::variable _selected = false; rpl::event_stream<> _clicks; From fedeac3f088849ee64bc1a5e8179105ef0a71617 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 19 Jan 2021 23:17:41 +0300 Subject: [PATCH 32/60] Removed redundant property of current selected item from menu. --- ui/widgets/menu/menu.cpp | 22 +++++++++++----------- ui/widgets/menu/menu.h | 1 - 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 217e8f0..616236a 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -281,20 +281,20 @@ void Menu::clearMouseSelection() { } void Menu::setSelected(int selected) { - if (selected >= _actions.size()) { + if (selected >= _actionWidgets.size()) { selected = -1; } - if (_selected != selected) { - const auto source = _mouseSelection - ? TriggeredSource::Mouse - : TriggeredSource::Keyboard; - if (_selected >= 0) { - _actionWidgets[_selected]->setSelected(false, source); - } - _selected = selected; - if (_selected >= 0) { - _actionWidgets[_selected]->setSelected(true, source); + const auto source = _mouseSelection + ? TriggeredSource::Mouse + : TriggeredSource::Keyboard; + if (const auto selectedItem = findSelectedAction()) { + if (selectedItem->index() == selected) { + return; } + selectedItem->setSelected(false, source); + } + if (selected >= 0) { + _actionWidgets[selected]->setSelected(true, source); } } diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index 9f3d265..511fa8d 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -119,7 +119,6 @@ private: bool _mouseSelection = false; - int _selected = -1; bool _childShown = false; rpl::event_stream<> _resizesFromInner; From 70aa4833b7d3e81ad8cd4483136e4966b15bf99c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 19 Jan 2021 23:41:59 +0300 Subject: [PATCH 33/60] Removed redundant property of mouse selection item from menu. --- ui/widgets/menu/menu.cpp | 30 +++++++++++++----------------- ui/widgets/menu/menu.h | 4 +--- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 616236a..42ae922 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -6,8 +6,6 @@ // #include "ui/widgets/menu/menu.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/checkbox.h" #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/menu/menu_item_base.h" #include "ui/widgets/menu/menu_separator.h" @@ -157,7 +155,6 @@ not_null Menu::addSeparator() { } void Menu::clearActions() { - setSelected(-1); _actionWidgets.clear(); for (auto action : base::take(_actions)) { if (action->parent() == this) { @@ -190,8 +187,10 @@ rpl::producer<> Menu::resizesFromInner() const { } void Menu::setShowSource(TriggeredSource source) { - _mouseSelection = (source == TriggeredSource::Mouse); - setSelected((source == TriggeredSource::Mouse || _actions.empty()) ? -1 : 0); + const auto mouseSelection = (source == TriggeredSource::Mouse); + setSelected( + (mouseSelection || _actions.empty()) ? -1 : 0, + mouseSelection); } const std::vector> &Menu::actions() const { @@ -204,10 +203,6 @@ void Menu::setForceWidth(int forceWidth) { } void Menu::updateSelected(QPoint globalPosition) { - if (!_mouseSelection) { - return; - } - const auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip); for (const auto &widget : _actionWidgets) { const auto widgetRect = QRect(widget->pos(), widget->size()); @@ -264,27 +259,29 @@ void Menu::handleKeyPress(not_null e) { } while (newSelected != start && (!_actionWidgets[newSelected]->isEnabled())); if (_actionWidgets[newSelected]->isEnabled()) { - _mouseSelection = false; - setSelected(newSelected); + setSelected(newSelected, false); } } void Menu::clearSelection() { - _mouseSelection = false; - setSelected(-1); + setSelected(-1, false); } void Menu::clearMouseSelection() { - if (_mouseSelection && !_childShown) { + const auto selected = findSelectedAction(); + const auto mouseSelection = selected + ? (selected->lastTriggeredSource() == TriggeredSource::Mouse) + : false; + if (mouseSelection && !_childShown) { clearSelection(); } } -void Menu::setSelected(int selected) { +void Menu::setSelected(int selected, bool isMouseSelection) { if (selected >= _actionWidgets.size()) { selected = -1; } - const auto source = _mouseSelection + const auto source = isMouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard; if (const auto selectedItem = findSelectedAction()) { @@ -307,7 +304,6 @@ void Menu::handleMouseMove(QPoint globalPosition) { const auto inner = rect().marginsRemoved(margins); const auto localPosition = mapFromGlobal(globalPosition); if (inner.contains(localPosition)) { - _mouseSelection = true; updateSelected(globalPosition); } else { clearMouseSelection(); diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index 511fa8d..1eabfa1 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -93,7 +93,7 @@ private: const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); - void setSelected(int selected); + void setSelected(int selected, bool isMouseSelection); void clearMouseSelection(); void itemPressed(TriggeredSource source); @@ -117,8 +117,6 @@ private: int _forceWidth = 0; - bool _mouseSelection = false; - bool _childShown = false; rpl::event_stream<> _resizesFromInner; From 912f0b48a648e7f12e7bcba82a0f0ec326aab8dd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 20 Jan 2021 00:12:46 +0300 Subject: [PATCH 34/60] Fixed mouse selecting of menu item when it was deselected via keyboard. --- ui/widgets/menu/menu_item_base.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index f149b98..bf136e1 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -22,6 +22,7 @@ void ItemBase::setSelected( return; } if (_selected.current() != selected) { + setMouseTracking(!selected); _lastTriggeredSource = source; _selected = selected; update(); @@ -112,9 +113,10 @@ void ItemBase::enableMouseSelecting() { ) | rpl::filter([=](not_null e) { return action()->isEnabled() && ((e->type() == QEvent::Leave) - || (e->type() == QEvent::Enter)); + || (e->type() == QEvent::Enter) + || (e->type() == QEvent::MouseMove)); }) | rpl::map([=](not_null e) { - return (e->type() == QEvent::Enter); + return (e->type() != QEvent::Leave); }) | rpl::start_with_next([=](bool selected) { setSelected(selected); }, lifetime()); From a5fb99372184d5f3c00e5e851aaa16d8d28d2ce1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jan 2021 10:21:27 +0400 Subject: [PATCH 35/60] Make a generic NumberInput from PortInput. --- ui/widgets/input_fields.cpp | 14 ++++++++------ ui/widgets/input_fields.h | 12 ++++++++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index bcb3a38..071ba62 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -4010,18 +4010,20 @@ PasswordInput::PasswordInput( setEchoMode(QLineEdit::Password); } -PortInput::PortInput( +NumberInput::NumberInput( QWidget *parent, const style::InputField &st, rpl::producer placeholder, - const QString &val) -: MaskedInputField(parent, st, std::move(placeholder), val) { - if (!val.toInt() || val.toInt() > 65535) { + const QString &value, + int limit) +: MaskedInputField(parent, st, std::move(placeholder), value) +, _limit(limit) { + if (!value.toInt() || (limit > 0 && value.toInt() > limit)) { setText(QString()); } } -void PortInput::correctValue( +void NumberInput::correctValue( const QString &was, int wasCursor, QString &now, @@ -4039,7 +4041,7 @@ void PortInput::correctValue( if (!newText.toInt()) { newText = QString(); newPos = 0; - } else if (newText.toInt() > 65535) { + } else if (_limit > 0 && newText.toInt() > _limit) { newText = was; newPos = wasCursor; } diff --git a/ui/widgets/input_fields.h b/ui/widgets/input_fields.h index 2b7b7ce..7619274 100644 --- a/ui/widgets/input_fields.h +++ b/ui/widgets/input_fields.h @@ -695,9 +695,14 @@ public: }; -class PortInput : public MaskedInputField { +class NumberInput : public MaskedInputField { public: - PortInput(QWidget *parent, const style::InputField &st, rpl::producer placeholder, const QString &val); + NumberInput( + QWidget *parent, + const style::InputField &st, + rpl::producer placeholder, + const QString &value, + int limit); protected: void correctValue( @@ -706,6 +711,9 @@ protected: QString &now, int &nowCursor) override; +private: + int _limit = 0; + }; class HexInput : public MaskedInputField { From bda12f2becd2497056d325a897b8285cc57662de Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 23 Jan 2021 21:58:12 +0300 Subject: [PATCH 36/60] Fixed resizing menu with wide items. --- ui/widgets/menu/menu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 42ae922..e18c5e9 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -129,8 +129,8 @@ not_null Menu::addAction(base::unique_qptr widget) { ? _st.widthMin : (*ranges::max_element( _actionWidgets, - std::greater<>(), - &ItemBase::width))->minWidth(); + std::less<>(), + &ItemBase::minWidth))->minWidth(); resizeFromInner(newWidth, height()); }, widget->lifetime()); From 57fc9861a78231d8dc015096a54159627f459242 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 27 Jan 2021 04:27:26 +0300 Subject: [PATCH 37/60] Added ability to get width of displayed arcs. Renamed previous Arcs::width method to Arcs::maxWidth. --- ui/paint/arcs.cpp | 12 ++++++++++++ ui/paint/arcs.h | 1 + 2 files changed, 13 insertions(+) diff --git a/ui/paint/arcs.cpp b/ui/paint/arcs.cpp index 5014cc9..22ae2a5 100644 --- a/ui/paint/arcs.cpp +++ b/ui/paint/arcs.cpp @@ -150,6 +150,18 @@ void ArcsAnimation::updateArcStartTime( } float ArcsAnimation::width() const { + if (_arcs.empty()) { + return 0; + } + for (const auto &arc : ranges::view::reverse(_arcs)) { + if ((arc.progress != 1.)) { + return arc.rect.x() + arc.rect.width(); + } + } + return 0; +} + +float ArcsAnimation::maxWidth() const { if (_arcs.empty()) { return 0; } diff --git a/ui/paint/arcs.h b/ui/paint/arcs.h index 9efef71..a043482 100644 --- a/ui/paint/arcs.h +++ b/ui/paint/arcs.h @@ -53,6 +53,7 @@ public: bool isFinished() const; float width() const; + float maxWidth() const; float height() const; private: From 556a5dd90baec256ba50a07dcc3abf5889a7499f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 27 Jan 2021 20:02:40 +0300 Subject: [PATCH 38/60] Added ability to get width of arcs with finished animation. --- ui/paint/arcs.cpp | 12 ++++++++++++ ui/paint/arcs.h | 1 + 2 files changed, 13 insertions(+) diff --git a/ui/paint/arcs.cpp b/ui/paint/arcs.cpp index 22ae2a5..3f5213f 100644 --- a/ui/paint/arcs.cpp +++ b/ui/paint/arcs.cpp @@ -161,6 +161,18 @@ float ArcsAnimation::width() const { return 0; } +float ArcsAnimation::finishedWidth() const { + if (_arcs.empty()) { + return 0; + } + for (const auto &arc : ranges::view::reverse(_arcs)) { + if (arc.threshold <= _currentValue) { + return arc.rect.x() + arc.rect.width(); + } + } + return 0; +} + float ArcsAnimation::maxWidth() const { if (_arcs.empty()) { return 0; diff --git a/ui/paint/arcs.h b/ui/paint/arcs.h index a043482..e713444 100644 --- a/ui/paint/arcs.h +++ b/ui/paint/arcs.h @@ -54,6 +54,7 @@ public: float width() const; float maxWidth() const; + float finishedWidth() const; float height() const; private: From 315a0457cf3340983e3217246e5cac27e1d2e091 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 27 Jan 2021 23:08:27 +0400 Subject: [PATCH 39/60] Improve menu selection tracking. --- ui/widgets/menu/menu_item_base.cpp | 44 +++++++++++++----------------- ui/widgets/menu/menu_item_base.h | 2 +- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/ui/widgets/menu/menu_item_base.cpp b/ui/widgets/menu/menu_item_base.cpp index bf136e1..4bce600 100644 --- a/ui/widgets/menu/menu_item_base.cpp +++ b/ui/widgets/menu/menu_item_base.cpp @@ -12,7 +12,6 @@ ItemBase::ItemBase( not_null parent, const style::Menu &st) : RippleButton(parent, st.ripple) { - init(); } void ItemBase::setSelected( @@ -78,20 +77,6 @@ int ItemBase::minWidth() const { return _minWidth.current(); } -void ItemBase::init() { - events( - ) | rpl::filter([=](not_null e) { - return isEnabled() - && isSelected() - && (e->type() == QEvent::MouseButtonRelease); - }) | rpl::to_empty | rpl::start_with_next([=] { - const auto point = mapFromGlobal(QCursor::pos()); - if (!rect().contains(point)) { - setSelected(false); - } - }, lifetime()); -} - void ItemBase::initResizeHook(rpl::producer &&size) { std::move( size @@ -109,16 +94,25 @@ void ItemBase::finishAnimating() { } void ItemBase::enableMouseSelecting() { - events( - ) | rpl::filter([=](not_null e) { - return action()->isEnabled() - && ((e->type() == QEvent::Leave) - || (e->type() == QEvent::Enter) - || (e->type() == QEvent::MouseMove)); - }) | rpl::map([=](not_null e) { - return (e->type() != QEvent::Leave); - }) | rpl::start_with_next([=](bool selected) { - setSelected(selected); + enableMouseSelecting(this); +} + +void ItemBase::enableMouseSelecting(not_null widget) { + widget->events( + ) | rpl::start_with_next([=](not_null e) { + const auto type = e->type(); + if (((type == QEvent::Leave) + || (type == QEvent::Enter) + || (type == QEvent::MouseMove)) && action()->isEnabled()) { + setSelected(e->type() != QEvent::Leave); + } else if ((type == QEvent::MouseButtonRelease) + && isEnabled() + && isSelected()) { + const auto point = mapFromGlobal(QCursor::pos()); + if (!rect().contains(point)) { + setSelected(false); + } + } }, lifetime()); } diff --git a/ui/widgets/menu/menu_item_base.h b/ui/widgets/menu/menu_item_base.h index e233623..7a13bc6 100644 --- a/ui/widgets/menu/menu_item_base.h +++ b/ui/widgets/menu/menu_item_base.h @@ -45,10 +45,10 @@ public: virtual void finishAnimating(); protected: - void init(); void initResizeHook(rpl::producer &&size); void enableMouseSelecting(); + void enableMouseSelecting(not_null widget); virtual int contentHeight() const = 0; From f03209c1f9c7f9d47eff5740e927d3d41df2e09e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 28 Jan 2021 02:59:21 +0300 Subject: [PATCH 40/60] Added ability to set custom stroke ratio for arcs. --- ui/paint/arcs.cpp | 9 ++++++++- ui/paint/arcs.h | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ui/paint/arcs.cpp b/ui/paint/arcs.cpp index 3f5213f..d3713c4 100644 --- a/ui/paint/arcs.cpp +++ b/ui/paint/arcs.cpp @@ -209,7 +209,11 @@ bool ArcsAnimation::isArcFinished(const Arc &arc) const { void ArcsAnimation::paint(Painter &p, std::optional colorOverride) { PainterHighQualityEnabler hq(p); QPen pen; - pen.setWidth(_st.stroke); + if (_strokeRatio) { + pen.setWidthF(_st.stroke * _strokeRatio); + } else { + pen.setWidth(_st.stroke); + } pen.setCapStyle(Qt::RoundCap); pen.setColor(colorOverride ? (*colorOverride) : _st.fg->c); p.setPen(pen); @@ -228,5 +232,8 @@ void ArcsAnimation::paint(Painter &p, std::optional colorOverride) { } } +void ArcsAnimation::setStrokeRatio(float ratio) { + _strokeRatio = ratio; +} } // namespace Ui::Paint diff --git a/ui/paint/arcs.h b/ui/paint/arcs.h index e713444..a1275a6 100644 --- a/ui/paint/arcs.h +++ b/ui/paint/arcs.h @@ -57,6 +57,8 @@ public: float finishedWidth() const; float height() const; + void setStrokeRatio(float ratio); + private: struct Arc { QRectF rect; @@ -82,6 +84,7 @@ private: const QRectF _emptyRect; float64 _currentValue = 0.; + float _strokeRatio = 0.; rpl::event_stream<> _startUpdateRequests; rpl::event_stream<> _stopUpdateRequests; From d94cb34219e5795efc0efe24d980c54e40eb42e3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 29 Jan 2021 21:28:15 +0300 Subject: [PATCH 41/60] Fixed adjustment of menu items by largest width. --- ui/widgets/menu/menu.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index e18c5e9..5fa1e8c 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -122,15 +122,17 @@ not_null Menu::addAction(base::unique_qptr widget) { }, widget->lifetime()); widget->minWidthValue( - ) | rpl::start_with_next([=] { + ) | rpl::start_with_next([=](int minWidth) { const auto newWidth = _forceWidth ? _forceWidth : _actionWidgets.empty() ? _st.widthMin - : (*ranges::max_element( - _actionWidgets, - std::less<>(), - &ItemBase::minWidth))->minWidth(); + : std::max( + minWidth, + (*ranges::max_element( + _actionWidgets, + std::less<>(), + &ItemBase::minWidth))->minWidth()); resizeFromInner(newWidth, height()); }, widget->lifetime()); From 4748986bf4089ee37884e31020fab7516994ad08 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 30 Jan 2021 10:55:17 +0300 Subject: [PATCH 42/60] Returned ability to use right click for menu actions. --- ui/widgets/menu/menu_action.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp index 196f2ec..2f1234d 100644 --- a/ui/widgets/menu/menu_action.cpp +++ b/ui/widgets/menu/menu_action.cpp @@ -63,6 +63,8 @@ Action::Action( + _st.itemStyle.font->height + _st.itemPadding.bottom()) { + setAcceptBoth(true); + initResizeHook(parent->sizeValue()); processAction(); From 5f7ee9ed8138ee469c1ae830070b9e0004b123be Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 30 Jan 2021 13:26:21 +0300 Subject: [PATCH 43/60] United enum classes of arc directions into single enum class. --- ui/paint/arcs.cpp | 44 +++++++++++++++++++------------------------- ui/paint/arcs.h | 23 ++++++----------------- 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/ui/paint/arcs.cpp b/ui/paint/arcs.cpp index d3713c4..34a2c28 100644 --- a/ui/paint/arcs.cpp +++ b/ui/paint/arcs.cpp @@ -30,28 +30,18 @@ ArcsAnimation::ArcsAnimation( const style::ArcsAnimation &st, std::vector thresholds, float64 startValue, - VerticalDirection direction) + Direction direction) : _st(st) -, _horizontalDirection(HorizontalDirection::None) -, _verticalDirection(direction) -, _startAngle((st.deltaAngle - + ((direction == VerticalDirection::Up) ? 90 : 270)) * 16) -, _spanAngle(-st.deltaAngle * 2 * 16) -, _emptyRect(computeArcRect(0)) -, _currentValue(startValue) { - initArcs(std::move(thresholds)); -} - -ArcsAnimation::ArcsAnimation( - const style::ArcsAnimation &st, - std::vector thresholds, - float64 startValue, - HorizontalDirection direction) -: _st(st) -, _horizontalDirection(direction) -, _verticalDirection(VerticalDirection::None) -, _startAngle((st.deltaAngle - + ((direction == HorizontalDirection::Left) ? 180 : 0)) * 16) +, _direction(direction) +, _startAngle(16 + * (st.deltaAngle + + ((direction == Direction::Up) + ? 90 + : (direction == Direction::Down) + ? 270 + : (direction == Direction::Left) + ? 180 + : 0))) , _spanAngle(-st.deltaAngle * 2 * 16) , _emptyRect(computeArcRect(0)) , _currentValue(startValue) { @@ -74,20 +64,24 @@ void ArcsAnimation::initArcs(std::vector thresholds) { } } +bool ArcsAnimation::isHorizontal() const { + return _direction == Direction::Left || _direction == Direction::Right; +} + QRectF ArcsAnimation::computeArcRect(int index) const { const auto w = _st.startWidth + _st.deltaWidth * index; const auto h = _st.startHeight + _st.deltaHeight * index; - if (_horizontalDirection != HorizontalDirection::None) { + if (isHorizontal()) { auto rect = QRectF(0, -h / 2.0, w, h); - if (_horizontalDirection == HorizontalDirection::Right) { + if (_direction == Direction::Right) { rect.moveRight(index * _st.space); } else { rect.moveLeft(-index * _st.space); } return rect; - } else if (_verticalDirection != VerticalDirection::None) { + } else { auto rect = QRectF(-w / 2.0, 0, w, h); - if (_verticalDirection == VerticalDirection::Up) { + if (_direction == Direction::Up) { rect.moveTop(-index * _st.space); } else { rect.moveBottom(index * _st.space); diff --git a/ui/paint/arcs.h b/ui/paint/arcs.h index a1275a6..022b6d6 100644 --- a/ui/paint/arcs.h +++ b/ui/paint/arcs.h @@ -15,29 +15,18 @@ namespace Ui::Paint { class ArcsAnimation { public: - enum class HorizontalDirection { - Left, - Right, - None, - }; - - enum class VerticalDirection { + enum class Direction { Up, Down, - None, + Left, + Right, }; ArcsAnimation( const style::ArcsAnimation &st, std::vector thresholds, float64 startValue, - VerticalDirection direction); - - ArcsAnimation( - const style::ArcsAnimation &st, - std::vector thresholds, - float64 startValue, - HorizontalDirection direction); + Direction direction); void paint( Painter &p, @@ -69,6 +58,7 @@ private: void initArcs(std::vector thresholds); QRectF computeArcRect(int index) const; + bool isHorizontal() const; bool isArcFinished(const Arc &arc) const; void updateArcStartTime( @@ -77,8 +67,7 @@ private: crl::time now); const style::ArcsAnimation &_st; - const HorizontalDirection _horizontalDirection; - const VerticalDirection _verticalDirection; + const Direction _direction; const int _startAngle; const int _spanAngle; const QRectF _emptyRect; From 4dd6444cb21688fcb1a19f0cb107e8198bdb1bad Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 1 Feb 2021 08:34:32 +0300 Subject: [PATCH 44/60] Added ability to call RippleButton::paintRipple with QPoint. --- ui/widgets/buttons.cpp | 13 ++++++++++--- ui/widgets/buttons.h | 10 +++++++++- ui/widgets/call_button.cpp | 2 +- ui/widgets/checkbox.cpp | 6 +----- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/ui/widgets/buttons.cpp b/ui/widgets/buttons.cpp index 10df939..a76aab0 100644 --- a/ui/widgets/buttons.cpp +++ b/ui/widgets/buttons.cpp @@ -120,6 +120,13 @@ void RippleButton::setForceRippled( update(); } +void RippleButton::paintRipple( + QPainter &p, + const QPoint &point, + const QColor *colorOverride) { + paintRipple(p, point.x(), point.y(), colorOverride); +} + void RippleButton::paintRipple(QPainter &p, int x, int y, const QColor *colorOverride) { if (_ripple) { _ripple->paint(p, x, y, width(), colorOverride); @@ -365,7 +372,7 @@ void RoundButton::paintEvent(QPaintEvent *e) { drawRect(_roundRectOver); } - paintRipple(p, rounded.x(), rounded.y()); + paintRipple(p, rounded.topLeft()); p.setFont(_st.font); const auto textTop = _st.padding.top() + _st.textTop; @@ -438,7 +445,7 @@ void IconButton::setRippleColorOverride(const style::color *colorOverride) { void IconButton::paintEvent(QPaintEvent *e) { Painter p(this); - paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), _rippleColorOverride ? &(*_rippleColorOverride)->c : nullptr); + paintRipple(p, _st.rippleAreaPosition, _rippleColorOverride ? &(*_rippleColorOverride)->c : nullptr); auto down = isDown(); auto overIconOpacity = (down || forceRippled()) ? 1. : _a_over.value(isOver() ? 1. : 0.); @@ -554,7 +561,7 @@ void CrossButton::paintEvent(QPaintEvent *e) { auto shown = _showAnimation.value(_shown ? 1. : 0.); p.setOpacity(shown); - paintRipple(p, _st.crossPosition.x(), _st.crossPosition.y()); + paintRipple(p, _st.crossPosition); auto loading = 0.; if (_loadingAnimation.animating()) { diff --git a/ui/widgets/buttons.h b/ui/widgets/buttons.h index 3d21d55..64354fc 100644 --- a/ui/widgets/buttons.h +++ b/ui/widgets/buttons.h @@ -58,7 +58,15 @@ public: void clearState() override; - void paintRipple(QPainter &p, int x, int y, const QColor *colorOverride = nullptr); + void paintRipple( + QPainter &p, + const QPoint &point, + const QColor *colorOverride = nullptr); + void paintRipple( + QPainter &p, + int x, + int y, + const QColor *colorOverride = nullptr); void finishAnimating(); diff --git a/ui/widgets/call_button.cpp b/ui/widgets/call_button.cpp index 25bf401..638c67e 100644 --- a/ui/widgets/call_button.cpp +++ b/ui/widgets/call_button.cpp @@ -153,7 +153,7 @@ void CallButton::paintEvent(QPaintEvent *e) { } else { rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress); } - paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride); + paintRipple(p, _stFrom->button.rippleAreaPosition, rippleColorOverride); auto positionFrom = iconPosition(_stFrom); if (paintFrom) { diff --git a/ui/widgets/checkbox.cpp b/ui/widgets/checkbox.cpp index 7113f91..29014e9 100644 --- a/ui/widgets/checkbox.cpp +++ b/ui/widgets/checkbox.cpp @@ -570,11 +570,7 @@ void Checkbox::paintEvent(QPaintEvent *e) { p.setOpacity(_st.disabledOpacity); } else { auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active); - paintRipple( - p, - check.x() + _st.rippleAreaPosition.x(), - check.y() + _st.rippleAreaPosition.y(), - &color); + paintRipple(p, check.topLeft() + _st.rippleAreaPosition, &color); } auto realCheckRect = myrtlrect(check); From 52e1d6d7405ccddf90f206a7625f6f071847ffb6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 1 Feb 2021 08:18:50 +0300 Subject: [PATCH 45/60] Added ability to cache both colorized and default CrossLineAnimation. --- ui/effects/cross_line.cpp | 10 +++++++--- ui/effects/cross_line.h | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ui/effects/cross_line.cpp b/ui/effects/cross_line.cpp index 4c70455..9f92016 100644 --- a/ui/effects/cross_line.cpp +++ b/ui/effects/cross_line.cpp @@ -43,11 +43,14 @@ void CrossLineAnimation::paint( _st.icon.paint(p, left, top, _st.icon.width()); } } else if (progress == 1.) { - if (_completeCross.isNull()) { + auto &complete = colorOverride + ? _completeCrossOverride + : _completeCross; + if (complete.isNull()) { fillFrame(progress, colorOverride); - _completeCross = _frame; + complete = _frame; } - p.drawImage(left, top, _completeCross); + p.drawImage(left, top, complete); } else { fillFrame(progress, colorOverride); p.drawImage(left, top, _frame); @@ -94,6 +97,7 @@ void CrossLineAnimation::fillFrame( void CrossLineAnimation::invalidate() { _completeCross = QImage(); + _completeCrossOverride = QImage(); } } // namespace Ui diff --git a/ui/effects/cross_line.h b/ui/effects/cross_line.h index d1336c7..93981b4 100644 --- a/ui/effects/cross_line.h +++ b/ui/effects/cross_line.h @@ -43,6 +43,7 @@ private: QLineF _line; QImage _frame; QImage _completeCross; + QImage _completeCrossOverride; }; From 6acc6f0483f6c91ba75d644013ed009302cbdbaa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 1 Feb 2021 09:26:02 +0300 Subject: [PATCH 46/60] Fixed invalidation of CrossLineAnimation. --- ui/effects/cross_line.cpp | 1 + ui/effects/cross_line.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/effects/cross_line.cpp b/ui/effects/cross_line.cpp index 9f92016..9eb95b6 100644 --- a/ui/effects/cross_line.cpp +++ b/ui/effects/cross_line.cpp @@ -98,6 +98,7 @@ void CrossLineAnimation::fillFrame( void CrossLineAnimation::invalidate() { _completeCross = QImage(); _completeCrossOverride = QImage(); + _strokePen = QPen(_st.fg, _st.stroke, Qt::SolidLine, Qt::RoundCap); } } // namespace Ui diff --git a/ui/effects/cross_line.h b/ui/effects/cross_line.h index 93981b4..f8a27ea 100644 --- a/ui/effects/cross_line.h +++ b/ui/effects/cross_line.h @@ -39,7 +39,7 @@ private: const style::CrossLineAnimation &_st; const bool _reversed; const QPen _transparentPen; - const QPen _strokePen; + QPen _strokePen; QLineF _line; QImage _frame; QImage _completeCross; From e2633c4b6f29f04dbf95d47c78d3ef20b043b7a9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 2 Feb 2021 21:56:43 +0300 Subject: [PATCH 47/60] Fixed updating selected item when menu is moved. --- ui/widgets/menu/menu.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index 5fa1e8c..b30ef83 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -49,6 +49,11 @@ void Menu::init() { Painter p(this); p.fillRect(clip, _st.itemBg); }, lifetime()); + + positionValue( + ) | rpl::start_with_next([=] { + handleMouseMove(QCursor::pos()); + }, lifetime()); } not_null Menu::addAction( From 6500dc961055e797fc169e0fa308d4f111155fe7 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 1 Feb 2021 14:22:36 +0400 Subject: [PATCH 48/60] Qt-based title widget ported from tdesktop --- CMakeLists.txt | 9 + icons/calls/call_shadow_left.png | Bin 0 -> 100 bytes icons/calls/call_shadow_left@2x.png | Bin 0 -> 125 bytes icons/calls/call_shadow_left@3x.png | Bin 0 -> 139 bytes icons/calls/call_shadow_top.png | Bin 0 -> 103 bytes icons/calls/call_shadow_top@2x.png | Bin 0 -> 127 bytes icons/calls/call_shadow_top@3x.png | Bin 0 -> 141 bytes icons/calls/call_shadow_top_left.png | Bin 0 -> 295 bytes icons/calls/call_shadow_top_left@2x.png | Bin 0 -> 559 bytes icons/calls/call_shadow_top_left@3x.png | Bin 0 -> 927 bytes .../linux/ui_linux_wayland_integration.cpp | 46 +++ .../linux/ui_linux_wayland_integration.h | 25 ++ .../ui_linux_wayland_integration_dummy.cpp | 29 ++ ui/platform/linux/ui_utility_linux.cpp | 251 +++++++++++++ ui/platform/linux/ui_utility_linux.h | 2 + ui/platform/mac/ui_utility_mac.h | 17 + ui/platform/mac/ui_utility_mac.mm | 10 + ui/platform/ui_platform_utility.h | 8 + ui/platform/ui_platform_window.cpp | 271 +++++++++++++- ui/platform/ui_platform_window.h | 33 +- ui/platform/ui_platform_window_title.cpp | 334 ++++++++++++++++++ ui/platform/ui_platform_window_title.h | 100 ++++++ ui/platform/win/ui_utility_win.cpp | 22 ++ ui/platform/win/ui_utility_win.h | 14 + ui/platform/win/ui_window_title_win.cpp | 164 --------- ui/platform/win/ui_window_title_win.h | 36 +- ui/widgets/widgets.style | 14 + 27 files changed, 1177 insertions(+), 208 deletions(-) create mode 100644 icons/calls/call_shadow_left.png create mode 100644 icons/calls/call_shadow_left@2x.png create mode 100644 icons/calls/call_shadow_left@3x.png create mode 100644 icons/calls/call_shadow_top.png create mode 100644 icons/calls/call_shadow_top@2x.png create mode 100644 icons/calls/call_shadow_top@3x.png create mode 100644 icons/calls/call_shadow_top_left.png create mode 100644 icons/calls/call_shadow_top_left@2x.png create mode 100644 icons/calls/call_shadow_top_left@3x.png create mode 100644 ui/platform/linux/ui_linux_wayland_integration.cpp create mode 100644 ui/platform/linux/ui_linux_wayland_integration.h create mode 100644 ui/platform/linux/ui_linux_wayland_integration_dummy.cpp create mode 100644 ui/platform/ui_platform_window_title.cpp create mode 100644 ui/platform/ui_platform_window_title.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 770ba1a..b48b8f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,8 @@ PRIVATE ui/paint/blobs.h ui/paint/blobs_linear.cpp ui/paint/blobs_linear.h + ui/platform/linux/ui_linux_wayland_integration.cpp + ui/platform/linux/ui_linux_wayland_integration.h ui/platform/linux/ui_window_linux.cpp ui/platform/linux/ui_window_linux.h ui/platform/linux/ui_utility_linux.cpp @@ -95,6 +97,8 @@ PRIVATE ui/platform/win/ui_window_win.h ui/platform/win/ui_utility_win.cpp ui/platform/win/ui_utility_win.h + ui/platform/ui_platform_window_title.cpp + ui/platform/ui_platform_window_title.h ui/platform/ui_platform_window.cpp ui/platform/ui_platform_window.h ui/platform/ui_platform_utility.h @@ -228,6 +232,11 @@ if (NOT DESKTOP_APP_USE_PACKAGED_FONTS) nice_target_sources(lib_ui ${src_loc} PRIVATE fonts/fonts.qrc) endif() +if (DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION) + remove_target_sources(Telegram ${src_loc} ui/platform/linux/ui_linux_wayland_integration.cpp) + nice_target_sources(Telegram ${src_loc} PRIVATE ui/platform/linux/ui_linux_wayland_integration_dummy.cpp) +endif() + target_include_directories(lib_ui PUBLIC ${src_loc} diff --git a/icons/calls/call_shadow_left.png b/icons/calls/call_shadow_left.png new file mode 100644 index 0000000000000000000000000000000000000000..74864ad4b58577ca3a3d1856559ea719998b84c5 GIT binary patch literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^5S&IOG=oClave$R06yz;H^)?a3(FUic47BRliXODFQYGd$p^>bP0l+XkK{=Q^Ny44$rjF6*2UngGhbD!Kpw literal 0 HcmV?d00001 diff --git a/icons/calls/call_shadow_left@3x.png b/icons/calls/call_shadow_left@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..30257ebe7755d12085cb5c309c4fad0dd42aa4cc GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^9ze{@!2~4t3*6feq$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~-c73q4qIEGZ*N=ivcNN{2G@$osqqbA{yHkoCER8yw-9aWwy k3_d)EMR=n78CfJ4gajDgI;9(g0yQ#ty85}Sb4q9e0F2NjR{#J2 literal 0 HcmV?d00001 diff --git a/icons/calls/call_shadow_top.png b/icons/calls/call_shadow_top.png new file mode 100644 index 0000000000000000000000000000000000000000..653e0afa994e839c76ca58e1a20333f70799a448 GIT binary patch literal 103 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{G!3HF)&rH7sr1U&p978x}=AL%sVld!1a-g^S z$A9tb-4}xPoY534U<$ZVck;}_Rb_df_8w5kT*`Q0HIwMytuLj4IvG4&{an^LB{Ts5 D!GI#Y literal 0 HcmV?d00001 diff --git a/icons/calls/call_shadow_top@2x.png b/icons/calls/call_shadow_top@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..47c672ca3b028dbf40d89d5aa5825b95344fc3d6 GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^Oh9bF!3HF)SyndzDK}3S#}J9BdnXw3F(_~_dpG{K zj}$-JG121Wl{=0c91Uy?M>K>d+UmF(eVyYHI@zjormCo72uu0?l}USU$Sv8CG->+w bKRmJqnM^Htf2OejO=R$N^>bP0l+XkK@!lpq literal 0 HcmV?d00001 diff --git a/icons/calls/call_shadow_top@3x.png b/icons/calls/call_shadow_top@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..350db0481b414a97a62b64e5b4fce8198d667177 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^%s}kH!2~4tT)p8Aq$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~-c73q7rIEGZ*N=j;A6mxTP6N>cl@liRYbY#QEj!<)p$SWUJ nSs$KhG1n8^ba*DKKmvo#Nyck0LQ7qMS{XcD{an^LB{Ts5gEA{X literal 0 HcmV?d00001 diff --git a/icons/calls/call_shadow_top_left.png b/icons/calls/call_shadow_top_left.png new file mode 100644 index 0000000000000000000000000000000000000000..baba49364f09da2655e03782fd31be9da4594658 GIT binary patch literal 295 zcmV+?0oeYDP)uNtfcakpfN-YxxTVa_qeZ)~hNXY1VB5$cMUfGE;=&cG6YgDi$! zop}W)fof;M5>SbnnS9UOJOG(EgxvjlXIOOe$pOU7Y`FXFS-E1J3!s|G!fUPJ?l%y- zQN=D6=6E2E4M+r4G07=Hjc4T2IpqmK|lZi002ovPDHLkV1fWgeZv3% literal 0 HcmV?d00001 diff --git a/icons/calls/call_shadow_top_left@2x.png b/icons/calls/call_shadow_top_left@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0d9672fb51b6dd970b3413917c45f15c5453534d GIT binary patch literal 559 zcmV+~0?_@5P)Ec#O%U3)F~-mMV(o7mSk>o^n{M0IHrH(c zt0vX!+W^=NJ6C?y(_1ivWOh##dtVwi^=2E_!B?DxP+oO73kp#e2 z4jHciMSY$qfU-TjqteIbZvjxuN9PQR8D~Ht01G)XjLfSr89{)^JTg7T82i3UFbG8P zBLOH-s6`Mez)}*kL{&`BpAnFS$QTa+keGRPWYK5`U`c3Ao^AfDM57fzNv6m=1F-b%Y~@0pCiN;%*q?A(syxSU5@N&$#qXbwax?`@;g{0xD%R!1IO z09Y8K>f=R#K~y3T0(e5nX19+8IgXL?0cn?YD5$|(ILMtzm%!FinLi+rR2qKl$LR$*)m0G7}e!h?!T-%^DT1T)CYD-ac>--)9!tSJl727nAJhE$`dFQc|! zXjsIe)n18!44|4q$*s&)(JjA`Rn1g%0f@?`RmP-J1gdf)@Z<`vDz%0FB3Nq+{@elB}egPU73!TJ&h#UX_002ovPDHLkV1h57@K68% literal 0 HcmV?d00001 diff --git a/icons/calls/call_shadow_top_left@3x.png b/icons/calls/call_shadow_top_left@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..260bb2658094fd232fe80415fdbfa6e624f5214e GIT binary patch literal 927 zcmV;Q17Q4#P)x=B%+b((NP+-79ieeick9*wXEyl4wf@4YyX-nv@*K778NZ-WLTwsx)KLeN>WjR4r zV3kngPb9se0>No3fHmA+?pMzoBGfbxl@)i97S&|lzJBG=oxnPu!trm^;+bgoRYZZH z>0fYY1!KYKq2ejRqY4C9dpjw*o}nr*&LLhlT7U%~uo|>Aon-Vm+;Rw*N`%tXNsAS$ zi(KwJ&l3|>rPZ{?4B6C@!1A6l3HxVSYU2m%jmByTFY`m~1aMxJqxrz_gr+k=0?QFg zUwr}$ld)8SW*cm#tpgVHS!+mU+>~Z|i0?p!MiH|!NV-z{{#wuVg=9YR4_#vZ< zA(0!*{jnye0CRPi2`LdvsO2c&IS$OlnX1fP#ipXo3=}XHb=!aiD^58>7#^%i)%QD% zF}X!JZCAKPC+)#<%dw1gwFcDE1BYI?dOn|Y`dxqV+OTec#@3{!h=o0A^bsjWfK`_* zOn7V$ONdm`&i|uIlhc94YZ*1ZqBIp1TcE8JWJ*2O(1CNa`wOiqR9Re?bOhYdZ7n8M zftDtZ@W4jzI%I#hIK{cL?u}P!yde`%7;38g(y zP0D3JO1-~lnrdI|1o??%x}b6!2@Eg;>g9M%ZQ-HZQmE?f0! zS&ExsXZO3aW&N1?IfS|{V~X&h$Sd5PonXKi@CMfmd#2g>_|kDU7__^BfxbvX-{DOe zTky^0AC{t}H1(+bUZ6+M^j>YBLWdaZ$(26OJiZoK(cVT&*vwc&$+OkIL5f4XVHPmj zfeZ$^*pSW{I=o%+GlA`03~$D6U^lQESkw3sU;yU*jCdo?|6>3E002ovPDHLkV1l1} Bw%Gsx literal 0 HcmV?d00001 diff --git a/ui/platform/linux/ui_linux_wayland_integration.cpp b/ui/platform/linux/ui_linux_wayland_integration.cpp new file mode 100644 index 0000000..7cb7dd1 --- /dev/null +++ b/ui/platform/linux/ui_linux_wayland_integration.cpp @@ -0,0 +1,46 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/platform/linux/ui_linux_wayland_integration.h" + +#include "base/platform/base_platform_info.h" + +#include + +#include +#include +#include + +using QtWaylandClient::QWaylandWindow; + +namespace Ui { +namespace Platform { + +WaylandIntegration::WaylandIntegration() { +} + +WaylandIntegration *WaylandIntegration::Instance() { + if (!::Platform::IsWayland()) return nullptr; + static WaylandIntegration instance; + return &instance; +} + +bool WaylandIntegration::showWindowMenu(QWindow *window) { + if (const auto waylandWindow = static_cast( + window->handle())) { + if (const auto seat = waylandWindow->display()->lastInputDevice()) { + if (const auto shellSurface = waylandWindow->shellSurface()) { + return shellSurface->showWindowMenu(seat); + } + } + } + + return false; +} + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/linux/ui_linux_wayland_integration.h b/ui/platform/linux/ui_linux_wayland_integration.h new file mode 100644 index 0000000..bbc2331 --- /dev/null +++ b/ui/platform/linux/ui_linux_wayland_integration.h @@ -0,0 +1,25 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class QWindow; + +namespace Ui { +namespace Platform { + +class WaylandIntegration { +public: + static WaylandIntegration *Instance(); + bool showWindowMenu(QWindow *window); + +private: + WaylandIntegration(); +}; + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/linux/ui_linux_wayland_integration_dummy.cpp b/ui/platform/linux/ui_linux_wayland_integration_dummy.cpp new file mode 100644 index 0000000..11abd62 --- /dev/null +++ b/ui/platform/linux/ui_linux_wayland_integration_dummy.cpp @@ -0,0 +1,29 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/platform/linux/ui_linux_wayland_integration.h" + +#include "base/platform/base_platform_info.h" + +namespace Ui { +namespace Platform { + +WaylandIntegration::WaylandIntegration() { +} + +WaylandIntegration *WaylandIntegration::Instance() { + if (!::Platform::IsWayland()) return nullptr; + static WaylandIntegration instance; + return &instance; +} + +bool WaylandIntegration::showWindowMenu(QWindow *window) { + return false; +} + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index 01566c5..8e9d80e 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -8,16 +8,201 @@ #include "ui/ui_log.h" #include "base/platform/base_platform_info.h" +#include "base/platform/linux/base_xcb_utilities_linux.h" +#include "ui/platform/linux/ui_linux_wayland_integration.h" +#include "base/const_string.h" #include "base/qt_adapters.h" #include "base/flat_set.h" #include #include +#include #include #include +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION +#include +#include +#include +#include +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + +Q_DECLARE_METATYPE(QMargins); + namespace Ui { namespace Platform { +namespace { + +constexpr auto kXCBFrameExtentsAtomName = "_GTK_FRAME_EXTENTS"_cs; + +constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs; +constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs; +constexpr auto kSettingsPortalInterface = "org.freedesktop.portal.Settings"_cs; + +bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return false; + } + + const auto frameExtentsAtom = base::Platform::XCB::GetAtom( + connection, + kXCBFrameExtentsAtomName.utf16()); + + if (!frameExtentsAtom.has_value()) { + return false; + } + + const auto extentsVector = std::vector{ + uint(extents.left()), + uint(extents.right()), + uint(extents.top()), + uint(extents.bottom()), + }; + + xcb_change_property( + connection, + XCB_PROP_MODE_REPLACE, + window->winId(), + *frameExtentsAtom, + XCB_ATOM_CARDINAL, + 32, + extentsVector.size(), + extentsVector.data()); + + return true; +} + +bool UnsetXCBFrameExtents(QWindow *window) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return false; + } + + const auto frameExtentsAtom = base::Platform::XCB::GetAtom( + connection, + kXCBFrameExtentsAtomName.utf16()); + + if (!frameExtentsAtom.has_value()) { + return false; + } + + xcb_delete_property( + connection, + window->winId(), + *frameExtentsAtom); + + return true; +} + +bool ShowXCBWindowMenu(QWindow *window) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return false; + } + + const auto root = base::Platform::XCB::GetRootWindowFromQt(); + if (!root.has_value()) { + return false; + } + + const auto showWindowMenuAtom = base::Platform::XCB::GetAtom( + connection, + "_GTK_SHOW_WINDOW_MENU"); + + if (!showWindowMenuAtom.has_value()) { + return false; + } + + const auto globalPos = QCursor::pos(); + + xcb_client_message_event_t xev; + xev.response_type = XCB_CLIENT_MESSAGE; + xev.type = *showWindowMenuAtom; + xev.sequence = 0; + xev.window = window->winId(); + xev.format = 32; + xev.data.data32[0] = 0; + xev.data.data32[1] = globalPos.x(); + xev.data.data32[2] = globalPos.y(); + xev.data.data32[3] = 0; + xev.data.data32[4] = 0; + + xcb_ungrab_pointer(connection, XCB_CURRENT_TIME); + xcb_send_event( + connection, + false, + *root, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT + | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + reinterpret_cast(&xev)); + + return true; +} + +TitleControls::Control GtkKeywordToTitleControl(const QString &keyword) { + if (keyword == qstr("minimize")) { + return TitleControls::Control::Minimize; + } else if (keyword == qstr("maximize")) { + return TitleControls::Control::Maximize; + } else if (keyword == qstr("close")) { + return TitleControls::Control::Close; + } + + return TitleControls::Control::Unknown; +} + +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION +std::optional PortalTitleControlsLayout() { + auto message = QDBusMessage::createMethodCall( + kXDGDesktopPortalService.utf16(), + kXDGDesktopPortalObjectPath.utf16(), + kSettingsPortalInterface.utf16(), + "Read"); + + message.setArguments({ + "org.gnome.desktop.wm.preferences", + "button-layout" + }); + + const QDBusReply reply = QDBusConnection::sessionBus().call( + message); + + if (!reply.isValid() || !reply.value().canConvert()) { + return std::nullopt; + } + + const auto valueVariant = qvariant_cast( + reply.value()).variant(); + + if (!valueVariant.canConvert()) { + return std::nullopt; + } + + const auto valueBySides = valueVariant.toString().split(':'); + + std::vector controlsLeft; + ranges::transform( + valueBySides[0].split(','), + ranges::back_inserter(controlsLeft), + GtkKeywordToTitleControl); + + std::vector controlsRight; + if (valueBySides.size() > 1) { + ranges::transform( + valueBySides[1].split(','), + ranges::back_inserter(controlsRight), + GtkKeywordToTitleControl); + } + + return TitleControls::Layout{ + .left = controlsLeft, + .right = controlsRight + }; +} +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + +} // namespace bool IsApplicationActive() { return QApplication::activeWindow() != nullptr; @@ -50,5 +235,71 @@ bool TranslucentWindowsSupported(QPoint globalPosition) { void IgnoreAllActivation(not_null widget) { } +bool WindowExtentsSupported() { +#ifdef DESKTOP_APP_QT_PATCHED + if (::Platform::IsWayland()) { + return true; + } +#endif // DESKTOP_APP_QT_PATCHED + + namespace XCB = base::Platform::XCB; + if (!::Platform::IsWayland() + && XCB::IsSupportedByWM(kXCBFrameExtentsAtomName.utf16())) { + return true; + } + + return false; +} + +bool SetWindowExtents(QWindow *window, const QMargins &extents) { + if (::Platform::IsWayland()) { +#ifdef DESKTOP_APP_QT_PATCHED + window->setProperty("WaylandCustomMargins", QVariant::fromValue(extents)); + return true; +#else // DESKTOP_APP_QT_PATCHED + return false; +#endif // !DESKTOP_APP_QT_PATCHED + } else { + return SetXCBFrameExtents(window, extents); + } +} + +bool UnsetWindowExtents(QWindow *window) { + if (::Platform::IsWayland()) { +#ifdef DESKTOP_APP_QT_PATCHED + window->setProperty("WaylandCustomMargins", QVariant()); + return true; +#else // DESKTOP_APP_QT_PATCHED + return false; +#endif // !DESKTOP_APP_QT_PATCHED + } else { + return UnsetXCBFrameExtents(window); + } +} + +bool ShowWindowMenu(QWindow *window) { + if (const auto integration = WaylandIntegration::Instance()) { + return integration->showWindowMenu(window); + } else { + return ShowXCBWindowMenu(window); + } +} + +TitleControls::Layout TitleControlsLayout() { +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION + if (const auto portalLayout = PortalTitleControlsLayout()) { + return *portalLayout; + } +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + + return TitleControls::Layout{ + .right = { + TitleControls::Control::Minimize, + TitleControls::Control::Maximize, + TitleControls::Control::Close, + } + }; +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/linux/ui_utility_linux.h b/ui/platform/linux/ui_utility_linux.h index c9081fc..c81a814 100644 --- a/ui/platform/linux/ui_utility_linux.h +++ b/ui/platform/linux/ui_utility_linux.h @@ -6,6 +6,8 @@ // #pragma once +#include "ui/platform/ui_platform_utility.h" + class QPainter; class QPaintEvent; diff --git a/ui/platform/mac/ui_utility_mac.h b/ui/platform/mac/ui_utility_mac.h index 7d4bffe..97f3c11 100644 --- a/ui/platform/mac/ui_utility_mac.h +++ b/ui/platform/mac/ui_utility_mac.h @@ -6,6 +6,7 @@ // #pragma once +#include "ui/platform/ui_platform_utility.h" #include "base/platform/base_platform_info.h" #include @@ -23,5 +24,21 @@ inline constexpr bool UseMainQueueGeneric() { return ::Platform::IsMacStoreBuild(); } +inline bool WindowExtentsSupported() { + return false; +} + +inline bool SetWindowExtents(QWindow *window, const QMargins &extents) { + return false; +} + +inline bool UnsetWindowExtents(QWindow *window) { + return false; +} + +inline bool ShowWindowMenu(QWindow *window) { + return false; +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/mac/ui_utility_mac.mm b/ui/platform/mac/ui_utility_mac.mm index 46aeb11..fd8b492 100644 --- a/ui/platform/mac/ui_utility_mac.mm +++ b/ui/platform/mac/ui_utility_mac.mm @@ -99,5 +99,15 @@ void DrainMainQueue() { void IgnoreAllActivation(not_null widget) { } +TitleControls::Layout TitleControlsLayout() { + return TitleControls::Layout{ + .left = { + TitleControls::Control::Close, + TitleControls::Control::Minimize, + TitleControls::Control::Maximize, + } + }; +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/ui_platform_utility.h b/ui/platform/ui_platform_utility.h index a8eaedd..e8629a1 100644 --- a/ui/platform/ui_platform_utility.h +++ b/ui/platform/ui_platform_utility.h @@ -6,6 +6,8 @@ // #pragma once +#include "ui/platform/ui_platform_window_title.h" + class QPoint; class QPainter; class QPaintEvent; @@ -30,6 +32,12 @@ void IgnoreAllActivation(not_null widget); [[nodiscard]] constexpr bool UseMainQueueGeneric(); void DrainMainQueue(); // Needed only if UseMainQueueGeneric() is false. +[[nodiscard]] bool WindowExtentsSupported(); +bool SetWindowExtents(QWindow *window, const QMargins &extents); +bool UnsetWindowExtents(QWindow *window); +bool ShowWindowMenu(QWindow *window); +[[nodiscard]] TitleControls::Layout TitleControlsLayout(); + } // namespace Platform } // namespace Ui diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index c1b88eb..d6a6e18 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -6,21 +6,31 @@ // #include "ui/platform/ui_platform_window.h" +#include "ui/platform/ui_platform_window_title.h" +#include "ui/platform/ui_platform_utility.h" #include "ui/widgets/window.h" +#include "ui/widgets/shadow.h" +#include "ui/painter.h" +#include "styles/style_widgets.h" +#include "styles/style_layers.h" +#include #include #include namespace Ui { namespace Platform { +namespace { + +[[nodiscard]] const style::Shadow &Shadow() { + return st::callShadow; +} + +} // namespace BasicWindowHelper::BasicWindowHelper(not_null window) : _window(window) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) _window->setWindowFlag(Qt::Window); -#else // Qt >= 5.9 - _window->setWindowFlags(_window->windowFlags() | Qt::Window); -#endif // Qt >= 5.9 } not_null BasicWindowHelper::body() { @@ -100,8 +110,6 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() { && (static_cast(e.get())->button() == Qt::LeftButton)) { _mousePressed = true; - -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED } else if (e->type() == QEvent::MouseMove) { const auto mouseEvent = static_cast(e.get()); if (_mousePressed @@ -109,7 +117,6 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() { && !_window->isFullScreen() #endif // !Q_OS_WIN && (hitTest() & WindowTitleHitTestFlag::Move)) { - #ifdef Q_OS_WIN if (_window->isFullScreen()) { // On Windows we just jump out of fullscreen @@ -121,10 +128,258 @@ void BasicWindowHelper::setupBodyTitleAreaEvents() { _mousePressed = false; _window->windowHandle()->startSystemMove(); } -#endif // Qt >= 5.15 || DESKTOP_APP_QT_PATCHED } }, body()->lifetime()); } +DefaultWindowHelper::DefaultWindowHelper(not_null window) +: BasicWindowHelper(window) +, _title(Ui::CreateChild(window.get())) +, _body(Ui::CreateChild(window.get())) { + init(); +} + +void DefaultWindowHelper::init() { + window()->setWindowFlag(Qt::FramelessWindowHint); + + if (WindowExtentsSupported()) { + window()->setAttribute(Qt::WA_TranslucentBackground); + } + + window()->widthValue( + ) | rpl::start_with_next([=](int width) { + _title->setGeometry( + resizeArea().left(), + resizeArea().top(), + width - resizeArea().left() - resizeArea().right(), + _title->st()->height); + }, _title->lifetime()); + + rpl::combine( + window()->sizeValue(), + _title->heightValue() + ) | rpl::start_with_next([=](QSize size, int titleHeight) { + const auto sizeWithoutMargins = size + .shrunkBy({ 0, titleHeight, 0, 0 }) + .shrunkBy(resizeArea()); + + const auto topLeft = QPoint( + resizeArea().left(), + resizeArea().top() + titleHeight); + + _body->setGeometry(QRect(topLeft, sizeWithoutMargins)); + }, _body->lifetime()); + + window()->paintRequest( + ) | rpl::start_with_next([=] { + if (resizeArea().isNull()) { + return; + } + + Painter p(window()); + + if (hasShadow()) { + Ui::Shadow::paint( + p, + QRect(QPoint(), window()->size()).marginsRemoved(resizeArea()), + window()->width(), + Shadow()); + } else { + paintBorders(p); + } + }, window()->lifetime()); + + window()->shownValue( + ) | rpl::start_with_next([=](bool shown) { + if (shown) { + updateWindowExtents(); + } + }, window()->lifetime()); + + window()->events() | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::MouseButtonPress) { + const auto mouseEvent = static_cast(e.get()); + const auto currentPoint = mouseEvent->windowPos().toPoint(); + const auto edges = edgesFromPos(currentPoint); + + if (mouseEvent->button() == Qt::LeftButton && edges) { + window()->windowHandle()->startSystemResize(edges); + } + } else if (e->type() == QEvent::Move + || e->type() == QEvent::Resize + || e->type() == QEvent::WindowStateChange) { + updateWindowExtents(); + } + }, window()->lifetime()); + + QCoreApplication::instance()->installEventFilter(this); +} + +not_null DefaultWindowHelper::body() { + return _body; +} + +bool DefaultWindowHelper::hasShadow() const { + const auto center = window()->geometry().center(); + return WindowExtentsSupported() && TranslucentWindowsSupported(center); +} + +QMargins DefaultWindowHelper::resizeArea() const { + if (window()->isMaximized() || window()->isFullScreen()) { + return QMargins(); + } + + return Shadow().extend; +} + +Qt::Edges DefaultWindowHelper::edgesFromPos(const QPoint &pos) const { + if (pos.x() <= resizeArea().left()) { + if (pos.y() <= resizeArea().top()) { + return Qt::LeftEdge | Qt::TopEdge; + } else if (pos.y() >= (window()->height() - resizeArea().bottom())) { + return Qt::LeftEdge | Qt::BottomEdge; + } + + return Qt::LeftEdge; + } else if (pos.x() >= (window()->width() - resizeArea().right())) { + if (pos.y() <= resizeArea().top()) { + return Qt::RightEdge | Qt::TopEdge; + } else if (pos.y() >= (window()->height() - resizeArea().bottom())) { + return Qt::RightEdge | Qt::BottomEdge; + } + + return Qt::RightEdge; + } else if (pos.y() <= resizeArea().top()) { + return Qt::TopEdge; + } else if (pos.y() >= (window()->height() - resizeArea().bottom())) { + return Qt::BottomEdge; + } else { + return Qt::Edges(); + } +} + +bool DefaultWindowHelper::eventFilter(QObject *obj, QEvent *e) { + // doesn't work with RpWidget::events() for some reason + if (e->type() == QEvent::MouseMove + && obj->isWidgetType() + && static_cast(window()) == static_cast(obj)) { + const auto mouseEvent = static_cast(e); + const auto currentPoint = mouseEvent->windowPos().toPoint(); + const auto edges = edgesFromPos(currentPoint); + + if (mouseEvent->buttons() == Qt::NoButton) { + updateCursor(edges); + } + } + + return QObject::eventFilter(obj, e); +} + +void DefaultWindowHelper::setTitle(const QString &title) { + _title->setText(title); + window()->setWindowTitle(title); +} + +void DefaultWindowHelper::setTitleStyle(const style::WindowTitle &st) { + _title->setStyle(st); + _title->setGeometry( + resizeArea().left(), + resizeArea().top(), + window()->width() - resizeArea().left() - resizeArea().right(), + _title->st()->height); +} + +void DefaultWindowHelper::setMinimumSize(QSize size) { + const auto sizeWithMargins = size + .grownBy({ 0, _title->height(), 0, 0 }) + .grownBy(resizeArea()); + window()->setMinimumSize(sizeWithMargins); +} + +void DefaultWindowHelper::setFixedSize(QSize size) { + const auto sizeWithMargins = size + .grownBy({ 0, _title->height(), 0, 0 }) + .grownBy(resizeArea()); + window()->setFixedSize(sizeWithMargins); + _title->setResizeEnabled(false); +} + +void DefaultWindowHelper::setGeometry(QRect rect) { + window()->setGeometry(rect + .marginsAdded({ 0, _title->height(), 0, 0 }) + .marginsAdded(resizeArea())); +} + +void DefaultWindowHelper::paintBorders(QPainter &p) { + const auto titleBackground = window()->isActiveWindow() + ? _title->st()->bgActive + : _title->st()->bg; + + const auto defaultTitleBackground = window()->isActiveWindow() + ? st::defaultWindowTitle.bgActive + : st::defaultWindowTitle.bg; + + const auto borderColor = QBrush(titleBackground).isOpaque() + ? titleBackground + : defaultTitleBackground; + + p.fillRect( + 0, + resizeArea().top(), + resizeArea().left(), + window()->height() - resizeArea().top() - resizeArea().bottom(), + borderColor); + + p.fillRect( + window()->width() - resizeArea().right(), + resizeArea().top(), + resizeArea().right(), + window()->height() - resizeArea().top() - resizeArea().bottom(), + borderColor); + + p.fillRect( + 0, + 0, + window()->width(), + resizeArea().top(), + borderColor); + + p.fillRect( + 0, + window()->height() - resizeArea().bottom(), + window()->width(), + resizeArea().bottom(), + borderColor); +} + +void DefaultWindowHelper::updateWindowExtents() { + if (hasShadow()) { + Platform::SetWindowExtents( + window()->windowHandle(), + resizeArea()); + + _extentsSet = true; + } else if (_extentsSet) { + Platform::UnsetWindowExtents(window()->windowHandle()); + _extentsSet = false; + } +} + +void DefaultWindowHelper::updateCursor(Qt::Edges edges) { + if (((edges & Qt::LeftEdge) && (edges & Qt::TopEdge)) + || ((edges & Qt::RightEdge) && (edges & Qt::BottomEdge))) { + window()->setCursor(QCursor(Qt::SizeFDiagCursor)); + } else if (((edges & Qt::LeftEdge) && (edges & Qt::BottomEdge)) + || ((edges & Qt::RightEdge) && (edges & Qt::TopEdge))) { + window()->setCursor(QCursor(Qt::SizeBDiagCursor)); + } else if ((edges & Qt::LeftEdge) || (edges & Qt::RightEdge)) { + window()->setCursor(QCursor(Qt::SizeHorCursor)); + } else if ((edges & Qt::TopEdge) || (edges & Qt::BottomEdge)) { + window()->setCursor(QCursor(Qt::SizeVerCursor)); + } else { + window()->unsetCursor(); + } +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/ui_platform_window.h b/ui/platform/ui_platform_window.h index 0844545..46e4e9c 100644 --- a/ui/platform/ui_platform_window.h +++ b/ui/platform/ui_platform_window.h @@ -20,6 +20,8 @@ using WindowTitleHitTestFlags = base::flags; namespace Platform { +class DefaultTitleWidget; + class BasicWindowHelper { public: explicit BasicWindowHelper(not_null window); @@ -57,6 +59,35 @@ private: }; +class DefaultWindowHelper final : public QObject, public BasicWindowHelper { +public: + explicit DefaultWindowHelper(not_null window); + + not_null body() override; + void setTitle(const QString &title) override; + void setTitleStyle(const style::WindowTitle &st) override; + void setMinimumSize(QSize size) override; + void setFixedSize(QSize size) override; + void setGeometry(QRect rect) override; + +protected: + bool eventFilter(QObject *obj, QEvent *e) override; + +private: + void init(); + [[nodiscard]] bool hasShadow() const; + [[nodiscard]] QMargins resizeArea() const; + [[nodiscard]] Qt::Edges edgesFromPos(const QPoint &pos) const; + void paintBorders(QPainter &p); + void updateWindowExtents(); + void updateCursor(Qt::Edges edges); + + const not_null _title; + const not_null _body; + bool _extentsSet = false; + +}; + [[nodiscard]] std::unique_ptr CreateSpecialWindowHelper( not_null window); @@ -65,7 +96,7 @@ private: if (auto special = CreateSpecialWindowHelper(window)) { return special; } - return std::make_unique(window); + return std::make_unique(window); } } // namespace Platform diff --git a/ui/platform/ui_platform_window_title.cpp b/ui/platform/ui_platform_window_title.cpp new file mode 100644 index 0000000..e8de516 --- /dev/null +++ b/ui/platform/ui_platform_window_title.cpp @@ -0,0 +1,334 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/platform/ui_platform_window_title.h" + +#include "ui/platform/ui_platform_utility.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/shadow.h" +#include "ui/ui_utility.h" +#include "styles/style_widgets.h" +#include "styles/palette.h" +#include "base/algorithm.h" +#include "base/event_filter.h" + +#include +#include +#include + +namespace Ui { +namespace Platform { + +TitleControls::TitleControls( + not_null parent, + const style::WindowTitle &st, + Fn maximize) +: _st(&st) +, _minimize(parent, _st->minimize) +, _maximizeRestore(parent, _st->maximize) +, _close(parent, _st->close) +, _maximizedState(parent->windowState() + & (Qt::WindowMaximized | Qt::WindowFullScreen)) +, _activeState(parent->isActiveWindow()) { + init(std::move(maximize)); + + _close->paintRequest( + ) | rpl::start_with_next([=] { + const auto active = window()->isActiveWindow(); + if (_activeState != active) { + _activeState = active; + updateButtonsState(); + } + }, _close->lifetime()); +} + +void TitleControls::setStyle(const style::WindowTitle &st) { + _st = &st; + updateButtonsState(); +} + +not_null TitleControls::st() const { + return _st; +} + +QRect TitleControls::geometry() const { + auto result = QRect(); + const auto add = [&](auto &&control) { + if (!control->isHidden()) { + result = result.united(control->geometry()); + } + }; + add(_minimize); + add(_maximizeRestore); + add(_close); + return result; +} + +not_null TitleControls::parent() const { + return static_cast(_close->parentWidget()); +} + +not_null TitleControls::window() const { + return _close->window(); +} + +void TitleControls::init(Fn maximize) { + _minimize->setClickedCallback([=] { + window()->setWindowState( + window()->windowState() | Qt::WindowMinimized); + _minimize->clearState(); + }); + _minimize->setPointerCursor(false); + _maximizeRestore->setClickedCallback([=] { + if (maximize) { + maximize(!_maximizedState); + } else { + window()->setWindowState(_maximizedState + ? Qt::WindowNoState + : Qt::WindowMaximized); + } + _maximizeRestore->clearState(); + }); + _maximizeRestore->setPointerCursor(false); + _close->setClickedCallback([=] { + window()->close(); + _close->clearState(); + }); + _close->setPointerCursor(false); + + parent()->widthValue( + ) | rpl::start_with_next([=](int width) { + updateControlsPosition(); + }, _close->lifetime()); + + const auto winIdEventFilter = std::make_shared(nullptr); + *winIdEventFilter = base::install_event_filter( + window(), + [=](not_null e) { + if (!*winIdEventFilter || e->type() != QEvent::WinIdChange) { + return base::EventFilterResult::Continue; + } + + QObject::connect( + window()->windowHandle(), + &QWindow::windowStateChanged, + [=](Qt::WindowState state) { + handleWindowStateChanged(state); + }); + + base::take(*winIdEventFilter)->deleteLater(); + return base::EventFilterResult::Continue; + }); + + _activeState = parent()->isActiveWindow(); + updateButtonsState(); +} + +void TitleControls::setResizeEnabled(bool enabled) { + _resizeEnabled = enabled; + updateControlsPosition(); +} + +void TitleControls::raise() { + _minimize->raise(); + _maximizeRestore->raise(); + _close->raise(); +} + +void TitleControls::updateControlsPosition() { + const auto controlsLayout = TitleControlsLayout(); + auto controlsLeft = controlsLayout.left; + auto controlsRight = controlsLayout.right; + + if (!_resizeEnabled) { + controlsLeft.erase( + ranges::remove(controlsLeft, Control::Maximize), + end(controlsLeft)); + + controlsRight.erase( + ranges::remove(controlsRight, Control::Maximize), + end(controlsRight)); + } + + if (ranges::contains(controlsLeft, Control::Minimize) + || ranges::contains(controlsRight, Control::Minimize)) { + _minimize->show(); + } else { + _minimize->hide(); + } + + if (ranges::contains(controlsLeft, Control::Maximize) + || ranges::contains(controlsRight, Control::Maximize)) { + _maximizeRestore->show(); + } else { + _maximizeRestore->hide(); + } + + if (ranges::contains(controlsLeft, Control::Close) + || ranges::contains(controlsRight, Control::Close)) { + _close->show(); + } else { + _close->hide(); + } + + updateControlsPositionBySide(controlsLeft, false); + updateControlsPositionBySide(controlsRight, true); +} + +void TitleControls::updateControlsPositionBySide( + const std::vector &controls, + bool right) { + const auto preparedControls = right + ? (ranges::view::reverse(controls) | ranges::to_vector) + : controls; + + auto position = 0; + for (const auto &control : preparedControls) { + switch (control) { + case Control::Minimize: + if (right) { + _minimize->moveToRight(position, 0); + } else { + _minimize->moveToLeft(position, 0); + } + + position += _minimize->width(); + break; + case Control::Maximize: + if (right) { + _maximizeRestore->moveToRight(position, 0); + } else { + _maximizeRestore->moveToLeft(position, 0); + } + + position += _maximizeRestore->width(); + break; + case Control::Close: + if (right) { + _close->moveToRight(position, 0); + } else { + _close->moveToLeft(position, 0); + } + + position += _close->width(); + break; + } + } +} + +void TitleControls::handleWindowStateChanged(Qt::WindowState state) { + if (state == Qt::WindowMinimized) { + return; + } + + auto maximized = (state == Qt::WindowMaximized) + || (state == Qt::WindowFullScreen); + if (_maximizedState != maximized) { + _maximizedState = maximized; + updateButtonsState(); + } +} + +void TitleControls::updateButtonsState() { + const auto minimize = _activeState + ? &_st->minimizeIconActive + : &_st->minimize.icon; + const auto minimizeOver = _activeState + ? &_st->minimizeIconActiveOver + : &_st->minimize.iconOver; + _minimize->setIconOverride(minimize, minimizeOver); + if (_maximizedState) { + const auto restore = _activeState + ? &_st->restoreIconActive + : &_st->restoreIcon; + const auto restoreOver = _activeState + ? &_st->restoreIconActiveOver + : &_st->restoreIconOver; + _maximizeRestore->setIconOverride(restore, restoreOver); + } else { + const auto maximize = _activeState + ? &_st->maximizeIconActive + : &_st->maximize.icon; + const auto maximizeOver = _activeState + ? &_st->maximizeIconActiveOver + : &_st->maximize.iconOver; + _maximizeRestore->setIconOverride(maximize, maximizeOver); + } + const auto close = _activeState + ? &_st->closeIconActive + : &_st->close.icon; + const auto closeOver = _activeState + ? &_st->closeIconActiveOver + : &_st->close.iconOver; + _close->setIconOverride(close, closeOver); +} + +DefaultTitleWidget::DefaultTitleWidget(not_null parent) +: RpWidget(parent) +, _controls(this, st::defaultWindowTitle) +, _shadow(this, st::titleShadow) { + setAttribute(Qt::WA_OpaquePaintEvent); +} + +not_null DefaultTitleWidget::st() const { + return _controls.st(); +} + +void DefaultTitleWidget::setText(const QString &text) { + window()->setWindowTitle(text); +} + +void DefaultTitleWidget::setStyle(const style::WindowTitle &st) { + _controls.setStyle(st); + update(); +} + +void DefaultTitleWidget::setResizeEnabled(bool enabled) { + _controls.setResizeEnabled(enabled); +} + +void DefaultTitleWidget::paintEvent(QPaintEvent *e) { + const auto active = window()->isActiveWindow(); + QPainter(this).fillRect( + e->rect(), + active ? _controls.st()->bgActive : _controls.st()->bg); +} + +void DefaultTitleWidget::resizeEvent(QResizeEvent *e) { + _shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth); +} + +void DefaultTitleWidget::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + _mousePressed = true; + } else if (e->button() == Qt::RightButton) { + ShowWindowMenu(window()->windowHandle()); + } +} + +void DefaultTitleWidget::mouseReleaseEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + _mousePressed = false; + } +} + +void DefaultTitleWidget::mouseMoveEvent(QMouseEvent *e) { + if (_mousePressed) { + window()->windowHandle()->startSystemMove(); + } +} + +void DefaultTitleWidget::mouseDoubleClickEvent(QMouseEvent *e) { + const auto state = window()->windowState(); + if (state & Qt::WindowMaximized) { + window()->setWindowState(state & ~Qt::WindowMaximized); + } else { + window()->setWindowState(state | Qt::WindowMaximized); + } +} + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/ui_platform_window_title.h b/ui/platform/ui_platform_window_title.h new file mode 100644 index 0000000..b4f14d8 --- /dev/null +++ b/ui/platform/ui_platform_window_title.h @@ -0,0 +1,100 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/rp_widget.h" +#include "base/object_ptr.h" + +#include +#include + +namespace style { +struct WindowTitle; +} // namespace style + +namespace Ui { + +class IconButton; +class PlainShadow; + +namespace Platform { + +class TitleControls final { +public: + TitleControls( + not_null parent, + const style::WindowTitle &st, + Fn maximize = nullptr); + + void setStyle(const style::WindowTitle &st); + [[nodiscard]] not_null st() const; + [[nodiscard]] QRect geometry() const; + void setResizeEnabled(bool enabled); + void raise(); + + enum class Control { + Unknown, + Minimize, + Maximize, + Close, + }; + + struct Layout { + std::vector left; + std::vector right; + }; + +private: + [[nodiscard]] not_null parent() const; + [[nodiscard]] not_null window() const; + + void init(Fn maximize); + void updateButtonsState(); + void updateControlsPosition(); + void updateControlsPositionBySide( + const std::vector &controls, + bool right); + void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState); + + not_null _st; + + object_ptr _minimize; + object_ptr _maximizeRestore; + object_ptr _close; + + bool _maximizedState = false; + bool _activeState = false; + bool _resizeEnabled = true; + +}; + +class DefaultTitleWidget : public RpWidget { +public: + explicit DefaultTitleWidget(not_null parent); + + [[nodiscard]] not_null st() const; + void setText(const QString &text); + void setStyle(const style::WindowTitle &st); + void setResizeEnabled(bool enabled); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + +private: + TitleControls _controls; + object_ptr _shadow; + bool _mousePressed = false; + +}; + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/win/ui_utility_win.cpp b/ui/platform/win/ui_utility_win.cpp index 6af98ea..935564e 100644 --- a/ui/platform/win/ui_utility_win.cpp +++ b/ui/platform/win/ui_utility_win.cpp @@ -44,5 +44,27 @@ void IgnoreAllActivation(not_null widget) { ShowWindow(handle, SW_SHOW); } +bool ShowWindowMenu(QWindow *window) { + const auto pos = QCursor::pos(); + + SendMessage( + HWND(window->winId()), + WM_SYSCOMMAND, + SC_MOUSEMENU, + MAKELPARAM(pos.x(), pos.y())); + + return true; +} + +TitleControls::Layout TitleControlsLayout() { + return TitleControls::Layout{ + .right = { + TitleControls::Control::Minimize, + TitleControls::Control::Maximize, + TitleControls::Control::Close, + } + }; +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/win/ui_utility_win.h b/ui/platform/win/ui_utility_win.h index a7a1a14..d836b49 100644 --- a/ui/platform/win/ui_utility_win.h +++ b/ui/platform/win/ui_utility_win.h @@ -6,6 +6,8 @@ // #pragma once +#include "ui/platform/ui_platform_utility.h" + #include class QPainter; @@ -40,5 +42,17 @@ inline constexpr bool UseMainQueueGeneric() { return true; } +inline bool WindowExtentsSupported() { + return false; +} + +inline bool SetWindowExtents(QWindow *window, const QMargins &extents) { + return false; +} + +inline bool UnsetWindowExtents(QWindow *window) { + return false; +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/win/ui_window_title_win.cpp b/ui/platform/win/ui_window_title_win.cpp index c28cf16..ba578f9 100644 --- a/ui/platform/win/ui_window_title_win.cpp +++ b/ui/platform/win/ui_window_title_win.cpp @@ -19,170 +19,6 @@ namespace Ui { namespace Platform { -TitleControls::TitleControls( - not_null parent, - const style::WindowTitle &st, - Fn maximize) -: _st(&st) -, _minimize(parent, _st->minimize) -, _maximizeRestore(parent, _st->maximize) -, _close(parent, _st->close) -, _maximizedState(parent->windowState() - & (Qt::WindowMaximized | Qt::WindowFullScreen)) -, _activeState(parent->isActiveWindow()) { - init(std::move(maximize)); - - _close->paintRequest( - ) | rpl::start_with_next([=] { - const auto active = window()->isActiveWindow(); - if (_activeState != active) { - _activeState = active; - updateButtonsState(); - } - }, _close->lifetime()); -} - -void TitleControls::setStyle(const style::WindowTitle &st) { - _st = &st; - updateButtonsState(); -} - -not_null TitleControls::st() const { - return _st; -} - -QRect TitleControls::geometry() const { - auto result = QRect(); - const auto add = [&](auto &&control) { - if (!control->isHidden()) { - result = result.united(control->geometry()); - } - }; - add(_minimize); - add(_maximizeRestore); - add(_close); - return result; -} - -not_null TitleControls::parent() const { - return static_cast(_close->parentWidget()); -} - -not_null TitleControls::window() const { - return _close->window(); -} - -void TitleControls::init(Fn maximize) { - _minimize->setClickedCallback([=] { - window()->setWindowState( - window()->windowState() | Qt::WindowMinimized); - _minimize->clearState(); - }); - _minimize->setPointerCursor(false); - _maximizeRestore->setClickedCallback([=] { - if (maximize) { - maximize(!_maximizedState); - } else { - window()->setWindowState(_maximizedState - ? Qt::WindowNoState - : Qt::WindowMaximized); - } - _maximizeRestore->clearState(); - }); - _maximizeRestore->setPointerCursor(false); - _close->setClickedCallback([=] { - window()->close(); - _close->clearState(); - }); - _close->setPointerCursor(false); - - parent()->widthValue( - ) | rpl::start_with_next([=](int width) { - updateControlsPosition(); - }, _close->lifetime()); - - window()->createWinId(); - QObject::connect( - window()->windowHandle(), - &QWindow::windowStateChanged, - [=](Qt::WindowState state) { handleWindowStateChanged(state); }); - _activeState = parent()->isActiveWindow(); - updateButtonsState(); -} - -void TitleControls::setResizeEnabled(bool enabled) { - _resizeEnabled = enabled; - updateControlsVisibility(); -} - -void TitleControls::raise() { - _minimize->raise(); - _maximizeRestore->raise(); - _close->raise(); -} - -void TitleControls::updateControlsPosition() { - auto right = 0; - _close->moveToRight(right, 0); right += _close->width(); - _maximizeRestore->moveToRight(right, 0); - if (_resizeEnabled) { - right += _maximizeRestore->width(); - } - _minimize->moveToRight(right, 0); -} - -void TitleControls::updateControlsVisibility() { - _maximizeRestore->setVisible(_resizeEnabled); - updateControlsPosition(); -} - -void TitleControls::handleWindowStateChanged(Qt::WindowState state) { - if (state == Qt::WindowMinimized) { - return; - } - - auto maximized = (state == Qt::WindowMaximized) - || (state == Qt::WindowFullScreen); - if (_maximizedState != maximized) { - _maximizedState = maximized; - updateButtonsState(); - } -} - -void TitleControls::updateButtonsState() { - const auto minimize = _activeState - ? &_st->minimizeIconActive - : &_st->minimize.icon; - const auto minimizeOver = _activeState - ? &_st->minimizeIconActiveOver - : &_st->minimize.iconOver; - _minimize->setIconOverride(minimize, minimizeOver); - if (_maximizedState) { - const auto restore = _activeState - ? &_st->restoreIconActive - : &_st->restoreIcon; - const auto restoreOver = _activeState - ? &_st->restoreIconActiveOver - : &_st->restoreIconOver; - _maximizeRestore->setIconOverride(restore, restoreOver); - } else { - const auto maximize = _activeState - ? &_st->maximizeIconActive - : &_st->maximize.icon; - const auto maximizeOver = _activeState - ? &_st->maximizeIconActiveOver - : &_st->maximize.iconOver; - _maximizeRestore->setIconOverride(maximize, maximizeOver); - } - const auto close = _activeState - ? &_st->closeIconActive - : &_st->close.icon; - const auto closeOver = _activeState - ? &_st->closeIconActiveOver - : &_st->close.iconOver; - _close->setIconOverride(close, closeOver); -} - TitleWidget::TitleWidget(not_null parent) : RpWidget(parent) , _controls(this, st::defaultWindowTitle) diff --git a/ui/platform/win/ui_window_title_win.h b/ui/platform/win/ui_window_title_win.h index 6515065..bc5fed6 100644 --- a/ui/platform/win/ui_window_title_win.h +++ b/ui/platform/win/ui_window_title_win.h @@ -6,6 +6,7 @@ // #pragma once +#include "ui/platform/ui_platform_window_title.h" #include "ui/rp_widget.h" #include "base/object_ptr.h" @@ -38,41 +39,6 @@ enum class HitTestResult { TopLeft, }; -class TitleControls final { -public: - TitleControls( - not_null parent, - const style::WindowTitle &st, - Fn maximize = nullptr); - - void setStyle(const style::WindowTitle &st); - [[nodiscard]] not_null st() const; - [[nodiscard]] QRect geometry() const; - void setResizeEnabled(bool enabled); - void raise(); - -private: - [[nodiscard]] not_null parent() const; - [[nodiscard]] not_null window() const; - - void init(Fn maximize); - void updateControlsVisibility(); - void updateButtonsState(); - void updateControlsPosition(); - void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState); - - not_null _st; - - object_ptr _minimize; - object_ptr _maximizeRestore; - object_ptr _close; - - bool _maximizedState = false; - bool _activeState = false; - bool _resizeEnabled = true; - -}; - class TitleWidget : public RpWidget { public: explicit TitleWidget(not_null parent); diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 6da3006..ec57cd1 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1582,3 +1582,17 @@ defaultWindowTitle: WindowTitle { windowShadow: icon {{ "window_shadow", windowShadowFg }}; windowShadowShift: 1px; + +callRadius: 6px; +callShadow: Shadow { + left: icon {{ "calls/call_shadow_left", windowShadowFg }}; + topLeft: icon {{ "calls/call_shadow_top_left", windowShadowFg }}; + top: icon {{ "calls/call_shadow_top", windowShadowFg }}; + topRight: icon {{ "calls/call_shadow_top_left-flip_horizontal", windowShadowFg }}; + right: icon {{ "calls/call_shadow_left-flip_horizontal", windowShadowFg }}; + bottomRight: icon {{ "calls/call_shadow_top_left-flip_vertical-flip_horizontal", windowShadowFg }}; + bottom: icon {{ "calls/call_shadow_top-flip_vertical", windowShadowFg }}; + bottomLeft: icon {{ "calls/call_shadow_top_left-flip_vertical", windowShadowFg }}; + extend: margins(9px, 8px, 9px, 10px); + fallback: windowShadowFgFallback; +} From 77856c3a21870e656b6e9ae48bf9c9a19a2a86c3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 4 Feb 2021 18:45:35 +0400 Subject: [PATCH 49/60] Fix build on Windows. --- ui/platform/win/ui_utility_win.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/platform/win/ui_utility_win.cpp b/ui/platform/win/ui_utility_win.cpp index 935564e..4c4cfcb 100644 --- a/ui/platform/win/ui_utility_win.cpp +++ b/ui/platform/win/ui_utility_win.cpp @@ -6,10 +6,11 @@ // #include "ui/platform/win/ui_utility_win.h" -#include - #include "base/platform/win/base_windows_h.h" +#include +#include + namespace Ui { namespace Platform { From e2c940dcdcbf5bacfa4666154f44702ce487052a Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 11 Feb 2021 20:09:31 +0400 Subject: [PATCH 50/60] Expose menu and menu style in PopupMenu. --- ui/widgets/menu/menu.h | 4 ++++ ui/widgets/popup_menu.h | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/ui/widgets/menu/menu.h b/ui/widgets/menu/menu.h index 1eabfa1..19b40fb 100644 --- a/ui/widgets/menu/menu.h +++ b/ui/widgets/menu/menu.h @@ -24,6 +24,10 @@ public: Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu); ~Menu(); + [[nodiscard]] const style::Menu &st() const { + return _st; + } + not_null addAction(base::unique_qptr widget); not_null addAction( const QString &text, diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index 9caa56d..3b86873 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -21,6 +21,10 @@ public: PopupMenu(QWidget *parent, const style::PopupMenu &st = st::defaultPopupMenu); PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st = st::defaultPopupMenu); + [[nodiscard]] const style::PopupMenu &st() const { + return _st; + } + not_null addAction(base::unique_qptr widget); not_null addAction(const QString &text, Fn callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr); not_null addAction(const QString &text, std::unique_ptr submenu); @@ -42,6 +46,10 @@ public: _reactivateParent = false; } + [[nodiscard]] not_null menu() const { + return _menu.data(); + } + ~PopupMenu(); protected: From cc05f8174bd5014aec55f1cabc8ccc9d9c574260 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 16 Feb 2021 16:30:12 +0400 Subject: [PATCH 51/60] Allow overriding color for FlatButton. --- ui/widgets/buttons.cpp | 11 ++++++++++- ui/widgets/buttons.h | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ui/widgets/buttons.cpp b/ui/widgets/buttons.cpp index a76aab0..2760a79 100644 --- a/ui/widgets/buttons.cpp +++ b/ui/widgets/buttons.cpp @@ -206,6 +206,11 @@ void FlatButton::setWidth(int w) { resize(_width, height()); } +void FlatButton::setColorOverride(std::optional color) { + _colorOverride = color; + update(); +} + int32 FlatButton::textWidth() const { return _st.font->width(_text); } @@ -225,7 +230,11 @@ void FlatButton::paintEvent(QPaintEvent *e) { p.setFont(isOver() ? _st.overFont : _st.font); p.setRenderHint(QPainter::TextAntialiasing); - p.setPen(isOver() ? _st.overColor : _st.color); + if (_colorOverride) { + p.setPen(*_colorOverride); + } else { + p.setPen(isOver() ? _st.overColor : _st.color); + } const auto textRect = inner.marginsRemoved( _textMargins diff --git a/ui/widgets/buttons.h b/ui/widgets/buttons.h index 64354fc..9b1e3f8 100644 --- a/ui/widgets/buttons.h +++ b/ui/widgets/buttons.h @@ -94,6 +94,7 @@ public: void setText(const QString &text); void setWidth(int w); + void setColorOverride(std::optional color); void setTextMargins(QMargins margins); int32 textWidth() const; @@ -107,6 +108,7 @@ private: QString _text; QMargins _textMargins; int _width = 0; + std::optional _colorOverride; const style::FlatButton &_st; From 3bc42db7bc80eed5e1b581e0eeed8ccbbc45f736 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 6 Feb 2021 18:11:34 +0400 Subject: [PATCH 52/60] Add a private method to get control widget by enum to TitleControls --- ui/platform/ui_platform_window_title.cpp | 73 ++++++++++++------------ ui/platform/ui_platform_window_title.h | 1 + 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/ui/platform/ui_platform_window_title.cpp b/ui/platform/ui_platform_window_title.cpp index e8de516..8b0d926 100644 --- a/ui/platform/ui_platform_window_title.cpp +++ b/ui/platform/ui_platform_window_title.cpp @@ -138,37 +138,53 @@ void TitleControls::raise() { _close->raise(); } +Ui::IconButton *TitleControls::controlWidget(Control control) const { + switch (control) { + case Control::Minimize: return _minimize; + case Control::Maximize: return _maximizeRestore; + case Control::Close: return _close; + } + + return nullptr; +} + void TitleControls::updateControlsPosition() { const auto controlsLayout = TitleControlsLayout(); auto controlsLeft = controlsLayout.left; auto controlsRight = controlsLayout.right; - if (!_resizeEnabled) { + const auto controlPresent = [&](Control control) { + return ranges::contains(controlsLeft, control) + || ranges::contains(controlsRight, control); + }; + + const auto eraseControl = [&](Control control) { controlsLeft.erase( - ranges::remove(controlsLeft, Control::Maximize), + ranges::remove(controlsLeft, control), end(controlsLeft)); controlsRight.erase( - ranges::remove(controlsRight, Control::Maximize), + ranges::remove(controlsRight, control), end(controlsRight)); + }; + + if (!_resizeEnabled) { + eraseControl(Control::Maximize); } - if (ranges::contains(controlsLeft, Control::Minimize) - || ranges::contains(controlsRight, Control::Minimize)) { + if (controlPresent(Control::Minimize)) { _minimize->show(); } else { _minimize->hide(); } - if (ranges::contains(controlsLeft, Control::Maximize) - || ranges::contains(controlsRight, Control::Maximize)) { + if (controlPresent(Control::Maximize)) { _maximizeRestore->show(); } else { _maximizeRestore->hide(); } - if (ranges::contains(controlsLeft, Control::Close) - || ranges::contains(controlsRight, Control::Close)) { + if (controlPresent(Control::Close)) { _close->show(); } else { _close->hide(); @@ -187,35 +203,18 @@ void TitleControls::updateControlsPositionBySide( auto position = 0; for (const auto &control : preparedControls) { - switch (control) { - case Control::Minimize: - if (right) { - _minimize->moveToRight(position, 0); - } else { - _minimize->moveToLeft(position, 0); - } - - position += _minimize->width(); - break; - case Control::Maximize: - if (right) { - _maximizeRestore->moveToRight(position, 0); - } else { - _maximizeRestore->moveToLeft(position, 0); - } - - position += _maximizeRestore->width(); - break; - case Control::Close: - if (right) { - _close->moveToRight(position, 0); - } else { - _close->moveToLeft(position, 0); - } - - position += _close->width(); - break; + const auto widget = controlWidget(control); + if (!widget) { + continue; } + + if (right) { + widget->moveToRight(position, 0); + } else { + widget->moveToLeft(position, 0); + } + + position += widget->width(); } } diff --git a/ui/platform/ui_platform_window_title.h b/ui/platform/ui_platform_window_title.h index b4f14d8..af66155 100644 --- a/ui/platform/ui_platform_window_title.h +++ b/ui/platform/ui_platform_window_title.h @@ -51,6 +51,7 @@ public: private: [[nodiscard]] not_null parent() const; [[nodiscard]] not_null window() const; + [[nodiscard]] Ui::IconButton *controlWidget(Control control) const; void init(Fn maximize); void updateButtonsState(); From 34fc25a56c6933220b31075f7a5834b421170f34 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 7 Feb 2021 17:39:10 +0400 Subject: [PATCH 53/60] Ensure controls aren't duplicated --- ui/platform/ui_platform_window_title.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ui/platform/ui_platform_window_title.cpp b/ui/platform/ui_platform_window_title.cpp index 8b0d926..d83c9a9 100644 --- a/ui/platform/ui_platform_window_title.cpp +++ b/ui/platform/ui_platform_window_title.cpp @@ -21,6 +21,19 @@ namespace Ui { namespace Platform { +namespace { + +template +void RemoveDuplicates(std::vector &v) { + auto end = v.end(); + for (auto it = v.begin(); it != end; ++it) { + end = std::remove(it + 1, end, *it); + } + + v.erase(end, v.end()); +} + +} // namespace TitleControls::TitleControls( not_null parent, @@ -197,10 +210,12 @@ void TitleControls::updateControlsPosition() { void TitleControls::updateControlsPositionBySide( const std::vector &controls, bool right) { - const auto preparedControls = right + auto preparedControls = right ? (ranges::view::reverse(controls) | ranges::to_vector) : controls; + RemoveDuplicates(preparedControls); + auto position = 0; for (const auto &control : preparedControls) { const auto widget = controlWidget(control); From e14bc4681d69c1b538b8c5af51501077ae5a8a86 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 12 Feb 2021 17:03:15 +0400 Subject: [PATCH 54/60] Take in account device pixel ratio when setting window extents --- ui/platform/ui_platform_window.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index d6a6e18..741a73a 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -11,6 +11,7 @@ #include "ui/widgets/window.h" #include "ui/widgets/shadow.h" #include "ui/painter.h" +#include "ui/style/style_core_scale.h" #include "styles/style_widgets.h" #include "styles/style_layers.h" @@ -356,7 +357,7 @@ void DefaultWindowHelper::updateWindowExtents() { if (hasShadow()) { Platform::SetWindowExtents( window()->windowHandle(), - resizeArea()); + resizeArea() * style::DevicePixelRatio()); _extentsSet = true; } else if (_extentsSet) { From 9d57ba47b09deeff5d2a53542941c9337bbaef86 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 18 Feb 2021 07:58:35 +0300 Subject: [PATCH 55/60] Fixed adjustment of menu with single item. --- ui/widgets/menu/menu.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/ui/widgets/menu/menu.cpp b/ui/widgets/menu/menu.cpp index b30ef83..61ee9a4 100644 --- a/ui/widgets/menu/menu.cpp +++ b/ui/widgets/menu/menu.cpp @@ -126,22 +126,24 @@ not_null Menu::addAction(base::unique_qptr widget) { } }, widget->lifetime()); - widget->minWidthValue( - ) | rpl::start_with_next([=](int minWidth) { + const auto raw = widget.get(); + _actionWidgets.push_back(std::move(widget)); + + raw->minWidthValue( + ) | rpl::start_with_next([=] { const auto newWidth = _forceWidth ? _forceWidth - : _actionWidgets.empty() - ? _st.widthMin - : std::max( - minWidth, - (*ranges::max_element( - _actionWidgets, - std::less<>(), - &ItemBase::minWidth))->minWidth()); + : std::clamp( + _actionWidgets.empty() + ? 0 + : (*ranges::max_element( + _actionWidgets, + std::less<>(), + &ItemBase::minWidth))->minWidth(), + _st.widthMin, + _st.widthMax); resizeFromInner(newWidth, height()); - }, widget->lifetime()); - - _actionWidgets.push_back(std::move(widget)); + }, raw->lifetime()); const auto newHeight = ranges::accumulate( _actionWidgets, From 0b74c396cbc03c8daf8febfd79dbcefd21d925c3 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 19 Feb 2021 03:14:20 +0400 Subject: [PATCH 56/60] Use gtk integration from lib_base --- CMakeLists.txt | 1 + ui/platform/linux/ui_utility_linux.cpp | 101 +++++++++-------------- ui/platform/ui_platform_utility.cpp | 26 ++++++ ui/platform/ui_platform_utility.h | 3 + ui/platform/ui_platform_window_title.cpp | 5 ++ 5 files changed, 74 insertions(+), 62 deletions(-) create mode 100644 ui/platform/ui_platform_utility.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b48b8f9..e1976e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ PRIVATE ui/platform/ui_platform_window_title.h ui/platform/ui_platform_window.cpp ui/platform/ui_platform_window.h + ui/platform/ui_platform_utility.cpp ui/platform/ui_platform_utility.h ui/style/style_core.cpp ui/style/style_core.h diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index 8e9d80e..0bd36ea 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -8,7 +8,8 @@ #include "ui/ui_log.h" #include "base/platform/base_platform_info.h" -#include "base/platform/linux/base_xcb_utilities_linux.h" +#include "base/platform/linux/base_linux_xcb_utilities.h" +#include "base/platform/linux/base_linux_gtk_integration.h" #include "ui/platform/linux/ui_linux_wayland_integration.h" #include "base/const_string.h" #include "base/qt_adapters.h" @@ -20,13 +21,6 @@ #include #include -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION -#include -#include -#include -#include -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - Q_DECLARE_METATYPE(QMargins); namespace Ui { @@ -152,56 +146,6 @@ TitleControls::Control GtkKeywordToTitleControl(const QString &keyword) { return TitleControls::Control::Unknown; } -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION -std::optional PortalTitleControlsLayout() { - auto message = QDBusMessage::createMethodCall( - kXDGDesktopPortalService.utf16(), - kXDGDesktopPortalObjectPath.utf16(), - kSettingsPortalInterface.utf16(), - "Read"); - - message.setArguments({ - "org.gnome.desktop.wm.preferences", - "button-layout" - }); - - const QDBusReply reply = QDBusConnection::sessionBus().call( - message); - - if (!reply.isValid() || !reply.value().canConvert()) { - return std::nullopt; - } - - const auto valueVariant = qvariant_cast( - reply.value()).variant(); - - if (!valueVariant.canConvert()) { - return std::nullopt; - } - - const auto valueBySides = valueVariant.toString().split(':'); - - std::vector controlsLeft; - ranges::transform( - valueBySides[0].split(','), - ranges::back_inserter(controlsLeft), - GtkKeywordToTitleControl); - - std::vector controlsRight; - if (valueBySides.size() > 1) { - ranges::transform( - valueBySides[1].split(','), - ranges::back_inserter(controlsRight), - GtkKeywordToTitleControl); - } - - return TitleControls::Layout{ - .left = controlsLeft, - .right = controlsRight - }; -} -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - } // namespace bool IsApplicationActive() { @@ -286,11 +230,44 @@ bool ShowWindowMenu(QWindow *window) { } TitleControls::Layout TitleControlsLayout() { -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION - if (const auto portalLayout = PortalTitleControlsLayout()) { - return *portalLayout; + const auto gtkResult = []() -> std::optional { + const auto integration = base::Platform::GtkIntegration::Instance(); + if (!integration || !integration->checkVersion(3, 12, 0)) { + return std::nullopt; + } + + const auto decorationLayoutSetting = integration->getStringSetting( + "gtk-decoration-layout"); + + if (!decorationLayoutSetting.has_value()) { + return std::nullopt; + } + + const auto decorationLayout = decorationLayoutSetting->split(':'); + + std::vector controlsLeft; + ranges::transform( + decorationLayout[0].split(','), + ranges::back_inserter(controlsLeft), + GtkKeywordToTitleControl); + + std::vector controlsRight; + if (decorationLayout.size() > 1) { + ranges::transform( + decorationLayout[1].split(','), + ranges::back_inserter(controlsRight), + GtkKeywordToTitleControl); + } + + return TitleControls::Layout{ + .left = controlsLeft, + .right = controlsRight + }; + }(); + + if (gtkResult.has_value()) { + return *gtkResult; } -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION return TitleControls::Layout{ .right = { diff --git a/ui/platform/ui_platform_utility.cpp b/ui/platform/ui_platform_utility.cpp new file mode 100644 index 0000000..2d07ec5 --- /dev/null +++ b/ui/platform/ui_platform_utility.cpp @@ -0,0 +1,26 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#include "ui/platform/ui_platform_utility.h" + +namespace Ui { +namespace Platform { +namespace { + +rpl::event_stream<> TitleControlsLayoutChanges; + +} // namespace + +rpl::producer<> TitleControlsLayoutChanged() { + return TitleControlsLayoutChanges.events(); +} + +void NotifyTitleControlsLayoutChanged() { + TitleControlsLayoutChanges.fire({}); +} + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/ui_platform_utility.h b/ui/platform/ui_platform_utility.h index e8629a1..40dcdff 100644 --- a/ui/platform/ui_platform_utility.h +++ b/ui/platform/ui_platform_utility.h @@ -36,7 +36,10 @@ void DrainMainQueue(); // Needed only if UseMainQueueGeneric() is false. bool SetWindowExtents(QWindow *window, const QMargins &extents); bool UnsetWindowExtents(QWindow *window); bool ShowWindowMenu(QWindow *window); + [[nodiscard]] TitleControls::Layout TitleControlsLayout(); +[[nodiscard]] rpl::producer<> TitleControlsLayoutChanged(); +void NotifyTitleControlsLayoutChanged(); } // namespace Platform } // namespace Ui diff --git a/ui/platform/ui_platform_window_title.cpp b/ui/platform/ui_platform_window_title.cpp index d83c9a9..1079cfc 100644 --- a/ui/platform/ui_platform_window_title.cpp +++ b/ui/platform/ui_platform_window_title.cpp @@ -117,6 +117,11 @@ void TitleControls::init(Fn maximize) { updateControlsPosition(); }, _close->lifetime()); + TitleControlsLayoutChanged( + ) | rpl::start_with_next([=] { + updateControlsPosition(); + }, _close->lifetime()); + const auto winIdEventFilter = std::make_shared(nullptr); *winIdEventFilter = base::install_event_filter( window(), From 492121950da14615bfab6e6c9b4415a50899ddde Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 17 Feb 2021 22:04:17 +0400 Subject: [PATCH 57/60] Check if resize area is null --- ui/platform/ui_platform_window.cpp | 74 +++++++++++++++++------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index 741a73a..3898917 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -149,10 +149,11 @@ void DefaultWindowHelper::init() { window()->widthValue( ) | rpl::start_with_next([=](int width) { + const auto area = resizeArea(); _title->setGeometry( - resizeArea().left(), - resizeArea().top(), - width - resizeArea().left() - resizeArea().right(), + area.left(), + area.top(), + width - area.left() - area.right(), _title->st()->height); }, _title->lifetime()); @@ -160,20 +161,24 @@ void DefaultWindowHelper::init() { window()->sizeValue(), _title->heightValue() ) | rpl::start_with_next([=](QSize size, int titleHeight) { + const auto area = resizeArea(); + const auto sizeWithoutMargins = size .shrunkBy({ 0, titleHeight, 0, 0 }) - .shrunkBy(resizeArea()); + .shrunkBy(area); const auto topLeft = QPoint( - resizeArea().left(), - resizeArea().top() + titleHeight); + area.left(), + area.top() + titleHeight); _body->setGeometry(QRect(topLeft, sizeWithoutMargins)); }, _body->lifetime()); window()->paintRequest( ) | rpl::start_with_next([=] { - if (resizeArea().isNull()) { + const auto area = resizeArea(); + + if (area.isNull()) { return; } @@ -182,7 +187,7 @@ void DefaultWindowHelper::init() { if (hasShadow()) { Ui::Shadow::paint( p, - QRect(QPoint(), window()->size()).marginsRemoved(resizeArea()), + QRect(QPoint(), window()->size()).marginsRemoved(area), window()->width(), Shadow()); } else { @@ -234,29 +239,33 @@ QMargins DefaultWindowHelper::resizeArea() const { } Qt::Edges DefaultWindowHelper::edgesFromPos(const QPoint &pos) const { - if (pos.x() <= resizeArea().left()) { - if (pos.y() <= resizeArea().top()) { + const auto area = resizeArea(); + + if (area.isNull()) { + return Qt::Edges(); + } else if (pos.x() <= area.left()) { + if (pos.y() <= area.top()) { return Qt::LeftEdge | Qt::TopEdge; - } else if (pos.y() >= (window()->height() - resizeArea().bottom())) { + } else if (pos.y() >= (window()->height() - area.bottom())) { return Qt::LeftEdge | Qt::BottomEdge; } return Qt::LeftEdge; - } else if (pos.x() >= (window()->width() - resizeArea().right())) { - if (pos.y() <= resizeArea().top()) { + } else if (pos.x() >= (window()->width() - area.right())) { + if (pos.y() <= area.top()) { return Qt::RightEdge | Qt::TopEdge; - } else if (pos.y() >= (window()->height() - resizeArea().bottom())) { + } else if (pos.y() >= (window()->height() - area.bottom())) { return Qt::RightEdge | Qt::BottomEdge; } return Qt::RightEdge; - } else if (pos.y() <= resizeArea().top()) { + } else if (pos.y() <= area.top()) { return Qt::TopEdge; - } else if (pos.y() >= (window()->height() - resizeArea().bottom())) { + } else if (pos.y() >= (window()->height() - area.bottom())) { return Qt::BottomEdge; - } else { - return Qt::Edges(); } + + return Qt::Edges(); } bool DefaultWindowHelper::eventFilter(QObject *obj, QEvent *e) { @@ -282,11 +291,12 @@ void DefaultWindowHelper::setTitle(const QString &title) { } void DefaultWindowHelper::setTitleStyle(const style::WindowTitle &st) { + const auto area = resizeArea(); _title->setStyle(st); _title->setGeometry( - resizeArea().left(), - resizeArea().top(), - window()->width() - resizeArea().left() - resizeArea().right(), + area.left(), + area.top(), + window()->width() - area.left() - area.right(), _title->st()->height); } @@ -324,32 +334,34 @@ void DefaultWindowHelper::paintBorders(QPainter &p) { ? titleBackground : defaultTitleBackground; + const auto area = resizeArea(); + p.fillRect( 0, - resizeArea().top(), - resizeArea().left(), - window()->height() - resizeArea().top() - resizeArea().bottom(), + area.top(), + area.left(), + window()->height() - area.top() - area.bottom(), borderColor); p.fillRect( - window()->width() - resizeArea().right(), - resizeArea().top(), - resizeArea().right(), - window()->height() - resizeArea().top() - resizeArea().bottom(), + window()->width() - area.right(), + area.top(), + area.right(), + window()->height() - area.top() - area.bottom(), borderColor); p.fillRect( 0, 0, window()->width(), - resizeArea().top(), + area.top(), borderColor); p.fillRect( 0, - window()->height() - resizeArea().bottom(), + window()->height() - area.bottom(), window()->width(), - resizeArea().bottom(), + area.bottom(), borderColor); } From cf3975366630de43a9a5b25d1a68c0fb6404291a Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 21 Feb 2021 08:03:00 +0400 Subject: [PATCH 58/60] Revert "Take in account device pixel ratio when setting window extents" This reverts commit e14bc4681d69c1b538b8c5af51501077ae5a8a86. --- ui/platform/ui_platform_window.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index 3898917..38921e2 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -11,7 +11,6 @@ #include "ui/widgets/window.h" #include "ui/widgets/shadow.h" #include "ui/painter.h" -#include "ui/style/style_core_scale.h" #include "styles/style_widgets.h" #include "styles/style_layers.h" @@ -369,7 +368,7 @@ void DefaultWindowHelper::updateWindowExtents() { if (hasShadow()) { Platform::SetWindowExtents( window()->windowHandle(), - resizeArea() * style::DevicePixelRatio()); + resizeArea()); _extentsSet = true; } else if (_extentsSet) { From ed1bc07cc31d34d706ebd49e3b78b47776a02f22 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 22 Feb 2021 17:34:12 +0400 Subject: [PATCH 59/60] Add DESKTOP_APP_DISABLE_X11_INTEGRATION --- ui/platform/linux/ui_utility_linux.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index 0bd36ea..a6336fd 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -8,13 +8,16 @@ #include "ui/ui_log.h" #include "base/platform/base_platform_info.h" -#include "base/platform/linux/base_linux_xcb_utilities.h" #include "base/platform/linux/base_linux_gtk_integration.h" #include "ui/platform/linux/ui_linux_wayland_integration.h" #include "base/const_string.h" #include "base/qt_adapters.h" #include "base/flat_set.h" +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION +#include "base/platform/linux/base_linux_xcb_utilities.h" +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION + #include #include #include @@ -33,6 +36,7 @@ constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs; constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs; constexpr auto kSettingsPortalInterface = "org.freedesktop.portal.Settings"_cs; +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) { const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { @@ -133,6 +137,7 @@ bool ShowXCBWindowMenu(QWindow *window) { return true; } +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION TitleControls::Control GtkKeywordToTitleControl(const QString &keyword) { if (keyword == qstr("minimize")) { @@ -186,11 +191,13 @@ bool WindowExtentsSupported() { } #endif // DESKTOP_APP_QT_PATCHED +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION namespace XCB = base::Platform::XCB; if (!::Platform::IsWayland() && XCB::IsSupportedByWM(kXCBFrameExtentsAtomName.utf16())) { return true; } +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION return false; } @@ -204,7 +211,11 @@ bool SetWindowExtents(QWindow *window, const QMargins &extents) { return false; #endif // !DESKTOP_APP_QT_PATCHED } else { +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION return SetXCBFrameExtents(window, extents); +#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION + return false; +#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION } } @@ -217,7 +228,11 @@ bool UnsetWindowExtents(QWindow *window) { return false; #endif // !DESKTOP_APP_QT_PATCHED } else { +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION return UnsetXCBFrameExtents(window); +#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION + return false; +#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION } } @@ -225,7 +240,11 @@ bool ShowWindowMenu(QWindow *window) { if (const auto integration = WaylandIntegration::Instance()) { return integration->showWindowMenu(window); } else { +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION return ShowXCBWindowMenu(window); +#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION + return false; +#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION } } From 7d18c8687b536500c562c226c1224f3d07ed1e13 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 22 Feb 2021 17:34:31 +0400 Subject: [PATCH 60/60] Add Haiku title layout --- ui/platform/linux/ui_utility_linux.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index a6336fd..933ea21 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -288,6 +288,17 @@ TitleControls::Layout TitleControlsLayout() { return *gtkResult; } +#ifdef __HAIKU__ + return TitleControls::Layout{ + .left = { + TitleControls::Control::Close, + }, + .right = { + TitleControls::Control::Minimize, + TitleControls::Control::Maximize, + } + }; +#else // __HAIKU__ return TitleControls::Layout{ .right = { TitleControls::Control::Minimize, @@ -295,6 +306,7 @@ TitleControls::Layout TitleControlsLayout() { TitleControls::Control::Close, } }; +#endif // !__HAIKU__ } } // namespace Platform