From d4c99701b5210a2db83b1c0f13da1a62f48dfb80 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Nov 2020 16:04:28 +0300 Subject: [PATCH 01/79] Fix deprecation warnings on Qt 5.15.1. --- ui/effects/numbers_animation.cpp | 4 ++-- ui/layers/box_content.cpp | 2 +- ui/layers/box_layer_widget.cpp | 2 +- ui/layers/layer_widget.cpp | 4 ++-- ui/painter.h | 9 ++++----- ui/platform/linux/ui_utility_linux.cpp | 12 ++++++------ ui/platform/linux/ui_utility_linux.h | 2 +- ui/platform/mac/ui_utility_mac.mm | 6 +++--- ui/platform/ui_platform_utility.h | 2 +- ui/platform/win/ui_utility_win.h | 2 +- ui/style/style_core_font.cpp | 2 +- ui/style/style_core_font.h | 4 ++-- ui/text/text_entity.cpp | 3 ++- ui/widgets/popup_menu.cpp | 9 ++++++--- ui/widgets/tooltip.cpp | 11 +++++++++-- 15 files changed, 42 insertions(+), 32 deletions(-) diff --git a/ui/effects/numbers_animation.cpp b/ui/effects/numbers_animation.cpp index b490374..eb0f5d4 100644 --- a/ui/effects/numbers_animation.cpp +++ b/ui/effects/numbers_animation.cpp @@ -19,7 +19,7 @@ NumbersAnimation::NumbersAnimation( : _font(font) , _animationCallback(std::move(animationCallback)) { for (auto ch = '0'; ch != '9'; ++ch) { - accumulate_max(_digitWidth, _font->m.width(ch)); + accumulate_max(_digitWidth, _font->m.horizontalAdvance(ch)); } } @@ -67,7 +67,7 @@ void NumbersAnimation::realSetText(QString text, int value) { digit.from = digit.to; digit.fromWidth = digit.toWidth; digit.to = (newSize + i < size) ? QChar(0) : text[newSize + i - size]; - digit.toWidth = digit.to.unicode() ? _font->m.width(digit.to) : 0; + digit.toWidth = digit.to.unicode() ? _font->m.horizontalAdvance(digit.to) : 0; if (digit.from != digit.to) { animating = true; } diff --git a/ui/layers/box_content.cpp b/ui/layers/box_content.cpp index bb6d3e2..2a9ff65 100644 --- a/ui/layers/box_content.cpp +++ b/ui/layers/box_content.cpp @@ -245,7 +245,7 @@ void BoxContent::paintEvent(QPaintEvent *e) { Painter p(this); if (testAttribute(Qt::WA_OpaquePaintEvent)) { - for (auto rect : e->region().rects()) { + for (const auto rect : e->region()) { p.fillRect(rect, st::boxBg); } } diff --git a/ui/layers/box_layer_widget.cpp b/ui/layers/box_layer_widget.cpp index 92adb6d..6d38cb0 100644 --- a/ui/layers/box_layer_widget.cpp +++ b/ui/layers/box_layer_widget.cpp @@ -117,7 +117,7 @@ void BoxLayerWidget::paintEvent(QPaintEvent *e) { } auto other = e->region().intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius)); if (!other.isEmpty()) { - for (auto rect : other.rects()) { + for (const auto rect : other) { p.fillRect(rect, st::boxBg); } } diff --git a/ui/layers/layer_widget.cpp b/ui/layers/layer_widget.cpp index 74a32e1..b60cf69 100644 --- a/ui/layers/layer_widget.cpp +++ b/ui/layers/layer_widget.cpp @@ -280,8 +280,8 @@ void LayerStackWidget::BackgroundWidget::paintEvent(QPaintEvent *e) { // (alpha_final - alpha_current) / (1 - alpha_current) so we won't get glitches // in the transparent special layer cache corners after filling special layer // rect above its cache with alpha_current opacity. - auto region = QRegion(bg) - specialLayerBox; - for (auto rect : region.rects()) { + const auto region = QRegion(bg) - specialLayerBox; + for (const auto rect : region) { p.fillRect(rect, st::layerBg); } p.setOpacity((bgOpacity - overSpecialOpacity) / (1. - (overSpecialOpacity * st::layerBg->c.alphaF()))); diff --git a/ui/painter.h b/ui/painter.h index c393100..941e812 100644 --- a/ui/painter.h +++ b/ui/painter.h @@ -18,12 +18,12 @@ public: void drawTextLeft(int x, int y, int outerw, const QString &text, int textWidth = -1) { QFontMetrics m(fontMetrics()); - if (style::RightToLeft() && textWidth < 0) textWidth = m.width(text); + if (style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text); drawText(style::RightToLeft() ? (outerw - x - textWidth) : x, y + m.ascent(), text); } void drawTextRight(int x, int y, int outerw, const QString &text, int textWidth = -1) { QFontMetrics m(fontMetrics()); - if (!style::RightToLeft() && textWidth < 0) textWidth = m.width(text); + if (!style::RightToLeft() && textWidth < 0) textWidth = m.horizontalAdvance(text); drawText(style::RightToLeft() ? x : (outerw - x - textWidth), y + m.ascent(), text); } void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix, const QRect &from) { @@ -84,8 +84,7 @@ public: static constexpr QPainter::RenderHint Hints[] = { QPainter::Antialiasing, QPainter::SmoothPixmapTransform, - QPainter::TextAntialiasing, - QPainter::HighQualityAntialiasing + QPainter::TextAntialiasing }; const auto hints = _painter.renderHints(); @@ -112,6 +111,6 @@ public: private: QPainter &_painter; - QPainter::RenderHints _hints = 0; + QPainter::RenderHints _hints; }; diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index bab2e97..01566c5 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -6,13 +6,14 @@ // #include "ui/platform/linux/ui_utility_linux.h" -#include "base/flat_set.h" #include "ui/ui_log.h" #include "base/platform/base_platform_info.h" +#include "base/qt_adapters.h" +#include "base/flat_set.h" #include +#include #include -#include #include namespace Ui { @@ -28,19 +29,18 @@ bool TranslucentWindowsSupported(QPoint globalPosition) { } if (const auto native = QGuiApplication::platformNativeInterface()) { if (const auto desktop = QApplication::desktop()) { - const auto index = desktop->screenNumber(globalPosition); - const auto screens = QGuiApplication::screens(); - if (const auto screen = (index >= 0 && index < screens.size()) ? screens[index] : QGuiApplication::primaryScreen()) { + if (const auto screen = base::QScreenNearestTo(globalPosition)) { if (native->nativeResourceForScreen(QByteArray("compositingEnabled"), screen)) { return true; } + const auto index = QGuiApplication::screens().indexOf(screen); static auto WarnedAbout = base::flat_set(); if (!WarnedAbout.contains(index)) { WarnedAbout.emplace(index); UI_LOG(("WARNING: Compositing is disabled for screen index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y())); } } else { - UI_LOG(("WARNING: Could not get screen for index %1 (for position %2,%3)").arg(index).arg(globalPosition.x()).arg(globalPosition.y())); + UI_LOG(("WARNING: Could not get screen for position %1,%2").arg(globalPosition.x()).arg(globalPosition.y())); } } } diff --git a/ui/platform/linux/ui_utility_linux.h b/ui/platform/linux/ui_utility_linux.h index 73bfff9..c9081fc 100644 --- a/ui/platform/linux/ui_utility_linux.h +++ b/ui/platform/linux/ui_utility_linux.h @@ -12,7 +12,7 @@ class QPaintEvent; namespace Ui { namespace Platform { -inline void StartTranslucentPaint(QPainter &p, gsl::span rects) { +inline void StartTranslucentPaint(QPainter &p, const QRegion ®ion) { } inline void InitOnTopPanel(not_null panel) { diff --git a/ui/platform/mac/ui_utility_mac.mm b/ui/platform/mac/ui_utility_mac.mm index 3478157..46aeb11 100644 --- a/ui/platform/mac/ui_utility_mac.mm +++ b/ui/platform/mac/ui_utility_mac.mm @@ -68,10 +68,10 @@ void ReInitOnTopPanel(not_null panel) { [platformPanel setCollectionBehavior:newBehavior]; } -void StartTranslucentPaint(QPainter &p, gsl::span rects) { +void StartTranslucentPaint(QPainter &p, const QRegion ®ion) { p.setCompositionMode(QPainter::CompositionMode_Source); - for (const auto &r : rects) { - p.fillRect(r, Qt::transparent); + for (const auto rect : region) { + p.fillRect(rect, Qt::transparent); } p.setCompositionMode(QPainter::CompositionMode_SourceOver); } diff --git a/ui/platform/ui_platform_utility.h b/ui/platform/ui_platform_utility.h index 0e23059..a8eaedd 100644 --- a/ui/platform/ui_platform_utility.h +++ b/ui/platform/ui_platform_utility.h @@ -16,7 +16,7 @@ namespace Platform { [[nodiscard]] bool IsApplicationActive(); [[nodiscard]] bool TranslucentWindowsSupported(QPoint globalPosition); -void StartTranslucentPaint(QPainter &p, gsl::span rects); +void StartTranslucentPaint(QPainter &p, const QRegion ®ion); void InitOnTopPanel(not_null panel); void DeInitOnTopPanel(not_null panel); diff --git a/ui/platform/win/ui_utility_win.h b/ui/platform/win/ui_utility_win.h index dcd82b2..a7a1a14 100644 --- a/ui/platform/win/ui_utility_win.h +++ b/ui/platform/win/ui_utility_win.h @@ -27,7 +27,7 @@ inline void DeInitOnTopPanel(not_null panel) { inline void ReInitOnTopPanel(not_null panel) { } -inline void StartTranslucentPaint(QPainter &p, gsl::span rects) { +inline void StartTranslucentPaint(QPainter &p, const QRegion ®ion) { } inline void ShowOverAll(not_null widget, bool canFocus) { diff --git a/ui/style/style_core_font.cpp b/ui/style/style_core_font.cpp index 83310a4..05b26d7 100644 --- a/ui/style/style_core_font.cpp +++ b/ui/style/style_core_font.cpp @@ -270,7 +270,7 @@ QString MonospaceFont() { // Prefer system monospace font. const auto metrics = QFontMetrics(QFont(system)); const auto useSystem = manual.isEmpty() - || (metrics.charWidth("i", 0) == metrics.charWidth("W", 0)); + || (metrics.horizontalAdvance(QChar('i')) == metrics.horizontalAdvance(QChar('W'))); #endif // Q_OS_WIN || Q_OS_MAC return useSystem ? system : manual; }(); diff --git a/ui/style/style_core_font.h b/ui/style/style_core_font.h index 5b72baf..638dcb5 100644 --- a/ui/style/style_core_font.h +++ b/ui/style/style_core_font.h @@ -69,13 +69,13 @@ enum FontFlags { class FontData { public: int width(const QString &str) const { - return m.width(str); + return m.horizontalAdvance(str); } int width(const QString &str, int32 from, int32 to) const { return width(str.mid(from, to)); } int width(QChar ch) const { - return m.width(ch); + return m.horizontalAdvance(ch); } QString elided( const QString &str, diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 4d6de85..fe43783 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -9,6 +9,7 @@ #include "base/qthelp_url.h" #include "base/qthelp_regex.h" #include "base/crc32hash.h" +#include "base/qt_adapters.h" #include "ui/text/text.h" #include "ui/widgets/input_fields.h" #include "ui/emoji_config.h" @@ -1403,7 +1404,7 @@ QStringList PrepareSearchWords( auto list = clean.split(SplitterOverride ? *SplitterOverride : RegExpWordSplit(), - QString::SkipEmptyParts); + base::QStringSkipEmptyParts); auto size = list.size(); result.reserve(list.size()); for (const auto &word : std::as_const(list)) { diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index 164f8af..c40ab0f 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -12,11 +12,12 @@ #include "ui/ui_utility.h" #include "ui/delayed_activation.h" #include "base/platform/base_platform_info.h" +#include "base/qt_adapters.h" #include #include +#include #include -#include namespace Ui { @@ -448,7 +449,9 @@ void PopupMenu::popup(const QPoint &p) { } void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) { - if (!parent && ::Platform::IsMac() && !Platform::IsApplicationActive()) { + const auto screen = base::QScreenNearestTo(p); + if (!screen + || (!parent && ::Platform::IsMac() && !Platform::IsApplicationActive())) { _hiding = false; _a_opacity.stop(); _a_show.stop(); @@ -476,7 +479,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou && (*_forcedOrigin == Origin::BottomLeft || *_forcedOrigin == Origin::BottomRight); auto w = p - QPoint(0, _padding.top()); - auto r = QApplication::desktop()->screenGeometry(p); + auto r = screen->availableGeometry(); _useTransparency = Platform::TranslucentWindowsSupported(p); setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); handleCompositingUpdate(); diff --git a/ui/widgets/tooltip.cpp b/ui/widgets/tooltip.cpp index 04d0b95..3899449 100644 --- a/ui/widgets/tooltip.cpp +++ b/ui/widgets/tooltip.cpp @@ -9,10 +9,11 @@ #include "ui/ui_utility.h" #include "ui/platform/ui_platform_utility.h" #include "base/invoke_queued.h" +#include "base/qt_adapters.h" #include "styles/style_widgets.h" +#include #include -#include namespace Ui { @@ -72,6 +73,12 @@ Tooltip::~Tooltip() { } void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *st) { + const auto screen = base::QScreenNearestTo(m); + if (!screen) { + Hide(); + return; + } + if (!_isEventFilter) { _isEventFilter = true; QCoreApplication::instance()->installEventFilter(this); @@ -108,7 +115,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip * } // adjust tooltip position - QRect r(QApplication::desktop()->screenGeometry(m)); + const auto r = screen->availableGeometry(); if (r.x() + r.width() - _st->skip < p.x() + s.width() && p.x() + s.width() > m.x()) { p.setX(qMax(r.x() + r.width() - int32(_st->skip) - s.width(), m.x() - s.width())); } From cffa5e11d8d1f340bd2356c0944a0d3e91e38f2c Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 6 Nov 2020 09:17:08 +0400 Subject: [PATCH 02/79] Use QClipboard::supportsSelection instead of ifdefs --- ui/widgets/labels.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/widgets/labels.cpp b/ui/widgets/labels.cpp index a5c58fd..ac5c900 100644 --- a/ui/widgets/labels.cpp +++ b/ui/widgets/labels.cpp @@ -454,13 +454,12 @@ Text::StateResult FlatLabel::dragActionFinish(const QPoint &p, Qt::MouseButton b } } -#if defined Q_OS_UNIX && !defined Q_OS_MAC - if (!_selection.empty()) { + if (QGuiApplication::clipboard()->supportsSelection() + && !_selection.empty()) { TextUtilities::SetClipboardText( _text.toTextForMimeData(_selection), QClipboard::Selection); } -#endif // Q_OS_UNIX && !Q_OS_MAC return state; } From f46a1be77c3ea7c23f016dd2b3375a39eb0a36cf Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 10 Nov 2020 21:50:49 +0300 Subject: [PATCH 03/79] Allow LambdaClickHandler to see context. --- ui/click_handler.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/click_handler.h b/ui/click_handler.h index 5f87046..4c13c47 100644 --- a/ui/click_handler.h +++ b/ui/click_handler.h @@ -105,16 +105,20 @@ protected: class LambdaClickHandler : public ClickHandler { public: - LambdaClickHandler(Fn handler) : _handler(std::move(handler)) { + LambdaClickHandler(Fn handler) + : _handler([handler = std::move(handler)](ClickContext) { handler(); }) { + } + LambdaClickHandler(Fn handler) + : _handler(std::move(handler)) { } void onClick(ClickContext context) const override final { if (context.button == Qt::LeftButton && _handler) { - _handler(); + _handler(context); } } private: - Fn _handler; + Fn _handler; }; From f06346fbf03900c278e1d59717e1387bffc03f39 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 13 Nov 2020 20:26:30 +0300 Subject: [PATCH 04/79] Allow precompute size of serialized tags. --- ui/text/text_entity.cpp | 14 ++++++++++++++ ui/text/text_entity.h | 11 +++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index fe43783..9bdfc43 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -1950,6 +1950,20 @@ void Trim(TextWithEntities &result) { } } +int SerializeTagsSize(const TextWithTags::Tags &tags) { + auto result = qint32(0); + if (tags.isEmpty()) { + return result; + } + result += sizeof(qint32); + for (const auto &tag : tags) { + result += 2 * sizeof(qint32) // offset, length + + sizeof(quint32) // id.size + + tag.id.size() * sizeof(ushort); + } + return result; +} + QByteArray SerializeTags(const TextWithTags::Tags &tags) { if (tags.isEmpty()) { return QByteArray(); diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index 83f4384..aa04175 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -344,10 +344,13 @@ inline QString PrepareForSending(const QString &text, PrepareTextOption option = // Replace bad symbols with space and remove '\r'. void ApplyServerCleaning(TextWithEntities &result); -QByteArray SerializeTags(const TextWithTags::Tags &tags); -TextWithTags::Tags DeserializeTags(QByteArray data, int textLength); -QString TagsMimeType(); -QString TagsTextMimeType(); +[[nodiscard]] int SerializeTagsSize(const TextWithTags::Tags &tags); +[[nodiscard]] QByteArray SerializeTags(const TextWithTags::Tags &tags); +[[nodiscard]] TextWithTags::Tags DeserializeTags( + QByteArray data, + int textLength); +[[nodiscard]] QString TagsMimeType(); +[[nodiscard]] QString TagsTextMimeType(); inline const auto kMentionTagStart = qstr("mention://user."); From a417bc87d01a7f733cfa72f40dd679b7b5ee449d Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 10 Nov 2020 19:38:29 +0400 Subject: [PATCH 05/79] Fix parent for PopupMenu submenus to eliminate the setGrabPopup warning on Wayland --- ui/widgets/popup_menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index c40ab0f..f97c562 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -38,7 +38,7 @@ PopupMenu::PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st) for (auto action : actions()) { if (auto submenu = action->menu()) { - auto it = _submenus.insert(action, new PopupMenu(parentWidget(), submenu, st)); + auto it = _submenus.insert(action, new PopupMenu(this, submenu, st)); it.value()->deleteOnHide(false); } } From 77b6e43b17df43d64354ba7198e6a7695a636627 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 16 Nov 2020 19:37:11 +0300 Subject: [PATCH 06/79] Revert "Fix parent for PopupMenu submenus to eliminate the setGrabPopup warning on Wayland" This reverts commit a417bc87d01a7f733cfa72f40dd679b7b5ee449d. --- ui/widgets/popup_menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index f97c562..c40ab0f 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -38,7 +38,7 @@ PopupMenu::PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st) for (auto action : actions()) { if (auto submenu = action->menu()) { - auto it = _submenus.insert(action, new PopupMenu(this, submenu, st)); + auto it = _submenus.insert(action, new PopupMenu(parentWidget(), submenu, st)); it.value()->deleteOnHide(false); } } From e73bae12e26811f70adff5adfafbdb2548e02cd0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Nov 2020 10:39:42 +0300 Subject: [PATCH 07/79] Add some new colors to palette. --- ui/colors.palette | 16 ++++++++++++++++ ui/widgets/widgets.style | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/ui/colors.palette b/ui/colors.palette index 7ee8530..7147e11 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -554,6 +554,22 @@ callHangupBg: #d75a5a; // phone call popup hangup button background callHangupRipple: #c04646; // phone call popup hangup button ripple effect callMuteRipple: #ffffff12; // phone call popup mute mic and camera ripple effect +groupCallBg: #131417; // group call popup background +groupCallMembersBg: #1a1c1f; // group call members list background +groupCallMembersFg: #ffffff; // group call member name text +groupCallMembersRipple: #22252a; // group call member row ripple effect +groupCallMemberActiveIcon: #3fe069; // group call active member icon +groupCallMemberActiveStatus: #67e66d; // group call active member status text +groupCallMemberInactiveIcon: #61666b; // group call inactive member icon +groupCallMemberInactiveStatus: #4db8ff; // group call inactive member status text +groupCallMemberMutedIcon: #ff4f4d; // group call muted by admin member icon +groupCallMemberNotJoinedStatus: #61666b; // group call non joined member status text +groupCallIconFg: #ffffff; // group call mute / settings / leave icon +groupCallLive1: #0dcc39; // group call live button color1 +groupCallLive2: #0bb6bd; // group call live button color2 +groupCallMuted1: #0992ef; // group call muted button color1 +groupCallMuted2: #16ccfb; // group call muted button color2 + callBarBg: dialogsBgActive; // active phone call bar background callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect callBarBgMuted: #8f8f8f | dialogsUnreadBgMuted; // phone call bar with muted mic background diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 443b830..bc1678d 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1115,6 +1115,8 @@ PeerListItem { photoPosition: point; namePosition: point; nameStyle: TextStyle; + nameFg: color; + nameFgChecked: color; statusPosition: point; photoSize: pixels; maximalWidth: pixels; @@ -1153,6 +1155,8 @@ defaultPeerListItem: PeerListItem { linkFont: semiboldFont; linkFontOver: semiboldFont; } + nameFg: contactsNameFg; + nameFgChecked: windowActiveTextFg; statusPosition: point(68px, 31px); photoSize: 46px; button: defaultPeerListButton; From 962c8d89e52a8c92bc7987eba4cfaef77a3a9ebf Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Nov 2020 14:59:01 +0300 Subject: [PATCH 08/79] Add two new button types. --- CMakeLists.txt | 4 + icons/calls/voice_muted_large.png | Bin 0 -> 1229 bytes icons/calls/voice_muted_large@2x.png | Bin 0 -> 2288 bytes icons/calls/voice_muted_large@3x.png | Bin 0 -> 3575 bytes icons/calls/voice_unmuted_large.png | Bin 0 -> 963 bytes icons/calls/voice_unmuted_large@2x.png | Bin 0 -> 1800 bytes icons/calls/voice_unmuted_large@3x.png | Bin 0 -> 2708 bytes ui/widgets/call_button.cpp | 213 +++++++++++++++++++++++++ ui/widgets/call_button.h | 55 +++++++ ui/widgets/call_mute_button.cpp | 112 +++++++++++++ ui/widgets/call_mute_button.h | 59 +++++++ ui/widgets/widgets.style | 41 +++++ 12 files changed, 484 insertions(+) create mode 100644 icons/calls/voice_muted_large.png create mode 100644 icons/calls/voice_muted_large@2x.png create mode 100644 icons/calls/voice_muted_large@3x.png create mode 100644 icons/calls/voice_unmuted_large.png create mode 100644 icons/calls/voice_unmuted_large@2x.png create mode 100644 icons/calls/voice_unmuted_large@3x.png create mode 100644 ui/widgets/call_button.cpp create mode 100644 ui/widgets/call_button.h create mode 100644 ui/widgets/call_mute_button.cpp create mode 100644 ui/widgets/call_mute_button.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ecf3f2b..4959564 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,10 @@ PRIVATE ui/widgets/box_content_divider.h ui/widgets/buttons.cpp ui/widgets/buttons.h + ui/widgets/call_button.cpp + ui/widgets/call_button.h + ui/widgets/call_mute_button.cpp + ui/widgets/call_mute_button.h ui/widgets/checkbox.cpp ui/widgets/checkbox.h ui/widgets/dropdown_menu.cpp diff --git a/icons/calls/voice_muted_large.png b/icons/calls/voice_muted_large.png new file mode 100644 index 0000000000000000000000000000000000000000..ae5a118860945395e7ffabea5fdc1bb93497a842 GIT binary patch literal 1229 zcmV;;1Ty=HP)_Pkx83pGC4kNg@eZ z$VTxG$U<275v43p3ME-s*vLjUHYf|p@1I4MN`8~rutAdY<9z4bueoOK%)Qrr-S=|p zb!X6BEhd;UPIVI3SOYkFJ=< zN9^}6E-ntn$H(F8>kD{!etw3Ah6cawa!fK@XlN)TB_-J!_xXIVv$Mll)$udg*n^Yil%5fP!9q>c6T^hiL)fsc<5C@3f}wP6E?^F+Xojt;@c%F2o< znFH^3BZz=bAQKW203m}KK?d2`*(vNZ>}+goKvYzekb)2r_bQv`$5s3m|JFcOR+a>4 z1{oV08yLX&_;^@aS^`>pLQPGL-~{iP$>!#!6iI&JAt50!GBN@uCnqpGJS^Dd^}gHY z5o~H|(iyM-4G#|&j2#^v>GzEdE69q93PIhf_Ov{c+}zyIMf{hTm>5!8TFScGm6lOL zpYPDBh%78D5V|M%VXe*Ed3$>!4-XHL4nH77h2GHnxOcGNWHL21rQbt6uh$D$E=}DW0d6f65X#W5yZo=H$obLbq{T(tgG6aM8xtpAv)OA!16=Y6M zj+TDv>goi03I=Pyva&M4Cq1!A5kaH5xw(Kxfd2k|7#$sz4pAydtb&aD+A$zbn3 zc_B37Bt1Qy)2+Xm8qKz=f@#Vq%DqCB9E}$j7wcb*ySqEnU0n^dG=sjrKCMDB=r{v0OcoY_koe}} r|20;6PVPDSl2F4z7uWV+pveCLXSK$G7W&?p00000NkvXXu0mjfDBLGG literal 0 HcmV?d00001 diff --git a/icons/calls/voice_muted_large@2x.png b/icons/calls/voice_muted_large@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cd1161ce379baba7d11a682029b993499072a897 GIT binary patch literal 2288 zcmVPc^Bva0u`R&TMR zMvNFC`t|E48Z~MpYSyeNzI^#2of_wQU*%5O^rd z4I4HT6)IGS3kl|Dn>KBNQDmvbctv{l?8zG7+IZsf<;(pznKsYKlP8UJk!*ra!&5kZ z{CE~bIpt!viOZHP(*m)LlvBN+;c)cv0OHEoK$LS@PT{n4=~7!x%Jjt^ARvw#H;(Oz zT!>4SEKx$SiO2|mVL@EHc(DyD&$Jl>&<5g*7cZzqix#ZgdC{Upo?y6v83iybhzl1k z(vWi3@S=+_`Ls84oZlh;!!5VL{9afVL1@wQ9w>ooCOUZG@OL00D8#m@%x51-CO7 z;;dP-v=Fljpbf-VuU@es&YU?@2{G#c+Cs!bCgd@FX3Uu32{9`Hh6Qo@^yx_uvld`j z5ECDEWi`OCAWoe+mHz(y8}Y`h2MCCxM~^P@d^zbpEDU)4`ZdXC9LDw#I;TvT5`kDE z0HVU&=OKXDwryJ$#L1H<)77h2S!rQIfS*8>u?(9qVS>u>lEQ5u_UhG(Repf*P+sZ* z4jVQs36w6t24WiO;|8!#pFXsI|9(RIvth#qYS5s8(F;0t>ZIf48TaPR8*10Col&<5 zVCBk{2~PvhoH;`mE?kKGE?v5`i(dZ#`}gmk)FFKO^oe@(=%F>JYSpTqkW7KD5Gz%x zM7M6;N-AdGzI|GI4Hv~f!1L$NCmDl)>({Rv4SM|ev1eXWpbJEFG%DfFf)Zh}wsy!& zo>Hey9nbJCUAhQt-_Xg{ty^``%D5UeYKYUPPYZcFE7PbV1`HUWiV~{2C^N)G7`;WN z^-Oc*$Pv{AB@A#?`4BGm(fl6hA=a&1mmWNL;Hh%^_U&3bHSmznKR|5m*6kf!xpJk^FnESehi8ow0s*l@hYrTF za6z$?HBm6Vbl~70APU{Mabu!AvHv)_Aol?pjl%<`d-v|e^7;9}g$S=dc<^AX2-sMz zTsfnS8o+Jkl}6~ zi2OJ^q7!UV_j961o|B^{O`1fGQ=q*aK72UqP>v160P61!5Jq4+q?5gS_YyXjyBb^! zAqE2w17pmCb%Hhy?T;%&tmgdu`BPU^BrZjs7z{u_!R*5%yLRnj^~@&7HV|#vU{kps z@xcMabZO6?Jtl^9|NebeZ|Pxd5$_#3bSTRc-@kuPAwmoeAS#S870%I_4GKNlvfxazk-$e_3MkHM~@1*&c;^!=+Pr_{P=Nk>Cz=3H)9I9fBZJIc7BFQ$6r6K@`sRJ1GM$B5UR*21&LCLvu=V;)-fw5Z>y)C-}HB9!dk-ailpTl~dtzf9MZrwU%<*^otBd{ECfS5jjQIPn?ECx$i zc=Q>gJYNWK;R3T+XMZ}IBJH)6_qlBDZdZv&M%RIy51GO$H6Q(>~H+%5=vNKaX-7qVdHKAOK^nPHv=(3~I4& z23zTZc&==^c((DOE$K@`N`w^4P9&4a=KwlhtsKA{Kt}?O+5Z8ZL&@M?>Oe360000< KMNUMnLSTZtj4W6H literal 0 HcmV?d00001 diff --git a/icons/calls/voice_muted_large@3x.png b/icons/calls/voice_muted_large@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3907746876820f9094207689a98f73532a12bb83 GIT binary patch literal 3575 zcmV^#u%rMAL3L4DdV(Wezo3czy}=Xw}KDY2OQ@bi1X_C7VHC# za}C6K^-{-q{Q}j?ojW%xQ>F~dlP6E$lQLyWcICxK;!D!EyH~ zQltnUJ9aGJvu6)~{rWYx3rCI|;p4`Q;}t4Yh&w&t&W)=|0qnea^Z4`U&+V9oy|!}Y zN?xi|sd&;!`~dFUxif$K_;FZxq8DDgc)=4A5_qaqsp0{2>;q1hE*)R8WQiz0;dmQ1 zY~b0mXODHru?-kjwtDqyVQ8`uPnmVeZ>F% z`|o#jcHqDPUcP*JqYC-pgAe%a+qd75ZSsdtKmF8boeaQ5dDQ>|u3d{2-ISIsTN+cy z!i5V>vJ9E`wr$&t)kl?sKstHAXxQx9wJW3&f`3GJ!vthBdY(La5-iHKzZ*7e7;BK_ z0avP2Nm!wP+@eJbN0l;o@L*y2PDlLp*Iym2i+}~{%997&ty?!Ky*X{kyLa!FlJ8{X zyLa!#REt_^x-dnG6m;W>2WnL_y7O79R;|Png^8IqZCXYRxG-1E|C}vbHddoX4fBE= zlAr{5|Ni~b;sJT-(xr^v$Ub@P$xmM1MD^A{Q2{{3OQK<2IMck_(JQQ+@?(%WlIL+lP6E=7^ZOA zWDD49$kct||Ni^0FeufC$r^BSfSl|BI{~?E+qTM&>%4jM@_+vMN7X3ZqD zlcN=~4>+iAtqb|fj0r}XmV~$v|4>$~9Cm^?L*G}1zX_p~)0Y$K6>=1mL>auBLMaqa;{vtIJS(c1|P5hU_fr%xUur00lbsH{q|es$F&Y*AFv?c zXn=h9@L_2~_5n)(b^>zy_U)A|IcLtC{LrC8QjmSXl7M3kvJY4RuoI9wbm*XL$=D8a z@ZdoqOZEXP0*(gA*cllq_<%J419GEAjkH?V*f)%PR6aR!|;399qSPC&Lcp8J3e0S4q* zvu0U_B>fG@UAuNwdQ?L;EE$kv39!=sV5+QVfwrD1%Z6uyxpU_-^690N2f1%xb?Ve% zuIM=n2!%lwzNK z_8BWytQb3g{yZb+Y7A~IXaR$H+pBXZ3DV=Q03{QrL({0j}QnKNfHg6vqXs2?5jA(Wq~hL+=Z z4b_Ij-LPnwGG&TX83+W|UA%5rHYY6^KBh%O_3G87^Z-2ze1sy2WRxflJi$|+Rj6** zP9jjyL~*bg&17&2@((}!AXH4aM0`Wx@@#W5*8R z{8b_Xa?hSUwUiDg;vxZEr7(cBemC|GroysKn>OJ~D9Ab~CFKXkl*zNaQ2r{Btaoy+ zUcFS32hxZR%xvAdRar))gj#n9Wga&D#~*(za`Oh_piiuFvI(}YxDg0)*m~OO00)$a z96wOIc5S8n?PR~X79Nm5V%tKYF$8d;L3K6ck3aq>Dnct>@7}#_>uaa4(W6I)m3{N( zO`bk|dgZ+BGO_k(K6SoS1(cT0u4Eucu>wy zK(1N9D8S;yi-mO#KhT*oXI8E-2JLBfhGToF$)E!nZ4)}XI8eOx>(?vSRRAvXHV?RB z#fnn8rs+HFO2aB>REj@(^hip!Oys_O`zn_cWA%6?VUM&)1~^?KKpCV>2I{l2YSpR< zi>P0}KBLn=h2w{dNKXS+rAie>BT9k8JThd+5IFOJ#=rw})AUGZ)Bs``GiD5zh3)VC z`t@ToX3VgS6Ye4{$z}KM-DBwTnG8nnAS!qu{{{^j$QTE|eEBlRnq(`z$!}VWpk?U< z*_CoC>cIRX+A7$xWy_=>V|0fs&K2c-{`u$e3fVota1x4xrDOsv?U9~Nv~DQbZ$QTM zrD}*$OLh-16pl%C*|2u)TBT7gS-^;2vt~^#$o>2GcjbvAQ8#x1V<2Pu_U*Ea`O1|m zIW>e_QC^%ub{8=2ywashC+h5r(uU{Qa^=do0-27l)3W3N0|vNKP611HA214qeMUGt zNj6|qOm*O2KpBak#0g|?07KcBYgG-u|NeVm)rc%K+4!`ES?OtG)ob9ufr)M!S@{mc z_YN=$kL5b50mG5#dYOD|<1p-r6DO(`F_6w<$le0R$_(t*(+L=`U$bTn$3VVQ{bt&Q z!N-mr%P(EJq*JuFX-%w{6tr${0i%$Bq6^v5B-S?35GO|?2Mhm1rXLXp3+i$F!1U?U z!|p(^WT<}QU6rESf528?Z+xAt8a05^UqiUZjQ`yK&P8qcWC)u)d9oX#-pX+7ZlN5t^TIx%P?O@SRuhfsT()F=C{BP4&ZBPK zx}0WNqLGbbWg<(~HilrG+58=SfKgc54Zyc-+2WW&qcJ0wAV())>yE@iJU5o%cy$9{ zEtyqUL*LO27?p-k=S7PadBq^CTyi?~K^_%+-sql0ZU}Ph0k)J1?*PMx4>xi?p#}HKl`9IS&tVEsDTp)AU zuwfD9gp(iU_t*s-Tp_HY#G-U84geI@K!YM2t4J_RS-Em$p^>oQa=>$Zu-ikGaySe* zJ^%+R8Gpl?7A#mGtY6Gd$?kAWD%1KuYu2p%ufP5hmK`c0o=jGLn&0sW7)p`ZHsi!v z>|hXBeS#-}PH+OU(e0yD;ba>gKYq+;Q9GkqE@d4|m@t7+Yh5`#68eC()MuXmRhfy1fe;LcU|>Wv(1~oKQR91{gVr<)D?%o&aHfJ0;V=%Z5iWo0Af>VRqQI`+AtcwP68R2>PD^;rW);UY4a-TbQj_1miOSYWxJK2C; z6FYbA45h0?ztK4ou5C>v%d!Q8Lot^uS(0e!FukKWCPOew6)Am)>-GXeBv29JS(=h1 zOWJM&#TslwZhW=s49|DuJch$Vk*ve?B+TL)Fe-~BxS=c=Zj3OgpF|;X^?%fCT_GhK z4cHn}wrp8O?>R&)kYa#MO-;b(ST!cqwbj*CVL;(kJUBSW>~_1ZMSzP(v1(09oVK(m=`WU!Gzvl%e!-*y>3|{XMSxZpbVo`g1 ze9Y46G@F>1&~h#7zN|Fn=H}$eRMd(CC%{oC7HVxtt4*&7)dAtsSXgv6{I|Fi!OsEQIUFE2?zx7$qu_V@S0 z>FFu4K>>@|#l=P7Pl4DU92^i^uh&cTRT#7Z1Azbucy)CJot>Q|sMTtP(a}*@US5V^ zFbF=M4|okoY;fR}l@$^zpU(r%*R%ooN>*)*;_*1xY_`fEHT#p3lkoojP5|fU z=WF_Lcx^z`kByDN$HxZ=espvMPN!4P0DvHmkB`LvAX)1&UkJnVA_lJ3CXl0e`PTcXzkS#}ptsudlDG29qw_+}uDSk$_k%2B}mEUS3`# z6**@z8Q}jy=!HW&eHoPOW@l%iSS*r3^$Pp@`_SIrZUoLaAcEp5-rU>-+?4BS7Z(>W zH8o`vOtLqYK`E1XZfk1;zuynxa9H)i#zllvVt9C18c6gk4p<4hy}eD6Xu`W!@oe<^#~pdFyH_H002ovPDHLkV1gE4tS|ro literal 0 HcmV?d00001 diff --git a/icons/calls/voice_unmuted_large@2x.png b/icons/calls/voice_unmuted_large@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c2e70b23c8c6ba9f6b7c08a3de1db0beb05107ed GIT binary patch literal 1800 zcmbtV={wtr7XF2ZiY-ygsCCjQW>9NMnIf3l5;7GLI#+5*LnaklG&G1^uNmqYJC%s7 z6YZcQT}H)7a7~-5T3p*ST7=P}VyU9)ruW0Wf583lp7%ZHJm=ec&bf{a^ihYNfC2!Z z?&s@$LC)AuI0li&68PpFIf2P23x8ZD#>#siQsn`g8{yh>tg_@Fb)9A6-bT~ zasYq=N8!H}EJyKw`cqTOBAyNakZXS4C>#T{Rv+MNX!jYb={;4J{qFN96lyS+U=zNx z)M$#p@swrx7C966_MC9-(hIADp1t9y7Hy*MxgtFiWw_GdF3Luicn3cSb)#UmY}V~l zoNK22Qiq;{D{C;D?we9}4caNpIS*!LQj?;UF!`nVVEi#(R(+!>$e&dYMh>1sO7n}& z&CQ#{Vsv?J?caq$AvPhwc3-xjJTo)n866!Rn~(s*1O=(yf6@eN5VmGDe4UmfALy!d zw6n`Te*8FQcdwvsZ-YXiY)nj4=%(It!5;0rnY<6;J_P0j8QP`jt`+I5{e+{GTzY-M|spaRr9_Nb7 z1CUNm_+nb%WMV}`<;hk+OFcb$z&dV8#l_;)1~FGU(zmK zG*CE7`)OSYD=RD0sEQq*n6RVcaX4&M?1uIuhAbzp>NdM8STg|4<#MZ&z_#u6VZ&Mj zhlhu$8mOTTV&6vZwcT5HBzXG=bG zEmoA5V{dNCWc_VzZMd*=O72-72P6^+uCAq}1;-!N=rw#`jEJewNnwe+x&4uDF%4{X zQ)A=jl!pxp1ZHMpB1(s}=cV(Uz^QXv(<=ByGB-y)?VNj{@!f00bE))H!GT>|he*?^ z%@v8{)V^L@qg(ilwTasbZ$S1J78b_C7mZ`XhR(Nl3IrvR-fKRVsbBT+`8rpqR4nPz z8!)wsa|-VoYyX(}VtZkJ{#GwkDI+5T{Q{;h5{b}8l>8Xq9F%4pY)4U31&??#f9tMQ zuQMq{L=ko(%Bifz{dWhuG(#h!f2NE~PU>atZ(rx-hK!Dl<-W=u*mN*6gAj?t;0fm8 zennb)4JP+>VbtUSZ?A-dqv4fNseb%mX{0Lc|67a2nG)WGooY7QMyr9r8YNiodXWNN-7Prl{Qw--d3z6b z1=}GIM2ty!-6uB$TvAKdC(xKdIR5q8fy*K1?9Z{Q2j=Q?~n6 z`uLYS2FfrGxh3`bn0s2H_M5|Cnh9rjZ<(7T(p<(_Snc8PWLo zUe@$Y>4lE|r`_+#kMMvuCQ1q=%;FZnLe-JZW z3{`p71O!uUD>(9rc9Xb3S5MCsOu*p=%7Yyr52xSPG-a{o{4bNqc2@OyP5lm5wXVOD yg7?9~tg`qQUW>%0)1ahx)nE%ylGaP@BSl>CMYA{BX6B#Xk>59g-oJQW%K104elwo{ literal 0 HcmV?d00001 diff --git a/icons/calls/voice_unmuted_large@3x.png b/icons/calls/voice_unmuted_large@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d15a5fcbbccdccd2830ff49227ae8bf194435a84 GIT binary patch literal 2708 zcmc&$_d6Tf8;>?-8=ERFdaY8cGmCb2!Wt#3slF002OcTSnG& z8}biWnCPtrT6K(WfbRNGeE^^~<-(~mBfYQgiiEg9p#WJrX8|z8KL7yfjDZe8Iskxk z1?T=d5>jyffBc`xYqG%;000{^#7G|z!m#CNm%w+Ecc{98bdbo%dm%9qS%XqUqC^q! zu0)P&80u3#D|iGaV{MF10*fkwck>m;hU_a$0TOOC9rym=OY`yG;LbwSWcW^=ib|GN zUw%7fd*~))=(zP|)~W;!&3dKyrHk7Y>&$b-V zCuOlICQ$4>*B~z$`chOxH&SkIch~#MOaw+q%~wv*@qO1)4Pr)AH?sG#iW^yvGy`n( zoHWx3Kd3}}JY}oA+cXxKJ}CLlpEg^+hhTg36lA)d!^!}+>(Nr*d&iK9iPNg_AH zSn9Q^8&^*0ht*Z>*i3fySMgXNdy#9LcYn~Pqqgo4_0k`$eRiIHve+Ke)}MHSneBOA z61=xINIBkH$Ca1e${-_drx9sIRVa->>PWE(;e)#KM_`7t*L1af)=lxKirmM&niX3j ztMqu|8=*foZrj(o7zbp1?kR|0450M)-D&hK0bkTDKd+4J8O+scoKOy8i#XYbxVgLI z`sgBKfaHemt)&elGj{OqK#@A06IV(8xmv+6^g!?7ud|GdF7 zHrR_8SWwdOlzALTn8;Fb|FGiMd)a`0zByR2$*5ka#bfrZ45Sr;lakP=PF2PDt8#(q z$yuUZUDLV?qS0J~Pua3dU%KR< zJe`u09XytK+E-{AsCEEJj#)9Funky0|I`072&Pa-q%Tg|N47?2)KlTBe#G=y*k6_6 zkK^r}Al2ccuZ&kod=G`~Gn7hEQ&wuGSYc;^W1Snc3`-P0FM#kn*qo6&3p?B%EVuTt z`7E2SPtw6;Dma>{?ye8#bDORz@n$+UKPIsJMTKbvZ{y2T+Q)o8VgEqa`+aaC$lgX$ zRQ$$Fm0+?<#ULTonis-qzQUz!4V$qSE+JO}0V%zF>wgFCQ>_{nYQ^4p&yeVf%0$CQ z(}iw$w5#$<(7vzU|Ad8Tvk9QWNb??Z6J<6tK}vyR>m6`|A0d4}L%;4szSiWeu@dvw z!>f}OcIG$WOsRR5v!2Lu5uKT}6j9x5p(G}Cb-UC@%^2wh--T?D+=ju3$ncrimx%3o zjj}kk){S`f`SZ$4Tk|Ge_-R@P+-*4T<|;o(A1sAWa8&`xWY~@4xZT;mv_<736h%Pv zLYKjk6`*A!8H6b9NZq(5GeLyrxU`sO@J)SX5iLAd#hNAyzUVR;gX*cYuS15Wvj$T~ z^e+mlD^XY%%XuW(MYOL@;N3`@t6t?hViT;dF?IHI!F)2#*Ll(#bi_9?i@{qv9kUQ0 zar@Hxk8TB;Q>lkSb>>voM?6yK)2Y7R(%<|bSCeFcj{1A8A%7c`bGq@09aW!i+z+Bn z)%tXPzrgIRoUnUM2-Kluw7@o_rycd{?nWf+nDO#zK)ewE5G-vGeo~d0rKpJ!0(-7P zzdy2%JlQYX$piCTRHljr4`ze!+GrmqaO7gKsOT3EMqO43=F6?mAc2&ALW#o->Zglu z+*3}SH*C!8N4b;N*?Y1hxtAyDI=^t^nJiY(|D2JUhm=l9Zd!!U(tCnqPd8qO*Mb*|wcQ4`!=G!m$! zaIbYU(@s&8Xn6Ka$FN3T1!(9mmj%jRw4y%&_>B=W<+Lqz*K z*f3*6h8iqUXjX#mFOCy(87D$rHp#UzuBWF37zw8kL}&rrv@YDMtR~OwM^CH+TEQSIU^ROI-bUl%gwrRvJhjDGJRQ|fPe%=3gYbaOC|8leY z0e_VP`ugbc@iDiG7O@m3ZE03-{gzyvuIje9^BXXvM~R9l@O{9&1hL`KjL;`)g8euZ zG0p`J6h*FNHJPQMBBI)F&l*9Nl_@S=3OlK`tsc4aEvJx)HY-dZ z!49nU@Qbi{daVok==;*$ltc%eFF@&po;nSMMjtu+oev?`!!>+9W+b!?im5Z0(J%hZ z6n0Y0(9Ra1!kTE2^=F0M8 zDk6Q>C`MR<-V#(NVuB|3r<$4?Jgf2HSAIrDMuVWd4<2NCpmT)GZ=X#XEEpZETdx0n zTGo{&Zra}*yp#Tc*8u4nV;mW`5Q%g7&aB!Ob7Gia4c|n=@^zq6>IxOcAA20ot9P!Szml{^aq_$?MJ1FPn3KOoIWjmcFDb6*bw;}h=C9DSibgIyFzEwEuU zt$_vJhd2D|=7~v4Ozb85qx)ie^hP`}^evlXj?R7_8u-jY)4|YQPYgY8g{#e9s_(Tq zy;i#fUBOm9+;oedqfHkM=%%T_pfiMV}*z$fz86&YSlGIbutton.width, _stFrom->button.height); + + _bgMask = RippleAnimation::ellipseMask(QSize(_stFrom->bgSize, _stFrom->bgSize)); + _bgFrom = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stFrom->bg)); + if (_stTo) { + Assert(_stFrom->button.width == _stTo->button.width); + Assert(_stFrom->button.height == _stTo->button.height); + Assert(_stFrom->bgPosition == _stTo->bgPosition); + Assert(_stFrom->bgSize == _stTo->bgSize); + + _bg = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _bg.setDevicePixelRatio(style::DevicePixelRatio()); + _bgTo = Ui::PixmapFromImage(style::colorizeImage(_bgMask, _stTo->bg)); + _iconMixedMask = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconMixedMask.setDevicePixelRatio(style::DevicePixelRatio()); + _iconFrom = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconFrom.setDevicePixelRatio(style::DevicePixelRatio()); + _iconFrom.fill(Qt::black); + { + QPainter p(&_iconFrom); + p.drawImage( + (_stFrom->bgSize + - _stFrom->button.icon.width()) / 2, + (_stFrom->bgSize + - _stFrom->button.icon.height()) / 2, + _stFrom->button.icon.instance(Qt::white)); + } + _iconTo = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconTo.setDevicePixelRatio(style::DevicePixelRatio()); + _iconTo.fill(Qt::black); + { + QPainter p(&_iconTo); + p.drawImage( + (_stTo->bgSize + - _stTo->button.icon.width()) / 2, + (_stTo->bgSize + - _stTo->button.icon.height()) / 2, + _stTo->button.icon.instance(Qt::white)); + } + _iconMixed = QImage(_bgMask.size(), QImage::Format_ARGB32_Premultiplied); + _iconMixed.setDevicePixelRatio(style::DevicePixelRatio()); + } +} + +void CallButton::setOuterValue(float64 value) { + if (_outerValue != value) { + _outerAnimation.start([this] { + if (_progress == 0. || _progress == 1.) { + update(); + } + }, _outerValue, value, kOuterBounceDuration); + _outerValue = value; + } +} + +void CallButton::setText(rpl::producer text) { + _label.create(this, std::move(text), _stFrom->label); + _label->show(); + rpl::combine( + sizeValue(), + _label->sizeValue() + ) | rpl::start_with_next([=](QSize my, QSize label) { + _label->moveToLeft( + (my.width() - label.width()) / 2, + my.height() - label.height(), + my.width()); + }, _label->lifetime()); +} + +void CallButton::setProgress(float64 progress) { + _progress = progress; + update(); +} + +void CallButton::paintEvent(QPaintEvent *e) { + QPainter p(this); + + auto bgPosition = myrtlpoint(_stFrom->bgPosition); + auto paintFrom = (_progress == 0.) || !_stTo; + auto paintTo = !paintFrom && (_progress == 1.); + + auto outerValue = _outerAnimation.value(_outerValue); + if (outerValue > 0.) { + auto outerRadius = paintFrom ? _stFrom->outerRadius : paintTo ? _stTo->outerRadius : (_stFrom->outerRadius * (1. - _progress) + _stTo->outerRadius * _progress); + auto outerPixels = outerValue * outerRadius; + auto outerRect = QRectF(myrtlrect(bgPosition.x(), bgPosition.y(), _stFrom->bgSize, _stFrom->bgSize)); + outerRect = outerRect.marginsAdded(QMarginsF(outerPixels, outerPixels, outerPixels, outerPixels)); + + PainterHighQualityEnabler hq(p); + if (paintFrom) { + p.setBrush(_stFrom->outerBg); + } else if (paintTo) { + p.setBrush(_stTo->outerBg); + } else { + p.setBrush(anim::brush(_stFrom->outerBg, _stTo->outerBg, _progress)); + } + p.setPen(Qt::NoPen); + p.drawEllipse(outerRect); + } + + if (paintFrom) { + p.drawPixmap(bgPosition, _bgFrom); + } else if (paintTo) { + p.drawPixmap(bgPosition, _bgTo); + } else { + style::colorizeImage(_bgMask, anim::color(_stFrom->bg, _stTo->bg, _progress), &_bg); + p.drawImage(bgPosition, _bg); + } + + auto rippleColorInterpolated = QColor(); + auto rippleColorOverride = &rippleColorInterpolated; + if (paintFrom) { + rippleColorOverride = nullptr; + } else if (paintTo) { + rippleColorOverride = &_stTo->button.ripple.color->c; + } else { + rippleColorInterpolated = anim::color(_stFrom->button.ripple.color, _stTo->button.ripple.color, _progress); + } + paintRipple(p, _stFrom->button.rippleAreaPosition.x(), _stFrom->button.rippleAreaPosition.y(), rippleColorOverride); + + auto positionFrom = iconPosition(_stFrom); + if (paintFrom) { + const auto icon = &_stFrom->button.icon; + icon->paint(p, positionFrom, width()); + } else { + auto positionTo = iconPosition(_stTo); + if (paintTo) { + _stTo->button.icon.paint(p, positionTo, width()); + } else { + mixIconMasks(); + style::colorizeImage(_iconMixedMask, st::callIconFg->c, &_iconMixed); + p.drawImage(myrtlpoint(_stFrom->bgPosition), _iconMixed); + } + } +} + +QPoint CallButton::iconPosition(not_null st) const { + auto result = st->button.iconPosition; + if (result.x() < 0) { + result.setX((width() - st->button.icon.width()) / 2); + } + if (result.y() < 0) { + result.setY((height() - st->button.icon.height()) / 2); + } + return result; +} + +void CallButton::mixIconMasks() { + _iconMixedMask.fill(Qt::black); + + Painter p(&_iconMixedMask); + PainterHighQualityEnabler hq(p); + auto paintIconMask = [this, &p](const QImage &mask, float64 angle) { + auto skipFrom = _stFrom->bgSize / 2; + p.translate(skipFrom, skipFrom); + p.rotate(angle); + p.translate(-skipFrom, -skipFrom); + p.drawImage(0, 0, mask); + }; + p.save(); + paintIconMask(_iconFrom, (_stFrom->angle - _stTo->angle) * _progress); + p.restore(); + p.setOpacity(_progress); + paintIconMask(_iconTo, (_stTo->angle - _stFrom->angle) * (1. - _progress)); +} + +void CallButton::onStateChanged(State was, StateChangeSource source) { + RippleButton::onStateChanged(was, source); + + auto over = isOver(); + auto wasOver = static_cast(was & StateFlag::Over); + if (over != wasOver) { + update(); + } +} + +QPoint CallButton::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition; +} + +QImage CallButton::prepareRippleMask() const { + return RippleAnimation::ellipseMask(QSize(_stFrom->button.rippleAreaSize, _stFrom->button.rippleAreaSize)); +} + +} // namespace Ui diff --git a/ui/widgets/call_button.h b/ui/widgets/call_button.h new file mode 100644 index 0000000..c02c441 --- /dev/null +++ b/ui/widgets/call_button.h @@ -0,0 +1,55 @@ +// 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 "base/object_ptr.h" +#include "ui/widgets/buttons.h" +#include "ui/effects/animations.h" + +namespace Ui { + +class FlatLabel; + +class CallButton final : public RippleButton { +public: + CallButton( + QWidget *parent, + const style::CallButton &stFrom, + const style::CallButton *stTo = nullptr); + + void setProgress(float64 progress); + void setOuterValue(float64 value); + void setText(rpl::producer text); + +protected: + void paintEvent(QPaintEvent *e) override; + + void onStateChanged(State was, StateChangeSource source) override; + + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + QPoint iconPosition(not_null st) const; + void mixIconMasks(); + + not_null _stFrom; + const style::CallButton *_stTo = nullptr; + float64 _progress = 0.; + + object_ptr _label = { nullptr }; + + QImage _bgMask, _bg; + QPixmap _bgFrom, _bgTo; + QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed; + + float64 _outerValue = 0.; + Animations::Simple _outerAnimation; + +}; + +} // namespace Ui diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp new file mode 100644 index 0000000..26081b6 --- /dev/null +++ b/ui/widgets/call_mute_button.cpp @@ -0,0 +1,112 @@ +// 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/call_mute_button.h" + +#include "styles/style_widgets.h" + +namespace Ui { + +CallMuteButton::CallMuteButton( + not_null parent, + CallMuteButtonState initial) +: _state(initial) +, _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) +, _connecting(parent, st::callMuteButtonConnecting) { + if (_state.type == CallMuteButtonType::Connecting + || _state.type == CallMuteButtonType::ForceMuted) { + _connecting.setText(rpl::single(_state.text)); + _connecting.show(); + _content.hide(); + } else { + _content.setText(rpl::single(_state.text)); + _content.setProgress((_state.type == CallMuteButtonType::Muted) ? 1. : 0.); + _connecting.hide(); + _content.show(); + } + _connecting.setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void CallMuteButton::setState(const CallMuteButtonState &state) { + if (state.type == CallMuteButtonType::Connecting + || state.type == CallMuteButtonType::ForceMuted) { + if (_state.text != state.text) { + _connecting.setText(rpl::single(state.text)); + } + if (!_connecting.isHidden() || !_content.isHidden()) { + _connecting.show(); + } + _content.setOuterValue(0.); + _content.hide(); + } else { + if (_state.text != state.text) { + _content.setText(rpl::single(state.text)); + } + _content.setProgress((state.type == CallMuteButtonType::Muted) ? 1. : 0.); + if (!_connecting.isHidden() || !_content.isHidden()) { + _content.show(); + } + _connecting.hide(); + if (state.type == CallMuteButtonType::Active) { + _content.setOuterValue(_level); + } else { + _content.setOuterValue(0.); + } + } + _state = state; +} + +void CallMuteButton::setLevel(float level) { + _level = level; + if (_state.type == CallMuteButtonType::Active) { + _content.setOuterValue(level); + } +} + +rpl::producer CallMuteButton::clicks() const { + return _content.clicks(); +} + +QSize CallMuteButton::innerSize() const { + const auto skip = st::callMuteButtonActive.outerRadius; + return QSize( + _content.width() - 2 * skip, + _content.width() - 2 * skip); +} + +void CallMuteButton::moveInner(QPoint position) { + const auto skip = st::callMuteButtonActive.outerRadius; + _content.move(position - QPoint(skip, skip)); + _connecting.move(_content.pos()); +} + +void CallMuteButton::setVisible(bool visible) { + if (!visible) { + _content.hide(); + _connecting.hide(); + } else if (_state.type == CallMuteButtonType::Connecting + || _state.type == CallMuteButtonType::ForceMuted) { + _connecting.show(); + } else { + _content.show(); + } +} + +void CallMuteButton::raise() { + _content.raise(); + _connecting.raise(); +} + +void CallMuteButton::lower() { + _content.lower(); + _connecting.lower(); +} + +rpl::lifetime &CallMuteButton::lifetime() { + return _content.lifetime(); +} + +} // namespace Ui diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h new file mode 100644 index 0000000..b0faea7 --- /dev/null +++ b/ui/widgets/call_mute_button.h @@ -0,0 +1,59 @@ +// 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/call_button.h" + +namespace Ui { + +enum class CallMuteButtonType { + Connecting, + Active, + Muted, + ForceMuted, +}; + +struct CallMuteButtonState { + QString text; + CallMuteButtonType type = CallMuteButtonType::Connecting; +}; + +class CallMuteButton final { +public: + explicit CallMuteButton( + not_null parent, + CallMuteButtonState initial = CallMuteButtonState()); + + void setState(const CallMuteButtonState &state); + void setLevel(float level); + [[nodiscard]] rpl::producer clicks() const; + + [[nodiscard]] QSize innerSize() const; + void moveInner(QPoint position); + + void setVisible(bool visible); + void show() { + setVisible(true); + } + void hide() { + setVisible(false); + } + void raise(); + void lower(); + + [[nodiscard]] rpl::lifetime &lifetime(); + +private: + CallMuteButtonState _state; + float _level = 0.; + + CallButton _content; + CallButton _connecting; + +}; + +} // namespace Ui diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index bc1678d..01d9cf4 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -379,6 +379,8 @@ MultiSelect { CallButton { button: IconButton; bg: color; + bgSize: pixels; + bgPosition: point; angle: double; outerRadius: pixels; outerBg: color; @@ -1299,6 +1301,45 @@ defaultToast: Toast { durationSlide: 160; } +callMuteButtonActiveInner: IconButton { + width: 136px; + height: 151px; + + iconPosition: point(-1px, 50px); + icon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }}; +} +callMuteButtonLabel: FlatLabel(defaultFlatLabel) { + textFg: callNameFg; + style: TextStyle(defaultTextStyle) { + font: font(13px); + linkFont: font(13px); + linkFontOver: font(13px underline); + } +} +callMuteButtonActive: CallButton { + button: callMuteButtonActiveInner; + bg: groupCallLive1; + bgSize: 100px; + bgPosition: point(18px, 18px); + outerRadius: 18px; + outerBg: callAnswerBgOuter; + label: callMuteButtonLabel; +} +callMuteButtonMuted: CallButton(callMuteButtonActive) { + button: IconButton(callMuteButtonActiveInner) { + icon: icon {{ "calls/voice_muted_large", groupCallIconFg }}; + } + bg: groupCallMuted1; + label: callMuteButtonLabel; +} +callMuteButtonConnecting: CallButton(callMuteButtonMuted) { + button: IconButton(callMuteButtonActiveInner) { + icon: icon {{ "calls/voice_muted_large", groupCallIconFg }}; + } + bg: callIconBg; + label: callMuteButtonLabel; +} + // Windows specific title windowTitleButton: IconButton { From 41bae1eba67801eb8768887100c17b7e84e7daf7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 28 Nov 2020 17:07:06 +0300 Subject: [PATCH 09/79] Add CallMuteButton::innerGeometry method. --- ui/widgets/call_mute_button.cpp | 8 +++++++- ui/widgets/call_mute_button.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 26081b6..7696a67 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -71,8 +71,14 @@ rpl::producer CallMuteButton::clicks() const { } QSize CallMuteButton::innerSize() const { + return innerGeometry().size(); +} + +QRect CallMuteButton::innerGeometry() const { const auto skip = st::callMuteButtonActive.outerRadius; - return QSize( + return QRect( + _content.x(), + _content.y(), _content.width() - 2 * skip, _content.width() - 2 * skip); } diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index b0faea7..31cc78e 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -33,6 +33,7 @@ public: [[nodiscard]] rpl::producer clicks() const; [[nodiscard]] QSize innerSize() const; + [[nodiscard]] QRect innerGeometry() const; void moveInner(QPoint position); void setVisible(bool visible); From 7287e235e0205fd878cd35329d08aa1aa1c218fa Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 29 Nov 2020 21:26:20 +0300 Subject: [PATCH 10/79] Allow different styles for box layers. --- ui/colors.palette | 3 ++ ui/layers/box_content.cpp | 7 +-- ui/layers/box_content.h | 1 + ui/layers/box_layer_widget.cpp | 43 +++++++++++---- ui/layers/box_layer_widget.h | 1 + ui/layers/layer_manager.cpp | 11 ++++ ui/layers/layer_manager.h | 11 ++++ ui/layers/layer_widget.cpp | 7 +++ ui/layers/layer_widget.h | 17 ++++++ ui/layers/layers.style | 17 +++--- ui/round_rect.cpp | 19 ++++--- ui/round_rect.h | 2 + ui/widgets/widgets.style | 97 +++++++++++++++++++++++++++++++++- 13 files changed, 205 insertions(+), 31 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index 7147e11..47d0f4d 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -555,6 +555,7 @@ callHangupRipple: #c04646; // phone call popup hangup button ripple effect callMuteRipple: #ffffff12; // phone call popup mute mic and camera ripple effect groupCallBg: #131417; // group call popup background +groupCallActiveFg: #4db8ff; // group call active controls text groupCallMembersBg: #1a1c1f; // group call members list background groupCallMembersFg: #ffffff; // group call member name text groupCallMembersRipple: #22252a; // group call member row ripple effect @@ -569,6 +570,8 @@ groupCallLive1: #0dcc39; // group call live button color1 groupCallLive2: #0bb6bd; // group call live button color2 groupCallMuted1: #0992ef; // group call muted button color1 groupCallMuted2: #16ccfb; // group call muted button color2 +groupCallBoxButtonBgOver: #1d2a39; // group call button in box with mouse over +groupCallBoxButtonBgRipple: #223143; // group call button in box ripple effect callBarBg: dialogsBgActive; // active phone call bar background callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect diff --git a/ui/layers/box_content.cpp b/ui/layers/box_content.cpp index 2a9ff65..cd6a406 100644 --- a/ui/layers/box_content.cpp +++ b/ui/layers/box_content.cpp @@ -29,7 +29,7 @@ QPointer BoxContent::addButton( return addButton( std::move(text), std::move(clickCallback), - st::defaultBoxButton); + getDelegate()->style().button); } QPointer BoxContent::addLeftButton( @@ -38,7 +38,7 @@ QPointer BoxContent::addLeftButton( return getDelegate()->addLeftButton( std::move(text), std::move(clickCallback), - st::defaultBoxButton); + getDelegate()->style().button); } void BoxContent::setInner(object_ptr inner) { @@ -245,8 +245,9 @@ void BoxContent::paintEvent(QPaintEvent *e) { Painter p(this); if (testAttribute(Qt::WA_OpaquePaintEvent)) { + const auto &color = getDelegate()->style().bg; for (const auto rect : e->region()) { - p.fillRect(rect, st::boxBg); + p.fillRect(rect, color); } } } diff --git a/ui/layers/box_content.h b/ui/layers/box_content.h index f293fe4..1c81444 100644 --- a/ui/layers/box_content.h +++ b/ui/layers/box_content.h @@ -45,6 +45,7 @@ class BoxContentDelegate { public: virtual void setLayerType(bool layerType) = 0; virtual void setStyle(const style::Box &st) = 0; + virtual const style::Box &style() = 0; virtual void setTitle(rpl::producer title) = 0; virtual void setAdditionalTitle(rpl::producer additional) = 0; virtual void setCloseByOutsideClick(bool close) = 0; diff --git a/ui/layers/box_layer_widget.cpp b/ui/layers/box_layer_widget.cpp index 6d38cb0..a7075ec 100644 --- a/ui/layers/box_layer_widget.cpp +++ b/ui/layers/box_layer_widget.cpp @@ -42,7 +42,7 @@ BoxLayerWidget::BoxLayerWidget( : LayerWidget(layer) , _layer(layer) , _content(std::move(content)) -, _roundRect(ImageRoundRadius::Small, st::boxBg) { +, _roundRect(ImageRoundRadius::Small, st().bg) { _content->setParent(this); _content->setDelegate(this); @@ -74,12 +74,21 @@ const style::Box &BoxLayerWidget::st() const { return _st ? *_st : _layerType - ? st::layerBox - : st::defaultBox; + ? (_layer->boxStyleOverrideLayer() + ? *_layer->boxStyleOverrideLayer() + : st::layerBox) + : (_layer->boxStyleOverride() + ? *_layer->boxStyleOverride() + : st::defaultBox); } void BoxLayerWidget::setStyle(const style::Box &st) { _st = &st; + _roundRect.setColor(st.bg); +} + +const style::Box &BoxLayerWidget::style() { + return st(); } int BoxLayerWidget::buttonsHeight() const { @@ -118,7 +127,7 @@ void BoxLayerWidget::paintEvent(QPaintEvent *e) { auto other = e->region().intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius)); if (!other.isEmpty()) { for (const auto rect : other) { - p.fillRect(rect, st::boxBg); + p.fillRect(rect, st().bg); } } if (!_additionalTitle.current().isEmpty() @@ -137,21 +146,29 @@ void BoxLayerWidget::paintEvent(QPaintEvent *e) { void BoxLayerWidget::paintAdditionalTitle(Painter &p) { p.setFont(st::boxTitleAdditionalFont); - p.setPen(st::boxTitleAdditionalFg); - p.drawTextLeft(_titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip, _titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->ascent, width(), _additionalTitle.current()); + p.setPen(st().titleAdditionalFg); + p.drawTextLeft( + _titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip, + _titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->ascent, + width(), + _additionalTitle.current()); } void BoxLayerWidget::parentResized() { auto newHeight = countRealHeight(); auto parentSize = parentWidget()->size(); - setGeometry((parentSize.width() - width()) / 2, (parentSize.height() - newHeight) / 2, width(), newHeight); + setGeometry( + (parentSize.width() - width()) / 2, + (parentSize.height() - newHeight) / 2, + width(), + newHeight); update(); } void BoxLayerWidget::setTitle(rpl::producer title) { const auto wasTitle = hasTitle(); if (title) { - _title.create(this, rpl::duplicate(title), st::boxTitle); + _title.create(this, rpl::duplicate(title), st().title); _title->show(); std::move( title @@ -323,9 +340,10 @@ void BoxLayerWidget::setDimensions(int newWidth, int maxHeight, bool forceCenter resize(newWidth, countRealHeight()); auto newGeometry = geometry(); auto parentHeight = parentWidget()->height(); - if (newGeometry.top() + newGeometry.height() + st::boxVerticalMargin > parentHeight + const auto bottomMargin = st().margin.bottom(); + if (newGeometry.top() + newGeometry.height() + bottomMargin > parentHeight || forceCenterPosition) { - const auto top1 = parentHeight - int(st::boxVerticalMargin) - newGeometry.height(); + const auto top1 = parentHeight - bottomMargin - newGeometry.height(); const auto top2 = (parentHeight - newGeometry.height()) / 2; const auto newTop = forceCenterPosition ? std::min(top1, top2) @@ -343,7 +361,10 @@ void BoxLayerWidget::setDimensions(int newWidth, int maxHeight, bool forceCenter } int BoxLayerWidget::countRealHeight() const { - return qMin(_fullHeight, parentWidget()->height() - 2 * st::boxVerticalMargin); + const auto &margin = st().margin; + return std::min( + _fullHeight, + parentWidget()->height() - margin.top() - margin.bottom()); } int BoxLayerWidget::countFullHeight() const { diff --git a/ui/layers/box_layer_widget.h b/ui/layers/box_layer_widget.h index 0313b66..9d195ce 100644 --- a/ui/layers/box_layer_widget.h +++ b/ui/layers/box_layer_widget.h @@ -45,6 +45,7 @@ public: void setLayerType(bool layerType) override; void setStyle(const style::Box &st) override; + const style::Box &style() override; void setTitle(rpl::producer title) override; void setAdditionalTitle(rpl::producer additional) override; void showBox( diff --git a/ui/layers/layer_manager.cpp b/ui/layers/layer_manager.cpp index 6ebbaa0..b5ca389 100644 --- a/ui/layers/layer_manager.cpp +++ b/ui/layers/layer_manager.cpp @@ -11,6 +11,16 @@ namespace Ui { LayerManager::LayerManager(not_null widget) : _widget(widget) { } +void LayerManager::setStyleOverrides( + const style::Box *boxSt, + const style::Box *layerSt) { + _boxSt = boxSt; + _layerSt = layerSt; + if (_layer) { + _layer->setStyleOverrides(_boxSt, _layerSt); + } +} + void LayerManager::setHideByBackgroundClick(bool hide) { _hideByBackgroundClick = hide; if (_layer) { @@ -55,6 +65,7 @@ void LayerManager::ensureLayerCreated() { } _layer.emplace(_widget); _layer->setHideByBackgroundClick(_hideByBackgroundClick); + _layer->setStyleOverrides(_boxSt, _layerSt); _layer->hideFinishEvents( ) | rpl::filter([=] { diff --git a/ui/layers/layer_manager.h b/ui/layers/layer_manager.h index 1f0d31e..4f53169 100644 --- a/ui/layers/layer_manager.h +++ b/ui/layers/layer_manager.h @@ -10,6 +10,10 @@ #include +namespace style { +struct Box; +} // namespace style + namespace Ui { class BoxContent; @@ -19,6 +23,10 @@ class LayerManager final { public: explicit LayerManager(not_null widget); + void setStyleOverrides( + const style::Box *boxSt, + const style::Box *layerSt); + void setHideByBackgroundClick(bool hide); void showBox( object_ptr box, @@ -34,6 +42,9 @@ private: const not_null _widget; base::unique_qptr _layer; + + const style::Box *_boxSt = nullptr; + const style::Box *_layerSt = nullptr; bool _hideByBackgroundClick = false; }; diff --git a/ui/layers/layer_widget.cpp b/ui/layers/layer_widget.cpp index b60cf69..c0ffe93 100644 --- a/ui/layers/layer_widget.cpp +++ b/ui/layers/layer_widget.cpp @@ -458,6 +458,13 @@ bool LayerStackWidget::layerShown() const { return _specialLayer || currentLayer() || _mainMenu; } +void LayerStackWidget::setStyleOverrides( + const style::Box *boxSt, + const style::Box *layerSt) { + _boxSt = boxSt; + _layerSt = layerSt; +} + void LayerStackWidget::setCacheImages() { auto bodyCache = QPixmap(), mainMenuCache = QPixmap(); auto specialLayerCache = QPixmap(); diff --git a/ui/layers/layer_widget.h b/ui/layers/layer_widget.h index 94344a0..f673678 100644 --- a/ui/layers/layer_widget.h +++ b/ui/layers/layer_widget.h @@ -16,6 +16,10 @@ class SectionMemento; struct SectionShow; } // namespace Window +namespace style { +struct Box; +} // namespace style + namespace Ui { class BoxContent; @@ -93,6 +97,16 @@ public: void finishAnimating(); rpl::producer<> hideFinishEvents() const; + void setStyleOverrides( + const style::Box *boxSt, + const style::Box *layerSt); + [[nodiscard]] const style::Box *boxStyleOverrideLayer() const { + return _layerSt; + } + [[nodiscard]] const style::Box *boxStyleOverride() const { + return _boxSt; + } + void showBox( object_ptr box, LayerOptions options, @@ -201,6 +215,9 @@ private: class BackgroundWidget; object_ptr _background; + + const style::Box *_boxSt = nullptr; + const style::Box *_layerSt = nullptr; bool _hideByBackgroundClick = true; rpl::event_stream<> _hideFinishStream; diff --git a/ui/layers/layers.style b/ui/layers/layers.style index ddb32ea..10046f9 100644 --- a/ui/layers/layers.style +++ b/ui/layers/layers.style @@ -24,6 +24,11 @@ ServiceCheck { Box { buttonPadding: margins; buttonHeight: pixels; + button: RoundButton; + margin: margins; + title: FlatLabel; + bg: color; + titleAdditionalFg: color; } boxDuration: 200; @@ -36,12 +41,6 @@ defaultBoxButton: RoundButton(defaultLightButton) { font: boxButtonFont; } -boxTextStyle: TextStyle(defaultTextStyle) { - font: font(boxFontSize); - linkFont: font(boxFontSize); - linkFontOver: font(boxFontSize underline); -} - boxLabelStyle: TextStyle(boxTextStyle) { lineHeight: 22px; } @@ -118,7 +117,6 @@ boxOptionListPadding: margins(0px, 0px, 0px, 0px); boxOptionListSkip: 20px; boxOptionInputSkip: 6px; -boxVerticalMargin: 10px; boxWidth: 320px; boxWideWidth: 364px; boxPadding: margins(22px, 30px, 22px, 8px); @@ -129,6 +127,11 @@ boxMediumSkip: 20px; defaultBox: Box { buttonPadding: margins(8px, 12px, 13px, 12px); buttonHeight: 36px; + button: defaultBoxButton; + margin: margins(0px, 10px, 0px, 10px); + bg: boxBg; + title: boxTitle; + titleAdditionalFg: boxTitleAdditionalFg; } layerBox: Box(defaultBox) { buttonPadding: margins(8px, 8px, 8px, 8px); diff --git a/ui/round_rect.cpp b/ui/round_rect.cpp index eb42a51..8d19b22 100644 --- a/ui/round_rect.cpp +++ b/ui/round_rect.cpp @@ -67,22 +67,25 @@ RoundRect::RoundRect( ImageRoundRadius radius, const style::color &color) : _color(color) -, _corners(Images::PrepareCorners(radius, color)) { +, _refresh([=] { _corners = Images::PrepareCorners(radius, _color); }) { + _refresh(); style::PaletteChanged( - ) | rpl::start_with_next([=] { - _corners = Images::PrepareCorners(radius, _color); - }, _lifetime); + ) | rpl::start_with_next(_refresh, _lifetime); } RoundRect::RoundRect( int radius, const style::color &color) : _color(color) -, _corners(Images::PrepareCorners(radius, color)) { +, _refresh([=] { _corners = Images::PrepareCorners(radius, _color); }) { + _refresh(); style::PaletteChanged( - ) | rpl::start_with_next([=] { - _corners = Images::PrepareCorners(radius, _color); - }, _lifetime); + ) | rpl::start_with_next(_refresh, _lifetime); +} + +void RoundRect::setColor(const style::color &color) { + _color = color; + _refresh(); } const style::color &RoundRect::color() const { diff --git a/ui/round_rect.h b/ui/round_rect.h index 920c3dd..e2c184b 100644 --- a/ui/round_rect.h +++ b/ui/round_rect.h @@ -27,6 +27,7 @@ public: RoundRect(int radius, const style::color &color); [[nodiscard]] const style::color &color() const; + void setColor(const style::color &color); void paint( QPainter &p, const QRect &rect, @@ -39,6 +40,7 @@ public: private: style::color _color; std::array _corners; + Fn _refresh; rpl::lifetime _lifetime; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 01d9cf4..8f84da0 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -495,6 +495,7 @@ SettingsButton { textBgOver: color; font: font; + rightLabel: FlatLabel; height: pixels; padding: margins; @@ -780,6 +781,7 @@ defaultToggle: Toggle { } defaultCheckbox: Checkbox { textFg: windowFg; + textFgActive: windowFg; width: -44px; margin: margins(8px, 8px, 8px, 8px); @@ -821,12 +823,76 @@ defaultMultiSelectItem: MultiSelectItem { minScale: 0.3; } -widgetSlideDuration: 200; -widgetFadeDuration: 200; +defaultMultiSelectSearchField: InputField(defaultInputField) { + textBg: transparent; + textMargins: margins(2px, 7px, 2px, 0px); + + placeholderFg: placeholderFg; + placeholderFgActive: placeholderFgActive; + placeholderFgError: placeholderFgActive; + placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; + + border: 0px; + borderActive: 0px; + + heightMin: 32px; + + font: normalFont; +} fieldSearchIcon: icon {{ "box_search", menuIconFg, point(9px, 8px) }}; boxFieldSearchIcon: icon {{ "box_search", menuIconFg, point(10px, 9px) }}; +defaultMultiSelectSearchCancel: CrossButton { + width: 44px; + height: 44px; + + cross: CrossAnimation { + size: 36px; + skip: 12px; + stroke: 2px; + minScale: 0.3; + } + crossFg: boxTitleCloseFg; + crossFgOver: boxTitleCloseFgOver; + crossPosition: point(4px, 4px); + + duration: 150; + loadingPeriod: 1000; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +defaultMultiSelect: MultiSelect { + bg: boxSearchBg; + padding: margins(8px, 6px, 8px, 6px); + maxHeight: 104px; + scroll: ScrollArea(defaultSolidScroll) { + deltat: 3px; + deltab: 3px; + round: 1px; + width: 8px; + deltax: 3px; + hiding: 1000; + } + + item: defaultMultiSelectItem; + itemSkip: 8px; + + field: defaultMultiSelectSearchField; + fieldMinWidth: 42px; + fieldIcon: boxFieldSearchIcon; + fieldIconSkip: 36px; + + fieldCancel: defaultMultiSelectSearchCancel; + fieldCancelSkip: 40px; +} + +widgetSlideDuration: 200; +widgetFadeDuration: 200; + SettingsSlider { height: pixels; barTop: pixels; @@ -1125,6 +1191,7 @@ PeerListItem { button: OutlineButton; checkbox: RoundImageCheckbox; + disabledCheckFg: color; statusFg: color; statusFgOver: color; statusFgActive: color; @@ -1132,6 +1199,8 @@ PeerListItem { PeerList { padding: margins; + bg: color; + about: FlatLabel; item: PeerListItem; } @@ -1163,13 +1232,31 @@ defaultPeerListItem: PeerListItem { photoSize: 46px; button: defaultPeerListButton; checkbox: defaultPeerListCheckbox; + disabledCheckFg: menuIconFg; statusFg: windowSubTextFg; statusFgOver: windowSubTextFgOver; statusFgActive: windowActiveTextFg; } +boxTextStyle: TextStyle(defaultTextStyle) { + font: font(boxFontSize); + linkFont: font(boxFontSize); + linkFontOver: font(boxFontSize underline); +} + +defaultPeerListAbout: FlatLabel(defaultFlatLabel) { + minWidth: 240px; + textFg: membersAboutLimitFg; + align: align(top); + style: TextStyle(boxTextStyle) { + lineHeight: 22px; + } +} + defaultPeerList: PeerList { padding: margins(0px, 0px, 0px, 0px); + bg: contactsBg; + about: defaultPeerListAbout; item: defaultPeerListItem; } @@ -1256,6 +1343,11 @@ defaultSettingsToggle: Toggle(defaultToggle) { defaultSettingsToggleOver: Toggle(defaultSettingsToggle) { untoggledFg: menuIconFgOver; } +defaultSettingsRightLabel: FlatLabel(defaultFlatLabel) { + textFg: windowActiveTextFg; + style: boxTextStyle; + maxHeight: 20px; +} defaultSettingsButton: SettingsButton { textFg: windowBoldFg; textFgOver: windowBoldFgOver; @@ -1263,6 +1355,7 @@ defaultSettingsButton: SettingsButton { textBgOver: windowBgOver; font: boxTextFont; + rightLabel: defaultSettingsRightLabel; height: 20px; padding: margins(22px, 10px, 22px, 8px); From 79ea651127962fff366e9d76ce4d932490b30158 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Nov 2020 10:14:34 +0300 Subject: [PATCH 11/79] Divide group call button over and ripple colors. --- ui/colors.palette | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/colors.palette b/ui/colors.palette index 47d0f4d..a43d8aa 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -557,8 +557,9 @@ callMuteRipple: #ffffff12; // phone call popup mute mic and camera ripple effect groupCallBg: #131417; // group call popup background groupCallActiveFg: #4db8ff; // group call active controls text groupCallMembersBg: #1a1c1f; // group call members list background +groupCallMembersBgOver: #22252a; // group call members list row with mouse over +groupCallMembersBgRipple: #272b30; // group call member row ripple effect groupCallMembersFg: #ffffff; // group call member name text -groupCallMembersRipple: #22252a; // group call member row ripple effect groupCallMemberActiveIcon: #3fe069; // group call active member icon groupCallMemberActiveStatus: #67e66d; // group call active member status text groupCallMemberInactiveIcon: #61666b; // group call inactive member icon From 3a05cdc11e5ceb6b4d4dda976c69dced0941d067 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Nov 2020 14:13:54 +0300 Subject: [PATCH 12/79] Allow PopupMenu not to reactivate parent window. --- ui/widgets/popup_menu.cpp | 4 +++- ui/widgets/popup_menu.h | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/widgets/popup_menu.cpp b/ui/widgets/popup_menu.cpp index c40ab0f..de300ec 100644 --- a/ui/widgets/popup_menu.cpp +++ b/ui/widgets/popup_menu.cpp @@ -538,7 +538,9 @@ PopupMenu::~PopupMenu() { delete submenu; } if (const auto parent = parentWidget()) { - if (QApplication::focusWidget() != nullptr + const auto focused = QApplication::focusWidget(); + if (_reactivateParent + && focused != nullptr && Ui::InFocusChain(parent->window())) { ActivateWindowDelayed(parent); } diff --git a/ui/widgets/popup_menu.h b/ui/widgets/popup_menu.h index 03f8583..ca08f98 100644 --- a/ui/widgets/popup_menu.h +++ b/ui/widgets/popup_menu.h @@ -37,6 +37,9 @@ public: void setDestroyedCallback(Fn callback) { _destroyedCallback = std::move(callback); } + void discardParentReActivate() { + _reactivateParent = false; + } ~PopupMenu(); @@ -122,6 +125,7 @@ private: bool _deleteOnHide = true; bool _triggering = false; bool _deleteLater = false; + bool _reactivateParent = true; Fn _destroyedCallback; From e0fb1129d145054410476bd83d1cecf5c2a2644d Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Nov 2020 14:19:38 +0300 Subject: [PATCH 13/79] Allow custom PopupMenu style in InputField. --- ui/widgets/input_fields.cpp | 2 +- ui/widgets/widgets.style | 351 ++++++++++++++++++------------------ 2 files changed, 178 insertions(+), 175 deletions(-) diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index c435025..bee6c57 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -3341,7 +3341,7 @@ bool InputField::revertFormatReplace() { void InputField::contextMenuEventInner(QContextMenuEvent *e, QMenu *m) { if (const auto menu = m ? m : _inner->createStandardContextMenu()) { addMarkdownActions(menu, e); - _contextMenu = base::make_unique_q(this, menu); + _contextMenu = base::make_unique_q(this, menu, _st.menu); _contextMenu->popup(e->globalPos()); } } diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 8f84da0..aa57e61 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -182,6 +182,74 @@ ScrollArea { hiding: int; } +Shadow { + left: icon; + topLeft: icon; + top: icon; + topRight: icon; + right: icon; + bottomRight: icon; + bottom: icon; + bottomLeft: icon; + extend: margins; + fallback: color; +} + +PanelAnimation { + startWidth: double; + widthDuration: double; + startHeight: double; + heightDuration: double; + startOpacity: double; + opacityDuration: double; + startFadeTop: double; + fadeHeight: double; + fadeOpacity: double; + fadeBg: color; + shadow: Shadow; +} + +Menu { + skip: pixels; + + itemBg: color; + itemBgOver: color; + itemFg: color; + itemFgOver: color; + itemFgDisabled: color; + itemFgShortcut: color; + itemFgShortcutOver: color; + itemFgShortcutDisabled: color; + itemPadding: margins; + itemIconPosition: point; + itemStyle: TextStyle; + itemToggle: Toggle; + itemToggleOver: Toggle; + itemToggleShift: pixels; + + separatorPadding: margins; + separatorWidth: pixels; + separatorFg: color; + + arrow: icon; + + widthMin: pixels; + widthMax: pixels; + + ripple: RippleAnimation; +} + +PopupMenu { + shadow: Shadow; + scrollPadding: margins; + animation: PanelAnimation; + + menu: Menu; + + duration: int; + showDuration: int; +} + FlatInput { textColor: color; bgColor: color; @@ -231,6 +299,7 @@ InputField { borderActive: pixels; font: font; + menu: PopupMenu; width: pixels; heightMin: pixels; @@ -265,19 +334,6 @@ IconButton { ripple: RippleAnimation; } -Shadow { - left: icon; - topLeft: icon; - top: icon; - topRight: icon; - right: icon; - bottomRight: icon; - bottom: icon; - bottomLeft: icon; - extend: margins; - fallback: color; -} - MediaSlider { width: pixels; activeFg: color; @@ -387,61 +443,6 @@ CallButton { label: FlatLabel; } -Menu { - skip: pixels; - - itemBg: color; - itemBgOver: color; - itemFg: color; - itemFgOver: color; - itemFgDisabled: color; - itemFgShortcut: color; - itemFgShortcutOver: color; - itemFgShortcutDisabled: color; - itemPadding: margins; - itemIconPosition: point; - itemStyle: TextStyle; - itemToggle: Toggle; - itemToggleOver: Toggle; - itemToggleShift: pixels; - - separatorPadding: margins; - separatorWidth: pixels; - separatorFg: color; - - arrow: icon; - - widthMin: pixels; - widthMax: pixels; - - ripple: RippleAnimation; -} - -PanelAnimation { - startWidth: double; - widthDuration: double; - startHeight: double; - heightDuration: double; - startOpacity: double; - opacityDuration: double; - startFadeTop: double; - fadeHeight: double; - fadeOpacity: double; - fadeBg: color; - shadow: Shadow; -} - -PopupMenu { - shadow: Shadow; - scrollPadding: margins; - animation: PanelAnimation; - - menu: Menu; - - duration: int; - showDuration: int; -} - InnerDropdown { padding: margins; shadow: Shadow; @@ -712,35 +713,6 @@ defaultFlatInput: FlatInput { phDuration: 100; } -defaultInputField: InputField { - textBg: windowBg; - textFg: windowFg; - textMargins: margins(0px, 26px, 0px, 4px); - textAlign: align(topleft); - - placeholderFg: windowSubTextFg; - placeholderFgActive: windowActiveTextFg; - placeholderFgError: attentionButtonFg; - placeholderMargins: margins(0px, 0px, 0px, 0px); - placeholderAlign: align(topleft); - placeholderScale: 0.9; - placeholderShift: -20px; - placeholderFont: font(semibold 14px); - duration: 150; - - borderFg: inputBorderFg; - borderFgActive: activeLineFg; - borderFgError: activeLineFgError; - - border: 1px; - borderActive: 2px; - - font: boxTextFont; - - heightMin: 52px; - heightMax: 148px; -} - defaultCheckboxIcon: icon {{ "default_checkbox_check", overviewCheckFgActive, point(4px, 7px) }}; defaultCheck: Check { @@ -799,6 +771,114 @@ defaultCheckbox: Checkbox { disabledOpacity: 0.5; } +defaultRoundShadow: Shadow { + left: icon {{ "round_shadow_left", windowShadowFg }}; + topLeft: icon {{ "round_shadow_top_left", windowShadowFg }}; + top: icon {{ "round_shadow_top", windowShadowFg }}; + topRight: icon {{ "round_shadow_top_left-flip_horizontal", windowShadowFg }}; + right: icon {{ "round_shadow_left-flip_horizontal", windowShadowFg }}; + bottomRight: icon {{ "round_shadow_bottom_left-flip_horizontal", windowShadowFg }}; + bottom: icon {{ "round_shadow_bottom", windowShadowFg }}; + bottomLeft: icon {{ "round_shadow_bottom_left", windowShadowFg }}; + extend: margins(3px, 2px, 3px, 4px); + fallback: windowShadowFgFallback; +} +defaultEmptyShadow: Shadow { + fallback: windowBg; +} + +defaultPanelAnimation: PanelAnimation { + startWidth: 0.5; + widthDuration: 0.6; + startHeight: 0.3; + heightDuration: 0.9; + startOpacity: 0.2; + opacityDuration: 0.3; + startFadeTop: 0.; + fadeHeight: 0.2; + fadeOpacity: 1.0; + fadeBg: menuBg; + shadow: defaultRoundShadow; +} + +defaultMenuArrow: icon {{ "dropdown_submenu_arrow", menuSubmenuArrowFg }}; +defaultMenuToggle: Toggle(defaultToggle) { + untoggledFg: menuIconFg; +} +defaultMenuToggleOver: Toggle(defaultToggle) { + untoggledFg: menuIconFgOver; +} +defaultMenu: Menu { + skip: 0px; + + itemBg: windowBg; + itemBgOver: windowBgOver; + itemFg: windowFg; + itemFgOver: windowFgOver; + itemFgDisabled: menuFgDisabled; + itemFgShortcut: windowSubTextFg; + itemFgShortcutOver: windowSubTextFgOver; + itemFgShortcutDisabled: menuFgDisabled; + itemIconPosition: point(0px, 0px); + itemPadding: margins(17px, 8px, 17px, 7px); + itemStyle: defaultTextStyle; + itemToggle: defaultMenuToggle; + itemToggleOver: defaultMenuToggleOver; + itemToggleShift: 0px; + + separatorPadding: margins(0px, 5px, 0px, 5px); + separatorWidth: 1px; + separatorFg: menuSeparatorFg; + + arrow: defaultMenuArrow; + + widthMin: 180px; + widthMax: 300px; + + ripple: defaultRippleAnimation; +} +defaultPopupMenu: PopupMenu { + shadow: defaultRoundShadow; + animation: defaultPanelAnimation; + + scrollPadding: margins(0px, 8px, 0px, 8px); + + menu: defaultMenu; + + duration: 150; + showDuration: 200; +} + +defaultInputField: InputField { + textBg: windowBg; + textFg: windowFg; + textMargins: margins(0px, 26px, 0px, 4px); + textAlign: align(topleft); + + placeholderFg: windowSubTextFg; + placeholderFgActive: windowActiveTextFg; + placeholderFgError: attentionButtonFg; + placeholderMargins: margins(0px, 0px, 0px, 0px); + placeholderAlign: align(topleft); + placeholderScale: 0.9; + placeholderShift: -20px; + placeholderFont: font(semibold 14px); + duration: 150; + + borderFg: inputBorderFg; + borderFgActive: activeLineFg; + borderFgError: activeLineFgError; + + border: 1px; + borderActive: 2px; + + font: boxTextFont; + menu: defaultPopupMenu; + + heightMin: 52px; + heightMax: 148px; +} + defaultIconButton: IconButton { iconPosition: point(-1px, -1px); } @@ -940,36 +1020,6 @@ defaultTabsSlider: SettingsSlider(defaultSettingsSlider) { ripple: defaultRippleAnimation; } -defaultRoundShadow: Shadow { - left: icon {{ "round_shadow_left", windowShadowFg }}; - topLeft: icon {{ "round_shadow_top_left", windowShadowFg }}; - top: icon {{ "round_shadow_top", windowShadowFg }}; - topRight: icon {{ "round_shadow_top_left-flip_horizontal", windowShadowFg }}; - right: icon {{ "round_shadow_left-flip_horizontal", windowShadowFg }}; - bottomRight: icon {{ "round_shadow_bottom_left-flip_horizontal", windowShadowFg }}; - bottom: icon {{ "round_shadow_bottom", windowShadowFg }}; - bottomLeft: icon {{ "round_shadow_bottom_left", windowShadowFg }}; - extend: margins(3px, 2px, 3px, 4px); - fallback: windowShadowFgFallback; -} -defaultEmptyShadow: Shadow { - fallback: windowBg; -} - -defaultPanelAnimation: PanelAnimation { - startWidth: 0.5; - widthDuration: 0.6; - startHeight: 0.3; - heightDuration: 0.9; - startOpacity: 0.2; - opacityDuration: 0.3; - startFadeTop: 0.; - fadeHeight: 0.2; - fadeOpacity: 1.0; - fadeBg: menuBg; - shadow: defaultRoundShadow; -} - defaultContinuousSlider: MediaSlider { width: 3px; activeFg: mediaPlayerActiveFg; @@ -1012,53 +1062,6 @@ defaultPeerListCheckbox: RoundImageCheckbox { check: defaultPeerListCheck; } -defaultMenuArrow: icon {{ "dropdown_submenu_arrow", menuSubmenuArrowFg }}; -defaultMenuToggle: Toggle(defaultToggle) { - untoggledFg: menuIconFg; -} -defaultMenuToggleOver: Toggle(defaultToggle) { - untoggledFg: menuIconFgOver; -} -defaultMenu: Menu { - skip: 0px; - - itemBg: windowBg; - itemBgOver: windowBgOver; - itemFg: windowFg; - itemFgOver: windowFgOver; - itemFgDisabled: menuFgDisabled; - itemFgShortcut: windowSubTextFg; - itemFgShortcutOver: windowSubTextFgOver; - itemFgShortcutDisabled: menuFgDisabled; - itemIconPosition: point(0px, 0px); - itemPadding: margins(17px, 8px, 17px, 7px); - itemStyle: defaultTextStyle; - itemToggle: defaultMenuToggle; - itemToggleOver: defaultMenuToggleOver; - itemToggleShift: 0px; - - separatorPadding: margins(0px, 5px, 0px, 5px); - separatorWidth: 1px; - separatorFg: menuSeparatorFg; - - arrow: defaultMenuArrow; - - widthMin: 180px; - widthMax: 300px; - - ripple: defaultRippleAnimation; -} -defaultPopupMenu: PopupMenu { - shadow: defaultRoundShadow; - animation: defaultPanelAnimation; - - scrollPadding: margins(0px, 8px, 0px, 8px); - - menu: defaultMenu; - - duration: 150; - showDuration: 200; -} defaultInnerDropdown: InnerDropdown { padding: margins(10px, 10px, 10px, 10px); shadow: defaultRoundShadow; From 7dd1950c234c6ea1dd643b95610b5ac2c8334665 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Nov 2020 15:09:22 +0300 Subject: [PATCH 14/79] Added colors for leave group call button. --- ui/colors.palette | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index a43d8aa..85ac37e 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -571,8 +571,11 @@ groupCallLive1: #0dcc39; // group call live button color1 groupCallLive2: #0bb6bd; // group call live button color2 groupCallMuted1: #0992ef; // group call muted button color1 groupCallMuted2: #16ccfb; // group call muted button color2 -groupCallBoxButtonBgOver: #1d2a39; // group call button in box with mouse over -groupCallBoxButtonBgRipple: #223143; // group call button in box ripple effect +groupCallMenuBg: #292d33; // group call popup menu background +groupCallMenuBgOver: #343940; // group call popup menu with mouse over +groupCallMenuBgRipple: #3a4047; // group call popup menu ripple effect +groupCallLeaveBg: #f75c5c7f; // group call leave button background +groupCallLeaveBgRipple: #f75c5c9e; // group call leave button ripple effect callBarBg: dialogsBgActive; // active phone call bar background callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect From 75171a058591a438b7c8938414d801f5c18d1daa Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 28 Nov 2020 03:10:14 +0300 Subject: [PATCH 15/79] Added class for linear gradient color animation. --- ui/effects/animation_value.h | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/ui/effects/animation_value.h b/ui/effects/animation_value.h index 0a7d226..dc5d530 100644 --- a/ui/effects/animation_value.h +++ b/ui/effects/animation_value.h @@ -11,6 +11,7 @@ #include "ui/style/style_core.h" #include +#include namespace anim { @@ -354,4 +355,53 @@ void DrawStaticLoading( QPen pen, QBrush brush = Qt::NoBrush); +class linear_gradient { +public: + linear_gradient( + std::vector colors_from, + std::vector colors_to, + QPointF point1, + QPointF point2) + : _colors_from(colors_from) + , _colors_to(colors_to) + , _point1(point1) + , _point2(point2) + , _gradient_from(gradient(colors_from)) + , _gradient_to(gradient(colors_to)) { + Expects(colors_from.size() == colors_to.size()); + } + + QLinearGradient gradient(float64 b_ratio) const { + if (b_ratio == 0.) { + return _gradient_from; + } else if (b_ratio == 1.) { + return _gradient_to; + } + auto colors = std::vector(_colors_to.size()); + for (auto i = 0; i < colors.size(); i++) { + colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); + } + return gradient(colors); + } + +private: + QLinearGradient gradient(const std::vector &colors) const { + auto gradient = QLinearGradient(_point1, _point2); + const auto size = colors.size(); + for (auto i = 0; i < size; i++) { + gradient.setColorAt(i / (size - 1), colors[i]); + } + return gradient; + } + + std::vector _colors_from; + std::vector _colors_to; + QPointF _point1; + QPointF _point2; + + QLinearGradient _gradient_from; + QLinearGradient _gradient_to; + }; + +} // namespace anim From 419989760c4c0444b9602846187ce278c0ac1858 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 29 Nov 2020 03:19:12 +0300 Subject: [PATCH 16/79] Added blob bezier implementation. --- CMakeLists.txt | 2 + ui/paint/blob.cpp | 136 ++++++++++++++++++++++++++++++++++++++++++++++ ui/paint/blob.h | 49 +++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 ui/paint/blob.cpp create mode 100644 ui/paint/blob.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4959564..3d0597b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,8 @@ PRIVATE ui/layers/layer_manager.h ui/layers/layer_widget.cpp ui/layers/layer_widget.h + ui/paint/blob.cpp + ui/paint/blob.h ui/platform/linux/ui_window_linux.cpp ui/platform/linux/ui_window_linux.h ui/platform/linux/ui_utility_linux.cpp diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp new file mode 100644 index 0000000..749afc7 --- /dev/null +++ b/ui/paint/blob.cpp @@ -0,0 +1,136 @@ +// 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/blob.h" + +#include "base/openssl_help.h" +#include "ui/painter.h" + +#include + +namespace Ui::Paint { + +namespace { + +constexpr auto kMaxSpeed = 8.2; +constexpr auto kMinSpeed = 0.8; + +float64 RandomAdditional() { + return (openssl::RandomValue() % 100 / 100.); +} + +} // namespace + +BlobBezier::BlobBezier(int n, float minScale, float minSpeed, float maxSpeed) +: _segmentsCount(n) +, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n))) +, _minScale(minScale) +, _minSpeed(minSpeed ? minSpeed : kMinSpeed) +, _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed) +, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) { + _radius.reserve(n); + _angle.reserve(n); + _radiusNext.reserve(n); + _angleNext.reserve(n); + _progress.reserve(n); + _speed.reserve(n); +} + +void BlobBezier::paint(Painter &p, const QBrush &brush) { + auto path = QPainterPath(); + auto m = QMatrix(); + + p.save(); + const auto scale = _minScale + _scale * (1. - _minScale); + if (scale != 1.) { + p.scale(scale, scale); + } + + for (auto i = 0; i < _segmentsCount; i++) { + const auto progress = _progress[i]; + const auto nextIndex = i + 1 < _segmentsCount ? (i + 1) : 0; + const auto progressNext = _progress[nextIndex]; + const auto r1 = _radius[i] * (1. - progress) + + _radiusNext[i] * progress; + const auto r2 = _radius[nextIndex] * (1. - progressNext) + + _radiusNext[nextIndex] * progressNext; + const auto angle1 = _angle[i] * (1. - progress) + + _angleNext[i] * progress; + const auto angle2 = _angle[nextIndex] * (1. - progressNext) + + _angleNext[nextIndex] * progressNext; + + const auto l = _segmentLength * (std::min(r1, r2) + + (std::max(r1, r2) - std::min(r1, r2)) / 2.); + + m.reset(); + m.rotate(angle1); + + const auto pointStart1 = m.map(QPointF(0, -r1)); + const auto pointStart2 = m.map(QPointF(l, -r1)); + + m.reset(); + m.rotate(angle2); + const auto pointEnd1 = m.map(QPointF(0, -r2)); + const auto pointEnd2 = m.map(QPointF(-l, -r2)); + + if (i == 0) { + path.moveTo(pointStart1); + } + + path.cubicTo(pointStart2, pointEnd2, pointEnd1); + } + + p.setBrush(Qt::NoBrush); + + p.setPen(_pen); + p.fillPath(path, brush); + p.drawPath(path); + + p.restore(); +} + +void BlobBezier::generateBlob() { + for (auto i = 0; i < _segmentsCount; i++) { + generateBlob(_radius, _angle, i); + generateBlob(_radiusNext, _angleNext, i); + _progress[i] = 0; + } +} + +void BlobBezier::generateBlob( + std::vector &radius, + std::vector &angle, + int i) { + const auto angleSegment = 360. / _segmentsCount; + const auto angleDiff = angleSegment * 0.05; + const auto radDiff = _maxRadius - _minRadius; + + radius[i] = _minRadius + std::abs(RandomAdditional()) * radDiff; + angle[i] = angleSegment * i + RandomAdditional() * angleDiff; + _speed[i] = 0.017 + 0.003 * std::abs(RandomAdditional()); +} + +void BlobBezier::update(float level, float speedScale) { + _scale = level; + for (auto i = 0; i < _segmentsCount; i++) { + _progress[i] += (_speed[i] * _minSpeed) + + level * _speed[i] * _maxSpeed * speedScale; + if (_progress[i] >= 1) { + _progress[i] = 0.; + _radius[i] = _radiusNext[i]; + _angle[i] = _angleNext[i]; + generateBlob(_radiusNext, _angleNext, i); + } + } +} + +void BlobBezier::setRadius(float min, float max) { + _minRadius = std::min(min, max); + _maxRadius = std::max(min, max); + generateBlob(); +} + +} // namespace Ui::Paint diff --git a/ui/paint/blob.h b/ui/paint/blob.h new file mode 100644 index 0000000..f50eb94 --- /dev/null +++ b/ui/paint/blob.h @@ -0,0 +1,49 @@ +// 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 + +class Painter; + +namespace Ui::Paint { + +class BlobBezier final { +public: + BlobBezier(int n, float minScale, float minSpeed = 0, float maxSpeed = 0); + + void paint(Painter &p, const QBrush &brush); + void update(float level, float speedScale); + void generateBlob(); + + void setRadius(float min, float max); + +private: + void generateBlob( + std::vector &radius, + std::vector &angle, + int i); + + const int _segmentsCount; + const float64 _segmentLength; + const float _minScale; + const float _minSpeed; + const float _maxSpeed; + const QPen _pen; + + std::vector _radius; + std::vector _angle; + std::vector _radiusNext; + std::vector _angleNext; + std::vector _progress; + std::vector _speed; + + float64 _scale = 0; + float _minRadius = 0.; + float _maxRadius = 0.; + +}; + +} // namespace Ui::Paint From df8eed957662a6cff9e31cc59387933cb587f501 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 29 Nov 2020 03:19:22 +0300 Subject: [PATCH 17/79] Added implementation of blobs painter. --- CMakeLists.txt | 2 ++ ui/effects/animation_value.h | 46 ++++++++++++++++++++++++++ ui/paint/blobs.cpp | 63 ++++++++++++++++++++++++++++++++++++ ui/paint/blobs.h | 50 ++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 ui/paint/blobs.cpp create mode 100644 ui/paint/blobs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d0597b..47285df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,8 @@ PRIVATE ui/layers/layer_widget.h ui/paint/blob.cpp ui/paint/blob.h + ui/paint/blobs.cpp + ui/paint/blobs.h ui/platform/linux/ui_window_linux.cpp ui/platform/linux/ui_window_linux.h ui/platform/linux/ui_utility_linux.cpp diff --git a/ui/effects/animation_value.h b/ui/effects/animation_value.h index dc5d530..93110ce 100644 --- a/ui/effects/animation_value.h +++ b/ui/effects/animation_value.h @@ -404,4 +404,50 @@ private: }; +class continuous_value { +public: + continuous_value() = default; + continuous_value(float64 duration) : _duration(duration) { + } + void start(float64 to, float64 duration) { + _to = to; + _delta = (_to - _cur) / duration; + } + void start(float64 to) { + start(to, _duration); + } + void reset() { + _to = _cur = _delta = 0.; + } + + float64 current() const { + return _cur; + } + float64 to() const { + return _to; + } + float64 delta() const { + return _delta; + } + void update(crl::time dt, Fn &&callback = nullptr) { + if (_to != _cur) { + _cur += _delta * dt; + if ((_to != _cur) && ((_delta > 0) == (_cur > _to))) { + _cur = _to; + } + if (callback) { + callback(_cur); + } + } + } + +private: + float64 _duration = 0.; + float64 _to = 0.; + + float64 _cur = 0.; + float64 _delta = 0.; + +}; + } // namespace anim diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp new file mode 100644 index 0000000..a235725 --- /dev/null +++ b/ui/paint/blobs.cpp @@ -0,0 +1,63 @@ +// 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/blobs.h" + +#include "ui/painter.h" + +namespace Ui::Paint { + +Blobs::Blobs( + std::vector blobDatas, + float levelDuration, + float maxLevel) +: _maxLevel(maxLevel) +, _blobDatas(blobDatas) +, _levelValue(levelDuration) { + init(); +} + +void Blobs::init() { + for (const auto &data : _blobDatas) { + auto blob = Paint::BlobBezier(data.segmentsCount, data.minScale); + blob.setRadius(data.minRadius, data.maxRadius); + _blobs.push_back(std::move(blob)); + } +} + +float Blobs::maxRadius() const { + const auto maxOfRadiuses = [](const BlobData data) { + return std::max(data.maxRadius, data.minRadius); + }; + const auto max = *ranges::max_element( + _blobDatas, + std::less<>(), + maxOfRadiuses); + return maxOfRadiuses(max); +} + +void Blobs::setLevel(float value) { + const auto to = std::min(_maxLevel, value) / _maxLevel; + _levelValue.start(to); +} + +void Blobs::paint(Painter &p, const QBrush &brush) { + const auto dt = crl::now() - _lastUpdateTime; + _levelValue.update((dt > 20) ? 17 : dt); + + 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); + } + _blobs[i].paint(p, brush); + } + _lastUpdateTime = crl::now(); +} + +} // namespace Ui::Paint diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h new file mode 100644 index 0000000..6d53eab --- /dev/null +++ b/ui/paint/blobs.h @@ -0,0 +1,50 @@ +// 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/effects/animation_value.h" +#include "ui/paint/blob.h" + +class Painter; + +namespace Ui::Paint { + +class Blobs final { +public: + struct BlobData { + int segmentsCount = 0; + float minScale = 0; + float minRadius = 0; + float maxRadius = 0; + float speedScale = 0; + float alpha = 0; + }; + + Blobs( + std::vector blobDatas, + float levelDuration, + float maxLevel); + + void setLevel(float value); + void paint(Painter &p, const QBrush &brush); + + [[nodiscard]] float maxRadius() const; + +private: + void init(); + + const float _maxLevel; + + std::vector _blobDatas; + std::vector _blobs; + + crl::time _lastUpdateTime = 0; + anim::continuous_value _levelValue; + +}; + +} // namespace Ui::Paint From 024f77728722115520677444c594b0ea3cd0409f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 29 Nov 2020 03:20:44 +0300 Subject: [PATCH 18/79] Added blobs widget to CallMuteButton. --- ui/widgets/call_mute_button.cpp | 160 +++++++++++++++++++++++++++++++- ui/widgets/call_mute_button.h | 9 +- 2 files changed, 166 insertions(+), 3 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 7696a67..857d65b 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -6,14 +6,147 @@ // #include "ui/widgets/call_mute_button.h" +#include "ui/paint/blobs.h" +#include "ui/painter.h" + +#include "styles/palette.h" #include "styles/style_widgets.h" namespace Ui { +namespace { + +constexpr auto kMaxLevel = 1.; + +constexpr auto kLevelDuration = 100. + 500. * 0.33; + +constexpr auto kScaleBig = 0.807 - 0.1; +constexpr auto kScaleSmall = 0.704 - 0.1; + +constexpr auto kScaleBigMin = 0.878; +constexpr auto kScaleSmallMin = 0.926; + +constexpr auto kScaleBigMax = kScaleBigMin + kScaleBig; +constexpr auto kScaleSmallMax = kScaleSmallMin + kScaleSmall; + +constexpr auto kMainRadiusFactor = 50. / 57.; + +constexpr auto kSwitchStateDuration = 120; + +constexpr auto MuteBlobs() -> std::array { + return {{ + { + .segmentsCount = 6, + .minScale = 1., + .minRadius = 57. * kMainRadiusFactor, + .maxRadius = 63. * kMainRadiusFactor, + .speedScale = .4, + .alpha = 1., + }, + { + .segmentsCount = 9, + .minScale = kScaleSmallMin / kScaleSmallMax, + .minRadius = 62 * kScaleSmallMax * kMainRadiusFactor, + .maxRadius = 72 * kScaleSmallMax * kMainRadiusFactor, + .speedScale = 1., + .alpha = (76. / 255.), + }, + { + .segmentsCount = 12, + .minScale = kScaleBigMin / kScaleBigMax, + .minRadius = 65 * kScaleBigMax * kMainRadiusFactor, + .maxRadius = 75 * kScaleBigMax * kMainRadiusFactor, + .speedScale = 1., + .alpha = (76. / 255.), + }, + }}; +} + +} // namespace + +class BlobsWidget final : public RpWidget { +public: + BlobsWidget(not_null parent); + + void setLevel(float level); + void requestPaintProgress(float64 progress); + +private: + void init(); + + Paint::Blobs _blobs; + + int _center = 0; + float64 _progress = 0; + + Animations::Basic _animation; + +}; + +BlobsWidget::BlobsWidget(not_null parent) +: RpWidget(parent) +, _blobs(MuteBlobs() | ranges::to_vector, kLevelDuration, kMaxLevel) { + init(); +} + +void BlobsWidget::init() { + setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto maxBlobDiameter = _blobs.maxRadius() * 2; + resize(maxBlobDiameter, maxBlobDiameter); + + const auto gradient = anim::linear_gradient( + { st::groupCallMuted1->c, st::groupCallMuted2->c }, + { st::groupCallLive1->c, st::groupCallLive2->c }, + QPoint(0, height()), + QPoint(width(), 0)); + + sizeValue( + ) | rpl::start_with_next([=](QSize size) { + _center = size.width() / 2; + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + + PainterHighQualityEnabler hq(p); + p.translate(_center, _center); + _blobs.paint(p, QBrush(gradient.gradient(_progress))); + }, lifetime()); + + _animation.init([=](crl::time now) { + const auto dt = now - _animation.started(); + update(); + return true; + }); + shownValue( + ) | rpl::start_with_next([=](bool shown) { + if (shown) { + _animation.start(); + } else { + _animation.stop(); + } + }, lifetime()); +} + +void BlobsWidget::setLevel(float level) { + _blobs.setLevel(level); +} + +void BlobsWidget::requestPaintProgress(float64 progress) { + if (progress == _progress) { + return; + } + _progress = progress; + update(); +} + CallMuteButton::CallMuteButton( - not_null parent, + not_null parent, CallMuteButtonState initial) : _state(initial) +, _blobs(base::make_unique_q(parent)) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) , _connecting(parent, st::callMuteButtonConnecting) { if (_state.type == CallMuteButtonType::Connecting @@ -45,12 +178,25 @@ void CallMuteButton::setState(const CallMuteButtonState &state) { if (_state.text != state.text) { _content.setText(rpl::single(state.text)); } + + const auto isWasActive = (_state.type == CallMuteButtonType::Active); + const auto isActive = (state.type == CallMuteButtonType::Active); + { + const auto from = _switchAnimation.value(isWasActive ? 1. : 0.); + const auto to = isActive ? 1. : 0.; + _switchAnimation.start( + [=](auto value) { _blobs->requestPaintProgress(value); }, + from, + to, + kSwitchStateDuration); + } + _content.setProgress((state.type == CallMuteButtonType::Muted) ? 1. : 0.); if (!_connecting.isHidden() || !_content.isHidden()) { _content.show(); } _connecting.hide(); - if (state.type == CallMuteButtonType::Active) { + if (isActive) { _content.setOuterValue(_level); } else { _content.setOuterValue(0.); @@ -61,6 +207,7 @@ void CallMuteButton::setState(const CallMuteButtonState &state) { void CallMuteButton::setLevel(float level) { _level = level; + _blobs->setLevel(level); if (_state.type == CallMuteButtonType::Active) { _content.setOuterValue(level); } @@ -87,6 +234,13 @@ void CallMuteButton::moveInner(QPoint position) { const auto skip = st::callMuteButtonActive.outerRadius; _content.move(position - QPoint(skip, skip)); _connecting.move(_content.pos()); + + { + const auto offset = QPoint( + (_blobs->width() - _content.width()) / 2, + (_blobs->height() - _content.width()) / 2); + _blobs->move(_content.pos() - offset); + } } void CallMuteButton::setVisible(bool visible) { @@ -102,6 +256,7 @@ void CallMuteButton::setVisible(bool visible) { } void CallMuteButton::raise() { + _blobs->raise(); _content.raise(); _connecting.raise(); } @@ -109,6 +264,7 @@ void CallMuteButton::raise() { void CallMuteButton::lower() { _content.lower(); _connecting.lower(); + _blobs->lower(); } rpl::lifetime &CallMuteButton::lifetime() { diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 31cc78e..45d1239 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -6,10 +6,14 @@ // #pragma once +#include "base/unique_qptr.h" +#include "ui/effects/animations.h" #include "ui/widgets/call_button.h" namespace Ui { +class BlobsWidget; + enum class CallMuteButtonType { Connecting, Active, @@ -25,7 +29,7 @@ struct CallMuteButtonState { class CallMuteButton final { public: explicit CallMuteButton( - not_null parent, + not_null parent, CallMuteButtonState initial = CallMuteButtonState()); void setState(const CallMuteButtonState &state); @@ -52,9 +56,12 @@ private: CallMuteButtonState _state; float _level = 0.; + const base::unique_qptr _blobs; CallButton _content; CallButton _connecting; + Animations::Simple _switchAnimation; + }; } // namespace Ui From 21eb3509bcf6f2fe1c99f444dcdda82d8767e3c4 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 29 Nov 2020 09:34:19 +0300 Subject: [PATCH 19/79] Added icon cross line animation. --- CMakeLists.txt | 2 ++ ui/effects/cross_line.cpp | 64 +++++++++++++++++++++++++++++++++++++++ ui/effects/cross_line.h | 34 +++++++++++++++++++++ ui/widgets/widgets.style | 8 +++++ 4 files changed, 108 insertions(+) create mode 100644 ui/effects/cross_line.cpp create mode 100644 ui/effects/cross_line.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 47285df..8bd2451 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,8 @@ PRIVATE ui/effects/animations.h ui/effects/cross_animation.cpp ui/effects/cross_animation.h + ui/effects/cross_line.cpp + ui/effects/cross_line.h ui/effects/fade_animation.cpp ui/effects/fade_animation.h ui/effects/numbers_animation.cpp diff --git a/ui/effects/cross_line.cpp b/ui/effects/cross_line.cpp new file mode 100644 index 0000000..5bede5b --- /dev/null +++ b/ui/effects/cross_line.cpp @@ -0,0 +1,64 @@ +// 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/effects/cross_line.h" + +#include "ui/painter.h" + +namespace Ui { + +CrossLineAnimation::CrossLineAnimation( + const style::CrossLineAnimation &st, + float angle) +: _st(st) +, _transparentPen(Qt::transparent, st.stroke, Qt::SolidLine, Qt::RoundCap) +, _strokePen(st.fg, st.stroke, Qt::SolidLine, Qt::RoundCap) +, _line(st.startPosition, st.endPosition) +, _completeCross(image(1.)) { + _line.setAngle(angle); +} + +void CrossLineAnimation::paint( + Painter &p, + int left, + int top, + float64 progress) { + if (progress == 0.) { + _st.icon.paint(p, left, top, _st.icon.width()); + } else if (progress == 1.) { + p.drawImage(left, top, _completeCross); + } else { + p.drawImage(left, top, image(progress)); + } +} + +QImage CrossLineAnimation::image(float64 progress) const { + const auto ratio = style::DevicePixelRatio(); + auto frame = QImage( + QSize(_st.icon.width() * ratio, _st.icon.height() * ratio), + QImage::Format_ARGB32_Premultiplied); + frame.setDevicePixelRatio(ratio); + frame.fill(Qt::transparent); + + auto topLine = _line; + topLine.setLength(topLine.length() * progress); + auto bottomLine = topLine.translated(0, _strokePen.widthF() + 1); + + Painter q(&frame); + PainterHighQualityEnabler hq(q); + _st.icon.paint(q, 0, 0, _st.icon.width()); + + q.setPen(_strokePen); + q.drawLine(bottomLine); + + q.setCompositionMode(QPainter::CompositionMode_Source); + q.setPen(_transparentPen); + q.drawLine(topLine); + + return frame; +} + +} // namespace Ui diff --git a/ui/effects/cross_line.h b/ui/effects/cross_line.h new file mode 100644 index 0000000..eb6edb5 --- /dev/null +++ b/ui/effects/cross_line.h @@ -0,0 +1,34 @@ +// 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 { + +class CrossLineAnimation { +public: + CrossLineAnimation( + const style::CrossLineAnimation &st, + float angle = 315); + + void paint(Painter &p, int left, int top, float64 progress); + +private: + QImage image(float64 progress) const; + + const style::CrossLineAnimation &_st; + const QPen _transparentPen; + const QPen _strokePen; + QLineF _line; + const QImage _completeCross; + +}; + +} // namespace Ui diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index aa57e61..0d2ffcd 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -400,6 +400,14 @@ CrossButton { ripple: RippleAnimation; } +CrossLineAnimation { + fg: color; + icon: icon; + startPosition: point; + endPosition: point; + stroke: pixels; +} + MultiSelectItem { padding: margins; maxWidth: pixels; From cc8e750c113876ff325188cb3b41d69ead394959 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 29 Nov 2020 21:34:07 +0300 Subject: [PATCH 20/79] Added cross line animation to icon of mute button. --- ui/effects/cross_line.cpp | 7 +++++ ui/effects/cross_line.h | 1 + ui/widgets/call_mute_button.cpp | 45 +++++++++++++++++++++++++++++++-- ui/widgets/call_mute_button.h | 7 +++++ ui/widgets/widgets.style | 11 +++++++- 5 files changed, 68 insertions(+), 3 deletions(-) diff --git a/ui/effects/cross_line.cpp b/ui/effects/cross_line.cpp index 5bede5b..a653ea3 100644 --- a/ui/effects/cross_line.cpp +++ b/ui/effects/cross_line.cpp @@ -21,6 +21,13 @@ CrossLineAnimation::CrossLineAnimation( _line.setAngle(angle); } +void CrossLineAnimation::paint( + Painter &p, + QPoint position, + float64 progress) { + paint(p, position.x(), position.y(), progress); +} + void CrossLineAnimation::paint( Painter &p, int left, diff --git a/ui/effects/cross_line.h b/ui/effects/cross_line.h index eb6edb5..0cf8d99 100644 --- a/ui/effects/cross_line.h +++ b/ui/effects/cross_line.h @@ -18,6 +18,7 @@ public: const style::CrossLineAnimation &st, float angle = 315); + void paint(Painter &p, QPoint position, float64 progress); void paint(Painter &p, int left, int top, float64 progress); private: diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 857d65b..1ad8109 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -6,6 +6,7 @@ // #include "ui/widgets/call_mute_button.h" +#include "base/event_filter.h" #include "ui/paint/blobs.h" #include "ui/painter.h" @@ -148,7 +149,12 @@ CallMuteButton::CallMuteButton( : _state(initial) , _blobs(base::make_unique_q(parent)) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) -, _connecting(parent, st::callMuteButtonConnecting) { +, _connecting(parent, st::callMuteButtonConnecting) +, _crossLineMuteAnimation(st::callMuteCrossLine) { + init(); +} + +void CallMuteButton::init() { if (_state.type == CallMuteButtonType::Connecting || _state.type == CallMuteButtonType::ForceMuted) { _connecting.setText(rpl::single(_state.text)); @@ -161,6 +167,37 @@ CallMuteButton::CallMuteButton( _content.show(); } _connecting.setAttribute(Qt::WA_TransparentForMouseEvents); + + _content.sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto &icon = st::callMuteButtonActive.button.icon; + const auto &pos = st::callMuteButtonActive.button.iconPosition; + + _muteIconPosition = QPoint( + (pos.x() < 0) ? ((size.width() - icon.width()) / 2) : pos.x(), + (pos.y() < 0) ? ((size.height() - icon.height()) / 2) : pos.y()); + }, lifetime()); + + auto filterCallback = [=](not_null e) { + if (e->type() != QEvent::Paint) { + return base::EventFilterResult::Continue; + } + contentPaint(); + return base::EventFilterResult::Cancel; + }; + + auto filter = base::install_event_filter( + &_content, + std::move(filterCallback)); + + lifetime().make_state>(std::move(filter)); +} + +void CallMuteButton::contentPaint() { + Painter p(&_content); + + const auto progress = 1. - _crossLineProgress; + _crossLineMuteAnimation.paint(p, _muteIconPosition, progress); } void CallMuteButton::setState(const CallMuteButtonState &state) { @@ -185,7 +222,11 @@ void CallMuteButton::setState(const CallMuteButtonState &state) { const auto from = _switchAnimation.value(isWasActive ? 1. : 0.); const auto to = isActive ? 1. : 0.; _switchAnimation.start( - [=](auto value) { _blobs->requestPaintProgress(value); }, + [=](auto value) { + _blobs->requestPaintProgress(value); + _crossLineProgress = value; + _content.update(); + }, from, to, kSwitchStateDuration); diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 45d1239..9df8504 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -8,6 +8,7 @@ #include "base/unique_qptr.h" #include "ui/effects/animations.h" +#include "ui/effects/cross_line.h" #include "ui/widgets/call_button.h" namespace Ui { @@ -53,13 +54,19 @@ public: [[nodiscard]] rpl::lifetime &lifetime(); private: + void init(); + void contentPaint(); + CallMuteButtonState _state; float _level = 0.; + float64 _crossLineProgress = 0.; + QPoint _muteIconPosition; const base::unique_qptr _blobs; CallButton _content; CallButton _connecting; + CrossLineAnimation _crossLineMuteAnimation; Animations::Simple _switchAnimation; }; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 0d2ffcd..db95ef1 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1405,12 +1405,13 @@ defaultToast: Toast { durationSlide: 160; } +callMuteButtonActiveIcon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }}; callMuteButtonActiveInner: IconButton { width: 136px; height: 151px; iconPosition: point(-1px, 50px); - icon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }}; + icon: callMuteButtonActiveIcon; } callMuteButtonLabel: FlatLabel(defaultFlatLabel) { textFg: callNameFg; @@ -1444,6 +1445,14 @@ callMuteButtonConnecting: CallButton(callMuteButtonMuted) { label: callMuteButtonLabel; } +callMuteCrossLine: CrossLineAnimation { + fg: groupCallIconFg; + icon: callMuteButtonActiveIcon; + startPosition: point(7px, 2px); + endPosition: point(34px, 30px); + stroke: 4px; +} + // Windows specific title windowTitleButton: IconButton { From d7edda1d679ba1d37ba41cbc83b70fca185aebd0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 01:57:38 +0300 Subject: [PATCH 21/79] Added linear gradient animation in different states of mute button. --- ui/widgets/call_mute_button.cpp | 101 +++++++++++++++++++++++--------- ui/widgets/call_mute_button.h | 4 ++ 2 files changed, 77 insertions(+), 28 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 1ad8109..de697b8 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -63,6 +63,35 @@ constexpr auto MuteBlobs() -> std::array { }}; } +auto Colors() { + return std::unordered_map>{ + { + CallMuteButtonType::ForceMuted, + { st::callIconBg->c, st::callIconBg->c } + }, + { + CallMuteButtonType::Active, + { st::groupCallLive1->c, st::groupCallLive2->c } + }, + { + CallMuteButtonType::Connecting, + { st::callIconBg->c, st::callIconBg->c } + }, + { + CallMuteButtonType::Muted, + { st::groupCallMuted1->c, st::groupCallMuted2->c } + }, + }; +} + +inline float64 InterpolateF(int a, int b, float64 b_ratio) { + return a + float64(b - a) * b_ratio; +} + +bool IsMuted(CallMuteButtonType type) { + return (type != CallMuteButtonType::Active); +} + } // namespace class BlobsWidget final : public RpWidget { @@ -70,15 +99,15 @@ public: BlobsWidget(not_null parent); void setLevel(float level); - void requestPaintProgress(float64 progress); + void setBrush(QBrush brush); private: void init(); Paint::Blobs _blobs; + QBrush _brush; int _center = 0; - float64 _progress = 0; Animations::Basic _animation; @@ -86,7 +115,8 @@ private: BlobsWidget::BlobsWidget(not_null parent) : RpWidget(parent) -, _blobs(MuteBlobs() | ranges::to_vector, kLevelDuration, kMaxLevel) { +, _blobs(MuteBlobs() | ranges::to_vector, kLevelDuration, kMaxLevel) +, _brush(Qt::transparent) { init(); } @@ -113,7 +143,7 @@ void BlobsWidget::init() { PainterHighQualityEnabler hq(p); p.translate(_center, _center); - _blobs.paint(p, QBrush(gradient.gradient(_progress))); + _blobs.paint(p, _brush); }, lifetime()); _animation.init([=](crl::time now) { @@ -131,18 +161,18 @@ void BlobsWidget::init() { }, lifetime()); } -void BlobsWidget::setLevel(float level) { - _blobs.setLevel(level); -} - -void BlobsWidget::requestPaintProgress(float64 progress) { - if (progress == _progress) { +void BlobsWidget::setBrush(QBrush brush) { + if (_brush == brush) { return; } - _progress = progress; + _brush = brush; update(); } +void BlobsWidget::setLevel(float level) { + _blobs.setLevel(level); +} + CallMuteButton::CallMuteButton( not_null parent, CallMuteButtonState initial) @@ -150,6 +180,7 @@ CallMuteButton::CallMuteButton( , _blobs(base::make_unique_q(parent)) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) , _connecting(parent, st::callMuteButtonConnecting) +, _colors(Colors()) , _crossLineMuteAnimation(st::callMuteCrossLine) { init(); } @@ -201,6 +232,36 @@ void CallMuteButton::contentPaint() { } void CallMuteButton::setState(const CallMuteButtonState &state) { + + if (_state.type != state.type) { + const auto crossFrom = IsMuted(_state.type) ? 0. : 1.; + const auto crossTo = IsMuted(state.type) ? 0. : 1.; + + const auto gradient = anim::linear_gradient( + _colors.at(_state.type), + _colors.at(state.type), + QPoint(0, _blobs->height()), + QPoint(_blobs->width(), 0)); + + const auto from = 0.; + const auto to = 1.; + + auto callback = [=](float64 value) { + _blobs->setBrush(QBrush(gradient.gradient(value))); + + const auto crossProgress = + InterpolateF(crossFrom, crossTo, value); + if (crossProgress != _crossLineProgress) { + _crossLineProgress = crossProgress; + _content.update(); + } + }; + + _switchAnimation.stop(); + const auto duration = kSwitchStateDuration; + _switchAnimation.start(std::move(callback), from, to, duration); + } + if (state.type == CallMuteButtonType::Connecting || state.type == CallMuteButtonType::ForceMuted) { if (_state.text != state.text) { @@ -216,28 +277,12 @@ void CallMuteButton::setState(const CallMuteButtonState &state) { _content.setText(rpl::single(state.text)); } - const auto isWasActive = (_state.type == CallMuteButtonType::Active); - const auto isActive = (state.type == CallMuteButtonType::Active); - { - const auto from = _switchAnimation.value(isWasActive ? 1. : 0.); - const auto to = isActive ? 1. : 0.; - _switchAnimation.start( - [=](auto value) { - _blobs->requestPaintProgress(value); - _crossLineProgress = value; - _content.update(); - }, - from, - to, - kSwitchStateDuration); - } - _content.setProgress((state.type == CallMuteButtonType::Muted) ? 1. : 0.); if (!_connecting.isHidden() || !_content.isHidden()) { _content.show(); } _connecting.hide(); - if (isActive) { + if (state.type == CallMuteButtonType::Active) { _content.setOuterValue(_level); } else { _content.setOuterValue(0.); diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 9df8504..922bdfd 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -11,6 +11,8 @@ #include "ui/effects/cross_line.h" #include "ui/widgets/call_button.h" +#include + namespace Ui { class BlobsWidget; @@ -66,6 +68,8 @@ private: CallButton _content; CallButton _connecting; + const std::unordered_map> _colors; + CrossLineAnimation _crossLineMuteAnimation; Animations::Simple _switchAnimation; From 4fdd0d19e61a67fc1925b624c9d33c0d1d742c1c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 02:34:36 +0300 Subject: [PATCH 22/79] Removed redundant widget from mute button. --- ui/widgets/call_mute_button.cpp | 141 ++++++++++++-------------------- ui/widgets/call_mute_button.h | 5 +- 2 files changed, 56 insertions(+), 90 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index de697b8..d820f07 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -179,36 +179,72 @@ CallMuteButton::CallMuteButton( : _state(initial) , _blobs(base::make_unique_q(parent)) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) -, _connecting(parent, st::callMuteButtonConnecting) , _colors(Colors()) , _crossLineMuteAnimation(st::callMuteCrossLine) { init(); } void CallMuteButton::init() { - if (_state.type == CallMuteButtonType::Connecting - || _state.type == CallMuteButtonType::ForceMuted) { - _connecting.setText(rpl::single(_state.text)); - _connecting.show(); - _content.hide(); - } else { - _content.setText(rpl::single(_state.text)); - _content.setProgress((_state.type == CallMuteButtonType::Muted) ? 1. : 0.); - _connecting.hide(); - _content.show(); - } - _connecting.setAttribute(Qt::WA_TransparentForMouseEvents); + // Label text. + auto text = _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.text; + }); + _content.setText(std::move(text)); + // State type. + const auto previousType = + lifetime().make_state(_state.current().type); + + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.type; + }) | rpl::start_with_next([=](CallMuteButtonType type) { + const auto previous = *previousType; + *previousType = type; + + const auto crossFrom = IsMuted(previous) ? 0. : 1.; + const auto crossTo = IsMuted(type) ? 0. : 1.; + + const auto gradient = anim::linear_gradient( + _colors.at(previous), + _colors.at(type), + QPoint(0, _blobs->height()), + QPoint(_blobs->width(), 0)); + + const auto from = 0.; + const auto to = 1.; + + auto callback = [=](float64 value) { + _blobs->setBrush(QBrush(gradient.gradient(value))); + + const auto crossProgress = + InterpolateF(crossFrom, crossTo, value); + if (crossProgress != _crossLineProgress) { + _crossLineProgress = crossProgress; + _content.update(_muteIconPosition); + } + }; + + _switchAnimation.stop(); + const auto duration = kSwitchStateDuration; + _switchAnimation.start(std::move(callback), from, to, duration); + }, lifetime()); + + // Icon rect. _content.sizeValue( ) | rpl::start_with_next([=](QSize size) { const auto &icon = st::callMuteButtonActive.button.icon; const auto &pos = st::callMuteButtonActive.button.iconPosition; - _muteIconPosition = QPoint( + _muteIconPosition = QRect( (pos.x() < 0) ? ((size.width() - icon.width()) / 2) : pos.x(), - (pos.y() < 0) ? ((size.height() - icon.height()) / 2) : pos.y()); + (pos.y() < 0) ? ((size.height() - icon.height()) / 2) : pos.y(), + icon.width(), + icon.height()); }, lifetime()); + // Paint. auto filterCallback = [=](not_null e) { if (e->type() != QEvent::Paint) { return base::EventFilterResult::Continue; @@ -228,75 +264,16 @@ void CallMuteButton::contentPaint() { Painter p(&_content); const auto progress = 1. - _crossLineProgress; - _crossLineMuteAnimation.paint(p, _muteIconPosition, progress); + _crossLineMuteAnimation.paint(p, _muteIconPosition.topLeft(), progress); } void CallMuteButton::setState(const CallMuteButtonState &state) { - - if (_state.type != state.type) { - const auto crossFrom = IsMuted(_state.type) ? 0. : 1.; - const auto crossTo = IsMuted(state.type) ? 0. : 1.; - - const auto gradient = anim::linear_gradient( - _colors.at(_state.type), - _colors.at(state.type), - QPoint(0, _blobs->height()), - QPoint(_blobs->width(), 0)); - - const auto from = 0.; - const auto to = 1.; - - auto callback = [=](float64 value) { - _blobs->setBrush(QBrush(gradient.gradient(value))); - - const auto crossProgress = - InterpolateF(crossFrom, crossTo, value); - if (crossProgress != _crossLineProgress) { - _crossLineProgress = crossProgress; - _content.update(); - } - }; - - _switchAnimation.stop(); - const auto duration = kSwitchStateDuration; - _switchAnimation.start(std::move(callback), from, to, duration); - } - - if (state.type == CallMuteButtonType::Connecting - || state.type == CallMuteButtonType::ForceMuted) { - if (_state.text != state.text) { - _connecting.setText(rpl::single(state.text)); - } - if (!_connecting.isHidden() || !_content.isHidden()) { - _connecting.show(); - } - _content.setOuterValue(0.); - _content.hide(); - } else { - if (_state.text != state.text) { - _content.setText(rpl::single(state.text)); - } - - _content.setProgress((state.type == CallMuteButtonType::Muted) ? 1. : 0.); - if (!_connecting.isHidden() || !_content.isHidden()) { - _content.show(); - } - _connecting.hide(); - if (state.type == CallMuteButtonType::Active) { - _content.setOuterValue(_level); - } else { - _content.setOuterValue(0.); - } - } _state = state; } void CallMuteButton::setLevel(float level) { _level = level; _blobs->setLevel(level); - if (_state.type == CallMuteButtonType::Active) { - _content.setOuterValue(level); - } } rpl::producer CallMuteButton::clicks() const { @@ -319,7 +296,6 @@ QRect CallMuteButton::innerGeometry() const { void CallMuteButton::moveInner(QPoint position) { const auto skip = st::callMuteButtonActive.outerRadius; _content.move(position - QPoint(skip, skip)); - _connecting.move(_content.pos()); { const auto offset = QPoint( @@ -330,26 +306,17 @@ void CallMuteButton::moveInner(QPoint position) { } void CallMuteButton::setVisible(bool visible) { - if (!visible) { - _content.hide(); - _connecting.hide(); - } else if (_state.type == CallMuteButtonType::Connecting - || _state.type == CallMuteButtonType::ForceMuted) { - _connecting.show(); - } else { - _content.show(); - } + _content.setVisible(visible); + _blobs->setVisible(visible); } void CallMuteButton::raise() { _blobs->raise(); _content.raise(); - _connecting.raise(); } void CallMuteButton::lower() { _content.lower(); - _connecting.lower(); _blobs->lower(); } diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 922bdfd..92f72ef 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -59,14 +59,13 @@ private: void init(); void contentPaint(); - CallMuteButtonState _state; + rpl::variable _state; float _level = 0.; float64 _crossLineProgress = 0.; - QPoint _muteIconPosition; + QRect _muteIconPosition; const base::unique_qptr _blobs; CallButton _content; - CallButton _connecting; const std::unordered_map> _colors; From c9f003546a36d9019c19b1b63a2450894fc98955 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 03:32:46 +0300 Subject: [PATCH 23/79] Added radial animation to mute button for connecting state. --- ui/widgets/call_mute_button.cpp | 48 ++++++++++++++++++++++++++++++--- ui/widgets/call_mute_button.h | 4 +++ ui/widgets/widgets.style | 6 +++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index d820f07..3801b75 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -7,6 +7,7 @@ #include "ui/widgets/call_mute_button.h" #include "base/event_filter.h" +#include "ui/effects/radial_animation.h" #include "ui/paint/blobs.h" #include "ui/painter.h" @@ -92,6 +93,10 @@ bool IsMuted(CallMuteButtonType type) { return (type != CallMuteButtonType::Active); } +bool IsConnecting(CallMuteButtonType type) { + return (type == CallMuteButtonType::Connecting); +} + } // namespace class BlobsWidget final : public RpWidget { @@ -179,6 +184,7 @@ CallMuteButton::CallMuteButton( : _state(initial) , _blobs(base::make_unique_q(parent)) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) +, _radial(nullptr) , _colors(Colors()) , _crossLineMuteAnimation(st::callMuteCrossLine) { init(); @@ -192,6 +198,21 @@ void CallMuteButton::init() { }); _content.setText(std::move(text)); + _radialShowProgress.value( + ) | rpl::start_with_next([=](float64 value) { + if (((value == 0.) || anim::Disabled()) && _radial) { + _radial->stop(); + _radial = nullptr; + return; + } + if ((value > 0.) && !anim::Disabled() && !_radial) { + _radial = std::make_unique( + [=] { _content.update(); }, + st::callConnectingRadial); + _radial->start(); + } + }, lifetime()); + // State type. const auto previousType = lifetime().make_state(_state.current().type); @@ -206,6 +227,9 @@ void CallMuteButton::init() { const auto crossFrom = IsMuted(previous) ? 0. : 1.; const auto crossTo = IsMuted(type) ? 0. : 1.; + const auto radialShowFrom = IsConnecting(previous) ? 1. : 0.; + const auto radialShowTo = IsConnecting(type) ? 1. : 0.; + const auto gradient = anim::linear_gradient( _colors.at(previous), _colors.at(type), @@ -218,12 +242,20 @@ void CallMuteButton::init() { auto callback = [=](float64 value) { _blobs->setBrush(QBrush(gradient.gradient(value))); - const auto crossProgress = - InterpolateF(crossFrom, crossTo, value); + const auto crossProgress = (crossFrom == crossTo) + ? crossTo + : InterpolateF(crossFrom, crossTo, value); if (crossProgress != _crossLineProgress) { _crossLineProgress = crossProgress; _content.update(_muteIconPosition); } + + const auto radialShowProgress = (radialShowFrom == radialShowTo) + ? radialShowTo + : InterpolateF(radialShowFrom, radialShowTo, value); + if (radialShowProgress != _radialShowProgress.current()) { + _radialShowProgress = radialShowProgress; + } }; _switchAnimation.stop(); @@ -265,6 +297,14 @@ void CallMuteButton::contentPaint() { const auto progress = 1. - _crossLineProgress; _crossLineMuteAnimation.paint(p, _muteIconPosition.topLeft(), progress); + + if (_radial) { + p.setOpacity(_radialShowProgress.current()); + _radial->draw( + p, + st::callMuteButtonActive.bgPosition, + _content.width()); + } } void CallMuteButton::setState(const CallMuteButtonState &state) { @@ -321,7 +361,9 @@ void CallMuteButton::lower() { } rpl::lifetime &CallMuteButton::lifetime() { - return _content.lifetime(); + return _blobs->lifetime(); } +CallMuteButton::~CallMuteButton() = default; + } // namespace Ui diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 92f72ef..6d59e30 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -16,6 +16,7 @@ namespace Ui { class BlobsWidget; +class InfiniteRadialAnimation; enum class CallMuteButtonType { Connecting, @@ -34,6 +35,7 @@ public: explicit CallMuteButton( not_null parent, CallMuteButtonState initial = CallMuteButtonState()); + ~CallMuteButton(); void setState(const CallMuteButtonState &state); void setLevel(float level); @@ -62,11 +64,13 @@ private: rpl::variable _state; float _level = 0.; float64 _crossLineProgress = 0.; + rpl::variable _radialShowProgress = 0.; QRect _muteIconPosition; const base::unique_qptr _blobs; CallButton _content; + std::unique_ptr _radial; const std::unordered_map> _colors; CrossLineAnimation _crossLineMuteAnimation; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index db95ef1..662ea2b 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1453,6 +1453,12 @@ callMuteCrossLine: CrossLineAnimation { stroke: 4px; } +callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + color: lightButtonFg; + thickness: 4px; + size: size(100px, 100px); +} + // Windows specific title windowTitleButton: IconButton { From 0018276c5f25dac1b16236b5e86229ded31d2ab0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 06:07:06 +0300 Subject: [PATCH 24/79] Added ability to change blob radius. --- ui/paint/blob.cpp | 10 ++++++---- ui/paint/blob.h | 2 ++ ui/paint/blobs.cpp | 24 +++++++++++++++++++++++- ui/paint/blobs.h | 4 ++++ 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index 749afc7..66a2e7c 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -127,10 +127,12 @@ void BlobBezier::update(float level, float speedScale) { } } -void BlobBezier::setRadius(float min, float max) { - _minRadius = std::min(min, max); - _maxRadius = std::max(min, max); - generateBlob(); +void BlobBezier::setMinRadius(float value) { + _minRadius = value; +} + +void BlobBezier::setMaxRadius(float value) { + _maxRadius = value; } } // namespace Ui::Paint diff --git a/ui/paint/blob.h b/ui/paint/blob.h index f50eb94..a8157cb 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -19,6 +19,8 @@ public: void generateBlob(); void setRadius(float min, float max); + void setMinRadius(float value); + void setMaxRadius(float value); private: void generateBlob( diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index a235725..48b6c2a 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -23,7 +23,9 @@ Blobs::Blobs( void Blobs::init() { for (const auto &data : _blobDatas) { auto blob = Paint::BlobBezier(data.segmentsCount, data.minScale); - blob.setRadius(data.minRadius, data.maxRadius); + blob.setMinRadius(data.minRadius); + blob.setMaxRadius(data.maxRadius); + blob.generateBlob(); _blobs.push_back(std::move(blob)); } } @@ -39,6 +41,26 @@ float Blobs::maxRadius() const { return maxOfRadiuses(max); } +int Blobs::size() const { + return _blobs.size(); +} + +void Blobs::setRadiusAt( + rpl::producer &&radius, + int index, + bool isMax) { + Expects(index >= 0 && index < size()); + std::move( + radius + ) | rpl::start_with_next([=](float r) { + if (isMax) { + _blobs[index].setMaxRadius(r); + } else { + _blobs[index].setMinRadius(r); + } + }, _lifetime); +} + void Blobs::setLevel(float value) { const auto to = std::min(_maxLevel, value) / _maxLevel; _levelValue.start(to); diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index 6d53eab..d28df40 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -29,10 +29,12 @@ public: float levelDuration, float maxLevel); + void setRadiusAt(rpl::producer &&radius, int index, bool isMax); void setLevel(float value); void paint(Painter &p, const QBrush &brush); [[nodiscard]] float maxRadius() const; + [[nodiscard]] int size() const; private: void init(); @@ -45,6 +47,8 @@ private: crl::time _lastUpdateTime = 0; anim::continuous_value _levelValue; + rpl::lifetime _lifetime; + }; } // namespace Ui::Paint From ee969568acec6b5de416276ecee7c0907780ed49 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 06:08:03 +0300 Subject: [PATCH 25/79] Added transforming blob to circle in mute button for connecting state. --- ui/widgets/call_mute_button.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 3801b75..f896f70 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -33,6 +33,9 @@ constexpr auto kScaleSmallMax = kScaleSmallMin + kScaleSmall; constexpr auto kMainRadiusFactor = 50. / 57.; +constexpr auto kMainMinRadius = 57. * kMainRadiusFactor; +constexpr auto kMainMaxRadius = 63. * kMainRadiusFactor; + constexpr auto kSwitchStateDuration = 120; constexpr auto MuteBlobs() -> std::array { @@ -40,8 +43,8 @@ constexpr auto MuteBlobs() -> std::array { { .segmentsCount = 6, .minScale = 1., - .minRadius = 57. * kMainRadiusFactor, - .maxRadius = 63. * kMainRadiusFactor, + .minRadius = kMainMinRadius, + .maxRadius = kMainMaxRadius, .speedScale = .4, .alpha = 1., }, @@ -105,6 +108,7 @@ public: void setLevel(float level); void setBrush(QBrush brush); + void setMainRadius(rpl::producer &&radius); private: void init(); @@ -178,6 +182,10 @@ void BlobsWidget::setLevel(float level) { _blobs.setLevel(level); } +void BlobsWidget::setMainRadius(rpl::producer &&radius) { + _blobs.setRadiusAt(std::move(radius), 0, true); +} + CallMuteButton::CallMuteButton( not_null parent, CallMuteButtonState initial) @@ -276,6 +284,17 @@ void CallMuteButton::init() { icon.height()); }, lifetime()); + // Main blob radius. + { + auto radius = _state.value( + ) | rpl::map([](const CallMuteButtonState &state) -> float { + return IsConnecting(state.type) + ? kMainMinRadius + : kMainMaxRadius; + }) | rpl::distinct_until_changed(); + _blobs->setMainRadius(std::move(radius)); + } + // Paint. auto filterCallback = [=](not_null e) { if (e->type() != QEvent::Paint) { From baea14611e37dfdeaa239b5567914f49dc298514 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 09:17:12 +0300 Subject: [PATCH 26/79] Added class for radial gradient color animation. --- ui/effects/animation_value.h | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/ui/effects/animation_value.h b/ui/effects/animation_value.h index 93110ce..93db8f2 100644 --- a/ui/effects/animation_value.h +++ b/ui/effects/animation_value.h @@ -12,6 +12,7 @@ #include #include +#include namespace anim { @@ -404,6 +405,55 @@ private: }; +class radial_gradient { +public: + radial_gradient( + std::vector colors_from, + std::vector colors_to, + QPointF center, + float radius) + : _colors_from(colors_from) + , _colors_to(colors_to) + , _center(center) + , _radius(radius) + , _gradient_from(gradient(colors_from)) + , _gradient_to(gradient(colors_to)) { + Expects(colors_from.size() == colors_to.size()); + } + + QRadialGradient gradient(float64 b_ratio) const { + if (b_ratio == 0.) { + return _gradient_from; + } else if (b_ratio == 1.) { + return _gradient_to; + } + auto colors = std::vector(_colors_to.size()); + for (auto i = 0; i < colors.size(); i++) { + colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); + } + return gradient(colors); + } + +private: + QRadialGradient gradient(const std::vector &colors) const { + auto gradient = QRadialGradient(_center, _radius); + const auto size = colors.size(); + for (auto i = 0; i < size; i++) { + gradient.setColorAt(i / (size - 1), colors[i]); + } + return gradient; + } + + std::vector _colors_from; + std::vector _colors_to; + QPointF _center; + float _radius; + + QRadialGradient _gradient_from; + QRadialGradient _gradient_to; + +}; + class continuous_value { public: continuous_value() = default; From e2210274de67b38638c6f28db82734003d50a785 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 09:17:22 +0300 Subject: [PATCH 27/79] Added glow to mute button. --- ui/paint/blobs.cpp | 12 +++-- ui/paint/blobs.h | 3 +- ui/widgets/call_mute_button.cpp | 83 +++++++++++++++++++++++++++------ 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index 48b6c2a..cb9001b 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -67,9 +67,6 @@ void Blobs::setLevel(float value) { } void Blobs::paint(Painter &p, const QBrush &brush) { - const auto dt = crl::now() - _lastUpdateTime; - _levelValue.update((dt > 20) ? 17 : dt); - const auto opacity = p.opacity(); for (auto i = 0; i < _blobs.size(); i++) { _blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale); @@ -79,7 +76,14 @@ void Blobs::paint(Painter &p, const QBrush &brush) { } _blobs[i].paint(p, brush); } - _lastUpdateTime = crl::now(); +} + +void Blobs::updateLevel(crl::time dt) { + _levelValue.update((dt > 20) ? 17 : dt); +} + +float64 Blobs::currentLevel() const { + return _levelValue.current(); } } // namespace Ui::Paint diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index d28df40..22ec519 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -32,9 +32,11 @@ public: void setRadiusAt(rpl::producer &&radius, int index, bool isMax); void setLevel(float value); void paint(Painter &p, const QBrush &brush); + void updateLevel(crl::time dt); [[nodiscard]] float maxRadius() const; [[nodiscard]] int size() const; + [[nodiscard]] float64 currentLevel() const; private: void init(); @@ -44,7 +46,6 @@ private: std::vector _blobDatas; std::vector _blobs; - crl::time _lastUpdateTime = 0; anim::continuous_value _levelValue; rpl::lifetime _lifetime; diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index f896f70..d4c4c8e 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -36,6 +36,10 @@ constexpr auto kMainRadiusFactor = 50. / 57.; constexpr auto kMainMinRadius = 57. * kMainRadiusFactor; constexpr auto kMainMaxRadius = 63. * kMainRadiusFactor; +constexpr auto kGlowPaddingFactor = 1.2; +constexpr auto kGlowMinScale = 0.6; +constexpr auto kGlowAlpha = 150; + constexpr auto kSwitchStateDuration = 120; constexpr auto MuteBlobs() -> std::array { @@ -107,16 +111,21 @@ public: BlobsWidget(not_null parent); void setLevel(float level); - void setBrush(QBrush brush); + void setBlobBrush(QBrush brush); + void setGlowBrush(QBrush brush); void setMainRadius(rpl::producer &&radius); + [[nodiscard]] QRect innerRect() const; + private: void init(); Paint::Blobs _blobs; - QBrush _brush; + QBrush _blobBrush; + QBrush _glowBrush; int _center = 0; + QRect _inner; Animations::Basic _animation; @@ -125,15 +134,18 @@ private: BlobsWidget::BlobsWidget(not_null parent) : RpWidget(parent) , _blobs(MuteBlobs() | ranges::to_vector, kLevelDuration, kMaxLevel) -, _brush(Qt::transparent) { +, _blobBrush(Qt::transparent) +, _glowBrush(Qt::transparent) { init(); } void BlobsWidget::init() { setAttribute(Qt::WA_TransparentForMouseEvents); - const auto maxBlobDiameter = _blobs.maxRadius() * 2; - resize(maxBlobDiameter, maxBlobDiameter); + { + const auto s = _blobs.maxRadius() * 2 * kGlowPaddingFactor; + resize(s, s); + } const auto gradient = anim::linear_gradient( { st::groupCallMuted1->c, st::groupCallMuted2->c }, @@ -144,19 +156,34 @@ void BlobsWidget::init() { sizeValue( ) | rpl::start_with_next([=](QSize size) { _center = size.width() / 2; + + const auto w = _blobs.maxRadius() * 2; + const auto margins = style::margins(w, w, w, w); + _inner = QRect(QPoint(), size).marginsRemoved(margins); }, lifetime()); paintRequest( ) | rpl::start_with_next([=] { Painter p(this); - PainterHighQualityEnabler hq(p); + + // Glow. + const auto s = kGlowMinScale + + (1. - kGlowMinScale) * _blobs.currentLevel(); p.translate(_center, _center); - _blobs.paint(p, _brush); + p.scale(s, s); + p.translate(-_center, -_center); + p.fillRect(rect(), _glowBrush); + p.resetTransform(); + + // Blobs. + p.translate(_center, _center); + _blobs.paint(p, _blobBrush); }, lifetime()); _animation.init([=](crl::time now) { const auto dt = now - _animation.started(); + _blobs.updateLevel(dt); update(); return true; }); @@ -170,12 +197,22 @@ void BlobsWidget::init() { }, lifetime()); } -void BlobsWidget::setBrush(QBrush brush) { - if (_brush == brush) { +QRect BlobsWidget::innerRect() const { + return _inner; +} + +void BlobsWidget::setBlobBrush(QBrush brush) { + if (_blobBrush == brush) { return; } - _brush = brush; - update(); + _blobBrush = brush; +} + +void BlobsWidget::setGlowBrush(QBrush brush) { + if (_glowBrush == brush) { + return; + } + _glowBrush = brush; } void BlobsWidget::setLevel(float level) { @@ -225,6 +262,15 @@ void CallMuteButton::init() { const auto previousType = lifetime().make_state(_state.current().type); + const auto glowColor = [=](CallMuteButtonType type) { + if (IsConnecting(type) || (type == CallMuteButtonType::ForceMuted)) { + return st::groupCallBg->c; + } + auto c = _colors.at(type)[0]; + c.setAlpha(kGlowAlpha); + return c; + }; + _state.value( ) | rpl::map([](const CallMuteButtonState &state) { return state.type; @@ -238,17 +284,26 @@ void CallMuteButton::init() { const auto radialShowFrom = IsConnecting(previous) ? 1. : 0.; const auto radialShowTo = IsConnecting(type) ? 1. : 0.; + const auto blobsInner = _blobs->innerRect(); const auto gradient = anim::linear_gradient( _colors.at(previous), _colors.at(type), - QPoint(0, _blobs->height()), - QPoint(_blobs->width(), 0)); + QPoint(blobsInner.x(), blobsInner.y() + blobsInner.height()), + QPoint(blobsInner.x() + blobsInner.width(), blobsInner.y())); + + const auto glow = anim::radial_gradient( + { glowColor(previous), Qt::transparent }, + { glowColor(type), Qt::transparent }, + blobsInner.center(), + _blobs->width() / 2); const auto from = 0.; const auto to = 1.; auto callback = [=](float64 value) { - _blobs->setBrush(QBrush(gradient.gradient(value))); + _blobs->setBlobBrush(QBrush(gradient.gradient(value))); + _blobs->setGlowBrush(QBrush(glow.gradient(value))); + _blobs->update(); const auto crossProgress = (crossFrom == crossTo) ? crossTo From 1b2869d9c9e1ec7fcc7dfb33e0b94ab5f8674dbb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 10:23:12 +0300 Subject: [PATCH 28/79] Added circle paint instead of blobs when animations are disabled. --- ui/widgets/call_mute_button.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index d4c4c8e..a39c979 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -167,6 +167,14 @@ void BlobsWidget::init() { Painter p(this); PainterHighQualityEnabler hq(p); + if (anim::Disabled()) { + p.translate(_center, _center); + p.setPen(Qt::NoPen); + p.setBrush(_blobBrush); + p.drawEllipse(QPointF(), kMainMinRadius, kMainMinRadius); + return; + } + // Glow. const auto s = kGlowMinScale + (1. - kGlowMinScale) * _blobs.currentLevel(); From 011d0ba692d86e66e749f213358009ccded18c6f Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 10:59:21 +0300 Subject: [PATCH 29/79] Fixed blobs paint for non-default scales. --- ui/widgets/call_mute_button.cpp | 43 ++++++++++++++++++++------------- ui/widgets/widgets.style | 7 ++++++ 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index a39c979..73be479 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -28,13 +28,10 @@ constexpr auto kScaleSmall = 0.704 - 0.1; constexpr auto kScaleBigMin = 0.878; constexpr auto kScaleSmallMin = 0.926; -constexpr auto kScaleBigMax = kScaleBigMin + kScaleBig; -constexpr auto kScaleSmallMax = kScaleSmallMin + kScaleSmall; +constexpr auto kScaleBigMax = (float)(kScaleBigMin + kScaleBig); +constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall); -constexpr auto kMainRadiusFactor = 50. / 57.; - -constexpr auto kMainMinRadius = 57. * kMainRadiusFactor; -constexpr auto kMainMaxRadius = 63. * kMainRadiusFactor; +constexpr auto kMainRadiusFactor = (float)(50. / 57.); constexpr auto kGlowPaddingFactor = 1.2; constexpr auto kGlowMinScale = 0.6; @@ -42,29 +39,39 @@ constexpr auto kGlowAlpha = 150; constexpr auto kSwitchStateDuration = 120; -constexpr auto MuteBlobs() -> std::array { +auto MuteBlobs() -> std::array { return {{ { .segmentsCount = 6, .minScale = 1., - .minRadius = kMainMinRadius, - .maxRadius = kMainMaxRadius, + .minRadius = st::callMuteMainBlobMinRadius + * kMainRadiusFactor, + .maxRadius = st::callMuteMainBlobMaxRadius + * kMainRadiusFactor, .speedScale = .4, .alpha = 1., }, { .segmentsCount = 9, .minScale = kScaleSmallMin / kScaleSmallMax, - .minRadius = 62 * kScaleSmallMax * kMainRadiusFactor, - .maxRadius = 72 * kScaleSmallMax * kMainRadiusFactor, + .minRadius = st::callMuteMinorBlobMinRadius + * kScaleSmallMax + * kMainRadiusFactor, + .maxRadius = st::callMuteMinorBlobMaxRadius + * kScaleSmallMax + * kMainRadiusFactor, .speedScale = 1., .alpha = (76. / 255.), }, { .segmentsCount = 12, .minScale = kScaleBigMin / kScaleBigMax, - .minRadius = 65 * kScaleBigMax * kMainRadiusFactor, - .maxRadius = 75 * kScaleBigMax * kMainRadiusFactor, + .minRadius = st::callMuteMajorBlobMinRadius + * kScaleBigMax + * kMainRadiusFactor, + .maxRadius = st::callMuteMajorBlobMaxRadius + * kScaleBigMax + * kMainRadiusFactor, .speedScale = 1., .alpha = (76. / 255.), }, @@ -171,7 +178,9 @@ void BlobsWidget::init() { p.translate(_center, _center); p.setPen(Qt::NoPen); p.setBrush(_blobBrush); - p.drawEllipse(QPointF(), kMainMinRadius, kMainMinRadius); + const auto radius = st::callMuteMainBlobMinRadius + * kMainRadiusFactor; + p.drawEllipse(QPointF(), radius, radius); return; } @@ -351,9 +360,9 @@ void CallMuteButton::init() { { auto radius = _state.value( ) | rpl::map([](const CallMuteButtonState &state) -> float { - return IsConnecting(state.type) - ? kMainMinRadius - : kMainMaxRadius; + return (IsConnecting(state.type) + ? st::callMuteMainBlobMinRadius + : st::callMuteMainBlobMaxRadius) * kMainRadiusFactor; }) | rpl::distinct_until_changed(); _blobs->setMainRadius(std::move(radius)); } diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 662ea2b..6fb9b39 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1405,6 +1405,13 @@ defaultToast: Toast { durationSlide: 160; } +callMuteMainBlobMinRadius: 57px; +callMuteMainBlobMaxRadius: 63px; +callMuteMinorBlobMinRadius: 62px; +callMuteMinorBlobMaxRadius: 72px; +callMuteMajorBlobMinRadius: 65px; +callMuteMajorBlobMaxRadius: 75px; + callMuteButtonActiveIcon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }}; callMuteButtonActiveInner: IconButton { width: 136px; From 07359392c2e19ac08b633258eb1d7a9f45e271cd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 12:29:59 +0300 Subject: [PATCH 30/79] Added ability to override background and ripple colors in CallButton. --- ui/widgets/call_button.cpp | 21 +++++++++++++++++++-- ui/widgets/call_button.h | 9 +++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ui/widgets/call_button.cpp b/ui/widgets/call_button.cpp index fbd67e9..c43f870 100644 --- a/ui/widgets/call_button.cpp +++ b/ui/widgets/call_button.cpp @@ -126,7 +126,12 @@ void CallButton::paintEvent(QPaintEvent *e) { p.drawEllipse(outerRect); } - if (paintFrom) { + if (_bgOverride) { + const auto &s = _stFrom->bgSize; + p.setPen(Qt::NoPen); + p.setBrush(*_bgOverride); + p.drawEllipse(QRect(_stFrom->bgPosition, QSize(s, s))); + } else if (paintFrom) { p.drawPixmap(bgPosition, _bgFrom); } else if (paintTo) { p.drawPixmap(bgPosition, _bgTo); @@ -137,7 +142,9 @@ void CallButton::paintEvent(QPaintEvent *e) { auto rippleColorInterpolated = QColor(); auto rippleColorOverride = &rippleColorInterpolated; - if (paintFrom) { + if (_rippleOverride) { + rippleColorOverride = &(*_rippleOverride); + } else if (paintFrom) { rippleColorOverride = nullptr; } else if (paintTo) { rippleColorOverride = &_stTo->button.ripple.color->c; @@ -202,6 +209,16 @@ void CallButton::onStateChanged(State was, StateChangeSource source) { } } +void CallButton::setColorOverrides(rpl::producer &&colors) { + std::move( + colors + ) | rpl::start_with_next([=](const CallButtonColors &c) { + _bgOverride = c.bg; + _rippleOverride = c.ripple; + update(); + }, lifetime()); +} + QPoint CallButton::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()) - _stFrom->button.rippleAreaPosition; } diff --git a/ui/widgets/call_button.h b/ui/widgets/call_button.h index c02c441..cb65c57 100644 --- a/ui/widgets/call_button.h +++ b/ui/widgets/call_button.h @@ -14,6 +14,11 @@ namespace Ui { class FlatLabel; +struct CallButtonColors { + std::optional bg; + std::optional ripple; +}; + class CallButton final : public RippleButton { public: CallButton( @@ -24,6 +29,7 @@ public: void setProgress(float64 progress); void setOuterValue(float64 value); void setText(rpl::producer text); + void setColorOverrides(rpl::producer &&colors); protected: void paintEvent(QPaintEvent *e) override; @@ -43,6 +49,9 @@ private: object_ptr _label = { nullptr }; + std::optional _bgOverride; + std::optional _rippleOverride; + QImage _bgMask, _bg; QPixmap _bgFrom, _bgTo; QImage _iconMixedMask, _iconFrom, _iconTo, _iconMixed; From 5897e357bfd6114ecede2711831b8c5e9d045fcb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 12:38:22 +0300 Subject: [PATCH 31/79] Added colors overriding for call settings button from mute button. --- ui/widgets/call_mute_button.cpp | 42 ++++++++++++++++++++++++++++++++- ui/widgets/call_mute_button.h | 8 +++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 73be479..f001baa 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -37,6 +37,9 @@ constexpr auto kGlowPaddingFactor = 1.2; constexpr auto kGlowMinScale = 0.6; constexpr auto kGlowAlpha = 150; +constexpr auto kOverrideColorBgAlpha = 76; +constexpr auto kOverrideColorRippleAlpha = 50; + constexpr auto kSwitchStateDuration = 120; auto MuteBlobs() -> std::array { @@ -111,6 +114,10 @@ bool IsConnecting(CallMuteButtonType type) { return (type == CallMuteButtonType::Connecting); } +bool IsInactive(CallMuteButtonType type) { + return IsConnecting(type) || (type == CallMuteButtonType::ForceMuted); +} + } // namespace class BlobsWidget final : public RpWidget { @@ -280,7 +287,7 @@ void CallMuteButton::init() { lifetime().make_state(_state.current().type); const auto glowColor = [=](CallMuteButtonType type) { - if (IsConnecting(type) || (type == CallMuteButtonType::ForceMuted)) { + if (IsInactive(type)) { return st::groupCallBg->c; } auto c = _colors.at(type)[0]; @@ -336,6 +343,8 @@ void CallMuteButton::init() { if (radialShowProgress != _radialShowProgress.current()) { _radialShowProgress = radialShowProgress; } + + overridesColors(previous, type, value); }; _switchAnimation.stop(); @@ -451,6 +460,37 @@ void CallMuteButton::lower() { _blobs->lower(); } +void CallMuteButton::overridesColors( + CallMuteButtonType fromType, + CallMuteButtonType toType, + float64 progress) { + const auto toInactive = IsInactive(toType); + const auto fromInactive = IsInactive(fromType); + if (toInactive && (progress == 1)) { + _colorOverrides.fire({ std::nullopt, std::nullopt }); + return; + } + auto from = _colors.at(fromType)[0]; + auto to = _colors.at(toType)[0]; + auto fromRipple = from; + auto toRipple = to; + if (!toInactive) { + toRipple.setAlpha(kOverrideColorRippleAlpha); + to.setAlpha(kOverrideColorBgAlpha); + } + if (!fromInactive) { + fromRipple.setAlpha(kOverrideColorRippleAlpha); + from.setAlpha(kOverrideColorBgAlpha); + } + const auto resultBg = anim::color(from, to, progress); + const auto resultRipple = anim::color(fromRipple, toRipple, progress); + _colorOverrides.fire({ resultBg, resultRipple }); +} + +rpl::producer CallMuteButton::colorOverrides() const { + return _colorOverrides.events(); +} + rpl::lifetime &CallMuteButton::lifetime() { return _blobs->lifetime(); } diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 6d59e30..8f12d9a 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -55,11 +55,17 @@ public: void raise(); void lower(); + [[nodiscard]] rpl::producer colorOverrides() const; + [[nodiscard]] rpl::lifetime &lifetime(); private: void init(); void contentPaint(); + void overridesColors( + CallMuteButtonType fromType, + CallMuteButtonType toType, + float64 progress); rpl::variable _state; float _level = 0.; @@ -76,6 +82,8 @@ private: CrossLineAnimation _crossLineMuteAnimation; Animations::Simple _switchAnimation; + rpl::event_stream _colorOverrides; + }; } // namespace Ui From 84b3ac9b58626f29952b8a0589e950893bfc0577 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Nov 2020 15:37:29 +0300 Subject: [PATCH 32/79] Fix build and run on Windows. --- ui/paint/blob.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index 66a2e7c..3495515 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -10,6 +10,7 @@ #include "ui/painter.h" #include +#include namespace Ui::Paint { @@ -31,12 +32,12 @@ BlobBezier::BlobBezier(int n, float minScale, float minSpeed, float maxSpeed) , _minSpeed(minSpeed ? minSpeed : kMinSpeed) , _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed) , _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) { - _radius.reserve(n); - _angle.reserve(n); - _radiusNext.reserve(n); - _angleNext.reserve(n); - _progress.reserve(n); - _speed.reserve(n); + _radius.resize(n); + _angle.resize(n); + _radiusNext.resize(n); + _angleNext.resize(n); + _progress.resize(n); + _speed.resize(n); } void BlobBezier::paint(Painter &p, const QBrush &brush) { From c4df097339aa7edf1d7aadde4df4a954971a3afb Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Nov 2020 18:49:30 +0300 Subject: [PATCH 33/79] Paint CallButton eliipse with high quality. --- ui/widgets/call_button.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/widgets/call_button.cpp b/ui/widgets/call_button.cpp index c43f870..25bf401 100644 --- a/ui/widgets/call_button.cpp +++ b/ui/widgets/call_button.cpp @@ -130,6 +130,8 @@ void CallButton::paintEvent(QPaintEvent *e) { const auto &s = _stFrom->bgSize; p.setPen(Qt::NoPen); p.setBrush(*_bgOverride); + + PainterHighQualityEnabler hq(p); p.drawEllipse(QRect(_stFrom->bgPosition, QSize(s, s))); } else if (paintFrom) { p.drawPixmap(bgPosition, _bgFrom); From 4761b307b44db51bc48ad287250b436ad0f84dd0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 20:08:24 +0300 Subject: [PATCH 34/79] Fixed reversed animations in mute button. --- ui/widgets/call_mute_button.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index f001baa..d03f154 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -321,7 +321,9 @@ void CallMuteButton::init() { blobsInner.center(), _blobs->width() / 2); - const auto from = 0.; + const auto from = _switchAnimation.animating() + ? (1. - _switchAnimation.value(0.)) + : 0.; const auto to = 1.; auto callback = [=](float64 value) { @@ -348,7 +350,7 @@ void CallMuteButton::init() { }; _switchAnimation.stop(); - const auto duration = kSwitchStateDuration; + const auto duration = (1. - from) * kSwitchStateDuration; _switchAnimation.start(std::move(callback), from, to, duration); }, lifetime()); From 5c6ddf9cb37659d38c9f9b0f0281656c4015e71a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 30 Nov 2020 20:27:21 +0300 Subject: [PATCH 35/79] Disabled mouse events for mute button when state is inactive. --- ui/widgets/call_mute_button.cpp | 15 +++++++++++++++ ui/widgets/call_mute_button.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index d03f154..69b9cc2 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -285,6 +285,7 @@ void CallMuteButton::init() { // State type. const auto previousType = lifetime().make_state(_state.current().type); + setEnableMouse(false); const auto glowColor = [=](CallMuteButtonType type) { if (IsInactive(type)) { @@ -302,6 +303,10 @@ void CallMuteButton::init() { const auto previous = *previousType; *previousType = type; + if (IsInactive(type) && !IsInactive(previous)) { + setEnableMouse(false); + } + const auto crossFrom = IsMuted(previous) ? 0. : 1.; const auto crossTo = IsMuted(type) ? 0. : 1.; @@ -347,6 +352,12 @@ void CallMuteButton::init() { } overridesColors(previous, type, value); + + if (value == to) { + if (!IsInactive(type) && IsInactive(previous)) { + setEnableMouse(true); + } + } }; _switchAnimation.stop(); @@ -462,6 +473,10 @@ void CallMuteButton::lower() { _blobs->lower(); } +void CallMuteButton::setEnableMouse(bool value) { + _content.setAttribute(Qt::WA_TransparentForMouseEvents, !value); +} + void CallMuteButton::overridesColors( CallMuteButtonType fromType, CallMuteButtonType toType, diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 8f12d9a..69b5946 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -67,6 +67,8 @@ private: CallMuteButtonType toType, float64 progress); + void setEnableMouse(bool value); + rpl::variable _state; float _level = 0.; float64 _crossLineProgress = 0.; From 7fd90cb38b6b1069af43039f93c991fffaf8ddea Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Nov 2020 21:06:12 +0300 Subject: [PATCH 36/79] Add onShowFinished callback to BoxContent. --- ui/layers/box_content.h | 2 ++ ui/layers/box_layer_widget.h | 4 ++++ ui/layers/generic_box.h | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/ui/layers/box_content.h b/ui/layers/box_content.h index 1c81444..8c738d1 100644 --- a/ui/layers/box_content.h +++ b/ui/layers/box_content.h @@ -126,6 +126,8 @@ public: void scrollToWidget(not_null widget); + virtual void showFinished() { + } void clearButtons() { getDelegate()->clearButtons(); } diff --git a/ui/layers/box_layer_widget.h b/ui/layers/box_layer_widget.h index 9d195ce..a26c5ad 100644 --- a/ui/layers/box_layer_widget.h +++ b/ui/layers/box_layer_widget.h @@ -53,6 +53,10 @@ public: LayerOptions options, anim::type animated) override; + void showFinished() override { + _content->showFinished(); + } + void clearButtons() override; QPointer addButton( rpl::producer text, diff --git a/ui/layers/generic_box.h b/ui/layers/generic_box.h index 058f05c..e0bceca 100644 --- a/ui/layers/generic_box.h +++ b/ui/layers/generic_box.h @@ -38,6 +38,9 @@ public: void setFocusCallback(Fn callback) { _focus = callback; } + void setShowFinishedCallback(Fn callback) { + _showFinished = callback; + } int rowsCount() const { return _content->count(); @@ -76,6 +79,11 @@ public: BoxContent::setInnerFocus(); } } + void showFinished() override { + if (_showFinished) { + _showFinished(); + } + } [[nodiscard]] not_null verticalLayout(); @@ -111,6 +119,7 @@ private: FnMut)> _init; Fn _focus; + Fn _showFinished; object_ptr _content; int _width = 0; From cc1f597c6e6d97c95fc339239b6ca5f80eab60a8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 1 Dec 2020 07:59:43 +0300 Subject: [PATCH 37/79] Added ability to reverse lines in cross line animation. --- ui/effects/cross_line.cpp | 6 ++++-- ui/effects/cross_line.h | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/effects/cross_line.cpp b/ui/effects/cross_line.cpp index a653ea3..2241fe1 100644 --- a/ui/effects/cross_line.cpp +++ b/ui/effects/cross_line.cpp @@ -12,8 +12,10 @@ namespace Ui { CrossLineAnimation::CrossLineAnimation( const style::CrossLineAnimation &st, + bool reversed, float angle) : _st(st) +, _reversed(reversed) , _transparentPen(Qt::transparent, st.stroke, Qt::SolidLine, Qt::RoundCap) , _strokePen(st.fg, st.stroke, Qt::SolidLine, Qt::RoundCap) , _line(st.startPosition, st.endPosition) @@ -59,11 +61,11 @@ QImage CrossLineAnimation::image(float64 progress) const { _st.icon.paint(q, 0, 0, _st.icon.width()); q.setPen(_strokePen); - q.drawLine(bottomLine); + q.drawLine(_reversed ? topLine : bottomLine); q.setCompositionMode(QPainter::CompositionMode_Source); q.setPen(_transparentPen); - q.drawLine(topLine); + q.drawLine(_reversed ? bottomLine : topLine); return frame; } diff --git a/ui/effects/cross_line.h b/ui/effects/cross_line.h index 0cf8d99..0f21e51 100644 --- a/ui/effects/cross_line.h +++ b/ui/effects/cross_line.h @@ -16,6 +16,7 @@ class CrossLineAnimation { public: CrossLineAnimation( const style::CrossLineAnimation &st, + bool reversed = false, float angle = 315); void paint(Painter &p, QPoint position, float64 progress); @@ -25,6 +26,7 @@ private: QImage image(float64 progress) const; const style::CrossLineAnimation &_st; + const bool _reversed; const QPen _transparentPen; const QPen _strokePen; QLineF _line; From a175ae7c80efb247b8aacfcc0ed7618f12a56bc2 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 1 Dec 2020 10:26:33 +0300 Subject: [PATCH 38/79] Moved gradient color animations to separate file. --- CMakeLists.txt | 1 + ui/effects/animation_value.h | 100 ------------------------------ ui/effects/gradient.h | 114 +++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 100 deletions(-) create mode 100644 ui/effects/gradient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bd2451..19ed39e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ PRIVATE ui/effects/cross_line.h ui/effects/fade_animation.cpp ui/effects/fade_animation.h + ui/effects/gradient.h ui/effects/numbers_animation.cpp ui/effects/numbers_animation.h ui/effects/panel_animation.cpp diff --git a/ui/effects/animation_value.h b/ui/effects/animation_value.h index 93db8f2..5ecf752 100644 --- a/ui/effects/animation_value.h +++ b/ui/effects/animation_value.h @@ -11,8 +11,6 @@ #include "ui/style/style_core.h" #include -#include -#include namespace anim { @@ -356,104 +354,6 @@ void DrawStaticLoading( QPen pen, QBrush brush = Qt::NoBrush); -class linear_gradient { -public: - linear_gradient( - std::vector colors_from, - std::vector colors_to, - QPointF point1, - QPointF point2) - : _colors_from(colors_from) - , _colors_to(colors_to) - , _point1(point1) - , _point2(point2) - , _gradient_from(gradient(colors_from)) - , _gradient_to(gradient(colors_to)) { - Expects(colors_from.size() == colors_to.size()); - } - - QLinearGradient gradient(float64 b_ratio) const { - if (b_ratio == 0.) { - return _gradient_from; - } else if (b_ratio == 1.) { - return _gradient_to; - } - auto colors = std::vector(_colors_to.size()); - for (auto i = 0; i < colors.size(); i++) { - colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); - } - return gradient(colors); - } - -private: - QLinearGradient gradient(const std::vector &colors) const { - auto gradient = QLinearGradient(_point1, _point2); - const auto size = colors.size(); - for (auto i = 0; i < size; i++) { - gradient.setColorAt(i / (size - 1), colors[i]); - } - return gradient; - } - - std::vector _colors_from; - std::vector _colors_to; - QPointF _point1; - QPointF _point2; - - QLinearGradient _gradient_from; - QLinearGradient _gradient_to; - -}; - -class radial_gradient { -public: - radial_gradient( - std::vector colors_from, - std::vector colors_to, - QPointF center, - float radius) - : _colors_from(colors_from) - , _colors_to(colors_to) - , _center(center) - , _radius(radius) - , _gradient_from(gradient(colors_from)) - , _gradient_to(gradient(colors_to)) { - Expects(colors_from.size() == colors_to.size()); - } - - QRadialGradient gradient(float64 b_ratio) const { - if (b_ratio == 0.) { - return _gradient_from; - } else if (b_ratio == 1.) { - return _gradient_to; - } - auto colors = std::vector(_colors_to.size()); - for (auto i = 0; i < colors.size(); i++) { - colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); - } - return gradient(colors); - } - -private: - QRadialGradient gradient(const std::vector &colors) const { - auto gradient = QRadialGradient(_center, _radius); - const auto size = colors.size(); - for (auto i = 0; i < size; i++) { - gradient.setColorAt(i / (size - 1), colors[i]); - } - return gradient; - } - - std::vector _colors_from; - std::vector _colors_to; - QPointF _center; - float _radius; - - QRadialGradient _gradient_from; - QRadialGradient _gradient_to; - -}; - class continuous_value { public: continuous_value() = default; diff --git a/ui/effects/gradient.h b/ui/effects/gradient.h new file mode 100644 index 0000000..ed1e16a --- /dev/null +++ b/ui/effects/gradient.h @@ -0,0 +1,114 @@ +// 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/effects/animation_value.h" + +#include +#include + +namespace anim { + +class linear_gradient { +public: + linear_gradient( + std::vector colors_from, + std::vector colors_to, + QPointF point1, + QPointF point2) + : _colors_from(colors_from) + , _colors_to(colors_to) + , _point1(point1) + , _point2(point2) + , _gradient_from(gradient(colors_from)) + , _gradient_to(gradient(colors_to)) { + Expects(colors_from.size() == colors_to.size()); + } + + QLinearGradient gradient(float64 b_ratio) const { + if (b_ratio == 0.) { + return _gradient_from; + } else if (b_ratio == 1.) { + return _gradient_to; + } + auto colors = std::vector(_colors_to.size()); + for (auto i = 0; i < colors.size(); i++) { + colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); + } + return gradient(colors); + } + +private: + QLinearGradient gradient(const std::vector &colors) const { + auto gradient = QLinearGradient(_point1, _point2); + const auto size = colors.size(); + for (auto i = 0; i < size; i++) { + gradient.setColorAt(i / (size - 1), colors[i]); + } + return gradient; + } + + std::vector _colors_from; + std::vector _colors_to; + QPointF _point1; + QPointF _point2; + + QLinearGradient _gradient_from; + QLinearGradient _gradient_to; + +}; + +class radial_gradient { +public: + radial_gradient( + std::vector colors_from, + std::vector colors_to, + QPointF center, + float radius) + : _colors_from(colors_from) + , _colors_to(colors_to) + , _center(center) + , _radius(radius) + , _gradient_from(gradient(colors_from)) + , _gradient_to(gradient(colors_to)) { + Expects(colors_from.size() == colors_to.size()); + } + + QRadialGradient gradient(float64 b_ratio) const { + if (b_ratio == 0.) { + return _gradient_from; + } else if (b_ratio == 1.) { + return _gradient_to; + } + auto colors = std::vector(_colors_to.size()); + for (auto i = 0; i < colors.size(); i++) { + colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); + } + return gradient(colors); + } + +private: + QRadialGradient gradient(const std::vector &colors) const { + auto gradient = QRadialGradient(_center, _radius); + const auto size = colors.size(); + for (auto i = 0; i < size; i++) { + gradient.setColorAt(i / (size - 1), colors[i]); + } + return gradient; + } + + std::vector _colors_from; + std::vector _colors_to; + QPointF _center; + float _radius; + + QRadialGradient _gradient_from; + QRadialGradient _gradient_to; + +}; + +} // namespace anim From ce7ad090f367223d1fef35787929a0199185502d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 1 Dec 2020 11:02:24 +0300 Subject: [PATCH 39/79] Added classes for gradient color animation with several states. --- ui/effects/gradient.h | 143 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/ui/effects/gradient.h b/ui/effects/gradient.h index ed1e16a..377ef2d 100644 --- a/ui/effects/gradient.h +++ b/ui/effects/gradient.h @@ -6,6 +6,7 @@ // #pragma once +#include "base/flat_map.h" #include "ui/effects/animation_value.h" #include @@ -62,6 +63,77 @@ private: }; +template +class linear_gradients { +public: + linear_gradients( + base::flat_map> colors, + QPointF point1, + QPointF point2) + : _colors(colors) + , _point1(point1) + , _point2(point2) { + Expects(_colors.size() > 0); + + cache_gradients(); + } + + QLinearGradient gradient(T state1, T state2, float64 b_ratio) const { + if (b_ratio == 0.) { + return _gradients.find(state1)->second; + } else if (b_ratio == 1.) { + return _gradients.find(state2)->second; + } + + auto gradient = QLinearGradient(_point1, _point2); + const auto size = _colors.front().second.size(); + const auto colors1 = _colors.find(state1); + const auto colors2 = _colors.find(state2); + + Assert(colors1 != end(_colors)); + Assert(colors2 != end(_colors)); + + for (auto i = 0; i < size; i++) { + auto c = color(colors1->second[i], colors2->second[i], b_ratio); + gradient.setColorAt(i / (size - 1), std::move(c)); + } + return gradient; + } + + void set_points(QPointF point1, QPointF point2) { + if (_point1 == point1 && _point2 == point2) { + return; + } + _point1 = point1; + _point2 = point2; + cache_gradients(); + } + +private: + void cache_gradients() { + _gradients = base::flat_map(); + for (const auto &[key, value] : _colors) { + _gradients.emplace(key, gradient(value)); + } + } + + QLinearGradient gradient(const std::vector &colors) const { + auto gradient = QLinearGradient(_point1, _point2); + const auto size = colors.size(); + for (auto i = 0; i < size; i++) { + gradient.setColorAt(i / (size - 1), colors[i]); + } + return gradient; + } + + base::flat_map> _colors; + QPointF _point1; + QPointF _point2; + + base::flat_map _gradients; + +}; + class radial_gradient { public: radial_gradient( @@ -111,4 +183,75 @@ private: }; +template +class radial_gradients { +public: + radial_gradients( + base::flat_map> colors, + QPointF center, + float radius) + : _colors(colors) + , _center(center) + , _radius(radius) { + Expects(_colors.size() > 0); + + cache_gradients(); + } + + QRadialGradient gradient(T state1, T state2, float64 b_ratio) const { + if (b_ratio == 0.) { + return _gradients.find(state1)->second; + } else if (b_ratio == 1.) { + return _gradients.find(state2)->second; + } + + auto gradient = QRadialGradient(_center, _radius); + const auto size = _colors.front().second.size(); + const auto colors1 = _colors.find(state1); + const auto colors2 = _colors.find(state2); + + Assert(colors1 != end(_colors)); + Assert(colors2 != end(_colors)); + + for (auto i = 0; i < size; i++) { + auto c = color(colors1->second[i], colors2->second[i], b_ratio); + gradient.setColorAt(i / (size - 1), std::move(c)); + } + return gradient; + } + + void set_points(QPointF center, float radius) { + if (_center == center && _radius == radius) { + return; + } + _center = center; + _radius = radius; + cache_gradients(); + } + +private: + void cache_gradients() { + _gradients = base::flat_map(); + for (const auto &[key, value] : _colors) { + _gradients.emplace(key, gradient(value)); + } + } + + QRadialGradient gradient(const std::vector &colors) const { + auto gradient = QRadialGradient(_center, _radius); + const auto size = colors.size(); + for (auto i = 0; i < size; i++) { + gradient.setColorAt(i / (size - 1), colors[i]); + } + return gradient; + } + + base::flat_map> _colors; + QPointF _center; + float _radius; + + base::flat_map _gradients; + +}; + } // namespace anim From c8c29c7c78fd4db9100d7405b551846d274bb4b3 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 1 Dec 2020 12:37:07 +0300 Subject: [PATCH 40/79] Replaced gradients in mute button. --- ui/widgets/call_mute_button.cpp | 70 ++++++++++++++++----------------- ui/widgets/call_mute_button.h | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 69b9cc2..b76b989 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -7,6 +7,8 @@ #include "ui/widgets/call_mute_button.h" #include "base/event_filter.h" +#include "base/flat_map.h" +#include "ui/effects/gradient.h" #include "ui/effects/radial_animation.h" #include "ui/paint/blobs.h" #include "ui/painter.h" @@ -82,22 +84,23 @@ auto MuteBlobs() -> std::array { } auto Colors() { - return std::unordered_map>{ + using Vector = std::vector; + return base::flat_map{ { CallMuteButtonType::ForceMuted, - { st::callIconBg->c, st::callIconBg->c } + Vector{ st::callIconBg->c, st::callIconBg->c } }, { CallMuteButtonType::Active, - { st::groupCallLive1->c, st::groupCallLive2->c } + Vector{ st::groupCallLive1->c, st::groupCallLive2->c } }, { CallMuteButtonType::Connecting, - { st::callIconBg->c, st::callIconBg->c } + Vector{ st::callIconBg->c, st::callIconBg->c } }, { CallMuteButtonType::Muted, - { st::groupCallMuted1->c, st::groupCallMuted2->c } + Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c } }, }; } @@ -161,12 +164,6 @@ void BlobsWidget::init() { resize(s, s); } - const auto gradient = anim::linear_gradient( - { st::groupCallMuted1->c, st::groupCallMuted2->c }, - { st::groupCallLive1->c, st::groupCallLive2->c }, - QPoint(0, height()), - QPoint(width(), 0)); - sizeValue( ) | rpl::start_with_next([=](QSize size) { _center = size.width() / 2; @@ -287,14 +284,28 @@ void CallMuteButton::init() { lifetime().make_state(_state.current().type); setEnableMouse(false); - const auto glowColor = [=](CallMuteButtonType type) { - if (IsInactive(type)) { - return st::groupCallBg->c; + const auto blobsInner = _blobs->innerRect(); + auto linearGradients = anim::linear_gradients( + _colors, + QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height()), + QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y())); + + auto glowColors = [&] { + auto copy = _colors; + for (auto &[type, colors] : copy) { + if (IsInactive(type)) { + colors[0] = st::groupCallBg->c; + } else { + colors[0].setAlpha(kGlowAlpha); + } + colors[1] = QColor(Qt::transparent); } - auto c = _colors.at(type)[0]; - c.setAlpha(kGlowAlpha); - return c; - }; + return copy; + }(); + auto glows = anim::radial_gradients( + std::move(glowColors), + blobsInner.center(), + _blobs->width() / 2); _state.value( ) | rpl::map([](const CallMuteButtonState &state) { @@ -313,27 +324,16 @@ void CallMuteButton::init() { const auto radialShowFrom = IsConnecting(previous) ? 1. : 0.; const auto radialShowTo = IsConnecting(type) ? 1. : 0.; - const auto blobsInner = _blobs->innerRect(); - const auto gradient = anim::linear_gradient( - _colors.at(previous), - _colors.at(type), - QPoint(blobsInner.x(), blobsInner.y() + blobsInner.height()), - QPoint(blobsInner.x() + blobsInner.width(), blobsInner.y())); - - const auto glow = anim::radial_gradient( - { glowColor(previous), Qt::transparent }, - { glowColor(type), Qt::transparent }, - blobsInner.center(), - _blobs->width() / 2); - const auto from = _switchAnimation.animating() ? (1. - _switchAnimation.value(0.)) : 0.; const auto to = 1.; auto callback = [=](float64 value) { - _blobs->setBlobBrush(QBrush(gradient.gradient(value))); - _blobs->setGlowBrush(QBrush(glow.gradient(value))); + _blobs->setBlobBrush(QBrush( + linearGradients.gradient(previous, type, value))); + _blobs->setGlowBrush(QBrush( + glows.gradient(previous, type, value))); _blobs->update(); const auto crossProgress = (crossFrom == crossTo) @@ -487,8 +487,8 @@ void CallMuteButton::overridesColors( _colorOverrides.fire({ std::nullopt, std::nullopt }); return; } - auto from = _colors.at(fromType)[0]; - auto to = _colors.at(toType)[0]; + auto from = _colors.find(fromType)->second[0]; + auto to = _colors.find(toType)->second[0]; auto fromRipple = from; auto toRipple = to; if (!toInactive) { diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 69b5946..8cad8a8 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -79,7 +79,7 @@ private: CallButton _content; std::unique_ptr _radial; - const std::unordered_map> _colors; + const base::flat_map> _colors; CrossLineAnimation _crossLineMuteAnimation; Animations::Simple _switchAnimation; From dffa36ede444dbe0bb3dc7325b55014437850300 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 1 Dec 2020 14:13:01 +0300 Subject: [PATCH 41/79] Slightly improved Blob. --- ui/paint/blob.cpp | 71 +++++++++++++++++++++++----------------------- ui/paint/blob.h | 23 ++++++++------- ui/paint/blobs.cpp | 5 +++- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index 3495515..9ea9ffc 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -31,13 +31,9 @@ BlobBezier::BlobBezier(int n, float minScale, float minSpeed, float maxSpeed) , _minScale(minScale) , _minSpeed(minSpeed ? minSpeed : kMinSpeed) , _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed) -, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) { - _radius.resize(n); - _angle.resize(n); - _radiusNext.resize(n); - _angleNext.resize(n); - _progress.resize(n); - _speed.resize(n); +, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) +, _segmentAngle(360. / n) +, _segments(n) { } void BlobBezier::paint(Painter &p, const QBrush &brush) { @@ -51,17 +47,22 @@ void BlobBezier::paint(Painter &p, const QBrush &brush) { } for (auto i = 0; i < _segmentsCount; i++) { - const auto progress = _progress[i]; + const auto &segment = _segments[i]; + const auto nextIndex = i + 1 < _segmentsCount ? (i + 1) : 0; - const auto progressNext = _progress[nextIndex]; - const auto r1 = _radius[i] * (1. - progress) - + _radiusNext[i] * progress; - const auto r2 = _radius[nextIndex] * (1. - progressNext) - + _radiusNext[nextIndex] * progressNext; - const auto angle1 = _angle[i] * (1. - progress) - + _angleNext[i] * progress; - const auto angle2 = _angle[nextIndex] * (1. - progressNext) - + _angleNext[nextIndex] * progressNext; + const auto nextSegment = _segments[nextIndex]; + + const auto progress = segment.progress; + const auto progressNext = nextSegment.progress; + + const auto r1 = segment.radius * (1. - progress) + + segment.radiusNext * progress; + const auto r2 = nextSegment.radius * (1. - progressNext) + + nextSegment.radiusNext * progressNext; + const auto angle1 = segment.angle * (1. - progress) + + segment.angleNext * progress; + const auto angle2 = nextSegment.angle * (1. - progressNext) + + nextSegment.angleNext * progressNext; const auto l = _segmentLength * (std::min(r1, r2) + (std::max(r1, r2) - std::min(r1, r2)) / 2.); @@ -95,35 +96,33 @@ void BlobBezier::paint(Painter &p, const QBrush &brush) { void BlobBezier::generateBlob() { for (auto i = 0; i < _segmentsCount; i++) { - generateBlob(_radius, _angle, i); - generateBlob(_radiusNext, _angleNext, i); - _progress[i] = 0; + auto &segment = _segments[i]; + generateBlob(segment.radius, segment.angle, i); + generateBlob(segment.radiusNext, segment.angleNext, i); + segment.progress = 0.; } } -void BlobBezier::generateBlob( - std::vector &radius, - std::vector &angle, - int i) { - const auto angleSegment = 360. / _segmentsCount; - const auto angleDiff = angleSegment * 0.05; +void BlobBezier::generateBlob(float &radius, float &angle, int i) { + const auto angleDiff = _segmentAngle * 0.05; const auto radDiff = _maxRadius - _minRadius; - radius[i] = _minRadius + std::abs(RandomAdditional()) * radDiff; - angle[i] = angleSegment * i + RandomAdditional() * angleDiff; - _speed[i] = 0.017 + 0.003 * std::abs(RandomAdditional()); + radius = _minRadius + std::abs(RandomAdditional()) * radDiff; + angle = _segmentAngle * i + RandomAdditional() * angleDiff; + _segments[i].speed = 0.017 + 0.003 * std::abs(RandomAdditional()); } void BlobBezier::update(float level, float speedScale) { _scale = level; for (auto i = 0; i < _segmentsCount; i++) { - _progress[i] += (_speed[i] * _minSpeed) - + level * _speed[i] * _maxSpeed * speedScale; - if (_progress[i] >= 1) { - _progress[i] = 0.; - _radius[i] = _radiusNext[i]; - _angle[i] = _angleNext[i]; - generateBlob(_radiusNext, _angleNext, i); + auto &segment = _segments[i]; + segment.progress += (segment.speed * _minSpeed) + + level * segment.speed * _maxSpeed * speedScale; + if (segment.progress >= 1) { + segment.progress = 0.; + segment.radius = segment.radiusNext; + segment.angle = segment.angleNext; + generateBlob(segment.radiusNext, segment.angleNext, i); } } } diff --git a/ui/paint/blob.h b/ui/paint/blob.h index a8157cb..c2f4050 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -18,15 +18,20 @@ public: void update(float level, float speedScale); void generateBlob(); - void setRadius(float min, float max); void setMinRadius(float value); void setMaxRadius(float value); private: - void generateBlob( - std::vector &radius, - std::vector &angle, - int i); + struct Segment { + float radius = 0.; + float angle = 0.; + float radiusNext = 0.; + float angleNext = 0.; + float progress = 0.; + float speed = 0.; + }; + + void generateBlob(float &radius, float &angle, int i); const int _segmentsCount; const float64 _segmentLength; @@ -34,13 +39,9 @@ private: const float _minSpeed; const float _maxSpeed; const QPen _pen; + const float _segmentAngle; - std::vector _radius; - std::vector _angle; - std::vector _radiusNext; - std::vector _angleNext; - std::vector _progress; - std::vector _speed; + std::vector _segments; float64 _scale = 0; float _minRadius = 0.; diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index cb9001b..055c0dc 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -15,7 +15,7 @@ Blobs::Blobs( float levelDuration, float maxLevel) : _maxLevel(maxLevel) -, _blobDatas(blobDatas) +, _blobDatas(std::move(blobDatas)) , _levelValue(levelDuration) { init(); } @@ -75,6 +75,9 @@ void Blobs::paint(Painter &p, const QBrush &brush) { p.setOpacity(opacity * alpha); } _blobs[i].paint(p, brush); + if (alpha != 1.) { + p.setOpacity(opacity); + } } } From 87ee83bc7336b4c814e2f0dc45261ff8e280cca0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 1 Dec 2020 14:17:52 +0300 Subject: [PATCH 42/79] Fixed delta time computing in BlobsWidget. --- ui/widgets/call_mute_button.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index b76b989..ac08ca9 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -144,6 +144,8 @@ private: int _center = 0; QRect _inner; + crl::time _blobsLastTime = 0; + Animations::Basic _animation; }; @@ -152,7 +154,8 @@ BlobsWidget::BlobsWidget(not_null parent) : RpWidget(parent) , _blobs(MuteBlobs() | ranges::to_vector, kLevelDuration, kMaxLevel) , _blobBrush(Qt::transparent) -, _glowBrush(Qt::transparent) { +, _glowBrush(Qt::transparent) +, _blobsLastTime(crl::now()) { init(); } @@ -203,8 +206,9 @@ void BlobsWidget::init() { }, lifetime()); _animation.init([=](crl::time now) { - const auto dt = now - _animation.started(); - _blobs.updateLevel(dt); + _blobs.updateLevel(now - _blobsLastTime); + _blobsLastTime = now; + update(); return true; }); From ab4ad89c4c709b2ec0f8296451d49c99d2ae4372 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Dec 2020 15:25:02 +0300 Subject: [PATCH 43/79] Allow colorizing CrossLineAnimation. --- ui/effects/cross_line.cpp | 60 +++++++++++++++++++++++---------- ui/effects/cross_line.h | 20 ++++++++--- ui/widgets/call_mute_button.cpp | 5 +++ 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/ui/effects/cross_line.cpp b/ui/effects/cross_line.cpp index 2241fe1..4c70455 100644 --- a/ui/effects/cross_line.cpp +++ b/ui/effects/cross_line.cpp @@ -18,56 +18,82 @@ CrossLineAnimation::CrossLineAnimation( , _reversed(reversed) , _transparentPen(Qt::transparent, st.stroke, Qt::SolidLine, Qt::RoundCap) , _strokePen(st.fg, st.stroke, Qt::SolidLine, Qt::RoundCap) -, _line(st.startPosition, st.endPosition) -, _completeCross(image(1.)) { +, _line(st.startPosition, st.endPosition) { _line.setAngle(angle); } void CrossLineAnimation::paint( Painter &p, QPoint position, - float64 progress) { - paint(p, position.x(), position.y(), progress); + float64 progress, + std::optional colorOverride) { + paint(p, position.x(), position.y(), progress, colorOverride); } void CrossLineAnimation::paint( Painter &p, int left, int top, - float64 progress) { + float64 progress, + std::optional colorOverride) { if (progress == 0.) { - _st.icon.paint(p, left, top, _st.icon.width()); + if (colorOverride) { + _st.icon.paint(p, left, top, _st.icon.width(), *colorOverride); + } else { + _st.icon.paint(p, left, top, _st.icon.width()); + } } else if (progress == 1.) { + if (_completeCross.isNull()) { + fillFrame(progress, colorOverride); + _completeCross = _frame; + } p.drawImage(left, top, _completeCross); } else { - p.drawImage(left, top, image(progress)); + fillFrame(progress, colorOverride); + p.drawImage(left, top, _frame); } } -QImage CrossLineAnimation::image(float64 progress) const { +void CrossLineAnimation::fillFrame( + float64 progress, + std::optional colorOverride) { const auto ratio = style::DevicePixelRatio(); - auto frame = QImage( - QSize(_st.icon.width() * ratio, _st.icon.height() * ratio), - QImage::Format_ARGB32_Premultiplied); - frame.setDevicePixelRatio(ratio); - frame.fill(Qt::transparent); + if (_frame.isNull()) { + _frame = QImage( + _st.icon.size() * ratio, + QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(ratio); + } + _frame.fill(Qt::transparent); auto topLine = _line; topLine.setLength(topLine.length() * progress); auto bottomLine = topLine.translated(0, _strokePen.widthF() + 1); - Painter q(&frame); + Painter q(&_frame); PainterHighQualityEnabler hq(q); - _st.icon.paint(q, 0, 0, _st.icon.width()); + if (colorOverride) { + _st.icon.paint(q, 0, 0, _st.icon.width(), *colorOverride); + } else { + _st.icon.paint(q, 0, 0, _st.icon.width()); + } - q.setPen(_strokePen); + if (colorOverride) { + auto pen = _strokePen; + pen.setColor(*colorOverride); + q.setPen(pen); + } else { + q.setPen(_strokePen); + } q.drawLine(_reversed ? topLine : bottomLine); q.setCompositionMode(QPainter::CompositionMode_Source); q.setPen(_transparentPen); q.drawLine(_reversed ? bottomLine : topLine); +} - return frame; +void CrossLineAnimation::invalidate() { + _completeCross = QImage(); } } // namespace Ui diff --git a/ui/effects/cross_line.h b/ui/effects/cross_line.h index 0f21e51..d1336c7 100644 --- a/ui/effects/cross_line.h +++ b/ui/effects/cross_line.h @@ -19,18 +19,30 @@ public: bool reversed = false, float angle = 315); - void paint(Painter &p, QPoint position, float64 progress); - void paint(Painter &p, int left, int top, float64 progress); + void paint( + Painter &p, + QPoint position, + float64 progress, + std::optional colorOverride = std::nullopt); + void paint( + Painter &p, + int left, + int top, + float64 progress, + std::optional colorOverride = std::nullopt); + + void invalidate(); private: - QImage image(float64 progress) const; + void fillFrame(float64 progress, std::optional colorOverride); const style::CrossLineAnimation &_st; const bool _reversed; const QPen _transparentPen; const QPen _strokePen; QLineF _line; - const QImage _completeCross; + QImage _frame; + QImage _completeCross; }; diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index ac08ca9..3892154 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -258,6 +258,11 @@ CallMuteButton::CallMuteButton( , _colors(Colors()) , _crossLineMuteAnimation(st::callMuteCrossLine) { init(); + + style::PaletteChanged( + ) | rpl::start_with_next([=] { + _crossLineMuteAnimation.invalidate(); + }, lifetime()); } void CallMuteButton::init() { From 5f44304a305f9d02823e2d7ded9aadd59463e5a5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 3 Dec 2020 21:11:31 +0300 Subject: [PATCH 44/79] Add PCH to the project file. --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 19ed39e..0884919 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,9 @@ PRIVATE ui/ui_log.h ui/ui_utility.cpp ui/ui_utility.h + + ui/ui_pch.h + emoji_suggestions/emoji_suggestions.cpp emoji_suggestions/emoji_suggestions.h emoji_suggestions/emoji_suggestions_helper.h From 5b0e499932c76b4df21a5c511f52316aba774c26 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 3 Dec 2020 09:52:54 +0300 Subject: [PATCH 45/79] Replaced raw radius values of Blob with struct. --- ui/paint/blob.cpp | 12 ++++----- ui/paint/blob.h | 15 +++++++++-- ui/paint/blobs.cpp | 25 +++++++++--------- ui/paint/blobs.h | 6 ++++- ui/widgets/call_mute_button.cpp | 45 +++++++++++++++++++-------------- 5 files changed, 62 insertions(+), 41 deletions(-) diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index 9ea9ffc..58d905a 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -105,9 +105,9 @@ void BlobBezier::generateBlob() { void BlobBezier::generateBlob(float &radius, float &angle, int i) { const auto angleDiff = _segmentAngle * 0.05; - const auto radDiff = _maxRadius - _minRadius; + const auto radDiff = _radiuses.max - _radiuses.min; - radius = _minRadius + std::abs(RandomAdditional()) * radDiff; + radius = _radiuses.min + std::abs(RandomAdditional()) * radDiff; angle = _segmentAngle * i + RandomAdditional() * angleDiff; _segments[i].speed = 0.017 + 0.003 * std::abs(RandomAdditional()); } @@ -127,12 +127,12 @@ void BlobBezier::update(float level, float speedScale) { } } -void BlobBezier::setMinRadius(float value) { - _minRadius = value; +void BlobBezier::setRadiuses(Radiuses values) { + _radiuses = values; } -void BlobBezier::setMaxRadius(float value) { - _maxRadius = value; +BlobBezier::Radiuses BlobBezier::radiuses() const { + return _radiuses; } } // namespace Ui::Paint diff --git a/ui/paint/blob.h b/ui/paint/blob.h index c2f4050..58a6ce5 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -12,6 +12,15 @@ namespace Ui::Paint { class BlobBezier final { public: + struct Radiuses { + float min = 0.; + float max = 0.; + + inline bool operator==(const Radiuses &other) const { + return (min == other.min) && (max == other.max); + } + }; + BlobBezier(int n, float minScale, float minSpeed = 0, float maxSpeed = 0); void paint(Painter &p, const QBrush &brush); @@ -21,6 +30,9 @@ public: void setMinRadius(float value); void setMaxRadius(float value); + void setRadiuses(Radiuses values); + Radiuses radiuses() const; + private: struct Segment { float radius = 0.; @@ -44,8 +56,7 @@ private: std::vector _segments; float64 _scale = 0; - float _minRadius = 0.; - float _maxRadius = 0.; + Radiuses _radiuses; }; diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index 055c0dc..0273971 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -23,8 +23,7 @@ Blobs::Blobs( void Blobs::init() { for (const auto &data : _blobDatas) { auto blob = Paint::BlobBezier(data.segmentsCount, data.minScale); - blob.setMinRadius(data.minRadius); - blob.setMaxRadius(data.maxRadius); + blob.setRadiuses({ data.minRadius, data.maxRadius }); blob.generateBlob(); _blobs.push_back(std::move(blob)); } @@ -45,22 +44,22 @@ int Blobs::size() const { return _blobs.size(); } -void Blobs::setRadiusAt( - rpl::producer &&radius, - int index, - bool isMax) { +void Blobs::setRadiusesAt( + rpl::producer &&radiuses, + int index) { Expects(index >= 0 && index < size()); std::move( - radius - ) | rpl::start_with_next([=](float r) { - if (isMax) { - _blobs[index].setMaxRadius(r); - } else { - _blobs[index].setMinRadius(r); - } + radiuses + ) | rpl::start_with_next([=](BlobBezier::Radiuses r) { + _blobs[index].setRadiuses(std::move(r)); }, _lifetime); } +BlobBezier::Radiuses Blobs::radiusesAt(int index) { + Expects(index >= 0 && index < size()); + return _blobs[index].radiuses(); +} + void Blobs::setLevel(float value) { const auto to = std::min(_maxLevel, value) / _maxLevel; _levelValue.start(to); diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index 22ec519..07d35f9 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -29,7 +29,11 @@ public: float levelDuration, float maxLevel); - void setRadiusAt(rpl::producer &&radius, int index, bool isMax); + void setRadiusesAt( + rpl::producer &&radiuses, + int index); + BlobBezier::Radiuses radiusesAt(int index); + void setLevel(float value); void paint(Painter &p, const QBrush &brush); void updateLevel(crl::time dt); diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 3892154..6821d53 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -20,6 +20,8 @@ namespace Ui { namespace { +using Radiuses = Paint::BlobBezier::Radiuses; + constexpr auto kMaxLevel = 1.; constexpr auto kLevelDuration = 100. + 500. * 0.33; @@ -125,12 +127,13 @@ bool IsInactive(CallMuteButtonType type) { class BlobsWidget final : public RpWidget { public: - BlobsWidget(not_null parent); + BlobsWidget( + not_null parent, + rpl::producer &&hideBlobs); void setLevel(float level); void setBlobBrush(QBrush brush); void setGlowBrush(QBrush brush); - void setMainRadius(rpl::producer &&radius); [[nodiscard]] QRect innerRect() const; @@ -150,13 +153,27 @@ private: }; -BlobsWidget::BlobsWidget(not_null parent) +BlobsWidget::BlobsWidget( + not_null parent, + rpl::producer &&hideBlobs) : RpWidget(parent) , _blobs(MuteBlobs() | ranges::to_vector, kLevelDuration, kMaxLevel) , _blobBrush(Qt::transparent) , _glowBrush(Qt::transparent) , _blobsLastTime(crl::now()) { init(); + + for (auto i = 0; i < _blobs.size(); i++) { + const auto radiuses = _blobs.radiusesAt(i); + auto radiusesChange = rpl::duplicate( + hideBlobs + ) | rpl::map([=](bool hide) -> Radiuses { + return hide + ? Radiuses{ radiuses.min, radiuses.min } + : radiuses; + }) | rpl::distinct_until_changed(); + _blobs.setRadiusesAt(std::move(radiusesChange), i); + } } void BlobsWidget::init() { @@ -244,15 +261,16 @@ void BlobsWidget::setLevel(float level) { _blobs.setLevel(level); } -void BlobsWidget::setMainRadius(rpl::producer &&radius) { - _blobs.setRadiusAt(std::move(radius), 0, true); -} - CallMuteButton::CallMuteButton( not_null parent, CallMuteButtonState initial) : _state(initial) -, _blobs(base::make_unique_q(parent)) +, _blobs(base::make_unique_q( + parent, + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return IsConnecting(state.type); + }))) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) , _radial(nullptr) , _colors(Colors()) @@ -387,17 +405,6 @@ void CallMuteButton::init() { icon.height()); }, lifetime()); - // Main blob radius. - { - auto radius = _state.value( - ) | rpl::map([](const CallMuteButtonState &state) -> float { - return (IsConnecting(state.type) - ? st::callMuteMainBlobMinRadius - : st::callMuteMainBlobMaxRadius) * kMainRadiusFactor; - }) | rpl::distinct_until_changed(); - _blobs->setMainRadius(std::move(radius)); - } - // Paint. auto filterCallback = [=](not_null e) { if (e->type() != QEvent::Paint) { From 5437c8865dc35dd75d7474cdbcbfcba56ad5eb76 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 3 Dec 2020 18:09:10 +0300 Subject: [PATCH 46/79] Added initial animation of Blobs hiding. --- ui/paint/blobs.h | 2 ++ ui/widgets/call_mute_button.cpp | 32 ++++++++++++++++++++++++++++---- ui/widgets/call_mute_button.h | 1 + 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index 07d35f9..6a61d70 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -42,6 +42,8 @@ public: [[nodiscard]] int size() const; [[nodiscard]] float64 currentLevel() const; + static constexpr auto kHideBlobsDuration = 2000; + private: void init(); diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 6821d53..ace6e9d 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -148,6 +148,7 @@ private: QRect _inner; crl::time _blobsLastTime = 0; + crl::time _blobsHideLastTime = 0; Animations::Basic _animation; @@ -174,6 +175,18 @@ BlobsWidget::BlobsWidget( }) | rpl::distinct_until_changed(); _blobs.setRadiusesAt(std::move(radiusesChange), i); } + + std::move( + hideBlobs + ) | rpl::start_with_next([=](bool hide) { + if (hide) { + setLevel(0.); + } + _blobsHideLastTime = hide ? crl::now() : 0; + if (!hide && !_animation.animating()) { + _animation.start(); + } + }, lifetime()); } void BlobsWidget::init() { @@ -223,6 +236,11 @@ void BlobsWidget::init() { }, lifetime()); _animation.init([=](crl::time now) { + if (const auto &last = _blobsHideLastTime; (last > 0) + && (now - last >= Paint::Blobs::kHideBlobsDuration)) { + _animation.stop(); + return false; + } _blobs.updateLevel(now - _blobsLastTime); _blobsLastTime = now; @@ -258,19 +276,25 @@ void BlobsWidget::setGlowBrush(QBrush brush) { } void BlobsWidget::setLevel(float level) { + if (_blobsHideLastTime) { + return; + } _blobs.setLevel(level); } CallMuteButton::CallMuteButton( not_null parent, + rpl::producer &&hideBlobs, CallMuteButtonState initial) : _state(initial) , _blobs(base::make_unique_q( parent, - _state.value( - ) | rpl::map([](const CallMuteButtonState &state) { - return IsConnecting(state.type); - }))) + rpl::merge( + std::move(hideBlobs), + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return IsConnecting(state.type); + })))) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) , _radial(nullptr) , _colors(Colors()) diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 8cad8a8..4dfb53d 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -34,6 +34,7 @@ class CallMuteButton final { public: explicit CallMuteButton( not_null parent, + rpl::producer &&hideBlobs, CallMuteButtonState initial = CallMuteButtonState()); ~CallMuteButton(); From 5c66d1fa0a847407658b421c807f8085f418d11b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 3 Dec 2020 21:08:53 +0300 Subject: [PATCH 47/79] Wrapped AnimationsDisabled with rpl::variable. --- ui/effects/animation_value.cpp | 8 ++++++-- ui/effects/animation_value.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/effects/animation_value.cpp b/ui/effects/animation_value.cpp index 2018c7f..f56c851 100644 --- a/ui/effects/animation_value.cpp +++ b/ui/effects/animation_value.cpp @@ -13,7 +13,7 @@ namespace anim { namespace { -bool AnimationsDisabled = false; +rpl::variable AnimationsDisabled = false; } // namespace @@ -64,8 +64,12 @@ transition easeOutQuint = [](const float64 &delta, const float64 &dt) { return delta * (t2 * t2 * t + 1); }; +rpl::producer Disables() { + return AnimationsDisabled.value(); +}; + bool Disabled() { - return AnimationsDisabled; + return AnimationsDisabled.current(); } void SetDisabled(bool disabled) { diff --git a/ui/effects/animation_value.h b/ui/effects/animation_value.h index 5ecf752..cf86236 100644 --- a/ui/effects/animation_value.h +++ b/ui/effects/animation_value.h @@ -344,6 +344,7 @@ QPainterPath path(QPointF (&from)[N]) { return result; } +rpl::producer Disables(); bool Disabled(); void SetDisabled(bool disabled); From f24411cd96b2e3503d572418a63d2df8bae90fc9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 3 Dec 2020 21:09:47 +0300 Subject: [PATCH 48/79] Added correct handling animations disabling in mute button. --- ui/widgets/call_mute_button.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index ace6e9d..f1f8847 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -211,16 +211,6 @@ void BlobsWidget::init() { Painter p(this); PainterHighQualityEnabler hq(p); - if (anim::Disabled()) { - p.translate(_center, _center); - p.setPen(Qt::NoPen); - p.setBrush(_blobBrush); - const auto radius = st::callMuteMainBlobMinRadius - * kMainRadiusFactor; - p.drawEllipse(QPointF(), radius, radius); - return; - } - // Glow. const auto s = kGlowMinScale + (1. - kGlowMinScale) * _blobs.currentLevel(); @@ -289,12 +279,17 @@ CallMuteButton::CallMuteButton( : _state(initial) , _blobs(base::make_unique_q( parent, - rpl::merge( - std::move(hideBlobs), - _state.value( - ) | rpl::map([](const CallMuteButtonState &state) { - return IsConnecting(state.type); - })))) + rpl::combine( + anim::Disables(), + rpl::merge( + std::move(hideBlobs), + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return IsConnecting(state.type); + })) + ) | rpl::map([](bool animDisabled, bool hide) { + return !(!animDisabled && !hide); + }))) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) , _radial(nullptr) , _colors(Colors()) From 577e8f94ab538b3b4b2a1365a078293c92506616 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 6 Dec 2020 17:51:49 +0400 Subject: [PATCH 49/79] Improve close window icon. --- icons/title_button_close@2x.png | Bin 224 -> 472 bytes icons/title_button_close@3x.png | Bin 376 -> 631 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/title_button_close@2x.png b/icons/title_button_close@2x.png index e43b4d8a7f57ad85cab66305bb7f748d60fcbf96..b281004394857af8156b7831ce53babf80b4003e 100644 GIT binary patch delta 446 zcmaFBc!PO@NGZx^prw85kH`QX@Rme0>?TfNTy1#`a7G79fieh^2s-fq{7e zBLg##W(0{XV1moaEntST!7A+!PCN=!=H%((7~;WucA76=ivmyU!3w9?7mR!MMTcu0 zRyGRE(EMRuf4OA_#!?DbWvP8B!v*Qg$rbI+AvGxnV< zdHkY@uW)a$-HYto*M1%lH{4OV&A#ncj_j2jv-6u@?$Q%I()+bG-{89RgAP|ij}22# zrM_i;pncF;qh^6vQfjlI;HP(+{SRtt)U@h$E&V3x6vuha(YStF8@qC&$R8$=Ps=>!=fX3=AgdA=dd@%t#2M`F}lYenbA_g&CY6OIzMMydGvex zI?CI}h+LU}?@D}caNc!)?`}@b75oK^e$9oaJwm;k#HXJzp3buM(R#{a zQSnWYp>ku;OT~t+2dAc+|8;A7etOrsb*{~wd^WWg-bf~f1qi{#6Lzopr0QrBbEdT%j delta 196 zcmV;#06YKK1KUQ~=oH?qN?+vjos0000Mmf0v(-Tgc_d^%0+l`Vba4!c;CwqHdsVZ6Kh2a+FFt&01Zwx78n^=;y+ zvrXqcjyF0a)Mvzh;5d+YA?QQB$AO0z7>;{Cf0by{F8@b4CQ<2(cV^XZWi4ICKAv~s zneq=h9u#G;-MIR=ajjBwS_9)3!*~}liMlsMFCq)qT?$IryyoYznLnoPmbNk}%=p8! zLt1Kg+HAw%GY#&ycfKj=|MBkbkEcb6cAwvUJK%Wg%8Y3dFI-=*YO={ToLp#b;ule; z&{5nuL*3^^;tNOdU)yi@Hre(3U$B2CZ+c$t?NxT2w`YDi5P9!qNlBno zb84-Y1(;u<5x66G96bYf2HVhQ-~nJe+5!&(pF@wpQ%X_b^XMb^vMf>HIrI@c2t1D- zg*$?M&;Xz*C0*Bbvs6+_Uf}Ci+T)PrhPKqSBYX~AO4%%lK)64+*1G$ic7XeXN4|)Y za6hp1>@fk416K#aW5Gf2cyIvxxDzwGZS5YMnf=gL{f+0GeQWw19&zts0FR?PoSEIu w5QwNEV!f!*SC`)@|NjE=ujD~rm0O~}0ZW?L*pRK6BLDyZ07*qoM6N<$g6MRn+yDRo From fe6daf12b8dd7849b2f717cbe5a1d3d571072f22 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 6 Dec 2020 17:47:14 +0300 Subject: [PATCH 50/79] Added not equal operator to BlobBezier::Radiuses. --- ui/paint/blob.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/paint/blob.h b/ui/paint/blob.h index 58a6ce5..bd1d990 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -19,6 +19,9 @@ public: inline bool operator==(const Radiuses &other) const { return (min == other.min) && (max == other.max); } + inline bool operator!=(const Radiuses &other) const { + return !(min == other.min) && (max == other.max); + } }; BlobBezier(int n, float minScale, float minSpeed = 0, float maxSpeed = 0); From ea840471d2330c2763567c2e46b2414af6e0429d Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 6 Dec 2020 18:00:07 +0300 Subject: [PATCH 51/79] Fixed blob animations in mute button with enabled push-to-talk. --- ui/widgets/call_mute_button.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index f1f8847..0eca708 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -168,11 +168,12 @@ BlobsWidget::BlobsWidget( const auto radiuses = _blobs.radiusesAt(i); auto radiusesChange = rpl::duplicate( hideBlobs + ) | rpl::distinct_until_changed( ) | rpl::map([=](bool hide) -> Radiuses { return hide ? Radiuses{ radiuses.min, radiuses.min } : radiuses; - }) | rpl::distinct_until_changed(); + }); _blobs.setRadiusesAt(std::move(radiusesChange), i); } @@ -280,15 +281,14 @@ CallMuteButton::CallMuteButton( , _blobs(base::make_unique_q( parent, rpl::combine( - anim::Disables(), - rpl::merge( - std::move(hideBlobs), - _state.value( - ) | rpl::map([](const CallMuteButtonState &state) { - return IsConnecting(state.type); - })) - ) | rpl::map([](bool animDisabled, bool hide) { - return !(!animDisabled && !hide); + rpl::single(anim::Disabled()) | rpl::then(anim::Disables()), + std::move(hideBlobs), + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return IsConnecting(state.type); + }) + ) | rpl::map([](bool animDisabled, bool hide, bool isConnecting) { + return isConnecting || !(!animDisabled && !hide); }))) , _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) , _radial(nullptr) From dfc0f0b889375c10782a870a377936a19fcac7f3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 8 Dec 2020 11:12:46 +0400 Subject: [PATCH 52/79] Change button round radius. --- ui/basic.style | 1 - ui/widgets/buttons.cpp | 4 ++-- ui/widgets/checkbox.cpp | 2 +- ui/widgets/input_fields.cpp | 2 +- ui/widgets/tooltip.cpp | 2 +- ui/widgets/widgets.style | 2 ++ 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/basic.style b/ui/basic.style index 3f0832e..c8b4703 100644 --- a/ui/basic.style +++ b/ui/basic.style @@ -73,7 +73,6 @@ roundRadiusLarge: 6px; roundRadiusSmall: 3px; dateRadius: roundRadiusLarge; -buttonRadius: roundRadiusSmall; setLittleSkip: 9px; diff --git a/ui/widgets/buttons.cpp b/ui/widgets/buttons.cpp index 7d7a0c3..d770d0e 100644 --- a/ui/widgets/buttons.cpp +++ b/ui/widgets/buttons.cpp @@ -236,8 +236,8 @@ RoundButton::RoundButton( : RippleButton(parent, st.ripple) , _textFull(std::move(text)) , _st(st) -, _roundRect(ImageRoundRadius::Small, _st.textBg) -, _roundRectOver(ImageRoundRadius::Small, _st.textBgOver) { +, _roundRect(st::buttonRadius, _st.textBg) +, _roundRectOver(st::buttonRadius, _st.textBgOver) { _textFull.value( ) | rpl::start_with_next([=](const QString &text) { resizeToText(text); diff --git a/ui/widgets/checkbox.cpp b/ui/widgets/checkbox.cpp index 3822d50..eefca13 100644 --- a/ui/widgets/checkbox.cpp +++ b/ui/widgets/checkbox.cpp @@ -262,7 +262,7 @@ void CheckView::paint(Painter &p, int left, int top, int outerWidth) { { PainterHighQualityEnabler hq(p); - p.drawRoundedRect(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth), st::buttonRadius - (_st->thickness / 2.), st::buttonRadius - (_st->thickness / 2.)); + p.drawRoundedRect(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth), st::roundRadiusSmall - (_st->thickness / 2.), st::roundRadiusSmall - (_st->thickness / 2.)); } if (toggled > 0) { diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index bee6c57..9d9ed90 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -1045,7 +1045,7 @@ void FlatInput::paintEvent(QPaintEvent *e) { p.setBrush(anim::brush(_st.bgColor, _st.bgActive, placeholderFocused)); { PainterHighQualityEnabler hq(p); - p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::buttonRadius - (_st.borderWidth / 2.), st::buttonRadius - (_st.borderWidth / 2.)); + p.drawRoundedRect(QRectF(0, 0, width(), height()).marginsRemoved(QMarginsF(_st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2., _st.borderWidth / 2.)), st::roundRadiusSmall - (_st.borderWidth / 2.), st::roundRadiusSmall - (_st.borderWidth / 2.)); } if (!_st.icon.empty()) { diff --git a/ui/widgets/tooltip.cpp b/ui/widgets/tooltip.cpp index 3899449..788756c 100644 --- a/ui/widgets/tooltip.cpp +++ b/ui/widgets/tooltip.cpp @@ -142,7 +142,7 @@ void Tooltip::paintEvent(QPaintEvent *e) { p.setPen(_st->textBorder); p.setBrush(_st->textBg); PainterHighQualityEnabler hq(p); - p.drawRoundedRect(QRectF(0.5, 0.5, width() - 1., height() - 1.), st::buttonRadius, st::buttonRadius); + p.drawRoundedRect(QRectF(0.5, 0.5, width() - 1., height() - 1.), st::roundRadiusSmall, st::roundRadiusSmall); } else { p.fillRect(rect(), _st->textBg); diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 6fb9b39..367887e 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -626,6 +626,8 @@ defaultRippleAnimation: RippleAnimation { emptyRippleAnimation: RippleAnimation { } +buttonRadius: 4px; + defaultActiveButton: RoundButton { textFg: activeButtonFg; textFgOver: activeButtonFgOver; From cffbdc7c58f5b5ead273562c95bd9471d65bc2d6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 8 Dec 2020 23:09:21 +0300 Subject: [PATCH 53/79] Added initial implementation of Linear Blobs. --- CMakeLists.txt | 4 + ui/paint/blob.h | 3 - ui/paint/blob_linear.cpp | 153 ++++++++++++++++++++++++++++++++++++++ ui/paint/blob_linear.h | 67 +++++++++++++++++ ui/paint/blobs_linear.cpp | 113 ++++++++++++++++++++++++++++ ui/paint/blobs_linear.h | 69 +++++++++++++++++ 6 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 ui/paint/blob_linear.cpp create mode 100644 ui/paint/blob_linear.h create mode 100644 ui/paint/blobs_linear.cpp create mode 100644 ui/paint/blobs_linear.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0884919..4628547 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,8 +71,12 @@ PRIVATE ui/layers/layer_widget.h ui/paint/blob.cpp ui/paint/blob.h + ui/paint/blob_linear.cpp + ui/paint/blob_linear.h ui/paint/blobs.cpp ui/paint/blobs.h + ui/paint/blobs_linear.cpp + ui/paint/blobs_linear.h ui/platform/linux/ui_window_linux.cpp ui/platform/linux/ui_window_linux.h ui/platform/linux/ui_utility_linux.cpp diff --git a/ui/paint/blob.h b/ui/paint/blob.h index bd1d990..700cebf 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -30,9 +30,6 @@ public: void update(float level, float speedScale); void generateBlob(); - void setMinRadius(float value); - void setMaxRadius(float value); - void setRadiuses(Radiuses values); Radiuses radiuses() const; diff --git a/ui/paint/blob_linear.cpp b/ui/paint/blob_linear.cpp new file mode 100644 index 0000000..92a5015 --- /dev/null +++ b/ui/paint/blob_linear.cpp @@ -0,0 +1,153 @@ +// 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/blob_linear.h" + +#include "base/openssl_help.h" +#include "ui/painter.h" + +#include +#include + +namespace Ui::Paint { + +namespace { + +constexpr auto kMaxSpeed = 8.2; +constexpr auto kMinSpeed = 0.8; + +float64 RandomAdditional() { + return (openssl::RandomValue() % 100 / 100.); +} + +} // namespace + +LinearBlobBezier::LinearBlobBezier( + int n, + float minScale, + float minSpeed, + float maxSpeed) +: _segmentsCount(n) +, _minSpeed(minSpeed ? minSpeed : kMinSpeed) +, _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed) +, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) +, _segments(n + 1) { +} + +void LinearBlobBezier::paint( + Painter &p, + const QBrush &brush, + const QRect &rect, + float pinnedTop, + float progressToPinned) { + auto path = QPainterPath(); + auto m = QMatrix(); + + const auto left = rect.x(); + const auto top = rect.y(); + const auto right = rect.x() + rect.width(); + const auto bottom = rect.y() + rect.height(); + + // path.moveTo(right, bottom); + // path.lineTo(left, bottom); + path.moveTo(right, top); + path.lineTo(left, top); + + const auto &n = _segmentsCount; + + p.save(); + + for (auto i = 0; i <= n; i++) { + const auto &segment = _segments[i]; + + if (!i) { + const auto progress = segment.progress; + const auto r1 = segment.radius * (1. - progress) + + segment.radiusNext * progress; + const auto y = (bottom + r1);// * progressToPinned + // const auto y = (top - r1) * progressToPinned + // + pinnedTop * (1. - progressToPinned); + path.lineTo(left, y); + } else { + const auto &prevSegment = _segments[i - 1]; + const auto progress = prevSegment.progress; + const auto r1 = prevSegment.radius * (1. - progress) + + prevSegment.radiusNext * progress; + + const auto progressNext = segment.progress; + const auto r2 = segment.radius * (1. - progressNext) + + segment.radiusNext * progressNext; + + const auto x1 = (right - left) / n * (i - 1); + const auto x2 = (right - left) / n * i; + const auto cx = x1 + (x2 - x1) / 2; + + const auto y1 = (bottom + r1);// * progressToPinned + // const auto y1 = (top - r1) * progressToPinned + // + pinnedTop * (1. - progressToPinned); + const auto y2 = (bottom + r2);// * progressToPinned + // const auto y2 = (top - r2) * progressToPinned + // + pinnedTop * (1. - progressToPinned); + path.cubicTo( + QPointF(cx, y1), + QPointF(cx, y2), + QPointF(x2, y2) + ); + if (i == n) { + path.lineTo(right, top); + // path.lineTo(right, bottom); + } + } + } + + p.setBrush(Qt::NoBrush); + + p.setPen(_pen); + p.fillPath(path, brush); + p.drawPath(path); + + p.restore(); +} + +void LinearBlobBezier::generateBlob() { + for (auto i = 0; i < _segmentsCount; i++) { + auto &segment = _segments[i]; + generateBlob(segment.radius, i); + generateBlob(segment.radiusNext, i); + segment.progress = 0.; + } +} + +void LinearBlobBezier::generateBlob(float &radius, int i) { + const auto radDiff = _radiuses.max - _radiuses.min; + + radius = _radiuses.min + std::abs(RandomAdditional()) * radDiff; + _segments[i].speed = 0.017 + 0.003 * std::abs(RandomAdditional()); +} + +void LinearBlobBezier::update(float level, float speedScale) { + _scale = level; + for (auto i = 0; i < _segmentsCount; i++) { + auto &segment = _segments[i]; + segment.progress += (segment.speed * _minSpeed) + + level * segment.speed * _maxSpeed * speedScale; + if (segment.progress >= 1) { + segment.progress = 0.; + segment.radius = segment.radiusNext; + generateBlob(segment.radiusNext, i); + } + } +} + +void LinearBlobBezier::setRadiuses(Radiuses values) { + _radiuses = values; +} + +LinearBlobBezier::Radiuses LinearBlobBezier::radiuses() const { + return _radiuses; +} + +} // namespace Ui::Paint diff --git a/ui/paint/blob_linear.h b/ui/paint/blob_linear.h new file mode 100644 index 0000000..688fb09 --- /dev/null +++ b/ui/paint/blob_linear.h @@ -0,0 +1,67 @@ +// 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 + +class Painter; + +namespace Ui::Paint { + +class LinearBlobBezier final { +public: + struct Radiuses { + float min = 0.; + float max = 0.; + + inline bool operator==(const Radiuses &other) const { + return (min == other.min) && (max == other.max); + } + inline bool operator!=(const Radiuses &other) const { + return !(min == other.min) && (max == other.max); + } + }; + + LinearBlobBezier( + int n, + float minScale, + float minSpeed = 0, + float maxSpeed = 0); + + void paint( + Painter &p, + const QBrush &brush, + const QRect &rect, + float pinnedTop, + float progressToPinned); + void update(float level, float speedScale); + void generateBlob(); + + void setRadiuses(Radiuses values); + Radiuses radiuses() const; + +private: + struct Segment { + float radius = 0.; + float radiusNext = 0.; + float progress = 0.; + float speed = 0.; + }; + + void generateBlob(float &radius, int i); + + const int _segmentsCount; + const float _minSpeed; + const float _maxSpeed; + const QPen _pen; + + std::vector _segments; + + float64 _scale = 0; + Radiuses _radiuses; + +}; + +} // namespace Ui::Paint diff --git a/ui/paint/blobs_linear.cpp b/ui/paint/blobs_linear.cpp new file mode 100644 index 0000000..0ebcaae --- /dev/null +++ b/ui/paint/blobs_linear.cpp @@ -0,0 +1,113 @@ +// 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/blobs_linear.h" + +#include "ui/painter.h" + +namespace Ui::Paint { + +LinearBlobs::LinearBlobs( + std::vector blobDatas, + float levelDuration, + float levelDuration2, + float maxLevel) +: _maxLevel(maxLevel) +, _blobDatas(std::move(blobDatas)) +, _levelValue(levelDuration) +, _levelValue2(levelDuration2) { + init(); +} + +void LinearBlobs::init() { + for (const auto &data : _blobDatas) { + auto blob = Paint::LinearBlobBezier( + data.segmentsCount, + data.minScale); + blob.setRadiuses({ 0, data.minRadius }); + blob.generateBlob(); + _blobs.push_back(std::move(blob)); + } +} + +float LinearBlobs::maxRadius() const { + const auto maxOfRadiuses = [](const BlobData data) { + return std::max(data.maxRadius, data.minRadius); + }; + const auto max = *ranges::max_element( + _blobDatas, + std::less<>(), + maxOfRadiuses); + return maxOfRadiuses(max); +} + +int LinearBlobs::size() const { + return _blobs.size(); +} + +void LinearBlobs::setRadiusesAt( + rpl::producer &&radiuses, + int index) { + Expects(index >= 0 && index < size()); + std::move( + radiuses + ) | rpl::start_with_next([=](LinearBlobBezier::Radiuses r) { + _blobs[index].setRadiuses(std::move(r)); + }, _lifetime); +} + +LinearBlobBezier::Radiuses LinearBlobs::radiusesAt(int index) { + Expects(index >= 0 && index < size()); + return _blobs[index].radiuses(); +} + +void LinearBlobs::setLevel(float value) { + const auto to = std::min(_maxLevel, value) / _maxLevel; + _levelValue.start(to); + _levelValue2.start(to); +} + +void LinearBlobs::paint( + Painter &p, + const QBrush &brush, + const QRect &rect, + float pinnedTop, + float progressToPinned) { + const auto opacity = p.opacity(); + for (auto i = 0; i < _blobs.size(); i++) { + auto r = rect; + r.setTop(r.top() - _blobDatas[i].topOffset * _levelValue2.current()); + + _blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale); + const auto alpha = _blobDatas[i].alpha; + if (alpha != 1.) { + p.setOpacity(opacity * alpha); + } + _blobs[i].paint(p, brush, r, pinnedTop, progressToPinned); + if (alpha != 1.) { + p.setOpacity(opacity); + } + } +} + +void LinearBlobs::updateLevel(crl::time dt) { + const auto d = (dt > 20) ? 17 : dt; + _levelValue.update(d); + _levelValue2.update(d); + + for (auto i = 0; i < _blobs.size(); i++) { + const auto &data = _blobDatas[i]; + _blobs[i].setRadiuses({ + 0, + data.minRadius + data.maxRadius * (float)currentLevel() }); + } +} + +float64 LinearBlobs::currentLevel() const { + return _levelValue.current(); +} + +} // namespace Ui::Paint diff --git a/ui/paint/blobs_linear.h b/ui/paint/blobs_linear.h new file mode 100644 index 0000000..a4a30e5 --- /dev/null +++ b/ui/paint/blobs_linear.h @@ -0,0 +1,69 @@ +// 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/effects/animation_value.h" +#include "ui/paint/blob_linear.h" + +class Painter; + +namespace Ui::Paint { + +class LinearBlobs final { +public: + struct BlobData { + int segmentsCount = 0; + float minScale = 0; + float minRadius = 0; + float maxRadius = 0; + float speedScale = 0; + float alpha = 0; + int topOffset = 0; + }; + + LinearBlobs( + std::vector blobDatas, + float levelDuration, + float levelDuration2, + float maxLevel); + + void setRadiusesAt( + rpl::producer &&radiuses, + int index); + LinearBlobBezier::Radiuses radiusesAt(int index); + + void setLevel(float value); + void paint( + Painter &p, + const QBrush &brush, + const QRect &rect, + float pinnedTop, + float progressToPinned); + void updateLevel(crl::time dt); + + [[nodiscard]] float maxRadius() const; + [[nodiscard]] int size() const; + [[nodiscard]] float64 currentLevel() const; + + static constexpr auto kHideBlobsDuration = 2000; + +private: + void init(); + + const float _maxLevel; + + std::vector _blobDatas; + std::vector _blobs; + + anim::continuous_value _levelValue; + anim::continuous_value _levelValue2; + + rpl::lifetime _lifetime; + +}; + +} // namespace Ui::Paint From a02c8923dc6d00bcb4f4c2b1470cd48e2743903a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 9 Dec 2020 11:17:07 +0400 Subject: [PATCH 54/79] Paint waves antialiased. --- ui/paint/blobs_linear.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/paint/blobs_linear.cpp b/ui/paint/blobs_linear.cpp index 0ebcaae..fcdea81 100644 --- a/ui/paint/blobs_linear.cpp +++ b/ui/paint/blobs_linear.cpp @@ -76,6 +76,7 @@ void LinearBlobs::paint( const QRect &rect, float pinnedTop, float progressToPinned) { + PainterHighQualityEnabler hq(p); const auto opacity = p.opacity(); for (auto i = 0; i < _blobs.size(); i++) { auto r = rect; From 9735510195627e614a2da5363225c515f7a01841 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 10 Dec 2020 10:53:20 +0300 Subject: [PATCH 55/79] Added ability to set direction of Linear Blobs. --- ui/paint/blob_linear.cpp | 47 +++++++++++---------------------------- ui/paint/blob_linear.h | 16 ++++++------- ui/paint/blobs_linear.cpp | 35 +++++++++++------------------ ui/paint/blobs_linear.h | 15 +++++-------- 4 files changed, 39 insertions(+), 74 deletions(-) diff --git a/ui/paint/blob_linear.cpp b/ui/paint/blob_linear.cpp index 92a5015..c609efe 100644 --- a/ui/paint/blob_linear.cpp +++ b/ui/paint/blob_linear.cpp @@ -10,7 +10,6 @@ #include "ui/painter.h" #include -#include namespace Ui::Paint { @@ -27,34 +26,25 @@ float64 RandomAdditional() { LinearBlobBezier::LinearBlobBezier( int n, - float minScale, + Direction direction, float minSpeed, float maxSpeed) : _segmentsCount(n) , _minSpeed(minSpeed ? minSpeed : kMinSpeed) , _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed) , _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) +, _topDown(direction == Direction::TopDown ? 1 : -1) , _segments(n + 1) { } -void LinearBlobBezier::paint( - Painter &p, - const QBrush &brush, - const QRect &rect, - float pinnedTop, - float progressToPinned) { +void LinearBlobBezier::paint(Painter &p, const QBrush &brush, int width) { auto path = QPainterPath(); - auto m = QMatrix(); - const auto left = rect.x(); - const auto top = rect.y(); - const auto right = rect.x() + rect.width(); - const auto bottom = rect.y() + rect.height(); + const auto left = 0; + const auto right = width; - // path.moveTo(right, bottom); - // path.lineTo(left, bottom); - path.moveTo(right, top); - path.lineTo(left, top); + path.moveTo(right, 0); + path.lineTo(left, 0); const auto &n = _segmentsCount; @@ -67,17 +57,15 @@ void LinearBlobBezier::paint( const auto progress = segment.progress; const auto r1 = segment.radius * (1. - progress) + segment.radiusNext * progress; - const auto y = (bottom + r1);// * progressToPinned - // const auto y = (top - r1) * progressToPinned - // + pinnedTop * (1. - progressToPinned); + const auto y = r1 * _topDown; path.lineTo(left, y); } else { const auto &prevSegment = _segments[i - 1]; - const auto progress = prevSegment.progress; + const auto &progress = prevSegment.progress; const auto r1 = prevSegment.radius * (1. - progress) + prevSegment.radiusNext * progress; - const auto progressNext = segment.progress; + const auto &progressNext = segment.progress; const auto r2 = segment.radius * (1. - progressNext) + segment.radiusNext * progressNext; @@ -85,26 +73,18 @@ void LinearBlobBezier::paint( const auto x2 = (right - left) / n * i; const auto cx = x1 + (x2 - x1) / 2; - const auto y1 = (bottom + r1);// * progressToPinned - // const auto y1 = (top - r1) * progressToPinned - // + pinnedTop * (1. - progressToPinned); - const auto y2 = (bottom + r2);// * progressToPinned - // const auto y2 = (top - r2) * progressToPinned - // + pinnedTop * (1. - progressToPinned); + const auto y1 = r1 * _topDown; + const auto y2 = r2 * _topDown; path.cubicTo( QPointF(cx, y1), QPointF(cx, y2), QPointF(x2, y2) ); - if (i == n) { - path.lineTo(right, top); - // path.lineTo(right, bottom); - } } } + path.lineTo(right, 0); p.setBrush(Qt::NoBrush); - p.setPen(_pen); p.fillPath(path, brush); p.drawPath(path); @@ -129,7 +109,6 @@ void LinearBlobBezier::generateBlob(float &radius, int i) { } void LinearBlobBezier::update(float level, float speedScale) { - _scale = level; for (auto i = 0; i < _segmentsCount; i++) { auto &segment = _segments[i]; segment.progress += (segment.speed * _minSpeed) diff --git a/ui/paint/blob_linear.h b/ui/paint/blob_linear.h index 688fb09..31db7d7 100644 --- a/ui/paint/blob_linear.h +++ b/ui/paint/blob_linear.h @@ -12,6 +12,11 @@ namespace Ui::Paint { class LinearBlobBezier final { public: + enum class Direction { + TopDown, + BottomUp, + }; + struct Radiuses { float min = 0.; float max = 0.; @@ -26,16 +31,11 @@ public: LinearBlobBezier( int n, - float minScale, + Direction direction = Direction::TopDown, float minSpeed = 0, float maxSpeed = 0); - void paint( - Painter &p, - const QBrush &brush, - const QRect &rect, - float pinnedTop, - float progressToPinned); + void paint(Painter &p, const QBrush &brush, int width); void update(float level, float speedScale); void generateBlob(); @@ -56,10 +56,10 @@ private: const float _minSpeed; const float _maxSpeed; const QPen _pen; + const int _topDown; std::vector _segments; - float64 _scale = 0; Radiuses _radiuses; }; diff --git a/ui/paint/blobs_linear.cpp b/ui/paint/blobs_linear.cpp index fcdea81..db03988 100644 --- a/ui/paint/blobs_linear.cpp +++ b/ui/paint/blobs_linear.cpp @@ -13,12 +13,12 @@ namespace Ui::Paint { LinearBlobs::LinearBlobs( std::vector blobDatas, float levelDuration, - float levelDuration2, - float maxLevel) + float maxLevel, + LinearBlobBezier::Direction direction) : _maxLevel(maxLevel) +, _direction(direction) , _blobDatas(std::move(blobDatas)) -, _levelValue(levelDuration) -, _levelValue2(levelDuration2) { +, _levelValue(levelDuration) { init(); } @@ -26,16 +26,16 @@ void LinearBlobs::init() { for (const auto &data : _blobDatas) { auto blob = Paint::LinearBlobBezier( data.segmentsCount, - data.minScale); - blob.setRadiuses({ 0, data.minRadius }); + _direction); + blob.setRadiuses({ data.minRadius, data.idleRadius }); blob.generateBlob(); _blobs.push_back(std::move(blob)); } } float LinearBlobs::maxRadius() const { - const auto maxOfRadiuses = [](const BlobData data) { - return std::max(data.maxRadius, data.minRadius); + const auto maxOfRadiuses = [](const BlobData &d) { + return std::max(d.idleRadius, std::max(d.maxRadius, d.minRadius)); }; const auto max = *ranges::max_element( _blobDatas, @@ -67,27 +67,18 @@ LinearBlobBezier::Radiuses LinearBlobs::radiusesAt(int index) { void LinearBlobs::setLevel(float value) { const auto to = std::min(_maxLevel, value) / _maxLevel; _levelValue.start(to); - _levelValue2.start(to); } -void LinearBlobs::paint( - Painter &p, - const QBrush &brush, - const QRect &rect, - float pinnedTop, - float progressToPinned) { +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++) { - auto r = rect; - r.setTop(r.top() - _blobDatas[i].topOffset * _levelValue2.current()); - _blobs[i].update(_levelValue.current(), _blobDatas[i].speedScale); const auto alpha = _blobDatas[i].alpha; if (alpha != 1.) { p.setOpacity(opacity * alpha); } - _blobs[i].paint(p, brush, r, pinnedTop, progressToPinned); + _blobs[i].paint(p, brush, width); if (alpha != 1.) { p.setOpacity(opacity); } @@ -97,13 +88,13 @@ void LinearBlobs::paint( void LinearBlobs::updateLevel(crl::time dt) { const auto d = (dt > 20) ? 17 : dt; _levelValue.update(d); - _levelValue2.update(d); + const auto level = (float)currentLevel(); for (auto i = 0; i < _blobs.size(); i++) { const auto &data = _blobDatas[i]; _blobs[i].setRadiuses({ - 0, - data.minRadius + data.maxRadius * (float)currentLevel() }); + data.minRadius, + data.idleRadius + (data.maxRadius - data.idleRadius) * level }); } } diff --git a/ui/paint/blobs_linear.h b/ui/paint/blobs_linear.h index a4a30e5..3187e8d 100644 --- a/ui/paint/blobs_linear.h +++ b/ui/paint/blobs_linear.h @@ -17,9 +17,9 @@ class LinearBlobs final { public: struct BlobData { int segmentsCount = 0; - float minScale = 0; float minRadius = 0; float maxRadius = 0; + float idleRadius = 0; float speedScale = 0; float alpha = 0; int topOffset = 0; @@ -28,8 +28,8 @@ public: LinearBlobs( std::vector blobDatas, float levelDuration, - float levelDuration2, - float maxLevel); + float maxLevel, + LinearBlobBezier::Direction direction); void setRadiusesAt( rpl::producer &&radiuses, @@ -37,12 +37,7 @@ public: LinearBlobBezier::Radiuses radiusesAt(int index); void setLevel(float value); - void paint( - Painter &p, - const QBrush &brush, - const QRect &rect, - float pinnedTop, - float progressToPinned); + void paint(Painter &p, const QBrush &brush, int width); void updateLevel(crl::time dt); [[nodiscard]] float maxRadius() const; @@ -55,12 +50,12 @@ private: void init(); const float _maxLevel; + const LinearBlobBezier::Direction _direction; std::vector _blobDatas; std::vector _blobs; anim::continuous_value _levelValue; - anim::continuous_value _levelValue2; rpl::lifetime _lifetime; From d032f4d8b521ad33103b4e6f29a0ce45eb388c59 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 10 Dec 2020 14:28:35 +0300 Subject: [PATCH 56/79] Extracted common code for radial and linear blob classes. --- CMakeLists.txt | 2 - ui/paint/blob.cpp | 178 +++++++++++++++++++++++++------- ui/paint/blob.h | 95 ++++++++++++----- ui/paint/blob_linear.cpp | 132 ----------------------- ui/paint/blob_linear.h | 67 ------------ ui/paint/blobs.cpp | 8 +- ui/paint/blobs.h | 6 +- ui/paint/blobs_linear.cpp | 10 +- ui/paint/blobs_linear.h | 13 ++- ui/widgets/call_mute_button.cpp | 2 +- 10 files changed, 229 insertions(+), 284 deletions(-) delete mode 100644 ui/paint/blob_linear.cpp delete mode 100644 ui/paint/blob_linear.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4628547..9860dbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,8 +71,6 @@ PRIVATE ui/layers/layer_widget.h ui/paint/blob.cpp ui/paint/blob.h - ui/paint/blob_linear.cpp - ui/paint/blob_linear.h ui/paint/blobs.cpp ui/paint/blobs.h ui/paint/blobs_linear.cpp diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index 58d905a..669dd37 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -19,24 +19,69 @@ namespace { constexpr auto kMaxSpeed = 8.2; constexpr auto kMinSpeed = 0.8; +constexpr auto kMinSegmentSpeed = 0.017; +constexpr auto kSegmentSpeedDiff = 0.003; + float64 RandomAdditional() { return (openssl::RandomValue() % 100 / 100.); } } // namespace -BlobBezier::BlobBezier(int n, float minScale, float minSpeed, float maxSpeed) +Blob::Blob(int n, float minSpeed, float maxSpeed) : _segmentsCount(n) -, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n))) -, _minScale(minScale) , _minSpeed(minSpeed ? minSpeed : kMinSpeed) , _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed) -, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) +, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) { +} + +void Blob::generateBlob() { + for (auto i = 0; i < _segmentsCount; i++) { + generateSingleValues(i); + // Fill nexts. + generateTwoValues(i); + // Fill currents. + generateTwoValues(i); + } +} + +void Blob::generateSingleValues(int i) { + auto &segment = segmentAt(i); + segment.progress = 0.; + segment.speed = kMinSegmentSpeed + + kSegmentSpeedDiff * std::abs(RandomAdditional()); +} + +void Blob::update(float level, float speedScale) { + for (auto i = 0; i < _segmentsCount; i++) { + auto &segment = segmentAt(i); + segment.progress += (segment.speed * _minSpeed) + + level * segment.speed * _maxSpeed * speedScale; + if (segment.progress >= 1) { + generateSingleValues(i); + generateTwoValues(i); + } + } +} + +void Blob::setRadiuses(Radiuses values) { + _radiuses = values; +} + +Blob::Radiuses Blob::radiuses() const { + return _radiuses; +} + +RadialBlob::RadialBlob(int n, float minScale, float minSpeed, float maxSpeed) +: Blob(n, minSpeed, maxSpeed) +, _segmentLength((4.0 / 3.0) * std::tan(M_PI / (2 * n))) +, _minScale(minScale) , _segmentAngle(360. / n) +, _angleDiff(_segmentAngle * 0.05) , _segments(n) { } -void BlobBezier::paint(Painter &p, const QBrush &brush) { +void RadialBlob::paint(Painter &p, const QBrush &brush) { auto path = QPainterPath(); auto m = QMatrix(); @@ -55,14 +100,14 @@ void BlobBezier::paint(Painter &p, const QBrush &brush) { const auto progress = segment.progress; const auto progressNext = nextSegment.progress; - const auto r1 = segment.radius * (1. - progress) - + segment.radiusNext * progress; - const auto r2 = nextSegment.radius * (1. - progressNext) - + nextSegment.radiusNext * progressNext; - const auto angle1 = segment.angle * (1. - progress) - + segment.angleNext * progress; - const auto angle2 = nextSegment.angle * (1. - progressNext) - + nextSegment.angleNext * progressNext; + const auto r1 = segment.radius.current * (1. - progress) + + segment.radius.next * progress; + const auto r2 = nextSegment.radius.current * (1. - progressNext) + + nextSegment.radius.next * progressNext; + const auto angle1 = segment.angle.current * (1. - progress) + + segment.angle.next * progress; + const auto angle2 = nextSegment.angle.current * (1. - progressNext) + + nextSegment.angle.next * progressNext; const auto l = _segmentLength * (std::min(r1, r2) + (std::max(r1, r2) - std::min(r1, r2)) / 2.); @@ -94,45 +139,98 @@ void BlobBezier::paint(Painter &p, const QBrush &brush) { p.restore(); } -void BlobBezier::generateBlob() { - for (auto i = 0; i < _segmentsCount; i++) { - auto &segment = _segments[i]; - generateBlob(segment.radius, segment.angle, i); - generateBlob(segment.radiusNext, segment.angleNext, i); - segment.progress = 0.; - } -} +void RadialBlob::generateTwoValues(int i) { + auto &radius = _segments[i].radius; + auto &angle = _segments[i].angle; -void BlobBezier::generateBlob(float &radius, float &angle, int i) { - const auto angleDiff = _segmentAngle * 0.05; const auto radDiff = _radiuses.max - _radiuses.min; - radius = _radiuses.min + std::abs(RandomAdditional()) * radDiff; - angle = _segmentAngle * i + RandomAdditional() * angleDiff; - _segments[i].speed = 0.017 + 0.003 * std::abs(RandomAdditional()); + angle.setNext(_segmentAngle * i + RandomAdditional() * _angleDiff); + radius.setNext(_radiuses.min + std::abs(RandomAdditional()) * radDiff); } -void BlobBezier::update(float level, float speedScale) { +void RadialBlob::update(float level, float speedScale) { _scale = level; + Blob::update(level, speedScale); +} + +Blob::Segment &RadialBlob::segmentAt(int i) { + return _segments[i]; +}; + +LinearBlob::LinearBlob( + int n, + Direction direction, + float minSpeed, + float maxSpeed) +: Blob(n + 1) +, _topDown(direction == Direction::TopDown ? 1 : -1) +, _segments(_segmentsCount) { +} + +void LinearBlob::paint(Painter &p, const QBrush &brush, int width) { + auto path = QPainterPath(); + + const auto left = 0; + const auto right = width; + + path.moveTo(right, 0); + path.lineTo(left, 0); + + const auto n = float(_segmentsCount - 1); + + p.save(); + for (auto i = 0; i < _segmentsCount; i++) { - auto &segment = _segments[i]; - segment.progress += (segment.speed * _minSpeed) - + level * segment.speed * _maxSpeed * speedScale; - if (segment.progress >= 1) { - segment.progress = 0.; - segment.radius = segment.radiusNext; - segment.angle = segment.angleNext; - generateBlob(segment.radiusNext, segment.angleNext, i); + const auto &segment = _segments[i]; + + if (!i) { + const auto &progress = segment.progress; + const auto r1 = segment.radius.current * (1. - progress) + + segment.radius.next * progress; + const auto y = r1 * _topDown; + path.lineTo(left, y); + } else { + const auto &prevSegment = _segments[i - 1]; + const auto &progress = prevSegment.progress; + const auto r1 = prevSegment.radius.current * (1. - progress) + + prevSegment.radius.next * progress; + + const auto &progressNext = segment.progress; + const auto r2 = segment.radius.current * (1. - progressNext) + + segment.radius.next * progressNext; + + const auto x1 = (right - left) / n * (i - 1); + const auto x2 = (right - left) / n * i; + const auto cx = x1 + (x2 - x1) / 2; + + const auto y1 = r1 * _topDown; + const auto y2 = r2 * _topDown; + path.cubicTo( + QPointF(cx, y1), + QPointF(cx, y2), + QPointF(x2, y2) + ); } } + path.lineTo(right, 0); + + p.setBrush(Qt::NoBrush); + p.setPen(_pen); + p.fillPath(path, brush); + p.drawPath(path); + + p.restore(); } -void BlobBezier::setRadiuses(Radiuses values) { - _radiuses = values; +void LinearBlob::generateTwoValues(int i) { + auto &radius = _segments[i].radius; + const auto radDiff = _radiuses.max - _radiuses.min; + radius.setNext(_radiuses.min + std::abs(RandomAdditional()) * radDiff); } -BlobBezier::Radiuses BlobBezier::radiuses() const { - return _radiuses; -} +Blob::Segment &LinearBlob::segmentAt(int i) { + return _segments[i]; +}; } // namespace Ui::Paint diff --git a/ui/paint/blob.h b/ui/paint/blob.h index 700cebf..85ae491 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -10,54 +10,103 @@ class Painter; namespace Ui::Paint { -class BlobBezier final { +class Blob { public: struct Radiuses { float min = 0.; float max = 0.; - - inline bool operator==(const Radiuses &other) const { - return (min == other.min) && (max == other.max); - } - inline bool operator!=(const Radiuses &other) const { - return !(min == other.min) && (max == other.max); - } }; - BlobBezier(int n, float minScale, float minSpeed = 0, float maxSpeed = 0); + Blob(int n, float minSpeed = 0, float maxSpeed = 0); - void paint(Painter &p, const QBrush &brush); void update(float level, float speedScale); void generateBlob(); void setRadiuses(Radiuses values); - Radiuses radiuses() const; + [[nodiscard]] Radiuses radiuses() const; + +protected: + struct TwoValues { + float current = 0.; + float next = 0.; + void setNext(float v) { + current = next; + next = v; + } + }; -private: struct Segment { - float radius = 0.; - float angle = 0.; - float radiusNext = 0.; - float angleNext = 0.; float progress = 0.; float speed = 0.; }; - void generateBlob(float &radius, float &angle, int i); + void generateSingleValues(int i); + virtual void generateTwoValues(int i) = 0; + virtual Segment &segmentAt(int i) = 0; const int _segmentsCount; - const float64 _segmentLength; - const float _minScale; const float _minSpeed; const float _maxSpeed; const QPen _pen; - const float _segmentAngle; - std::vector _segments; - - float64 _scale = 0; Radiuses _radiuses; }; +class RadialBlob final : public Blob { +public: + RadialBlob(int n, float minScale, float minSpeed = 0, float maxSpeed = 0); + + void paint(Painter &p, const QBrush &brush); + void update(float level, float speedScale); + +private: + struct Segment : Blob::Segment { + Blob::TwoValues radius; + Blob::TwoValues angle; + }; + + void generateTwoValues(int i) override; + Blob::Segment &segmentAt(int i) override; + + const float64 _segmentLength; + const float _minScale; + const float _segmentAngle; + const float _angleDiff; + + std::vector _segments; + + float64 _scale = 0; + +}; + +class LinearBlob final : public Blob { +public: + enum class Direction { + TopDown, + BottomUp, + }; + + LinearBlob( + int n, + Direction direction = Direction::TopDown, + float minSpeed = 0, + float maxSpeed = 0); + + void paint(Painter &p, const QBrush &brush, int width); + +private: + struct Segment : Blob::Segment { + Blob::TwoValues radius; + }; + + void generateTwoValues(int i) override; + Blob::Segment &segmentAt(int i) override; + + const int _topDown; + + std::vector _segments; + +}; + } // namespace Ui::Paint diff --git a/ui/paint/blob_linear.cpp b/ui/paint/blob_linear.cpp deleted file mode 100644 index c609efe..0000000 --- a/ui/paint/blob_linear.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// 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/blob_linear.h" - -#include "base/openssl_help.h" -#include "ui/painter.h" - -#include - -namespace Ui::Paint { - -namespace { - -constexpr auto kMaxSpeed = 8.2; -constexpr auto kMinSpeed = 0.8; - -float64 RandomAdditional() { - return (openssl::RandomValue() % 100 / 100.); -} - -} // namespace - -LinearBlobBezier::LinearBlobBezier( - int n, - Direction direction, - float minSpeed, - float maxSpeed) -: _segmentsCount(n) -, _minSpeed(minSpeed ? minSpeed : kMinSpeed) -, _maxSpeed(maxSpeed ? maxSpeed : kMaxSpeed) -, _pen(Qt::NoBrush, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) -, _topDown(direction == Direction::TopDown ? 1 : -1) -, _segments(n + 1) { -} - -void LinearBlobBezier::paint(Painter &p, const QBrush &brush, int width) { - auto path = QPainterPath(); - - const auto left = 0; - const auto right = width; - - path.moveTo(right, 0); - path.lineTo(left, 0); - - const auto &n = _segmentsCount; - - p.save(); - - for (auto i = 0; i <= n; i++) { - const auto &segment = _segments[i]; - - if (!i) { - const auto progress = segment.progress; - const auto r1 = segment.radius * (1. - progress) - + segment.radiusNext * progress; - const auto y = r1 * _topDown; - path.lineTo(left, y); - } else { - const auto &prevSegment = _segments[i - 1]; - const auto &progress = prevSegment.progress; - const auto r1 = prevSegment.radius * (1. - progress) - + prevSegment.radiusNext * progress; - - const auto &progressNext = segment.progress; - const auto r2 = segment.radius * (1. - progressNext) - + segment.radiusNext * progressNext; - - const auto x1 = (right - left) / n * (i - 1); - const auto x2 = (right - left) / n * i; - const auto cx = x1 + (x2 - x1) / 2; - - const auto y1 = r1 * _topDown; - const auto y2 = r2 * _topDown; - path.cubicTo( - QPointF(cx, y1), - QPointF(cx, y2), - QPointF(x2, y2) - ); - } - } - path.lineTo(right, 0); - - p.setBrush(Qt::NoBrush); - p.setPen(_pen); - p.fillPath(path, brush); - p.drawPath(path); - - p.restore(); -} - -void LinearBlobBezier::generateBlob() { - for (auto i = 0; i < _segmentsCount; i++) { - auto &segment = _segments[i]; - generateBlob(segment.radius, i); - generateBlob(segment.radiusNext, i); - segment.progress = 0.; - } -} - -void LinearBlobBezier::generateBlob(float &radius, int i) { - const auto radDiff = _radiuses.max - _radiuses.min; - - radius = _radiuses.min + std::abs(RandomAdditional()) * radDiff; - _segments[i].speed = 0.017 + 0.003 * std::abs(RandomAdditional()); -} - -void LinearBlobBezier::update(float level, float speedScale) { - for (auto i = 0; i < _segmentsCount; i++) { - auto &segment = _segments[i]; - segment.progress += (segment.speed * _minSpeed) - + level * segment.speed * _maxSpeed * speedScale; - if (segment.progress >= 1) { - segment.progress = 0.; - segment.radius = segment.radiusNext; - generateBlob(segment.radiusNext, i); - } - } -} - -void LinearBlobBezier::setRadiuses(Radiuses values) { - _radiuses = values; -} - -LinearBlobBezier::Radiuses LinearBlobBezier::radiuses() const { - return _radiuses; -} - -} // namespace Ui::Paint diff --git a/ui/paint/blob_linear.h b/ui/paint/blob_linear.h deleted file mode 100644 index 31db7d7..0000000 --- a/ui/paint/blob_linear.h +++ /dev/null @@ -1,67 +0,0 @@ -// 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 - -class Painter; - -namespace Ui::Paint { - -class LinearBlobBezier final { -public: - enum class Direction { - TopDown, - BottomUp, - }; - - struct Radiuses { - float min = 0.; - float max = 0.; - - inline bool operator==(const Radiuses &other) const { - return (min == other.min) && (max == other.max); - } - inline bool operator!=(const Radiuses &other) const { - return !(min == other.min) && (max == other.max); - } - }; - - LinearBlobBezier( - int n, - Direction direction = Direction::TopDown, - float minSpeed = 0, - float maxSpeed = 0); - - void paint(Painter &p, const QBrush &brush, int width); - void update(float level, float speedScale); - void generateBlob(); - - void setRadiuses(Radiuses values); - Radiuses radiuses() const; - -private: - struct Segment { - float radius = 0.; - float radiusNext = 0.; - float progress = 0.; - float speed = 0.; - }; - - void generateBlob(float &radius, int i); - - const int _segmentsCount; - const float _minSpeed; - const float _maxSpeed; - const QPen _pen; - const int _topDown; - - std::vector _segments; - - Radiuses _radiuses; - -}; - -} // namespace Ui::Paint diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index 0273971..700260e 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -22,7 +22,7 @@ Blobs::Blobs( void Blobs::init() { for (const auto &data : _blobDatas) { - auto blob = Paint::BlobBezier(data.segmentsCount, data.minScale); + auto blob = Paint::RadialBlob(data.segmentsCount, data.minScale); blob.setRadiuses({ data.minRadius, data.maxRadius }); blob.generateBlob(); _blobs.push_back(std::move(blob)); @@ -45,17 +45,17 @@ int Blobs::size() const { } void Blobs::setRadiusesAt( - rpl::producer &&radiuses, + rpl::producer &&radiuses, int index) { Expects(index >= 0 && index < size()); std::move( radiuses - ) | rpl::start_with_next([=](BlobBezier::Radiuses r) { + ) | rpl::start_with_next([=](Blob::Radiuses r) { _blobs[index].setRadiuses(std::move(r)); }, _lifetime); } -BlobBezier::Radiuses Blobs::radiusesAt(int index) { +Blob::Radiuses Blobs::radiusesAt(int index) { Expects(index >= 0 && index < size()); return _blobs[index].radiuses(); } diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index 6a61d70..a7d5d3d 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -30,9 +30,9 @@ public: float maxLevel); void setRadiusesAt( - rpl::producer &&radiuses, + rpl::producer &&radiuses, int index); - BlobBezier::Radiuses radiusesAt(int index); + Blob::Radiuses radiusesAt(int index); void setLevel(float value); void paint(Painter &p, const QBrush &brush); @@ -50,7 +50,7 @@ private: const float _maxLevel; std::vector _blobDatas; - std::vector _blobs; + std::vector _blobs; anim::continuous_value _levelValue; diff --git a/ui/paint/blobs_linear.cpp b/ui/paint/blobs_linear.cpp index db03988..8aa478b 100644 --- a/ui/paint/blobs_linear.cpp +++ b/ui/paint/blobs_linear.cpp @@ -14,7 +14,7 @@ LinearBlobs::LinearBlobs( std::vector blobDatas, float levelDuration, float maxLevel, - LinearBlobBezier::Direction direction) + LinearBlob::Direction direction) : _maxLevel(maxLevel) , _direction(direction) , _blobDatas(std::move(blobDatas)) @@ -24,7 +24,7 @@ LinearBlobs::LinearBlobs( void LinearBlobs::init() { for (const auto &data : _blobDatas) { - auto blob = Paint::LinearBlobBezier( + auto blob = Paint::LinearBlob( data.segmentsCount, _direction); blob.setRadiuses({ data.minRadius, data.idleRadius }); @@ -49,17 +49,17 @@ int LinearBlobs::size() const { } void LinearBlobs::setRadiusesAt( - rpl::producer &&radiuses, + rpl::producer &&radiuses, int index) { Expects(index >= 0 && index < size()); std::move( radiuses - ) | rpl::start_with_next([=](LinearBlobBezier::Radiuses r) { + ) | rpl::start_with_next([=](Blob::Radiuses r) { _blobs[index].setRadiuses(std::move(r)); }, _lifetime); } -LinearBlobBezier::Radiuses LinearBlobs::radiusesAt(int index) { +Blob::Radiuses LinearBlobs::radiusesAt(int index) { Expects(index >= 0 && index < size()); return _blobs[index].radiuses(); } diff --git a/ui/paint/blobs_linear.h b/ui/paint/blobs_linear.h index 3187e8d..5d88f8e 100644 --- a/ui/paint/blobs_linear.h +++ b/ui/paint/blobs_linear.h @@ -7,7 +7,7 @@ #pragma once #include "ui/effects/animation_value.h" -#include "ui/paint/blob_linear.h" +#include "ui/paint/blob.h" class Painter; @@ -22,19 +22,18 @@ public: float idleRadius = 0; float speedScale = 0; float alpha = 0; - int topOffset = 0; }; LinearBlobs( std::vector blobDatas, float levelDuration, float maxLevel, - LinearBlobBezier::Direction direction); + LinearBlob::Direction direction); void setRadiusesAt( - rpl::producer &&radiuses, + rpl::producer &&radiuses, int index); - LinearBlobBezier::Radiuses radiusesAt(int index); + Blob::Radiuses radiusesAt(int index); void setLevel(float value); void paint(Painter &p, const QBrush &brush, int width); @@ -50,10 +49,10 @@ private: void init(); const float _maxLevel; - const LinearBlobBezier::Direction _direction; + const LinearBlob::Direction _direction; std::vector _blobDatas; - std::vector _blobs; + std::vector _blobs; anim::continuous_value _levelValue; diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 0eca708..c85c34e 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -20,7 +20,7 @@ namespace Ui { namespace { -using Radiuses = Paint::BlobBezier::Radiuses; +using Radiuses = Paint::Blob::Radiuses; constexpr auto kMaxLevel = 1.; From d61e1a7bdeab1dfeec24b7e5fdc2894defdac045 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 11 Dec 2020 02:00:20 +0300 Subject: [PATCH 57/79] Extracted common code for gradient classes. --- ui/effects/gradient.h | 230 ++++++++++-------------------------------- 1 file changed, 53 insertions(+), 177 deletions(-) diff --git a/ui/effects/gradient.h b/ui/effects/gradient.h index 377ef2d..8bcbb4f 100644 --- a/ui/effects/gradient.h +++ b/ui/effects/gradient.h @@ -14,78 +14,24 @@ namespace anim { -class linear_gradient { -public: - linear_gradient( - std::vector colors_from, - std::vector colors_to, - QPointF point1, - QPointF point2) - : _colors_from(colors_from) - , _colors_to(colors_to) - , _point1(point1) - , _point2(point2) - , _gradient_from(gradient(colors_from)) - , _gradient_to(gradient(colors_to)) { - Expects(colors_from.size() == colors_to.size()); - } - - QLinearGradient gradient(float64 b_ratio) const { - if (b_ratio == 0.) { - return _gradient_from; - } else if (b_ratio == 1.) { - return _gradient_to; - } - auto colors = std::vector(_colors_to.size()); - for (auto i = 0; i < colors.size(); i++) { - colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); - } - return gradient(colors); - } - -private: - QLinearGradient gradient(const std::vector &colors) const { - auto gradient = QLinearGradient(_point1, _point2); - const auto size = colors.size(); - for (auto i = 0; i < size; i++) { - gradient.setColorAt(i / (size - 1), colors[i]); - } - return gradient; - } - - std::vector _colors_from; - std::vector _colors_to; - QPointF _point1; - QPointF _point2; - - QLinearGradient _gradient_from; - QLinearGradient _gradient_to; - -}; +namespace details { template -class linear_gradients { +class gradients { public: - linear_gradients( - base::flat_map> colors, - QPointF point1, - QPointF point2) - : _colors(colors) - , _point1(point1) - , _point2(point2) { + gradients(base::flat_map> colors) + : _colors(colors) { Expects(_colors.size() > 0); - - cache_gradients(); } - QLinearGradient gradient(T state1, T state2, float64 b_ratio) const { + QGradient gradient(T state1, T state2, float64 b_ratio) const { if (b_ratio == 0.) { return _gradients.find(state1)->second; } else if (b_ratio == 1.) { return _gradients.find(state2)->second; } - auto gradient = QLinearGradient(_point1, _point2); + auto gradient = empty_gradient(); const auto size = _colors.front().second.size(); const auto colors1 = _colors.find(state1); const auto colors2 = _colors.find(state2); @@ -100,124 +46,70 @@ public: return gradient; } +protected: + virtual QGradient empty_gradient() const = 0; + + void cache_gradients() { + _gradients = base::flat_map(); + for (const auto &[key, value] : _colors) { + _gradients.emplace(key, gradient(value)); + } + } + + QGradient gradient(const std::vector &colors) const { + auto gradient = empty_gradient(); + const auto size = colors.size(); + for (auto i = 0; i < size; i++) { + gradient.setColorAt(i / (size - 1), colors[i]); + } + return gradient; + } + + base::flat_map> _colors; + base::flat_map _gradients; + +}; + +} // namespace details + +template +class linear_gradients final : public details::gradients { +public: + linear_gradients( + base::flat_map> colors, + QPointF point1, + QPointF point2) + : details::gradients(std::move(colors)) { + set_points(point1, point2); + } + void set_points(QPointF point1, QPointF point2) { if (_point1 == point1 && _point2 == point2) { return; } _point1 = point1; _point2 = point2; - cache_gradients(); + details::gradients::cache_gradients(); } private: - void cache_gradients() { - _gradients = base::flat_map(); - for (const auto &[key, value] : _colors) { - _gradients.emplace(key, gradient(value)); - } + QGradient empty_gradient() const override { + return QLinearGradient(_point1, _point2); } - QLinearGradient gradient(const std::vector &colors) const { - auto gradient = QLinearGradient(_point1, _point2); - const auto size = colors.size(); - for (auto i = 0; i < size; i++) { - gradient.setColorAt(i / (size - 1), colors[i]); - } - return gradient; - } - - base::flat_map> _colors; QPointF _point1; QPointF _point2; - - base::flat_map _gradients; - -}; - -class radial_gradient { -public: - radial_gradient( - std::vector colors_from, - std::vector colors_to, - QPointF center, - float radius) - : _colors_from(colors_from) - , _colors_to(colors_to) - , _center(center) - , _radius(radius) - , _gradient_from(gradient(colors_from)) - , _gradient_to(gradient(colors_to)) { - Expects(colors_from.size() == colors_to.size()); - } - - QRadialGradient gradient(float64 b_ratio) const { - if (b_ratio == 0.) { - return _gradient_from; - } else if (b_ratio == 1.) { - return _gradient_to; - } - auto colors = std::vector(_colors_to.size()); - for (auto i = 0; i < colors.size(); i++) { - colors[i] = color(_colors_from[i], _colors_to[i], b_ratio); - } - return gradient(colors); - } - -private: - QRadialGradient gradient(const std::vector &colors) const { - auto gradient = QRadialGradient(_center, _radius); - const auto size = colors.size(); - for (auto i = 0; i < size; i++) { - gradient.setColorAt(i / (size - 1), colors[i]); - } - return gradient; - } - - std::vector _colors_from; - std::vector _colors_to; - QPointF _center; - float _radius; - - QRadialGradient _gradient_from; - QRadialGradient _gradient_to; - }; template -class radial_gradients { +class radial_gradients final : public details::gradients { public: radial_gradients( base::flat_map> colors, QPointF center, float radius) - : _colors(colors) - , _center(center) - , _radius(radius) { - Expects(_colors.size() > 0); - - cache_gradients(); - } - - QRadialGradient gradient(T state1, T state2, float64 b_ratio) const { - if (b_ratio == 0.) { - return _gradients.find(state1)->second; - } else if (b_ratio == 1.) { - return _gradients.find(state2)->second; - } - - auto gradient = QRadialGradient(_center, _radius); - const auto size = _colors.front().second.size(); - const auto colors1 = _colors.find(state1); - const auto colors2 = _colors.find(state2); - - Assert(colors1 != end(_colors)); - Assert(colors2 != end(_colors)); - - for (auto i = 0; i < size; i++) { - auto c = color(colors1->second[i], colors2->second[i], b_ratio); - gradient.setColorAt(i / (size - 1), std::move(c)); - } - return gradient; + : details::gradients(std::move(colors)) { + set_points(center, radius); } void set_points(QPointF center, float radius) { @@ -226,32 +118,16 @@ public: } _center = center; _radius = radius; - cache_gradients(); + details::gradients::cache_gradients(); } private: - void cache_gradients() { - _gradients = base::flat_map(); - for (const auto &[key, value] : _colors) { - _gradients.emplace(key, gradient(value)); - } + QGradient empty_gradient() const override { + return QRadialGradient(_center, _radius); } - QRadialGradient gradient(const std::vector &colors) const { - auto gradient = QRadialGradient(_center, _radius); - const auto size = colors.size(); - for (auto i = 0; i < size; i++) { - gradient.setColorAt(i / (size - 1), colors[i]); - } - return gradient; - } - - base::flat_map> _colors; QPointF _center; - float _radius; - - base::flat_map _gradients; - + float _radius = 0.; }; } // namespace anim From 37d939a65066a00eb60c0ad30a0641c6eaeb5b00 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 11 Dec 2020 09:54:22 +0300 Subject: [PATCH 58/79] Replaced CallButton with AbstractButton in mute button. --- ui/widgets/call_mute_button.cpp | 125 +++++++++++++++++--------------- ui/widgets/call_mute_button.h | 15 ++-- 2 files changed, 76 insertions(+), 64 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index c85c34e..019b452 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -6,12 +6,14 @@ // #include "ui/widgets/call_mute_button.h" -#include "base/event_filter.h" #include "base/flat_map.h" +#include "ui/abstract_button.h" #include "ui/effects/gradient.h" #include "ui/effects/radial_animation.h" #include "ui/paint/blobs.h" #include "ui/painter.h" +#include "ui/widgets/call_button.h" +#include "ui/widgets/labels.h" #include "styles/palette.h" #include "styles/style_widgets.h" @@ -46,8 +48,8 @@ constexpr auto kOverrideColorRippleAlpha = 50; constexpr auto kSwitchStateDuration = 120; -auto MuteBlobs() -> std::array { - return {{ +auto MuteBlobs() { + return std::vector{ { .segmentsCount = 6, .minScale = 1., @@ -82,7 +84,7 @@ auto MuteBlobs() -> std::array { .speedScale = 1., .alpha = (76. / 255.), }, - }}; + }; } auto Colors() { @@ -158,7 +160,7 @@ BlobsWidget::BlobsWidget( not_null parent, rpl::producer &&hideBlobs) : RpWidget(parent) -, _blobs(MuteBlobs() | ranges::to_vector, kLevelDuration, kMaxLevel) +, _blobs(MuteBlobs(), kLevelDuration, kMaxLevel) , _blobBrush(Qt::transparent) , _glowBrush(Qt::transparent) , _blobsLastTime(crl::now()) { @@ -278,6 +280,7 @@ CallMuteButton::CallMuteButton( rpl::producer &&hideBlobs, CallMuteButtonState initial) : _state(initial) +, _st(st::callMuteButtonActive) , _blobs(base::make_unique_q( parent, rpl::combine( @@ -290,25 +293,40 @@ CallMuteButton::CallMuteButton( ) | rpl::map([](bool animDisabled, bool hide, bool isConnecting) { return isConnecting || !(!animDisabled && !hide); }))) -, _content(parent, st::callMuteButtonActive, &st::callMuteButtonMuted) +, _content(base::make_unique_q(parent)) +, _label(base::make_unique_q( + _content, + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.text; + }), + _st.label)) , _radial(nullptr) , _colors(Colors()) , _crossLineMuteAnimation(st::callMuteCrossLine) { init(); +} + +void CallMuteButton::init() { + _content->resize(_st.button.width, _st.button.height); style::PaletteChanged( ) | rpl::start_with_next([=] { _crossLineMuteAnimation.invalidate(); }, lifetime()); -} -void CallMuteButton::init() { // Label text. - auto text = _state.value( - ) | rpl::map([](const CallMuteButtonState &state) { - return state.text; - }); - _content.setText(std::move(text)); + _label->show(); + rpl::combine( + _content->sizeValue(), + _label->sizeValue() + ) | rpl::start_with_next([=](QSize my, QSize label) { + _label->moveToLeft( + (my.width() - label.width()) / 2, + my.height() - label.height(), + my.width()); + }, _label->lifetime()); + _label->setAttribute(Qt::WA_TransparentForMouseEvents); _radialShowProgress.value( ) | rpl::start_with_next([=](float64 value) { @@ -319,7 +337,7 @@ void CallMuteButton::init() { } if ((value > 0.) && !anim::Disabled() && !_radial) { _radial = std::make_unique( - [=] { _content.update(); }, + [=] { _content->update(); }, st::callConnectingRadial); _radial->start(); } @@ -387,7 +405,7 @@ void CallMuteButton::init() { : InterpolateF(crossFrom, crossTo, value); if (crossProgress != _crossLineProgress) { _crossLineProgress = crossProgress; - _content.update(_muteIconPosition); + _content->update(_muteIconPosition); } const auto radialShowProgress = (radialShowFrom == radialShowTo) @@ -412,10 +430,10 @@ void CallMuteButton::init() { }, lifetime()); // Icon rect. - _content.sizeValue( + _content->sizeValue( ) | rpl::start_with_next([=](QSize size) { - const auto &icon = st::callMuteButtonActive.button.icon; - const auto &pos = st::callMuteButtonActive.button.iconPosition; + const auto &icon = _st.button.icon; + const auto &pos = _st.button.iconPosition; _muteIconPosition = QRect( (pos.x() < 0) ? ((size.width() - icon.width()) / 2) : pos.x(), @@ -425,34 +443,23 @@ void CallMuteButton::init() { }, lifetime()); // Paint. - auto filterCallback = [=](not_null e) { - if (e->type() != QEvent::Paint) { - return base::EventFilterResult::Continue; - } - contentPaint(); - return base::EventFilterResult::Cancel; - }; + _content->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + Painter p(_content); - auto filter = base::install_event_filter( - &_content, - std::move(filterCallback)); - - lifetime().make_state>(std::move(filter)); -} - -void CallMuteButton::contentPaint() { - Painter p(&_content); - - const auto progress = 1. - _crossLineProgress; - _crossLineMuteAnimation.paint(p, _muteIconPosition.topLeft(), progress); - - if (_radial) { - p.setOpacity(_radialShowProgress.current()); - _radial->draw( + _crossLineMuteAnimation.paint( p, - st::callMuteButtonActive.bgPosition, - _content.width()); - } + _muteIconPosition.topLeft(), + 1. - _crossLineProgress); + + if (_radial) { + p.setOpacity(_radialShowProgress.current()); + _radial->draw( + p, + _st.bgPosition, + _content->width()); + } + }, _content->lifetime()); } void CallMuteButton::setState(const CallMuteButtonState &state) { @@ -465,7 +472,7 @@ void CallMuteButton::setLevel(float level) { } rpl::producer CallMuteButton::clicks() const { - return _content.clicks(); + return _content->clicks(); } QSize CallMuteButton::innerSize() const { @@ -473,43 +480,43 @@ QSize CallMuteButton::innerSize() const { } QRect CallMuteButton::innerGeometry() const { - const auto skip = st::callMuteButtonActive.outerRadius; + const auto &skip = _st.outerRadius; return QRect( - _content.x(), - _content.y(), - _content.width() - 2 * skip, - _content.width() - 2 * skip); + _content->x(), + _content->y(), + _content->width() - 2 * skip, + _content->width() - 2 * skip); } void CallMuteButton::moveInner(QPoint position) { - const auto skip = st::callMuteButtonActive.outerRadius; - _content.move(position - QPoint(skip, skip)); + const auto &skip = _st.outerRadius; + _content->move(position - QPoint(skip, skip)); { const auto offset = QPoint( - (_blobs->width() - _content.width()) / 2, - (_blobs->height() - _content.width()) / 2); - _blobs->move(_content.pos() - offset); + (_blobs->width() - _content->width()) / 2, + (_blobs->height() - _content->width()) / 2); + _blobs->move(_content->pos() - offset); } } void CallMuteButton::setVisible(bool visible) { - _content.setVisible(visible); + _content->setVisible(visible); _blobs->setVisible(visible); } void CallMuteButton::raise() { _blobs->raise(); - _content.raise(); + _content->raise(); } void CallMuteButton::lower() { - _content.lower(); + _content->lower(); _blobs->lower(); } void CallMuteButton::setEnableMouse(bool value) { - _content.setAttribute(Qt::WA_TransparentForMouseEvents, !value); + _content->setAttribute(Qt::WA_TransparentForMouseEvents, !value); } void CallMuteButton::overridesColors( diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 4dfb53d..83bab67 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -9,14 +9,17 @@ #include "base/unique_qptr.h" #include "ui/effects/animations.h" #include "ui/effects/cross_line.h" -#include "ui/widgets/call_button.h" - -#include namespace Ui { class BlobsWidget; + +class AbstractButton; +class FlatLabel; class InfiniteRadialAnimation; +class RpWidget; + +struct CallButtonColors; enum class CallMuteButtonType { Connecting, @@ -62,7 +65,6 @@ public: private: void init(); - void contentPaint(); void overridesColors( CallMuteButtonType fromType, CallMuteButtonType toType, @@ -76,8 +78,11 @@ private: rpl::variable _radialShowProgress = 0.; QRect _muteIconPosition; + const style::CallButton &_st; + const base::unique_qptr _blobs; - CallButton _content; + const base::unique_qptr _content; + const base::unique_qptr _label; std::unique_ptr _radial; const base::flat_map> _colors; From b0925f97d0e87a1b470effcb6852c0d8c83472dd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 11 Dec 2020 10:30:51 +0300 Subject: [PATCH 59/79] Moved InterpolateF to animation_value header. --- ui/effects/animation_value.h | 6 +++++- ui/widgets/call_mute_button.cpp | 8 ++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/effects/animation_value.h b/ui/effects/animation_value.h index cf86236..faf308a 100644 --- a/ui/effects/animation_value.h +++ b/ui/effects/animation_value.h @@ -94,8 +94,12 @@ private: }; +TG_FORCE_INLINE float64 interpolateF(int a, int b, float64 b_ratio) { + return a + float64(b - a) * b_ratio; +} + TG_FORCE_INLINE int interpolate(int a, int b, float64 b_ratio) { - return qRound(a + float64(b - a) * b_ratio); + return std::round(interpolateF(a, b, b_ratio)); } #ifdef ARCH_CPU_32_BITS diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 019b452..e4a2151 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -109,10 +109,6 @@ auto Colors() { }; } -inline float64 InterpolateF(int a, int b, float64 b_ratio) { - return a + float64(b - a) * b_ratio; -} - bool IsMuted(CallMuteButtonType type) { return (type != CallMuteButtonType::Active); } @@ -402,7 +398,7 @@ void CallMuteButton::init() { const auto crossProgress = (crossFrom == crossTo) ? crossTo - : InterpolateF(crossFrom, crossTo, value); + : anim::interpolateF(crossFrom, crossTo, value); if (crossProgress != _crossLineProgress) { _crossLineProgress = crossProgress; _content->update(_muteIconPosition); @@ -410,7 +406,7 @@ void CallMuteButton::init() { const auto radialShowProgress = (radialShowFrom == radialShowTo) ? radialShowTo - : InterpolateF(radialShowFrom, radialShowTo, value); + : anim::interpolateF(radialShowFrom, radialShowTo, value); if (radialShowProgress != _radialShowProgress.current()) { _radialShowProgress = radialShowProgress; } From 417413f0262d0ddd71db86338a4e56617a5e09d1 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 14 Dec 2020 16:13:48 +0300 Subject: [PATCH 60/79] Updated appearance for force muted state in group calls. --- ui/colors.palette | 2 ++ ui/widgets/call_mute_button.cpp | 27 +++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index 85ac37e..e765c28 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -571,6 +571,8 @@ groupCallLive1: #0dcc39; // group call live button color1 groupCallLive2: #0bb6bd; // group call live button color2 groupCallMuted1: #0992ef; // group call muted button color1 groupCallMuted2: #16ccfb; // group call muted button color2 +groupCallForceMuted1: #d4527c; // group call force muted button color1 +groupCallForceMuted2: #5d8efa; // group call force muted button color2 groupCallMenuBg: #292d33; // group call popup menu background groupCallMenuBgOver: #343940; // group call popup menu with mouse over groupCallMenuBgRipple: #3a4047; // group call popup menu ripple effect diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index e4a2151..f566229 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -92,7 +92,7 @@ auto Colors() { return base::flat_map{ { CallMuteButtonType::ForceMuted, - Vector{ st::callIconBg->c, st::callIconBg->c } + Vector{ st::groupCallForceMuted1->c, st::groupCallForceMuted2->c } }, { CallMuteButtonType::Active, @@ -284,14 +284,14 @@ CallMuteButton::CallMuteButton( std::move(hideBlobs), _state.value( ) | rpl::map([](const CallMuteButtonState &state) { - return IsConnecting(state.type); + return IsInactive(state.type); }) - ) | rpl::map([](bool animDisabled, bool hide, bool isConnecting) { - return isConnecting || !(!animDisabled && !hide); + ) | rpl::map([](bool animDisabled, bool hide, bool isBadState) { + return isBadState || !(!animDisabled && !hide); }))) , _content(base::make_unique_q(parent)) , _label(base::make_unique_q( - _content, + parent, _state.value( ) | rpl::map([](const CallMuteButtonState &state) { return state.text; @@ -314,12 +314,12 @@ void CallMuteButton::init() { // Label text. _label->show(); rpl::combine( - _content->sizeValue(), - _label->sizeValue() - ) | rpl::start_with_next([=](QSize my, QSize label) { + _content->geometryValue(), + _label->geometryValue() + ) | rpl::start_with_next([=](QRect my, QRect label) { _label->moveToLeft( - (my.width() - label.width()) / 2, - my.height() - label.height(), + my.x() + (my.width() - label.width()) / 2, + my.y() + my.height() - label.height(), my.width()); }, _label->lifetime()); _label->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -519,6 +519,13 @@ void CallMuteButton::overridesColors( CallMuteButtonType fromType, CallMuteButtonType toType, float64 progress) { + const auto forceMutedToConnecting = [](CallMuteButtonType &type) { + if (type == CallMuteButtonType::ForceMuted) { + type = CallMuteButtonType::Connecting; + } + }; + forceMutedToConnecting(toType); + forceMutedToConnecting(fromType); const auto toInactive = IsInactive(toType); const auto fromInactive = IsInactive(fromType); if (toInactive && (progress == 1)) { From d5a076ccef0142a6a97aefde351572e2ab648009 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 14 Dec 2020 16:14:51 +0300 Subject: [PATCH 61/79] Fixed computing of inner rect from mute button. --- ui/widgets/call_mute_button.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index f566229..c3c7a2a 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -133,7 +133,7 @@ public: void setBlobBrush(QBrush brush); void setGlowBrush(QBrush brush); - [[nodiscard]] QRect innerRect() const; + [[nodiscard]] QRectF innerRect() const; private: void init(); @@ -143,7 +143,7 @@ private: QBrush _blobBrush; QBrush _glowBrush; int _center = 0; - QRect _inner; + QRectF _inner; crl::time _blobsLastTime = 0; crl::time _blobsHideLastTime = 0; @@ -200,9 +200,9 @@ void BlobsWidget::init() { ) | rpl::start_with_next([=](QSize size) { _center = size.width() / 2; - const auto w = _blobs.maxRadius() * 2; - const auto margins = style::margins(w, w, w, w); - _inner = QRect(QPoint(), size).marginsRemoved(margins); + const auto w = (size.width() - _blobs.maxRadius() * 2) / 2.; + const auto margins = QMarginsF(w, w, w, w); + _inner = QRectF(QPoint(), size).marginsRemoved(margins); }, lifetime()); paintRequest( @@ -246,7 +246,7 @@ void BlobsWidget::init() { }, lifetime()); } -QRect BlobsWidget::innerRect() const { +QRectF BlobsWidget::innerRect() const { return _inner; } @@ -344,11 +344,16 @@ void CallMuteButton::init() { lifetime().make_state(_state.current().type); setEnableMouse(false); - const auto blobsInner = _blobs->innerRect(); + const auto blobsInner = [&] { + // The point of the circle at 45 degrees. + const auto mF = std::sqrt(_blobs->innerRect().width()) / 2.; + return _blobs->innerRect().marginsRemoved(QMarginsF(mF, mF, mF, mF)); + }(); + auto linearGradients = anim::linear_gradients( _colors, - QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height()), - QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y())); + QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y()), + QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height())); auto glowColors = [&] { auto copy = _colors; From e08a0de35dd2d0bbf26d88d127ac7b80efb94150 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 14 Dec 2020 18:47:33 +0400 Subject: [PATCH 62/79] Use static polymorphism for gradients. --- ui/effects/gradient.h | 37 ++++++++++++++++++++++++------------- ui/paint/blob.h | 1 + 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/ui/effects/gradient.h b/ui/effects/gradient.h index 8bcbb4f..6b2f5d2 100644 --- a/ui/effects/gradient.h +++ b/ui/effects/gradient.h @@ -16,7 +16,7 @@ namespace anim { namespace details { -template +template class gradients { public: gradients(base::flat_map> colors) @@ -31,7 +31,7 @@ public: return _gradients.find(state2)->second; } - auto gradient = empty_gradient(); + auto gradient = static_cast(this)->empty_gradient(); const auto size = _colors.front().second.size(); const auto colors1 = _colors.find(state1); const auto colors2 = _colors.find(state2); @@ -47,8 +47,6 @@ public: } protected: - virtual QGradient empty_gradient() const = 0; - void cache_gradients() { _gradients = base::flat_map(); for (const auto &[key, value] : _colors) { @@ -56,8 +54,9 @@ protected: } } +private: QGradient gradient(const std::vector &colors) const { - auto gradient = empty_gradient(); + auto gradient = static_cast(this)->empty_gradient(); const auto size = colors.size(); for (auto i = 0; i < size; i++) { gradient.setColorAt(i / (size - 1), colors[i]); @@ -73,13 +72,16 @@ protected: } // namespace details template -class linear_gradients final : public details::gradients { +class linear_gradients final + : public details::gradients> { + using parent = details::gradients>; + public: linear_gradients( base::flat_map> colors, QPointF point1, QPointF point2) - : details::gradients(std::move(colors)) { + : parent(std::move(colors)) { set_points(point1, point2); } @@ -89,26 +91,32 @@ public: } _point1 = point1; _point2 = point2; - details::gradients::cache_gradients(); + parent::cache_gradients(); } private: - QGradient empty_gradient() const override { + friend class details::gradients>; + + QGradient empty_gradient() const { return QLinearGradient(_point1, _point2); } QPointF _point1; QPointF _point2; + }; template -class radial_gradients final : public details::gradients { +class radial_gradients final + : public details::gradients> { + using parent = details::gradients>; + public: radial_gradients( base::flat_map> colors, QPointF center, float radius) - : details::gradients(std::move(colors)) { + : parent(std::move(colors)) { set_points(center, radius); } @@ -118,16 +126,19 @@ public: } _center = center; _radius = radius; - details::gradients::cache_gradients(); + parent::cache_gradients(); } private: - QGradient empty_gradient() const override { + friend class details::gradients>; + + QGradient empty_gradient() const { return QRadialGradient(_center, _radius); } QPointF _center; float _radius = 0.; + }; } // namespace anim diff --git a/ui/paint/blob.h b/ui/paint/blob.h index 85ae491..f37e808 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -18,6 +18,7 @@ public: }; Blob(int n, float minSpeed = 0, float maxSpeed = 0); + virtual ~Blob() = default; void update(float level, float speedScale); void generateBlob(); From 300c5a9c6638fd5ad797357246c7ef08a2587ab8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 15 Dec 2020 19:59:52 +0400 Subject: [PATCH 63/79] Update voice chat colors. --- ui/colors.palette | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index e765c28..73c0858 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -554,18 +554,18 @@ callHangupBg: #d75a5a; // phone call popup hangup button background callHangupRipple: #c04646; // phone call popup hangup button ripple effect callMuteRipple: #ffffff12; // phone call popup mute mic and camera ripple effect -groupCallBg: #131417; // group call popup background +groupCallBg: #1a2026; // group call popup background groupCallActiveFg: #4db8ff; // group call active controls text -groupCallMembersBg: #1a1c1f; // group call members list background -groupCallMembersBgOver: #22252a; // group call members list row with mouse over -groupCallMembersBgRipple: #272b30; // group call member row ripple effect +groupCallMembersBg: #2c333d; // group call members list background +groupCallMembersBgOver: #323a45; // group call members list row with mouse over +groupCallMembersBgRipple: #39424f; // group call member row ripple effect groupCallMembersFg: #ffffff; // group call member name text -groupCallMemberActiveIcon: #3fe069; // group call active member icon -groupCallMemberActiveStatus: #67e66d; // group call active member status text -groupCallMemberInactiveIcon: #61666b; // group call inactive member icon -groupCallMemberInactiveStatus: #4db8ff; // group call inactive member status text +groupCallMemberActiveIcon: #8deb90; // group call active member icon +groupCallMemberActiveStatus: #8deb90; // group call active member status text +groupCallMemberInactiveIcon: #84888f; // group call inactive member icon +groupCallMemberInactiveStatus: #61c0ff; // group call inactive member status text groupCallMemberMutedIcon: #ff4f4d; // group call muted by admin member icon -groupCallMemberNotJoinedStatus: #61666b; // group call non joined member status text +groupCallMemberNotJoinedStatus: #91979e; // group call non joined member status text groupCallIconFg: #ffffff; // group call mute / settings / leave icon groupCallLive1: #0dcc39; // group call live button color1 groupCallLive2: #0bb6bd; // group call live button color2 From 3bd98ac5640523d2ccd241c2c0af49aba9494066 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 16 Dec 2020 15:27:59 +0400 Subject: [PATCH 64/79] Add shake-on-error animation to CallMuteButton. --- ui/colors.palette | 2 +- ui/widgets/call_mute_button.cpp | 87 +++++++++++++++++++++++++++------ ui/widgets/call_mute_button.h | 17 ++++++- ui/widgets/widgets.style | 2 + 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index 73c0858..6d1083c 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -564,7 +564,7 @@ groupCallMemberActiveIcon: #8deb90; // group call active member icon groupCallMemberActiveStatus: #8deb90; // group call active member status text groupCallMemberInactiveIcon: #84888f; // group call inactive member icon groupCallMemberInactiveStatus: #61c0ff; // group call inactive member status text -groupCallMemberMutedIcon: #ff4f4d; // group call muted by admin member icon +groupCallMemberMutedIcon: #ed7372; // group call muted by admin member icon groupCallMemberNotJoinedStatus: #91979e; // group call non joined member status text groupCallIconFg: #ffffff; // group call mute / settings / leave icon groupCallLive1: #0dcc39; // group call live button color1 diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index c3c7a2a..1488515 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -47,6 +47,7 @@ constexpr auto kOverrideColorBgAlpha = 76; constexpr auto kOverrideColorRippleAlpha = 50; constexpr auto kSwitchStateDuration = 120; +constexpr auto kShiftDuration = crl::time(300); auto MuteBlobs() { return std::vector{ @@ -315,12 +316,9 @@ void CallMuteButton::init() { _label->show(); rpl::combine( _content->geometryValue(), - _label->geometryValue() - ) | rpl::start_with_next([=](QRect my, QRect label) { - _label->moveToLeft( - my.x() + (my.width() - label.width()) / 2, - my.y() + my.height() - label.height(), - my.width()); + _label->sizeValue() + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateLabelGeometry(my, size); }, _label->lifetime()); _label->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -342,7 +340,7 @@ void CallMuteButton::init() { // State type. const auto previousType = lifetime().make_state(_state.current().type); - setEnableMouse(false); + setHandleMouseState(HandleMouseState::Disabled); const auto blobsInner = [&] { // The point of the circle at 45 degrees. @@ -379,8 +377,10 @@ void CallMuteButton::init() { const auto previous = *previousType; *previousType = type; - if (IsInactive(type) && !IsInactive(previous)) { - setEnableMouse(false); + const auto mouseState = HandleMouseStateFromType(type); + setHandleMouseState(HandleMouseState::Disabled); + if (mouseState != HandleMouseState::Enabled) { + setHandleMouseState(mouseState); } const auto crossFrom = IsMuted(previous) ? 0. : 1.; @@ -419,9 +419,7 @@ void CallMuteButton::init() { overridesColors(previous, type, value); if (value == to) { - if (!IsInactive(type) && IsInactive(previous)) { - setEnableMouse(true); - } + setHandleMouseState(mouseState); } }; @@ -463,6 +461,60 @@ void CallMuteButton::init() { }, _content->lifetime()); } +void CallMuteButton::updateLabelGeometry() { + updateLabelGeometry(_content->geometry(), _label->size()); +} + +void CallMuteButton::updateLabelGeometry(QRect my, QSize size) { + _label->moveToLeft( + my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, + my.y() + my.height() - size.height(), + my.width()); +} + +void CallMuteButton::shake() { + if (_shakeAnimation.animating()) { + return; + } + const auto update = [=] { + const auto fullProgress = _shakeAnimation.value(1.) * 6; + const auto segment = std::clamp(int(std::floor(fullProgress)), 0, 5); + const auto part = fullProgress - segment; + const auto from = (segment == 0) + ? 0. + : (segment == 1 || segment == 3 || segment == 5) + ? 1. + : -1.; + const auto to = (segment == 0 || segment == 2 || segment == 4) + ? 1. + : (segment == 1 || segment == 3) + ? -1. + : 0.; + const auto shift = from * (1. - part) + to * part; + _labelShakeShift = int(std::round(shift * st::shakeShift)); + updateLabelGeometry(); + }; + _shakeAnimation.start( + update, + 0., + 1., + kShiftDuration); +} + +CallMuteButton::HandleMouseState CallMuteButton::HandleMouseStateFromType( + CallMuteButtonType type) { + switch (type) { + case CallMuteButtonType::Active: + case CallMuteButtonType::Muted: + return HandleMouseState::Enabled; + case CallMuteButtonType::Connecting: + return HandleMouseState::Disabled; + case CallMuteButtonType::ForceMuted: + return HandleMouseState::Blocked; + } + Unexpected("Type in HandleMouseStateFromType."); +} + void CallMuteButton::setState(const CallMuteButtonState &state) { _state = state; } @@ -516,8 +568,15 @@ void CallMuteButton::lower() { _blobs->lower(); } -void CallMuteButton::setEnableMouse(bool value) { - _content->setAttribute(Qt::WA_TransparentForMouseEvents, !value); +void CallMuteButton::setHandleMouseState(HandleMouseState state) { + if (_handleMouseState == state) { + return; + } + _handleMouseState = state; + const auto handle = (_handleMouseState != HandleMouseState::Disabled); + const auto pointer = (_handleMouseState == HandleMouseState::Enabled); + _content->setAttribute(Qt::WA_TransparentForMouseEvents, !handle); + _content->setPointerCursor(pointer); } void CallMuteButton::overridesColors( diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 83bab67..39c5c1a 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -49,6 +49,8 @@ public: [[nodiscard]] QRect innerGeometry() const; void moveInner(QPoint position); + void shake(); + void setVisible(bool visible); void show() { setVisible(true); @@ -64,31 +66,44 @@ public: [[nodiscard]] rpl::lifetime &lifetime(); private: + enum class HandleMouseState { + Enabled, + Blocked, + Disabled, + }; void init(); void overridesColors( CallMuteButtonType fromType, CallMuteButtonType toType, float64 progress); - void setEnableMouse(bool value); + void setHandleMouseState(HandleMouseState state); + void updateLabelGeometry(QRect my, QSize size); + void updateLabelGeometry(); + + [[nodiscard]] static HandleMouseState HandleMouseStateFromType( + CallMuteButtonType type); rpl::variable _state; float _level = 0.; float64 _crossLineProgress = 0.; rpl::variable _radialShowProgress = 0.; QRect _muteIconPosition; + HandleMouseState _handleMouseState = HandleMouseState::Enabled; const style::CallButton &_st; const base::unique_qptr _blobs; const base::unique_qptr _content; const base::unique_qptr _label; + int _labelShakeShift = 0; std::unique_ptr _radial; const base::flat_map> _colors; CrossLineAnimation _crossLineMuteAnimation; Animations::Simple _switchAnimation; + Animations::Simple _shakeAnimation; rpl::event_stream _colorOverrides; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 367887e..8ef228e 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1468,6 +1468,8 @@ callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { size: size(100px, 100px); } +shakeShift: 4px; + // Windows specific title windowTitleButton: IconButton { From 5e3897fcff29c65c72fdf8f7f282aee0f6bed644 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 17 Dec 2020 15:54:04 +0400 Subject: [PATCH 65/79] Improve macOS custom window title. --- ui/platform/mac/ui_window_mac.h | 3 ++- ui/platform/mac/ui_window_mac.mm | 19 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/platform/mac/ui_window_mac.h b/ui/platform/mac/ui_window_mac.h index 717c5e3..284b7ae 100644 --- a/ui/platform/mac/ui_window_mac.h +++ b/ui/platform/mac/ui_window_mac.h @@ -33,11 +33,12 @@ private: void setupBodyTitleAreaEvents() override; void init(); - void toggleCustomTitle(bool visible); + void updateCustomTitleVisibility(bool force = false); const std::unique_ptr _private; const not_null _title; const not_null _body; + bool _titleVisible = true; #ifdef OS_OSX struct WindowDrag { diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm index 550584e..433bd73 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -207,7 +207,8 @@ void WindowHelper::Private::close() { Fn WindowHelper::Private::toggleCustomTitleCallback() { return crl::guard(_owner->window(), [=](bool visible) { - _owner->toggleCustomTitle(visible); + _owner->_titleVisible = visible; + _owner->updateCustomTitleVisibility(true); }); } @@ -286,7 +287,7 @@ WindowHelper::WindowHelper(not_null window) : nullptr) , _body(Ui::CreateChild(window.get())) { if (_title->shouldBeHidden()) { - toggleCustomTitle(false); + updateCustomTitleVisibility(); } init(); } @@ -303,27 +304,25 @@ void WindowHelper::setTitle(const QString &title) { _title->setText(title); } window()->setWindowTitle( - (!_title || _title->isHidden()) ? title : QString()); + (!_title || !_titleVisible) ? title : QString()); } void WindowHelper::setTitleStyle(const style::WindowTitle &st) { if (_title) { _title->setStyle(st); if (_title->shouldBeHidden()) { - toggleCustomTitle(false); + updateCustomTitleVisibility(); } } } -void WindowHelper::toggleCustomTitle(bool visible) { - if (_title->shouldBeHidden()) { - visible = false; - } - if (!_title || _title->isHidden() != visible) { +void WindowHelper::updateCustomTitleVisibility(bool force) { + auto visible = !_title->shouldBeHidden() && _titleVisible; + if (!_title || (!force && _title->isHidden() != visible)) { return; } _title->setVisible(visible); - window()->setWindowTitle(visible ? QString() : _title->text()); + window()->setWindowTitle(_titleVisible ? QString() : _title->text()); } void WindowHelper::setMinimumSize(QSize size) { From 967b18290c23ec2ecd25191fa345d4c2d6228b95 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 16 Dec 2020 08:48:38 +0300 Subject: [PATCH 66/79] Added ability to pass QGradientStops to gradients. --- ui/effects/gradient.h | 90 +++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/ui/effects/gradient.h b/ui/effects/gradient.h index 6b2f5d2..abb181c 100644 --- a/ui/effects/gradient.h +++ b/ui/effects/gradient.h @@ -14,14 +14,47 @@ namespace anim { +struct gradient_colors { + explicit gradient_colors(QColor color) { + stops.push_back({ 0., color }); + stops.push_back({ 1., color }); + } + explicit gradient_colors(std::vector colors) { + if (colors.size() == 1) { + gradient_colors(colors.front()); + return; + } + const auto last = float(colors.size() - 1); + for (auto i = 0; i < colors.size(); i++) { + stops.push_back({ i / last, std::move(colors[i]) }); + } + } + explicit gradient_colors(QGradientStops colors) + : stops(std::move(colors)) { + } + + QGradientStops stops; +}; + namespace details { template class gradients { public: - gradients(base::flat_map> colors) - : _colors(colors) { - Expects(_colors.size() > 0); + gradients(base::flat_map> colors) { + Expects(colors.size() > 0); + + for (const auto &[key, value] : colors) { + auto c = gradient_colors(std::move(value)); + _gradients.emplace(key, gradient_with_stops(std::move(c.stops))); + } + } + gradients(base::flat_map colors) { + Expects(colors.size() > 0); + + for (const auto &[key, c] : colors) { + _gradients.emplace(key, gradient_with_stops(std::move(c.stops))); + } } QGradient gradient(T state1, T state2, float64 b_ratio) const { @@ -31,16 +64,20 @@ public: return _gradients.find(state2)->second; } - auto gradient = static_cast(this)->empty_gradient(); - const auto size = _colors.front().second.size(); - const auto colors1 = _colors.find(state1); - const auto colors2 = _colors.find(state2); + auto gradient = empty_gradient(); + const auto gradient1 = _gradients.find(state1); + const auto gradient2 = _gradients.find(state2); - Assert(colors1 != end(_colors)); - Assert(colors2 != end(_colors)); + Assert(gradient1 != end(_gradients)); + Assert(gradient2 != end(_gradients)); + + const auto stops1 = gradient1->second.stops(); + const auto stops2 = gradient2->second.stops(); + + const auto size = stops1.size(); for (auto i = 0; i < size; i++) { - auto c = color(colors1->second[i], colors2->second[i], b_ratio); + auto c = color(stops1[i].second, stops2[i].second, b_ratio); gradient.setColorAt(i / (size - 1), std::move(c)); } return gradient; @@ -48,23 +85,22 @@ public: protected: void cache_gradients() { - _gradients = base::flat_map(); - for (const auto &[key, value] : _colors) { - _gradients.emplace(key, gradient(value)); + auto copy = std::move(_gradients); + for (const auto &[key, value] : copy) { + _gradients.emplace(key, gradient_with_stops(value.stops())); } } private: - QGradient gradient(const std::vector &colors) const { - auto gradient = static_cast(this)->empty_gradient(); - const auto size = colors.size(); - for (auto i = 0; i < size; i++) { - gradient.setColorAt(i / (size - 1), colors[i]); - } + QGradient empty_gradient() const { + return static_cast(this)->empty_gradient(); + } + QGradient gradient_with_stops(QGradientStops stops) const { + auto gradient = empty_gradient(); + gradient.setStops(std::move(stops)); return gradient; } - base::flat_map> _colors; base::flat_map _gradients; }; @@ -84,6 +120,13 @@ public: : parent(std::move(colors)) { set_points(point1, point2); } + linear_gradients( + base::flat_map colors, + QPointF point1, + QPointF point2) + : parent(std::move(colors)) { + set_points(point1, point2); + } void set_points(QPointF point1, QPointF point2) { if (_point1 == point1 && _point2 == point2) { @@ -119,6 +162,13 @@ public: : parent(std::move(colors)) { set_points(center, radius); } + radial_gradients( + base::flat_map colors, + QPointF center, + float radius) + : parent(std::move(colors)) { + set_points(center, radius); + } void set_points(QPointF center, float radius) { if (_center == center && _radius == radius) { From dd4ddd69fd37a27e338849da78e340e620da31e9 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 16 Dec 2020 10:31:55 +0300 Subject: [PATCH 67/79] Added ability to pass different count of colors to gradients. --- ui/effects/gradient.h | 62 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/ui/effects/gradient.h b/ui/effects/gradient.h index abb181c..f6988c5 100644 --- a/ui/effects/gradient.h +++ b/ui/effects/gradient.h @@ -71,14 +71,64 @@ public: Assert(gradient1 != end(_gradients)); Assert(gradient2 != end(_gradients)); - const auto stops1 = gradient1->second.stops(); - const auto stops2 = gradient2->second.stops(); + const auto stopsFrom = gradient1->second.stops(); + const auto stopsTo = gradient2->second.stops(); - const auto size = stops1.size(); + if ((stopsFrom.size() == stopsTo.size()) + && ranges::equal( + stopsFrom, + stopsTo, + ranges::equal_to(), + &QGradientStop::first, + &QGradientStop::first)) { - for (auto i = 0; i < size; i++) { - auto c = color(stops1[i].second, stops2[i].second, b_ratio); - gradient.setColorAt(i / (size - 1), std::move(c)); + const auto size = stopsFrom.size(); + const auto &p = b_ratio; + for (auto i = 0; i < size; i++) { + auto c = color(stopsFrom[i].second, stopsTo[i].second, p); + gradient.setColorAt(stopsTo[i].first, std::move(c)); + } + return gradient; + } + + const auto invert = (stopsFrom.size() > stopsTo.size()); + if (invert) { + b_ratio = 1. - b_ratio; + } + + const auto &stops1 = invert ? stopsTo : stopsFrom; + const auto &stops2 = invert ? stopsFrom : stopsTo; + + const auto size1 = stops1.size(); + const auto size2 = stops2.size(); + + for (auto i = 0; i < size1; i++) { + const auto point1 = stops1[i].first; + const auto previousPoint1 = i ? stops1[i - 1].first : -1.; + + for (auto n = 0; n < size2; n++) { + const auto point2 = stops2[n].first; + + if ((point2 <= previousPoint1) || (point2 > point1)) { + continue; + } + const auto color2 = stops2[n].second; + QColor result; + if (point2 < point1) { + const auto pointRatio2 = (point2 - previousPoint1) + / (point1 - previousPoint1); + const auto color1 = color( + stops1[i - 1].second, + stops1[i].second, + pointRatio2); + + result = color(color1, color2, b_ratio); + } else { + // point2 == point1 + result = color(stops1[i].second, color2, b_ratio); + } + gradient.setColorAt(point2, std::move(result)); + } } return gradient; } From 9eef2289a77b2ac8e1ecc038ed980258ea61a7a0 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 16 Dec 2020 11:48:42 +0300 Subject: [PATCH 68/79] Replaced main blob in mute button with circle. --- ui/widgets/call_mute_button.cpp | 35 ++++++++++++++++----------------- ui/widgets/call_mute_button.h | 2 +- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 1488515..ae21a66 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -51,16 +51,6 @@ constexpr auto kShiftDuration = crl::time(300); auto MuteBlobs() { return std::vector{ - { - .segmentsCount = 6, - .minScale = 1., - .minRadius = st::callMuteMainBlobMinRadius - * kMainRadiusFactor, - .maxRadius = st::callMuteMainBlobMaxRadius - * kMainRadiusFactor, - .speedScale = .4, - .alpha = 1., - }, { .segmentsCount = 9, .minScale = kScaleSmallMin / kScaleSmallMax, @@ -141,10 +131,11 @@ private: Paint::Blobs _blobs; + const float _circleRaidus; QBrush _blobBrush; QBrush _glowBrush; int _center = 0; - QRectF _inner; + QRectF _circleRect; crl::time _blobsLastTime = 0; crl::time _blobsHideLastTime = 0; @@ -158,6 +149,7 @@ BlobsWidget::BlobsWidget( rpl::producer &&hideBlobs) : RpWidget(parent) , _blobs(MuteBlobs(), kLevelDuration, kMaxLevel) +, _circleRaidus(st::callMuteMainBlobMinRadius * kMainRadiusFactor) , _blobBrush(Qt::transparent) , _glowBrush(Qt::transparent) , _blobsLastTime(crl::now()) { @@ -201,9 +193,11 @@ void BlobsWidget::init() { ) | rpl::start_with_next([=](QSize size) { _center = size.width() / 2; - const auto w = (size.width() - _blobs.maxRadius() * 2) / 2.; - const auto margins = QMarginsF(w, w, w, w); - _inner = QRectF(QPoint(), size).marginsRemoved(margins); + { + const auto &r = _circleRaidus; + const auto left = (size.width() - r * 2.) / 2.; + _circleRect = QRectF(left, left, r * 2, r * 2); + } }, lifetime()); paintRequest( @@ -223,6 +217,11 @@ void BlobsWidget::init() { // Blobs. p.translate(_center, _center); _blobs.paint(p, _blobBrush); + + // Main circle. + p.setPen(Qt::NoPen); + p.setBrush(_blobBrush); + p.drawEllipse(QPointF(), _circleRaidus, _circleRaidus); }, lifetime()); _animation.init([=](crl::time now) { @@ -248,7 +247,7 @@ void BlobsWidget::init() { } QRectF BlobsWidget::innerRect() const { - return _inner; + return _circleRect; } void BlobsWidget::setBlobBrush(QBrush brush) { @@ -406,7 +405,7 @@ void CallMuteButton::init() { : anim::interpolateF(crossFrom, crossTo, value); if (crossProgress != _crossLineProgress) { _crossLineProgress = crossProgress; - _content->update(_muteIconPosition); + _content->update(_muteIconRect); } const auto radialShowProgress = (radialShowFrom == radialShowTo) @@ -434,7 +433,7 @@ void CallMuteButton::init() { const auto &icon = _st.button.icon; const auto &pos = _st.button.iconPosition; - _muteIconPosition = QRect( + _muteIconRect = QRect( (pos.x() < 0) ? ((size.width() - icon.width()) / 2) : pos.x(), (pos.y() < 0) ? ((size.height() - icon.height()) / 2) : pos.y(), icon.width(), @@ -448,7 +447,7 @@ void CallMuteButton::init() { _crossLineMuteAnimation.paint( p, - _muteIconPosition.topLeft(), + _muteIconRect.topLeft(), 1. - _crossLineProgress); if (_radial) { diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 39c5c1a..58d3116 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -88,7 +88,7 @@ private: float _level = 0.; float64 _crossLineProgress = 0.; rpl::variable _radialShowProgress = 0.; - QRect _muteIconPosition; + QRect _muteIconRect; HandleMouseState _handleMouseState = HandleMouseState::Enabled; const style::CallButton &_st; From 526a816b7e579386730f1157ead6f04e1a6339fb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 16 Dec 2020 13:04:19 +0300 Subject: [PATCH 69/79] Updated colors for force muted state in group calls. --- ui/colors.palette | 8 +++++-- ui/widgets/call_mute_button.cpp | 38 +++++++++++++++++++-------------- ui/widgets/call_mute_button.h | 3 ++- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index 6d1083c..4fd9208 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -571,8 +571,12 @@ groupCallLive1: #0dcc39; // group call live button color1 groupCallLive2: #0bb6bd; // group call live button color2 groupCallMuted1: #0992ef; // group call muted button color1 groupCallMuted2: #16ccfb; // group call muted button color2 -groupCallForceMuted1: #d4527c; // group call force muted button color1 -groupCallForceMuted2: #5d8efa; // group call force muted button color2 +groupCallForceMutedBar1: #c65493; // group call force muted top bar color1 +groupCallForceMutedBar2: #7a6af1; // group call force muted top bar color2 +groupCallForceMutedBar3: #5f95e8; // group call force muted top bar color3 +groupCallForceMuted1: #4f9cff; // group call force muted button color1 +groupCallForceMuted2: #9b52e9; // group call force muted button color2 +groupCallForceMuted3: #eb5353; // group call force muted button color3 groupCallMenuBg: #292d33; // group call popup menu background groupCallMenuBgOver: #343940; // group call popup menu with mouse over groupCallMenuBgRipple: #3a4047; // group call popup menu ripple effect diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index ae21a66..e959e90 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -8,7 +8,6 @@ #include "base/flat_map.h" #include "ui/abstract_button.h" -#include "ui/effects/gradient.h" #include "ui/effects/radial_animation.h" #include "ui/paint/blobs.h" #include "ui/painter.h" @@ -80,22 +79,26 @@ auto MuteBlobs() { auto Colors() { using Vector = std::vector; - return base::flat_map{ + using Colors = anim::gradient_colors; + return base::flat_map{ { CallMuteButtonType::ForceMuted, - Vector{ st::groupCallForceMuted1->c, st::groupCallForceMuted2->c } + Colors(QGradientStops{ + { .0, st::groupCallForceMuted1->c }, + { .5, st::groupCallForceMuted2->c }, + { 1., st::groupCallForceMuted3->c } }) }, { CallMuteButtonType::Active, - Vector{ st::groupCallLive1->c, st::groupCallLive2->c } + Colors(Vector{ st::groupCallLive1->c, st::groupCallLive2->c }) }, { CallMuteButtonType::Connecting, - Vector{ st::callIconBg->c, st::callIconBg->c } + Colors(st::callIconBg->c) }, { CallMuteButtonType::Muted, - Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c } + Colors(Vector{ st::groupCallMuted1->c, st::groupCallMuted2->c }) }, }; } @@ -343,7 +346,8 @@ void CallMuteButton::init() { const auto blobsInner = [&] { // The point of the circle at 45 degrees. - const auto mF = std::sqrt(_blobs->innerRect().width()) / 2.; + const auto w = _blobs->innerRect().width(); + const auto mF = (1 - std::cos(M_PI / 4.)) * (w / 2.); return _blobs->innerRect().marginsRemoved(QMarginsF(mF, mF, mF, mF)); }(); @@ -354,13 +358,15 @@ void CallMuteButton::init() { auto glowColors = [&] { auto copy = _colors; - for (auto &[type, colors] : copy) { - if (IsInactive(type)) { - colors[0] = st::groupCallBg->c; - } else { - colors[0].setAlpha(kGlowAlpha); - } - colors[1] = QColor(Qt::transparent); + for (auto &[type, stops] : copy) { + auto firstColor = IsInactive(type) + ? st::groupCallBg->c + : stops.stops[0].second; + firstColor.setAlpha(kGlowAlpha); + stops.stops = QGradientStops{ + { 0., std::move(firstColor) }, + { 1., QColor(Qt::transparent) } + }; } return copy; }(); @@ -595,8 +601,8 @@ void CallMuteButton::overridesColors( _colorOverrides.fire({ std::nullopt, std::nullopt }); return; } - auto from = _colors.find(fromType)->second[0]; - auto to = _colors.find(toType)->second[0]; + auto from = _colors.find(fromType)->second.stops[0].second; + auto to = _colors.find(toType)->second.stops[0].second; auto fromRipple = from; auto toRipple = to; if (!toInactive) { diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 58d3116..cc55df4 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -9,6 +9,7 @@ #include "base/unique_qptr.h" #include "ui/effects/animations.h" #include "ui/effects/cross_line.h" +#include "ui/effects/gradient.h" namespace Ui { @@ -99,7 +100,7 @@ private: int _labelShakeShift = 0; std::unique_ptr _radial; - const base::flat_map> _colors; + const base::flat_map _colors; CrossLineAnimation _crossLineMuteAnimation; Animations::Simple _switchAnimation; From c0e1dfcc8d99433e7fff815107b564eddf919e7c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 17 Dec 2020 11:35:50 +0300 Subject: [PATCH 70/79] Added ability to pass additional scale to RadialBlob. --- ui/paint/blob.cpp | 4 ++-- ui/paint/blob.h | 2 +- ui/paint/blobs.cpp | 4 ++-- ui/paint/blobs.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index 669dd37..d962c58 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -81,12 +81,12 @@ RadialBlob::RadialBlob(int n, float minScale, float minSpeed, float maxSpeed) , _segments(n) { } -void RadialBlob::paint(Painter &p, const QBrush &brush) { +void RadialBlob::paint(Painter &p, const QBrush &brush, float outerScale) { auto path = QPainterPath(); auto m = QMatrix(); p.save(); - const auto scale = _minScale + _scale * (1. - _minScale); + const auto scale = (_minScale + (1. - _minScale) * _scale) * outerScale; if (scale != 1.) { p.scale(scale, scale); } diff --git a/ui/paint/blob.h b/ui/paint/blob.h index f37e808..e48e621 100644 --- a/ui/paint/blob.h +++ b/ui/paint/blob.h @@ -58,7 +58,7 @@ class RadialBlob final : public Blob { public: RadialBlob(int n, float minScale, float minSpeed = 0, float maxSpeed = 0); - void paint(Painter &p, const QBrush &brush); + void paint(Painter &p, const QBrush &brush, float outerScale = 1.); void update(float level, float speedScale); private: diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index 700260e..2c84f86 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -65,7 +65,7 @@ void Blobs::setLevel(float value) { _levelValue.start(to); } -void Blobs::paint(Painter &p, const QBrush &brush) { +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); @@ -73,7 +73,7 @@ void Blobs::paint(Painter &p, const QBrush &brush) { if (alpha != 1.) { p.setOpacity(opacity * alpha); } - _blobs[i].paint(p, brush); + _blobs[i].paint(p, brush, outerScale); if (alpha != 1.) { p.setOpacity(opacity); } diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index a7d5d3d..f3c5c52 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -35,7 +35,7 @@ public: Blob::Radiuses radiusesAt(int index); void setLevel(float value); - void paint(Painter &p, const QBrush &brush); + void paint(Painter &p, const QBrush &brush, float outerScale = 1.); void updateLevel(crl::time dt); [[nodiscard]] float maxRadius() const; From ac71aabba2684c65f5c6b054c3ef14db7729530c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 17 Dec 2020 10:40:29 +0300 Subject: [PATCH 71/79] Improved animation from Connecting state in mute button. --- ui/widgets/call_mute_button.cpp | 171 +++++++++++++++++++++++++++----- ui/widgets/call_mute_button.h | 4 +- ui/widgets/widgets.style | 8 +- 3 files changed, 151 insertions(+), 32 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index e959e90..f862fa2 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -8,7 +8,6 @@ #include "base/flat_map.h" #include "ui/abstract_button.h" -#include "ui/effects/radial_animation.h" #include "ui/paint/blobs.h" #include "ui/painter.h" #include "ui/widgets/call_button.h" @@ -45,8 +44,26 @@ constexpr auto kGlowAlpha = 150; constexpr auto kOverrideColorBgAlpha = 76; constexpr auto kOverrideColorRippleAlpha = 50; -constexpr auto kSwitchStateDuration = 120; constexpr auto kShiftDuration = crl::time(300); +constexpr auto kSwitchStateDuration = crl::time(120); + +// Switch state from Connecting animation. +constexpr auto kSwitchRadialDuration = crl::time(225); +constexpr auto kSwitchCirclelDuration = crl::time(275); +constexpr auto kBlobsScaleEnterDuration = crl::time(400); +constexpr auto kSwitchStateFromConnectingDuration = kSwitchRadialDuration + + kSwitchCirclelDuration + + kBlobsScaleEnterDuration; + +constexpr auto kRadialEndPartAnimation = float(kSwitchRadialDuration) + / kSwitchStateFromConnectingDuration; +constexpr auto kBlobsWidgetPartAnimation = 1. - kRadialEndPartAnimation; +constexpr auto kFillCirclePartAnimation = float(kSwitchCirclelDuration) + / (kSwitchCirclelDuration + kBlobsScaleEnterDuration); +constexpr auto kBlobPartAnimation = float(kBlobsScaleEnterDuration) + / (kSwitchCirclelDuration + kBlobsScaleEnterDuration); + +constexpr auto kOverlapProgressRadialHide = 1.2; auto MuteBlobs() { return std::vector{ @@ -129,6 +146,9 @@ public: [[nodiscard]] QRectF innerRect() const; + [[nodiscard]] float64 switchConnectingProgress() const; + void setSwitchConnectingProgress(float64 progress); + private: void init(); @@ -140,9 +160,16 @@ private: int _center = 0; QRectF _circleRect; + float64 _switchConnectingProgress = 0.; + crl::time _blobsLastTime = 0; crl::time _blobsHideLastTime = 0; + float64 _blobsScaleEnter = 0.; + crl::time _blobsScaleLastTime = 0; + + bool _hideBlobs = true; + Animations::Basic _animation; }; @@ -155,25 +182,20 @@ BlobsWidget::BlobsWidget( , _circleRaidus(st::callMuteMainBlobMinRadius * kMainRadiusFactor) , _blobBrush(Qt::transparent) , _glowBrush(Qt::transparent) -, _blobsLastTime(crl::now()) { +, _blobsLastTime(crl::now()) +, _blobsScaleLastTime(crl::now()) { init(); - for (auto i = 0; i < _blobs.size(); i++) { - const auto radiuses = _blobs.radiusesAt(i); - auto radiusesChange = rpl::duplicate( - hideBlobs - ) | rpl::distinct_until_changed( - ) | rpl::map([=](bool hide) -> Radiuses { - return hide - ? Radiuses{ radiuses.min, radiuses.min } - : radiuses; - }); - _blobs.setRadiusesAt(std::move(radiusesChange), i); - } - std::move( hideBlobs ) | rpl::start_with_next([=](bool hide) { + if (_hideBlobs != hide) { + const auto now = crl::now(); + if ((now - _blobsScaleLastTime) >= kBlobsScaleEnterDuration) { + _blobsScaleLastTime = now; + } + _hideBlobs = hide; + } if (hide) { setLevel(0.); } @@ -199,7 +221,9 @@ void BlobsWidget::init() { { const auto &r = _circleRaidus; const auto left = (size.width() - r * 2.) / 2.; - _circleRect = QRectF(left, left, r * 2, r * 2); + const auto add = st::callConnectingRadial.thickness / 2; + _circleRect = QRectF(left, left, r * 2, r * 2).marginsAdded( + style::margins(add, add, add, add)); } }, lifetime()); @@ -219,23 +243,69 @@ void BlobsWidget::init() { // Blobs. p.translate(_center, _center); - _blobs.paint(p, _blobBrush); + const auto scale = (_switchConnectingProgress > 0.) + ? anim::easeOutBack( + 1., + _blobsScaleEnter * (1. - std::clamp( + (_switchConnectingProgress / kBlobPartAnimation), + 0., + 1.))) + : _blobsScaleEnter; + _blobs.paint(p, _blobBrush, scale); // Main circle. + p.translate(-_center, -_center); p.setPen(Qt::NoPen); p.setBrush(_blobBrush); - p.drawEllipse(QPointF(), _circleRaidus, _circleRaidus); + p.drawEllipse(_circleRect); + + if (_switchConnectingProgress > 0.) { + p.resetTransform(); + + const auto circleProgress = std::clamp( + (_switchConnectingProgress - kBlobPartAnimation), + 0., + 1.) + / 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); + + p.setOpacity(1.); + p.setBrush(st::callIconBg); + + p.save(); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.drawEllipse(cutOutRect); + p.restore(); + + p.drawEllipse(cutOutRect); + } }, lifetime()); _animation.init([=](crl::time now) { if (const auto &last = _blobsHideLastTime; (last > 0) - && (now - last >= Paint::Blobs::kHideBlobsDuration)) { + && (now - last >= kBlobsScaleEnterDuration)) { _animation.stop(); return false; } _blobs.updateLevel(now - _blobsLastTime); _blobsLastTime = now; + const auto dt = std::clamp( + (now - _blobsScaleLastTime) / float64(kBlobsScaleEnterDuration), + 0., + 1.); + _blobsScaleEnter = _hideBlobs + ? (1. - anim::linear(1., dt)) + : anim::easeOutBack(1., dt); + update(); return true; }); @@ -274,6 +344,14 @@ void BlobsWidget::setLevel(float level) { _blobs.setLevel(level); } +float64 BlobsWidget::switchConnectingProgress() const { + return _switchConnectingProgress; +} + +void BlobsWidget::setSwitchConnectingProgress(float64 progress) { + _switchConnectingProgress = progress; +} + CallMuteButton::CallMuteButton( not_null parent, rpl::producer &&hideBlobs, @@ -337,6 +415,14 @@ void CallMuteButton::init() { st::callConnectingRadial); _radial->start(); } + + if (value == 1.) { + _lastRadialState = std::nullopt; + } else { + if (_radial && !_lastRadialState.has_value()) { + _lastRadialState = _radial->computeState(); + } + } }, lifetime()); // State type. @@ -388,20 +474,24 @@ void CallMuteButton::init() { setHandleMouseState(mouseState); } + const auto fromConnecting = IsConnecting(previous); + const auto toConnecting = IsConnecting(type); + const auto crossFrom = IsMuted(previous) ? 0. : 1.; const auto crossTo = IsMuted(type) ? 0. : 1.; - const auto radialShowFrom = IsConnecting(previous) ? 1. : 0.; - const auto radialShowTo = IsConnecting(type) ? 1. : 0.; + const auto radialShowFrom = fromConnecting ? 1. : 0.; + const auto radialShowTo = toConnecting ? 1. : 0.; - const auto from = _switchAnimation.animating() + const auto from = (_switchAnimation.animating() && !fromConnecting) ? (1. - _switchAnimation.value(0.)) : 0.; const auto to = 1.; auto callback = [=](float64 value) { + const auto brushProgress = fromConnecting ? 1. : value; _blobs->setBlobBrush(QBrush( - linearGradients.gradient(previous, type, value))); + linearGradients.gradient(previous, type, brushProgress))); _blobs->setGlowBrush(QBrush( glows.gradient(previous, type, value))); _blobs->update(); @@ -419,6 +509,10 @@ void CallMuteButton::init() { : anim::interpolateF(radialShowFrom, radialShowTo, value); if (radialShowProgress != _radialShowProgress.current()) { _radialShowProgress = radialShowProgress; + _blobs->setSwitchConnectingProgress(std::clamp( + radialShowProgress / kBlobsWidgetPartAnimation, + 0., + 1.)); } overridesColors(previous, type, value); @@ -429,7 +523,9 @@ void CallMuteButton::init() { }; _switchAnimation.stop(); - const auto duration = (1. - from) * kSwitchStateDuration; + const auto duration = (1. - from) * ((fromConnecting || toConnecting) + ? kSwitchStateFromConnectingDuration + : kSwitchStateDuration); _switchAnimation.start(std::move(callback), from, to, duration); }, lifetime()); @@ -456,8 +552,29 @@ void CallMuteButton::init() { _muteIconRect.topLeft(), 1. - _crossLineProgress); - if (_radial) { - p.setOpacity(_radialShowProgress.current()); + if (_lastRadialState.has_value() && _switchAnimation.animating()) { + const auto radialProgress = (1. - _radialShowProgress.current()) + / kRadialEndPartAnimation; + + auto r = *_lastRadialState; + r.arcLength = anim::interpolate( + r.arcLength, + -RadialState::kFull, + std::clamp(radialProgress, 0., 1.)); + + const auto opacity = (radialProgress > kOverlapProgressRadialHide) + ? 0. + : _blobs->switchConnectingProgress(); + p.setOpacity(opacity); + InfiniteRadialAnimation::Draw( + p, + r, + _st.bgPosition, + st::callConnectingRadial.size, + _content->width(), + QPen(st::callConnectingRadial.color), + st::callConnectingRadial.thickness); + } else if (_radial) { _radial->draw( p, _st.bgPosition, diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index cc55df4..17f9f38 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -10,6 +10,7 @@ #include "ui/effects/animations.h" #include "ui/effects/cross_line.h" #include "ui/effects/gradient.h" +#include "ui/effects/radial_animation.h" namespace Ui { @@ -17,7 +18,6 @@ class BlobsWidget; class AbstractButton; class FlatLabel; -class InfiniteRadialAnimation; class RpWidget; struct CallButtonColors; @@ -92,6 +92,8 @@ private: QRect _muteIconRect; HandleMouseState _handleMouseState = HandleMouseState::Enabled; + std::optional _lastRadialState = std::nullopt; + const style::CallButton &_st; const base::unique_qptr _blobs; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 8ef228e..1735b34 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1409,10 +1409,10 @@ defaultToast: Toast { callMuteMainBlobMinRadius: 57px; callMuteMainBlobMaxRadius: 63px; -callMuteMinorBlobMinRadius: 62px; -callMuteMinorBlobMaxRadius: 72px; -callMuteMajorBlobMinRadius: 65px; -callMuteMajorBlobMaxRadius: 75px; +callMuteMinorBlobMinRadius: 64px; +callMuteMinorBlobMaxRadius: 74px; +callMuteMajorBlobMinRadius: 67px; +callMuteMajorBlobMaxRadius: 77px; callMuteButtonActiveIcon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }}; callMuteButtonActiveInner: IconButton { From 407079e5b1cffdad2a08b0e00eafb78a175680c0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 17 Dec 2020 17:24:43 +0400 Subject: [PATCH 72/79] Add a sub-label to CallMuteButton. --- ui/widgets/call_mute_button.cpp | 33 +++++++++++++++++++++++++++++---- ui/widgets/call_mute_button.h | 5 ++++- ui/widgets/widgets.style | 14 +++++++++----- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index f862fa2..fa8223e 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -16,8 +16,9 @@ #include "styles/palette.h" #include "styles/style_widgets.h" -namespace Ui { +#include +namespace Ui { namespace { using Radiuses = Paint::Blob::Radiuses; @@ -35,7 +36,7 @@ constexpr auto kScaleSmallMin = 0.926; constexpr auto kScaleBigMax = (float)(kScaleBigMin + kScaleBig); constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall); -constexpr auto kMainRadiusFactor = (float)(50. / 57.); +constexpr auto kMainRadiusFactor = (float)(48. / 57.); constexpr auto kGlowPaddingFactor = 1.2; constexpr auto kGlowMinScale = 0.6; @@ -378,6 +379,13 @@ CallMuteButton::CallMuteButton( return state.text; }), _st.label)) +, _sublabel(base::make_unique_q( + parent, + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.subtext; + }), + st::callMuteButtonSublabel)) , _radial(nullptr) , _colors(Colors()) , _crossLineMuteAnimation(st::callMuteCrossLine) { @@ -402,6 +410,15 @@ void CallMuteButton::init() { }, _label->lifetime()); _label->setAttribute(Qt::WA_TransparentForMouseEvents); + _sublabel->show(); + rpl::combine( + _content->geometryValue(), + _sublabel->sizeValue() + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateSublabelGeometry(my, size); + }, _sublabel->lifetime()); + _sublabel->setAttribute(Qt::WA_TransparentForMouseEvents); + _radialShowProgress.value( ) | rpl::start_with_next([=](float64 value) { if (((value == 0.) || anim::Disabled()) && _radial) { @@ -583,12 +600,20 @@ void CallMuteButton::init() { }, _content->lifetime()); } -void CallMuteButton::updateLabelGeometry() { +void CallMuteButton::updateLabelsGeometry() { updateLabelGeometry(_content->geometry(), _label->size()); + updateSublabelGeometry(_content->geometry(), _sublabel->size()); } void CallMuteButton::updateLabelGeometry(QRect my, QSize size) { _label->moveToLeft( + my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, + my.y() + my.height() - size.height() - st::callMuteButtonSublabelSkip, + my.width()); +} + +void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) { + _sublabel->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, my.y() + my.height() - size.height(), my.width()); @@ -614,7 +639,7 @@ void CallMuteButton::shake() { : 0.; const auto shift = from * (1. - part) + to * part; _labelShakeShift = int(std::round(shift * st::shakeShift)); - updateLabelGeometry(); + updateLabelsGeometry(); }; _shakeAnimation.start( update, diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 17f9f38..8535684 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -31,6 +31,7 @@ enum class CallMuteButtonType { struct CallMuteButtonState { QString text; + QString subtext; CallMuteButtonType type = CallMuteButtonType::Connecting; }; @@ -80,7 +81,8 @@ private: void setHandleMouseState(HandleMouseState state); void updateLabelGeometry(QRect my, QSize size); - void updateLabelGeometry(); + void updateSublabelGeometry(QRect my, QSize size); + void updateLabelsGeometry(); [[nodiscard]] static HandleMouseState HandleMouseStateFromType( CallMuteButtonType type); @@ -99,6 +101,7 @@ private: const base::unique_qptr _blobs; const base::unique_qptr _content; const base::unique_qptr _label; + const base::unique_qptr _sublabel; int _labelShakeShift = 0; std::unique_ptr _radial; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 1735b34..289f290 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1417,19 +1417,23 @@ callMuteMajorBlobMaxRadius: 77px; callMuteButtonActiveIcon: icon {{ "calls/voice_unmuted_large", groupCallIconFg }}; callMuteButtonActiveInner: IconButton { width: 136px; - height: 151px; + height: 165px; iconPosition: point(-1px, 50px); icon: callMuteButtonActiveIcon; } callMuteButtonLabel: FlatLabel(defaultFlatLabel) { - textFg: callNameFg; + textFg: groupCallMembersFg; style: TextStyle(defaultTextStyle) { - font: font(13px); - linkFont: font(13px); - linkFontOver: font(13px underline); + font: font(14px); + linkFont: font(14px); + linkFontOver: font(14px underline); } } +callMuteButtonSublabel: FlatLabel(defaultFlatLabel) { + textFg: groupCallMemberNotJoinedStatus; +} +callMuteButtonSublabelSkip: 19px; callMuteButtonActive: CallButton { button: callMuteButtonActiveInner; bg: groupCallLive1; From a8edd6567ea76fce66dcf13ac27dc04a41e1ed08 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 18 Dec 2020 06:51:10 +0300 Subject: [PATCH 73/79] Slightly optimized blobs paint. --- ui/paint/blob.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/paint/blob.cpp b/ui/paint/blob.cpp index d962c58..b7f783e 100644 --- a/ui/paint/blob.cpp +++ b/ui/paint/blob.cpp @@ -87,7 +87,10 @@ void RadialBlob::paint(Painter &p, const QBrush &brush, float outerScale) { p.save(); const auto scale = (_minScale + (1. - _minScale) * _scale) * outerScale; - if (scale != 1.) { + if (scale == 0.) { + p.restore(); + return; + } else if (scale != 1.) { p.scale(scale, scale); } @@ -169,6 +172,10 @@ LinearBlob::LinearBlob( } void LinearBlob::paint(Painter &p, const QBrush &brush, int width) { + if (!width) { + return; + } + auto path = QPainterPath(); const auto left = 0; From 1d2357eed97addff55ed2fc104053c87c529919e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 18 Dec 2020 06:55:29 +0300 Subject: [PATCH 74/79] Moved progress clamping in mute button to single place. --- ui/widgets/call_mute_button.cpp | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index fa8223e..2926c7c 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -133,6 +133,10 @@ bool IsInactive(CallMuteButtonType type) { return IsConnecting(type) || (type == CallMuteButtonType::ForceMuted); } +auto Clamp(float64 value) { + return std::clamp(value, 0., 1.); +} + } // namespace class BlobsWidget final : public RpWidget { @@ -247,10 +251,8 @@ void BlobsWidget::init() { const auto scale = (_switchConnectingProgress > 0.) ? anim::easeOutBack( 1., - _blobsScaleEnter * (1. - std::clamp( - (_switchConnectingProgress / kBlobPartAnimation), - 0., - 1.))) + _blobsScaleEnter * (1. - Clamp( + _switchConnectingProgress / kBlobPartAnimation))) : _blobsScaleEnter; _blobs.paint(p, _blobBrush, scale); @@ -263,11 +265,9 @@ void BlobsWidget::init() { if (_switchConnectingProgress > 0.) { p.resetTransform(); - const auto circleProgress = std::clamp( - (_switchConnectingProgress - kBlobPartAnimation), - 0., - 1.) - / kFillCirclePartAnimation; + const auto circleProgress = + Clamp(_switchConnectingProgress - kBlobPartAnimation) + / kFillCirclePartAnimation; const auto mF = (_circleRect.width() / 2) * (1. - circleProgress); const auto cutOutRect = _circleRect.marginsRemoved( @@ -299,10 +299,8 @@ void BlobsWidget::init() { _blobs.updateLevel(now - _blobsLastTime); _blobsLastTime = now; - const auto dt = std::clamp( - (now - _blobsScaleLastTime) / float64(kBlobsScaleEnterDuration), - 0., - 1.); + const auto dt = Clamp( + (now - _blobsScaleLastTime) / float64(kBlobsScaleEnterDuration)); _blobsScaleEnter = _hideBlobs ? (1. - anim::linear(1., dt)) : anim::easeOutBack(1., dt); @@ -526,10 +524,8 @@ void CallMuteButton::init() { : anim::interpolateF(radialShowFrom, radialShowTo, value); if (radialShowProgress != _radialShowProgress.current()) { _radialShowProgress = radialShowProgress; - _blobs->setSwitchConnectingProgress(std::clamp( - radialShowProgress / kBlobsWidgetPartAnimation, - 0., - 1.)); + _blobs->setSwitchConnectingProgress(Clamp( + radialShowProgress / kBlobsWidgetPartAnimation)); } overridesColors(previous, type, value); @@ -577,7 +573,7 @@ void CallMuteButton::init() { r.arcLength = anim::interpolate( r.arcLength, -RadialState::kFull, - std::clamp(radialProgress, 0., 1.)); + Clamp(radialProgress)); const auto opacity = (radialProgress > kOverlapProgressRadialHide) ? 0. From d7ee715b63bd5613727b84d1c733496dd67d1c57 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 18 Dec 2020 09:47:27 +0300 Subject: [PATCH 75/79] Improved animation from any state to Connecting state in mute button. --- ui/widgets/call_mute_button.cpp | 80 ++++++++++++++++++++++----------- ui/widgets/call_mute_button.h | 11 +++-- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 2926c7c..6376bbf 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -36,7 +36,7 @@ constexpr auto kScaleSmallMin = 0.926; constexpr auto kScaleBigMax = (float)(kScaleBigMin + kScaleBig); constexpr auto kScaleSmallMax = (float)(kScaleSmallMin + kScaleSmall); -constexpr auto kMainRadiusFactor = (float)(48. / 57.); +constexpr auto kMainRadiusFactor = (float)(50. / 57.); constexpr auto kGlowPaddingFactor = 1.2; constexpr auto kGlowMinScale = 0.6; @@ -49,7 +49,7 @@ constexpr auto kShiftDuration = crl::time(300); constexpr auto kSwitchStateDuration = crl::time(120); // Switch state from Connecting animation. -constexpr auto kSwitchRadialDuration = crl::time(225); +constexpr auto kSwitchRadialDuration = crl::time(350); constexpr auto kSwitchCirclelDuration = crl::time(275); constexpr auto kBlobsScaleEnterDuration = crl::time(400); constexpr auto kSwitchStateFromConnectingDuration = kSwitchRadialDuration @@ -66,6 +66,8 @@ constexpr auto kBlobPartAnimation = float(kBlobsScaleEnterDuration) constexpr auto kOverlapProgressRadialHide = 1.2; +constexpr auto kRadialFinishArcShift = 1200; + auto MuteBlobs() { return std::vector{ { @@ -137,6 +139,13 @@ auto Clamp(float64 value) { return std::clamp(value, 0., 1.); } +void ComputeRadialFinish( + int &value, + float64 progress, + int to = -RadialState::kFull) { + value = anim::interpolate(value, to, Clamp(progress)); +} + } // namespace class BlobsWidget final : public RpWidget { @@ -159,7 +168,7 @@ private: Paint::Blobs _blobs; - const float _circleRaidus; + const float _circleRadius; QBrush _blobBrush; QBrush _glowBrush; int _center = 0; @@ -184,7 +193,7 @@ BlobsWidget::BlobsWidget( rpl::producer &&hideBlobs) : RpWidget(parent) , _blobs(MuteBlobs(), kLevelDuration, kMaxLevel) -, _circleRaidus(st::callMuteMainBlobMinRadius * kMainRadiusFactor) +, _circleRadius(st::callMuteButtonActive.bgSize / 2.) , _blobBrush(Qt::transparent) , _glowBrush(Qt::transparent) , _blobsLastTime(crl::now()) @@ -224,7 +233,7 @@ void BlobsWidget::init() { _center = size.width() / 2; { - const auto &r = _circleRaidus; + const auto &r = _circleRadius; const auto left = (size.width() - r * 2.) / 2.; const auto add = st::callConnectingRadial.thickness / 2; _circleRect = QRectF(left, left, r * 2, r * 2).marginsAdded( @@ -302,7 +311,7 @@ void BlobsWidget::init() { const auto dt = Clamp( (now - _blobsScaleLastTime) / float64(kBlobsScaleEnterDuration)); _blobsScaleEnter = _hideBlobs - ? (1. - anim::linear(1., dt)) + ? (1. - anim::easeInCirc(1., dt)) : anim::easeOutBack(1., dt); update(); @@ -417,8 +426,11 @@ void CallMuteButton::init() { }, _sublabel->lifetime()); _sublabel->setAttribute(Qt::WA_TransparentForMouseEvents); - _radialShowProgress.value( + _radialInfo.rawShowProgress.value( ) | rpl::start_with_next([=](float64 value) { + auto &info = _radialInfo; + info.realShowProgress = (1. - value) / kRadialEndPartAnimation; + if (((value == 0.) || anim::Disabled()) && _radial) { _radial->stop(); _radial = nullptr; @@ -427,15 +439,21 @@ void CallMuteButton::init() { if ((value > 0.) && !anim::Disabled() && !_radial) { _radial = std::make_unique( [=] { _content->update(); }, - st::callConnectingRadial); + _radialInfo.st); _radial->start(); } + if ((info.realShowProgress < 1.) && !info.isDirectionToShow) { + _radial->stop(anim::type::instant); + _radial->start(); + info.state = std::nullopt; + return; + } if (value == 1.) { - _lastRadialState = std::nullopt; + info.state = std::nullopt; } else { - if (_radial && !_lastRadialState.has_value()) { - _lastRadialState = _radial->computeState(); + if (_radial && !info.state.has_value()) { + info.state = _radial->computeState(); } } }, lifetime()); @@ -503,6 +521,8 @@ void CallMuteButton::init() { : 0.; const auto to = 1.; + _radialInfo.isDirectionToShow = fromConnecting; + auto callback = [=](float64 value) { const auto brushProgress = fromConnecting ? 1. : value; _blobs->setBlobBrush(QBrush( @@ -522,8 +542,8 @@ void CallMuteButton::init() { const auto radialShowProgress = (radialShowFrom == radialShowTo) ? radialShowTo : anim::interpolateF(radialShowFrom, radialShowTo, value); - if (radialShowProgress != _radialShowProgress.current()) { - _radialShowProgress = radialShowProgress; + if (radialShowProgress != _radialInfo.rawShowProgress.current()) { + _radialInfo.rawShowProgress = radialShowProgress; _blobs->setSwitchConnectingProgress(Clamp( radialShowProgress / kBlobsWidgetPartAnimation)); } @@ -565,15 +585,16 @@ void CallMuteButton::init() { _muteIconRect.topLeft(), 1. - _crossLineProgress); - if (_lastRadialState.has_value() && _switchAnimation.animating()) { - const auto radialProgress = (1. - _radialShowProgress.current()) - / kRadialEndPartAnimation; + if (_radialInfo.state.has_value() && _switchAnimation.animating()) { + const auto radialProgress = _radialInfo.realShowProgress; - auto r = *_lastRadialState; - r.arcLength = anim::interpolate( - r.arcLength, - -RadialState::kFull, - Clamp(radialProgress)); + auto r = *_radialInfo.state; + r.shown = 1.; + if (_radialInfo.isDirectionToShow) { + const auto to = r.arcFrom - kRadialFinishArcShift; + ComputeRadialFinish(r.arcFrom, radialProgress, to); + ComputeRadialFinish(r.arcLength, radialProgress); + } const auto opacity = (radialProgress > kOverlapProgressRadialHide) ? 0. @@ -583,15 +604,22 @@ void CallMuteButton::init() { p, r, _st.bgPosition, - st::callConnectingRadial.size, + _radialInfo.st.size, _content->width(), - QPen(st::callConnectingRadial.color), - st::callConnectingRadial.thickness); + QPen(_radialInfo.st.color), + _radialInfo.st.thickness); } else if (_radial) { - _radial->draw( + auto state = _radial->computeState(); + state.shown = 1.; + + InfiniteRadialAnimation::Draw( p, + std::move(state), _st.bgPosition, - _content->width()); + _radialInfo.st.size, + _content->width(), + QPen(_radialInfo.st.color), + _radialInfo.st.thickness); } }, _content->lifetime()); } diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 8535684..f6b0b4f 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -73,6 +73,13 @@ private: Blocked, Disabled, }; + struct RadialInfo { + std::optional state = std::nullopt; + bool isDirectionToShow = false; + rpl::variable rawShowProgress = 0.; + float64 realShowProgress = 0.; + const style::InfiniteRadialAnimation &st = st::callConnectingRadial; + }; void init(); void overridesColors( CallMuteButtonType fromType, @@ -90,12 +97,9 @@ private: rpl::variable _state; float _level = 0.; float64 _crossLineProgress = 0.; - rpl::variable _radialShowProgress = 0.; QRect _muteIconRect; HandleMouseState _handleMouseState = HandleMouseState::Enabled; - std::optional _lastRadialState = std::nullopt; - const style::CallButton &_st; const base::unique_qptr _blobs; @@ -104,6 +108,7 @@ private: const base::unique_qptr _sublabel; int _labelShakeShift = 0; + RadialInfo _radialInfo; std::unique_ptr _radial; const base::flat_map _colors; From abb615a7c9cb3a3992f67637592b481ccc50f17a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 18 Dec 2020 12:42:00 +0400 Subject: [PATCH 76/79] Improve label positions in mute button. --- ui/widgets/call_mute_button.cpp | 17 ++++++++++++----- ui/widgets/call_mute_button.h | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 6376bbf..9589ec8 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -411,9 +411,10 @@ void CallMuteButton::init() { _label->show(); rpl::combine( _content->geometryValue(), + _sublabel->widthValue(), _label->sizeValue() - ) | rpl::start_with_next([=](QRect my, QSize size) { - updateLabelGeometry(my, size); + ) | rpl::start_with_next([=](QRect my, int subWidth, QSize size) { + updateLabelGeometry(my, subWidth, size); }, _label->lifetime()); _label->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -625,14 +626,20 @@ void CallMuteButton::init() { } void CallMuteButton::updateLabelsGeometry() { - updateLabelGeometry(_content->geometry(), _label->size()); + updateLabelGeometry( + _content->geometry(), + _sublabel->width(), + _label->size()); updateSublabelGeometry(_content->geometry(), _sublabel->size()); } -void CallMuteButton::updateLabelGeometry(QRect my, QSize size) { +void CallMuteButton::updateLabelGeometry(QRect my, int subWidth, QSize size) { + const auto skip = subWidth + ? st::callMuteButtonSublabelSkip + : (st::callMuteButtonSublabelSkip / 2); _label->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height() - st::callMuteButtonSublabelSkip, + my.y() + my.height() - size.height() - skip, my.width()); } diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index f6b0b4f..0a799cf 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -87,7 +87,7 @@ private: float64 progress); void setHandleMouseState(HandleMouseState state); - void updateLabelGeometry(QRect my, QSize size); + void updateLabelGeometry(QRect my, int subWidth, QSize size); void updateSublabelGeometry(QRect my, QSize size); void updateLabelsGeometry(); From a29e93ca55d40c3b50d654fd2a9a153ab0227b6c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 18 Dec 2020 12:35:53 +0300 Subject: [PATCH 77/79] Added initial implementation of label animation in mute button. --- ui/widgets/call_mute_button.cpp | 165 ++++++++++++++++++++++++++++---- ui/widgets/call_mute_button.h | 9 +- ui/widgets/widgets.style | 2 + 3 files changed, 157 insertions(+), 19 deletions(-) diff --git a/ui/widgets/call_mute_button.cpp b/ui/widgets/call_mute_button.cpp index 9589ec8..15e3820 100644 --- a/ui/widgets/call_mute_button.cpp +++ b/ui/widgets/call_mute_button.cpp @@ -47,6 +47,7 @@ constexpr auto kOverrideColorRippleAlpha = 50; constexpr auto kShiftDuration = crl::time(300); constexpr auto kSwitchStateDuration = crl::time(120); +constexpr auto kSwitchLabelDuration = crl::time(180); // Switch state from Connecting animation. constexpr auto kSwitchRadialDuration = crl::time(350); @@ -148,6 +149,110 @@ void ComputeRadialFinish( } // namespace +class AnimatedLabel final : public RpWidget { +public: + AnimatedLabel( + QWidget *parent, + rpl::producer &&text, + crl::time duration, + int additionalHeight, + const style::FlatLabel &st = st::defaultFlatLabel); + + int height() const; + +private: + int realHeight() const; + + void setText(const QString &text); + + const style::FlatLabel &_st; + const crl::time _duration; + const int _additionalHeight; + const TextParseOptions _options; + + Text::String _text; + Text::String _previousText; + + Animations::Simple _animation; + +}; + +AnimatedLabel::AnimatedLabel( + QWidget *parent, + rpl::producer &&text, + crl::time duration, + int additionalHeight, + const style::FlatLabel &st) +: RpWidget(parent) +, _st(st) +, _duration(duration) +, _additionalHeight(additionalHeight) +, _options({ 0, 0, 0, Qt::LayoutDirectionAuto }) { + std::move( + text + ) | rpl::start_with_next([=](const QString &value) { + setText(value); + }, lifetime()); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + const auto progress = _animation.value(1.); + + p.setFont(_st.style.font); + p.setPen(_st.textFg); + p.setTextPalette(_st.palette); + + const auto textHeight = height(); + const auto diffHeight = realHeight() - textHeight; + const auto center = (diffHeight) / 2; + + p.setOpacity(1. - progress); + if (p.opacity()) { + _previousText.draw( + p, + 0, + anim::interpolate(center, diffHeight, progress), + width(), + style::al_center); + } + + p.setOpacity(progress); + if (p.opacity()) { + _text.draw( + p, + 0, + anim::interpolate(0, center, progress), + width(), + style::al_center); + } + }, lifetime()); +} + +int AnimatedLabel::height() const { + return _st.style.font->height; +} + +int AnimatedLabel::realHeight() const { + return RpWidget::height(); +} + +void AnimatedLabel::setText(const QString &text) { + if (_text.toString() == text) { + return; + } + _previousText = _text; + _text.setText(_st.style, text, _options); + + const auto width = std::max( + _st.style.font->width(_text.toString()), + _st.style.font->width(_previousText.toString())); + resize(width + _additionalHeight, height() + _additionalHeight * 2); + + _animation.stop(); + _animation.start([=] { update(); }, 0., 1., _duration); +} + class BlobsWidget final : public RpWidget { public: BlobsWidget( @@ -379,19 +484,32 @@ CallMuteButton::CallMuteButton( return isBadState || !(!animDisabled && !hide); }))) , _content(base::make_unique_q(parent)) -, _label(base::make_unique_q( +, _centerLabel(base::make_unique_q( parent, _state.value( ) | rpl::map([](const CallMuteButtonState &state) { - return state.text; + return state.subtext.isEmpty() ? state.text : QString(); }), + kSwitchLabelDuration, + st::callMuteButtonLabelAdditional, _st.label)) -, _sublabel(base::make_unique_q( +, _label(base::make_unique_q( + parent, + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.subtext.isEmpty() ? QString() : state.text; + }), + kSwitchLabelDuration, + st::callMuteButtonLabelAdditional, + _st.label)) +, _sublabel(base::make_unique_q( parent, _state.value( ) | rpl::map([](const CallMuteButtonState &state) { return state.subtext; }), + kSwitchLabelDuration, + st::callMuteButtonLabelAdditional, st::callMuteButtonSublabel)) , _radial(nullptr) , _colors(Colors()) @@ -411,10 +529,9 @@ void CallMuteButton::init() { _label->show(); rpl::combine( _content->geometryValue(), - _sublabel->widthValue(), _label->sizeValue() - ) | rpl::start_with_next([=](QRect my, int subWidth, QSize size) { - updateLabelGeometry(my, subWidth, size); + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateLabelGeometry(my, size); }, _label->lifetime()); _label->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -427,6 +544,15 @@ void CallMuteButton::init() { }, _sublabel->lifetime()); _sublabel->setAttribute(Qt::WA_TransparentForMouseEvents); + _centerLabel->show(); + rpl::combine( + _content->geometryValue(), + _centerLabel->sizeValue() + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateCenterLabelGeometry(my, size); + }, _centerLabel->lifetime()); + _centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + _radialInfo.rawShowProgress.value( ) | rpl::start_with_next([=](float64 value) { auto &info = _radialInfo; @@ -626,27 +752,34 @@ void CallMuteButton::init() { } void CallMuteButton::updateLabelsGeometry() { - updateLabelGeometry( - _content->geometry(), - _sublabel->width(), - _label->size()); + updateLabelGeometry(_content->geometry(), _label->size()); + updateCenterLabelGeometry(_content->geometry(), _centerLabel->size()); updateSublabelGeometry(_content->geometry(), _sublabel->size()); } -void CallMuteButton::updateLabelGeometry(QRect my, int subWidth, QSize size) { - const auto skip = subWidth - ? st::callMuteButtonSublabelSkip - : (st::callMuteButtonSublabelSkip / 2); +void CallMuteButton::updateLabelGeometry(QRect my, QSize size) { + const auto skip = st::callMuteButtonSublabelSkip + + st::callMuteButtonLabelsSkip; _label->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height() - skip, + my.y() + my.height() - _label->height() - skip, + my.width()); +} + +void CallMuteButton::updateCenterLabelGeometry(QRect my, QSize size) { + const auto skip = (st::callMuteButtonSublabelSkip / 2) + + st::callMuteButtonLabelsSkip; + _centerLabel->moveToLeft( + my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, + my.y() + my.height() - _centerLabel->height() - skip, my.width()); } void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) { + const auto skip = st::callMuteButtonLabelsSkip; _sublabel->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height(), + my.y() + my.height() - _sublabel->height() - skip, my.width()); } diff --git a/ui/widgets/call_mute_button.h b/ui/widgets/call_mute_button.h index 0a799cf..154bfb3 100644 --- a/ui/widgets/call_mute_button.h +++ b/ui/widgets/call_mute_button.h @@ -19,6 +19,7 @@ class BlobsWidget; class AbstractButton; class FlatLabel; class RpWidget; +class AnimatedLabel; struct CallButtonColors; @@ -87,7 +88,8 @@ private: float64 progress); void setHandleMouseState(HandleMouseState state); - void updateLabelGeometry(QRect my, int subWidth, QSize size); + void updateCenterLabelGeometry(QRect my, QSize size); + void updateLabelGeometry(QRect my, QSize size); void updateSublabelGeometry(QRect my, QSize size); void updateLabelsGeometry(); @@ -104,8 +106,9 @@ private: const base::unique_qptr _blobs; const base::unique_qptr _content; - const base::unique_qptr _label; - const base::unique_qptr _sublabel; + const base::unique_qptr _centerLabel; + const base::unique_qptr _label; + const base::unique_qptr _sublabel; int _labelShakeShift = 0; RadialInfo _radialInfo; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 289f290..ced097c 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1433,6 +1433,7 @@ callMuteButtonLabel: FlatLabel(defaultFlatLabel) { callMuteButtonSublabel: FlatLabel(defaultFlatLabel) { textFg: groupCallMemberNotJoinedStatus; } +callMuteButtonLabelsSkip: 5px; callMuteButtonSublabelSkip: 19px; callMuteButtonActive: CallButton { button: callMuteButtonActiveInner; @@ -1457,6 +1458,7 @@ callMuteButtonConnecting: CallButton(callMuteButtonMuted) { bg: callIconBg; label: callMuteButtonLabel; } +callMuteButtonLabelAdditional: 5px; callMuteCrossLine: CrossLineAnimation { fg: groupCallIconFg; From d340430e8588a503759da96f7610440e28076c9c Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 21 Dec 2020 09:43:08 +0300 Subject: [PATCH 78/79] Added ability to pass min and max speeds to Blobs. --- ui/paint/blobs.cpp | 6 +++++- ui/paint/blobs.h | 2 ++ ui/paint/blobs_linear.cpp | 4 +++- ui/paint/blobs_linear.h | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index 2c84f86..ec0f576 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -22,7 +22,11 @@ Blobs::Blobs( void Blobs::init() { for (const auto &data : _blobDatas) { - auto blob = Paint::RadialBlob(data.segmentsCount, data.minScale); + auto blob = Paint::RadialBlob( + data.segmentsCount, + data.minScale, + data.minSpeed, + data.maxSpeed); blob.setRadiuses({ data.minRadius, data.maxRadius }); blob.generateBlob(); _blobs.push_back(std::move(blob)); diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index f3c5c52..f9c58a7 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -22,6 +22,8 @@ public: float maxRadius = 0; float speedScale = 0; float alpha = 0; + float minSpeed = 0; + float maxSpeed = 0; }; Blobs( diff --git a/ui/paint/blobs_linear.cpp b/ui/paint/blobs_linear.cpp index 8aa478b..37d92ec 100644 --- a/ui/paint/blobs_linear.cpp +++ b/ui/paint/blobs_linear.cpp @@ -26,7 +26,9 @@ void LinearBlobs::init() { for (const auto &data : _blobDatas) { auto blob = Paint::LinearBlob( data.segmentsCount, - _direction); + _direction, + data.minSpeed, + data.maxSpeed); blob.setRadiuses({ data.minRadius, data.idleRadius }); blob.generateBlob(); _blobs.push_back(std::move(blob)); diff --git a/ui/paint/blobs_linear.h b/ui/paint/blobs_linear.h index 5d88f8e..e175870 100644 --- a/ui/paint/blobs_linear.h +++ b/ui/paint/blobs_linear.h @@ -22,6 +22,8 @@ public: float idleRadius = 0; float speedScale = 0; float alpha = 0; + float minSpeed = 0; + float maxSpeed = 0; }; LinearBlobs( From 1e2799245cf2720a329ecb5cf5644fded669cce6 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 21 Dec 2020 14:02:37 +0300 Subject: [PATCH 79/79] Added ability to reset level value in Blobs. --- ui/paint/blobs.cpp | 4 ++++ ui/paint/blobs.h | 1 + 2 files changed, 5 insertions(+) diff --git a/ui/paint/blobs.cpp b/ui/paint/blobs.cpp index ec0f576..6bce7f6 100644 --- a/ui/paint/blobs.cpp +++ b/ui/paint/blobs.cpp @@ -69,6 +69,10 @@ void Blobs::setLevel(float value) { _levelValue.start(to); } +void Blobs::resetLevel() { + _levelValue.reset(); +} + void Blobs::paint(Painter &p, const QBrush &brush, float outerScale) { const auto opacity = p.opacity(); for (auto i = 0; i < _blobs.size(); i++) { diff --git a/ui/paint/blobs.h b/ui/paint/blobs.h index f9c58a7..c93f419 100644 --- a/ui/paint/blobs.h +++ b/ui/paint/blobs.h @@ -37,6 +37,7 @@ public: Blob::Radiuses radiusesAt(int index); void setLevel(float value); + void resetLevel(); void paint(Painter &p, const QBrush &brush, float outerScale = 1.); void updateLevel(crl::time dt);