From cdef9fa14ff5f1e2ac59b1ec83710f929b41521a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 16 Nov 2016 15:06:02 +0300 Subject: [PATCH] Active state for IconButton added, when ripple is not hidden. --- Telegram/SourceFiles/dialogswidget.cpp | 28 +++++----- Telegram/SourceFiles/dialogswidget.h | 1 + Telegram/SourceFiles/historywidget.cpp | 2 +- Telegram/SourceFiles/mainwidget.h | 10 ++++ .../ui/effects/ripple_animation.cpp | 51 ++++++++++++++++++- .../SourceFiles/ui/effects/ripple_animation.h | 5 +- Telegram/SourceFiles/ui/widgets/buttons.cpp | 48 ++++++++++++----- Telegram/SourceFiles/ui/widgets/buttons.h | 9 ++++ .../SourceFiles/ui/widgets/inner_dropdown.cpp | 13 ++++- .../SourceFiles/ui/widgets/inner_dropdown.h | 8 +++ Telegram/SourceFiles/ui/widgets/widgets.style | 8 +-- .../SourceFiles/window/top_bar_widget.cpp | 51 ++++++++++++++----- Telegram/SourceFiles/window/top_bar_widget.h | 1 + Telegram/SourceFiles/window/window.style | 2 +- 14 files changed, 192 insertions(+), 45 deletions(-) diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 6e11b7f73..12965fc44 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1154,8 +1154,8 @@ bool DialogsInner::hasFilteredResults() const { } void DialogsInner::searchInPeer(PeerData *peer) { - _searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : 0; - _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : 0; + _searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr; + _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : nullptr; if (_searchInPeer) { onHashtagFilterUpdate(QStringRef()); _cancelSearchInPeer->show(); @@ -2021,9 +2021,7 @@ void DialogsWidget::searchMessages(const QString &query, PeerData *inPeer) { if ((_filter->getLastText() != query) || (inPeer && inPeer != _searchInPeer && inPeer->migrateTo() != _searchInPeer)) { if (inPeer) { onCancelSearch(); - _searchInPeer = inPeer->migrateTo() ? inPeer->migrateTo() : inPeer; - _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : 0; - _inner->searchInPeer(_searchInPeer); + setSearchInPeer(inPeer); } _filter->setText(query); _filter->updatePlaceholder(); @@ -2309,12 +2307,20 @@ void DialogsWidget::onFilterUpdate(bool force) { void DialogsWidget::searchInPeer(PeerData *peer) { onCancelSearch(); - _searchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : 0; - _searchInMigrated = _searchInPeer ? _searchInPeer->migrateFrom() : 0; - _inner->searchInPeer(_searchInPeer); + setSearchInPeer(peer); onFilterUpdate(true); } +void DialogsWidget::setSearchInPeer(PeerData *peer) { + auto newSearchInPeer = peer ? (peer->migrateTo() ? peer->migrateTo() : peer) : nullptr; + _searchInMigrated = newSearchInPeer ? newSearchInPeer->migrateFrom() : nullptr; + if (newSearchInPeer != _searchInPeer) { + _searchInPeer = newSearchInPeer; + App::main()->searchInPeerChanged().notify(_searchInPeer, true); + } + _inner->searchInPeer(_searchInPeer); +} + void DialogsWidget::onFilterCursorMoved(int from, int to) { if (to < 0) to = _filter->cursorPosition(); QString t = _filter->getLastText(); @@ -2527,8 +2533,7 @@ bool DialogsWidget::onCancelSearch() { if (Adaptive::OneColumn()) { Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } - _searchInPeer = _searchInMigrated = 0; - _inner->searchInPeer(0); + setSearchInPeer(nullptr); clearing = true; } _inner->clearFilter(); @@ -2547,8 +2552,7 @@ void DialogsWidget::onCancelSearchInPeer() { if (Adaptive::OneColumn() && !App::main()->selectingPeer()) { Ui::showPeerHistory(_searchInPeer, ShowAtUnreadMsgId); } - _searchInPeer = _searchInMigrated = 0; - _inner->searchInPeer(0); + setSearchInPeer(nullptr); } _inner->clearFilter(); _filter->clear(); diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 917f78dfc..11155fbf1 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -308,6 +308,7 @@ private slots: #endif // TDESKTOP_DISABLE_AUTOUPDATE private: + void setSearchInPeer(PeerData *peer); void showMainMenu(); void updateLockUnlockVisibility(); void updateControlsGeometry(); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 226120e05..2ae434f17 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -4362,7 +4362,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re App::main()->dlgUpdated(wasHistory, wasMsgId); emit historyShown(_history, _showAtMsgId); - App::main()->topBar()->update(); + App::main()->historyPeerChanged().notify(_peer, true); update(); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 1c8a279a5..947f229da 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -371,6 +371,13 @@ public: bool contentOverlapped(const QRect &globalRect); + base::Observable &searchInPeerChanged() { + return _searchInPeerChanged; + } + base::Observable &historyPeerChanged() { + return _historyPeerChanged; + } + void rpcClear() override; bool isItemVisible(HistoryItem *item); @@ -579,6 +586,9 @@ private: void clearCachedBackground(); + base::Observable _searchInPeerChanged; + base::Observable _historyPeerChanged; + Animation _a_show; QPixmap _cacheUnder, _cacheOver; anim::ivalue a_coordUnder, a_coordOver; diff --git a/Telegram/SourceFiles/ui/effects/ripple_animation.cpp b/Telegram/SourceFiles/ui/effects/ripple_animation.cpp index 71823d0d9..c9c377e54 100644 --- a/Telegram/SourceFiles/ui/effects/ripple_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/ripple_animation.cpp @@ -26,10 +26,13 @@ namespace Ui { class RippleAnimation::Ripple { public: Ripple(const style::RippleAnimation &st, QPoint origin, int startRadius, const QPixmap &mask, UpdateCallback update); + Ripple(const style::RippleAnimation &st, const QPixmap &mask, UpdateCallback update); void paint(QPainter &p, const QPixmap &mask, uint64 ms); void stop(); + void unstop(); + void finish(); bool finished() const { return _hiding && !_hide.animating(); } @@ -72,6 +75,17 @@ RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, QPoint origin, _show.start(UpdateCallback(_update), 0., 1., _st.showDuration); } +RippleAnimation::Ripple::Ripple(const style::RippleAnimation &st, const QPixmap &mask, UpdateCallback update) +: _st(st) +, _update(update) +, _origin(mask.width() / (2 * cIntRetinaFactor()), mask.height() / (2 * cIntRetinaFactor())) +, _radiusFrom(mask.width() + mask.height()) +, _frame(mask.size(), QImage::Format_ARGB32_Premultiplied) { + _frame.setDevicePixelRatio(mask.devicePixelRatio()); + _radiusTo = _radiusFrom; + _hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration); +} + void RippleAnimation::Ripple::paint(QPainter &p, const QPixmap &mask, uint64 ms) { auto opacity = _hide.current(ms, _hiding ? 0. : 1.); if (opacity == 0.) { @@ -110,6 +124,23 @@ void RippleAnimation::Ripple::stop() { _hide.start(UpdateCallback(_update), 1., 0., _st.hideDuration); } +void RippleAnimation::Ripple::unstop() { + if (_hiding) { + if (_hide.animating()) { + _hide.start(UpdateCallback(_update), 0., 1., _st.hideDuration); + } + _hiding = false; + } +} + +void RippleAnimation::Ripple::finish() { + if (_update) { + _update(); + } + _show.finish(); + _hide.finish(); +} + RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, UpdateCallback callback) : _st(st) , _mask(App::pixmapFromImageInPlace(std_::move(mask))) @@ -118,15 +149,33 @@ RippleAnimation::RippleAnimation(const style::RippleAnimation &st, QImage mask, void RippleAnimation::add(QPoint origin, int startRadius) { + lastStop(); _ripples.push_back(new Ripple(_st, origin, startRadius, _mask, _update)); } -void RippleAnimation::stopLast() { +void RippleAnimation::addFading() { + lastStop(); + _ripples.push_back(new Ripple(_st, _mask, _update)); +} + +void RippleAnimation::lastStop() { if (!_ripples.isEmpty()) { _ripples.back()->stop(); } } +void RippleAnimation::lastUnstop() { + if (!_ripples.isEmpty()) { + _ripples.back()->unstop(); + } +} + +void RippleAnimation::lastFinish() { + if (!_ripples.isEmpty()) { + _ripples.back()->finish(); + } +} + void RippleAnimation::paint(QPainter &p, int x, int y, int outerWidth, uint64 ms) { if (_ripples.isEmpty()) { return; diff --git a/Telegram/SourceFiles/ui/effects/ripple_animation.h b/Telegram/SourceFiles/ui/effects/ripple_animation.h index ed531a3ff..2c95d1a3c 100644 --- a/Telegram/SourceFiles/ui/effects/ripple_animation.h +++ b/Telegram/SourceFiles/ui/effects/ripple_animation.h @@ -34,7 +34,10 @@ public: void setMask(QImage &&mask); void add(QPoint origin, int startRadius = 0); - void stopLast(); + void addFading(); + void lastStop(); + void lastUnstop(); + void lastFinish(); void paint(QPainter &p, int x, int y, int outerWidth, uint64 ms); diff --git a/Telegram/SourceFiles/ui/widgets/buttons.cpp b/Telegram/SourceFiles/ui/widgets/buttons.cpp index e382bdba5..99ba4c568 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.cpp +++ b/Telegram/SourceFiles/ui/widgets/buttons.cpp @@ -108,7 +108,7 @@ void FlatButton::handleRipples(bool wasDown, bool wasPress) { _ripple->add(clickPosition); } else if (!down && _ripple) { // Finish ripple anyway. - _ripple->stopLast(); + _ripple->lastStop(); } } @@ -311,7 +311,7 @@ void RoundButton::handleRipples(bool wasDown, bool wasPress) { _ripple->add(clickPosition); } else if (!down && _ripple) { // Finish ripple anyway. - _ripple->stopLast(); + _ripple->lastStop(); } } @@ -348,6 +348,26 @@ void IconButton::setIcon(const style::icon *icon, const style::icon *iconOver) { update(); } +void IconButton::setActiveState(bool activeState, SetStateWay way) { + if (_activeState != activeState) { + _activeState = activeState; + if (_activeState) { + ensureRipple(); + if (_ripple->empty()) { + _ripple->addFading(); + } else { + _ripple->lastUnstop(); + } + } else if (_ripple) { + _ripple->lastStop(); + } + } + if (way == SetStateWay::SkipAnimation && _ripple) { + _ripple->lastFinish(); + } + update(); +} + void IconButton::paintEvent(QPaintEvent *e) { Painter p(this); @@ -361,7 +381,7 @@ void IconButton::paintEvent(QPaintEvent *e) { } auto down = (_state & StateDown); - auto over = _a_over.current(getms(), (_state & StateOver) ? 1. : 0.); + auto overIconOpacity = (down || _activeState) ? 1. : _a_over.current(getms(), (_state & StateOver) ? 1. : 0.); auto overIcon = [this] { if (_iconOverrideOver) { return _iconOverrideOver; @@ -378,7 +398,7 @@ void IconButton::paintEvent(QPaintEvent *e) { } return &_st.icon; }; - auto icon = (over == 1. || down) ? overIcon() : justIcon(); + auto icon = (overIconOpacity == 1.) ? overIcon() : justIcon(); auto position = (_state & StateDown) ? _st.iconPositionDown : _st.iconPosition; if (position.x() < 0) { position.setX((width() - icon->width()) / 2); @@ -387,10 +407,10 @@ void IconButton::paintEvent(QPaintEvent *e) { position.setY((height() - icon->height()) / 2); } icon->paint(p, position, width()); - if (over > 0. && over < 1.) { + if (overIconOpacity > 0. && overIconOpacity < 1.) { auto iconOver = overIcon(); if (iconOver != icon) { - p.setOpacity(over); + p.setOpacity(overIconOpacity); iconOver->paint(p, position, width()); } } @@ -417,11 +437,9 @@ void IconButton::handleRipples(bool wasDown, bool wasPress) { return; } - if (down && wasPress) { + if (down && wasPress && !_activeState) { // Start a ripple only from mouse press. - if (!_ripple) { - _ripple = std_::make_unique(_st.ripple, prepareRippleMask(), [this] { update(); }); - } + ensureRipple(); auto clickPosition = mapFromGlobal(QCursor::pos()); auto rippleCenter = QRect(_st.rippleAreaPosition, QSize(_st.rippleAreaSize, _st.rippleAreaSize)).center(); auto clickRadiusSquare = style::point::dotProduct(clickPosition - rippleCenter, clickPosition - rippleCenter); @@ -430,9 +448,15 @@ void IconButton::handleRipples(bool wasDown, bool wasPress) { startRadius = sqrt(clickRadiusSquare) - (_st.rippleAreaSize / 2); } _ripple->add(clickPosition - _st.rippleAreaPosition, startRadius); - } else if (!down && _ripple) { + } else if (!down && _ripple && !_activeState) { // Finish ripple anyway. - _ripple->stopLast(); + _ripple->lastStop(); + } +} + +void IconButton::ensureRipple() { + if (!_ripple) { + _ripple = std_::make_unique(_st.ripple, prepareRippleMask(), [this] { update(); }); } } diff --git a/Telegram/SourceFiles/ui/widgets/buttons.h b/Telegram/SourceFiles/ui/widgets/buttons.h index c9f5ca38c..a6bdffd30 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.h +++ b/Telegram/SourceFiles/ui/widgets/buttons.h @@ -139,6 +139,13 @@ public: // Pass nullptr to restore the default icon. void setIcon(const style::icon *icon, const style::icon *iconOver = nullptr); + // Displays full ripple circle constantly. + enum class SetStateWay { + Default, + SkipAnimation, + }; + void setActiveState(bool activeState, SetStateWay way = SetStateWay::Default); + ~IconButton(); protected: @@ -147,6 +154,7 @@ protected: void onStateChanged(int oldState, StateChangeSource source) override; private: + void ensureRipple(); QImage prepareRippleMask() const; void handleRipples(bool wasDown, bool wasPress); @@ -157,6 +165,7 @@ private: FloatAnimation _a_over; std_::unique_ptr _ripple; + bool _activeState = false; }; diff --git a/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp index 7f85bb168..5fb4abb3b 100644 --- a/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp +++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp @@ -214,6 +214,14 @@ void InnerDropdown::prepareCache() { } void InnerDropdown::startOpacityAnimation(bool hiding) { + if (hiding) { + if (_hideStartCallback) { + _hideStartCallback(); + } + } else if (_showStartCallback) { + _showStartCallback(); + } + _hiding = false; prepareCache(); _hiding = hiding; @@ -234,6 +242,9 @@ void InnerDropdown::showStarted() { } void InnerDropdown::startShowAnimation() { + if (_showStartCallback) { + _showStartCallback(); + } if (!_a_show.animating()) { auto opacityAnimation = base::take(_a_opacity); showChildren(); @@ -289,7 +300,7 @@ bool InnerDropdown::eventFilter(QObject *obj, QEvent *e) { otherEnter(); } else if (e->type() == QEvent::Leave) { otherLeave(); - } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton) { + } else if (e->type() == QEvent::MouseButtonRelease && static_cast(e)->button() == Qt::LeftButton) { if (isHidden() || _hiding) { otherEnter(); } else { diff --git a/Telegram/SourceFiles/ui/widgets/inner_dropdown.h b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h index b58974c92..ebb93fe59 100644 --- a/Telegram/SourceFiles/ui/widgets/inner_dropdown.h +++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h @@ -48,6 +48,12 @@ public: void otherLeave(); void hideFast(); + void setShowStartCallback(base::lambda_unique callback) { + _showStartCallback = std_::move(callback); + } + void setHideStartCallback(base::lambda_unique callback) { + _hideStartCallback = std_::move(callback); + } void setHiddenCallback(base::lambda_unique callback) { _hiddenCallback = std_::move(callback); } @@ -110,6 +116,8 @@ private: QTimer _hideTimer; bool _ignoreShowEvents = false; + base::lambda_unique _showStartCallback; + base::lambda_unique _hideStartCallback; base::lambda_unique _hiddenCallback; ChildWidget _scroll; diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 769052e05..a15816d86 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -591,12 +591,12 @@ defaultLeftOutlineButton: OutlineButton { padding: margins(11px, 5px, 11px, 5px); } attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) { - outlineFgOver: #e43f3f; + outlineFgOver: attentionBoxButtonFg; - textBgOver: #faf2f2; + textBgOver: attentionBoxButtonBgOver; - textFg: #d15948; - textFgOver: #d15948; + textFg: attentionBoxButtonFg; + textFgOver: attentionBoxButtonFg; } defaultInputArea: InputArea { diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index 92133fe09..bd5f8ba0b 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -53,6 +53,16 @@ TopBarWidget::TopBarWidget(MainWidget *w) : TWidget(w) _search->setClickedCallback([this] { onSearch(); }); _menuToggle->setClickedCallback([this] { showMenu(); }); + subscribe(w->searchInPeerChanged(), [this](PeerData *peer) { + _searchInPeer = peer; + auto historyPeer = App::main() ? App::main()->historyPeer() : nullptr; + _search->setActiveState(historyPeer && historyPeer == _searchInPeer); + }); + subscribe(w->historyPeerChanged(), [this](PeerData *peer) { + _search->setActiveState(peer && peer == _searchInPeer, Ui::IconButton::SetStateWay::SkipAnimation); + update(); + }); + subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); }); if (Adaptive::OneColumn()) { _unreadCounterSubscription = subscribe(Global::RefUnreadCounterUpdate(), [this] { @@ -92,20 +102,36 @@ void TopBarWidget::onSearch() { void TopBarWidget::showMenu() { if (auto main = App::main()) { if (auto peer = main->peer()) { - if (auto menu = _menu.ptr()) { - _menu = nullptr; - _menuToggle->removeEventFilter(menu); - menu->setHiddenCallback([menu] { menu->deleteLater(); }); - menu->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow); - } else { + if (!_menu) { _menu.create(App::main()); + struct Data { + Ui::DropdownMenu *menu = nullptr; + QPointer that; + }; + auto data = MakeShared(); + data->that = weakThis(); + data->menu = _menu.ptr(); + _menu->setHiddenCallback([this, data] { + data->menu->deleteLater(); + if (data->that && _menu == data->menu) { + _menu = nullptr; + _menuToggle->setActiveState(false); + } + }); + _menu->setShowStartCallback([this, data] { + if (data->that && _menu == data->menu) { + _menuToggle->setActiveState(true); + } + }); + _menu->setHideStartCallback([this, data] { + if (data->that && _menu == data->menu) { + _menuToggle->setActiveState(false); + } + }); _menuToggle->installEventFilter(_menu); App::main()->fillPeerMenu(peer, [this](const QString &text, base::lambda_unique callback) { return _menu->addAction(text, std_::move(callback)); }); - _menu->setHiddenCallback([this] { - _menu.destroyDelayed(); - }); _menu->moveToRight(st::topBarMenuPosition.x(), st::topBarMenuPosition.y()); _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } @@ -261,7 +287,8 @@ void TopBarWidget::showAll() { resizeEvent(nullptr); return; } - PeerData *h = App::main() ? App::main()->historyPeer() : 0, *o = App::main() ? App::main()->overviewPeer() : 0; + auto historyPeer = App::main() ? App::main()->historyPeer() : nullptr; + auto overviewPeer = App::main() ? App::main()->overviewPeer() : nullptr; if (_selCount) { _clearSelection->show(); if (_canDelete) { @@ -281,9 +308,9 @@ void TopBarWidget::showAll() { _mediaType->hide(); } } - if (h && !o && _clearSelection->isHidden()) { + if (historyPeer && !overviewPeer && _clearSelection->isHidden()) { if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) { - _info->setPeer(h); + _info->setPeer(historyPeer); _info->show(); _menuToggle->hide(); _menu.destroy(); diff --git a/Telegram/SourceFiles/window/top_bar_widget.h b/Telegram/SourceFiles/window/top_bar_widget.h index 5fdd48fef..1e06e8bf6 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.h +++ b/Telegram/SourceFiles/window/top_bar_widget.h @@ -78,6 +78,7 @@ private: anim::fvalue a_over = { 0. }; Animation _a_appearance; + PeerData *_searchInPeer = nullptr; PeerData *_selPeer = nullptr; int _selCount = 0; bool _canDelete = false; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 5b27d9a8b..cc2623baa 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -172,7 +172,7 @@ titleButtonClose: IconButton(titleButtonMinimize) { // Legacy top bar. topBarHeight: 54px; -topBarMenuPosition: point(-2px, 37px); +topBarMenuPosition: point(-2px, 35px); topBarDuration: 200; topBarBackward: icon {{ "title_back", #a3a3a3 }}; topBarForwardAlpha: 0.6;