diff --git a/Telegram/Resources/colors.palette b/Telegram/Resources/colors.palette index 668888f2e..c7431da64 100644 --- a/Telegram/Resources/colors.palette +++ b/Telegram/Resources/colors.palette @@ -29,6 +29,7 @@ windowOverBg: #f0f0f0; // light gray: fallback for over background windowSubTextFgOver: #7c99b2; // gray over light blue: fallback for subtext over color windowActiveTextFg: #2687bf; // online blue: fallback for active color windowShadowFg: #000000; // black: fallback for shadow color +windowShadowFgFallback: #cdcdcd; // gray: fallback for shadow without imageBg: #000000; imageBgTransparent: #ffffff; @@ -46,6 +47,8 @@ lightButtonBgOver: #edf7ff; lightButtonFg: #2b99d5; lightButtonFgOver: lightButtonFg; +menuBg: windowBg; +menuBgOver: windowOverBg; menuIconFg: #a8a8a8; menuIconFgOver: #999999; diff --git a/Telegram/Resources/icons/round_shadow_bottom.png b/Telegram/Resources/icons/round_shadow_bottom.png new file mode 100644 index 000000000..28ff21173 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom.png differ diff --git a/Telegram/Resources/icons/round_shadow_bottom@2x.png b/Telegram/Resources/icons/round_shadow_bottom@2x.png new file mode 100644 index 000000000..a64ca5c8e Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_bottom_left.png b/Telegram/Resources/icons/round_shadow_bottom_left.png new file mode 100644 index 000000000..8beb9a7e1 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_bottom_left@2x.png b/Telegram/Resources/icons/round_shadow_bottom_left@2x.png new file mode 100644 index 000000000..c1704521b Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_bottom_left@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_left.png b/Telegram/Resources/icons/round_shadow_left.png new file mode 100644 index 000000000..e7df24dc1 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_left@2x.png b/Telegram/Resources/icons/round_shadow_left@2x.png new file mode 100644 index 000000000..2f78de2ab Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_left@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_top.png b/Telegram/Resources/icons/round_shadow_top.png new file mode 100644 index 000000000..39edc4a4e Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top.png differ diff --git a/Telegram/Resources/icons/round_shadow_top@2x.png b/Telegram/Resources/icons/round_shadow_top@2x.png new file mode 100644 index 000000000..af421f2a0 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top@2x.png differ diff --git a/Telegram/Resources/icons/round_shadow_top_left.png b/Telegram/Resources/icons/round_shadow_top_left.png new file mode 100644 index 000000000..555aeb74d Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top_left.png differ diff --git a/Telegram/Resources/icons/round_shadow_top_left@2x.png b/Telegram/Resources/icons/round_shadow_top_left@2x.png new file mode 100644 index 000000000..f8f634c11 Binary files /dev/null and b/Telegram/Resources/icons/round_shadow_top_left@2x.png differ diff --git a/Telegram/Resources/sample.tdesktop-theme b/Telegram/Resources/sample.tdesktop-theme index da0b103bf..d8e443105 100644 --- a/Telegram/Resources/sample.tdesktop-theme +++ b/Telegram/Resources/sample.tdesktop-theme @@ -30,6 +30,7 @@ windowOverBg: #f0f0f0; windowSubTextFgOver: #7c99b2; windowActiveTextFg: #2687bf; windowShadowFg: #000000; +windowShadowFgFallback: #cdcdcd; imageBg: #000000; imageBgTransparent: #ffffff; activeButtonBg: windowActiveBg; @@ -42,6 +43,8 @@ lightButtonBg: windowBg; lightButtonBgOver: #edf7ff; lightButtonFg: #2b99d5; lightButtonFgOver: lightButtonFg; +menuBg: windowBg; +menuBgOver: windowOverBg; menuIconFg: #a8a8a8; menuIconFgOver: #999999; titleBg: windowOverBg; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index f5cce4723..c62caffd5 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -2235,6 +2235,7 @@ namespace { ::cornersMaskSmall[i] = new QImage(mask[i].convertToFormat(QImage::Format_ARGB32_Premultiplied)); ::cornersMaskSmall[i]->setDevicePixelRatio(cRetinaFactor()); } + prepareCorners(MenuCorners, st::buttonRadius, st::menuBg); prepareCorners(BotKbOverCorners, st::dateRadius, st::msgBotKbOverBg); prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceSelectBg); diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h index 4e4abe2ae..a29afbaaa 100644 --- a/Telegram/SourceFiles/core/lambda_wrap.h +++ b/Telegram/SourceFiles/core/lambda_wrap.h @@ -147,8 +147,9 @@ struct lambda_wrap_helper_move_impl : static void construct_move_lambda_method(void *lambda, void *source) { static_assert(alignof(JustLambda) <= alignof(typename Parent::alignment), "Bad lambda alignment."); auto space = sizeof(JustLambda); - auto aligned = std_::align(alignof(JustLambda), space, lambda, space); - t_assert(aligned == lambda); + // We want to be able to pass lambda by value in 32bit Windows version. + //auto aligned = std_::align(alignof(JustLambda), space, lambda, space); + //t_assert(aligned == lambda); auto source_lambda = static_cast(source); new (lambda) JustLambda(static_cast(*source_lambda)); } @@ -222,8 +223,9 @@ struct lambda_wrap_helper_copy_impl : static void construct_copy_lambda_method(void *lambda, const void *source) { static_assert(alignof(JustLambda) <= alignof(typename Parent::alignment), "Bad lambda alignment."); auto space = sizeof(JustLambda); - auto aligned = std_::align(alignof(JustLambda), space, lambda, space); - t_assert(aligned == lambda); + // We want to be able to pass lambda by value in 32bit Windows version. + //auto aligned = std_::align(alignof(JustLambda), space, lambda, space); + //t_assert(aligned == lambda); auto source_lambda = static_cast(source); new (lambda) JustLambda(static_cast(*source_lambda)); } @@ -319,11 +321,9 @@ protected: lambda_unique(const BaseHelper *helper, const Private &) : helper_(helper) { } - static_assert(BaseHelper::kStorageSize % sizeof(void*) == 0, "Bad pointer size."); - union { - void *(storage_[BaseHelper::kStorageSize / sizeof(void*)]); - typename BaseHelper::alignment alignment_; - }; + using alignment = typename BaseHelper::alignment; + static_assert(BaseHelper::kStorageSize % sizeof(alignment) == 0, "Bad storage size."); + alignment storage_[BaseHelper::kStorageSize / sizeof(alignment)]; const BaseHelper *helper_; }; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index cf6a0a662..780e1355e 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -2031,7 +2031,7 @@ void DialogsWidget::showMainMenu() { }, &st::dialogsMenuHelp, &st::dialogsMenuHelpOver); } updateMainMenuGeometry(); - _mainMenu->showAnimated(); + _mainMenu->showAnimated(Ui::PanelAnimation::Origin::TopLeft); } void DialogsWidget::searchMessages(const QString &query, PeerData *inPeer) { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index bdb6ecee0..7d1b2e133 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3128,6 +3128,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) _silent->hide(); _botCommandStart->hide(); + _attachType->setOrigin(Ui::PanelAnimation::Origin::BottomLeft); _attachToggle->installEventFilter(_attachType); _attachEmoji->installEventFilter(_emojiPan); diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index db82d0759..eb8d59911 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -36,6 +36,7 @@ enum RoundCorners { SmallMaskCorners = 0x00, // for images LargeMaskCorners, + MenuCorners, BotKbOverCorners, StickerCorners, StickerSelectedCorners, diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 80c4cc1d5..720c031ed 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -160,6 +160,7 @@ MainWidget::MainWidget(QWidget *parent) : TWidget(parent) MTP::setGlobalFailHandler(rpcFail(&MainWidget::updateFail)); _mediaType->hide(); + _mediaType->setOrigin(Ui::PanelAnimation::Origin::TopRight); _topBar->mediaTypeButton()->installEventFilter(_mediaType); show(); diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index 1211d3c34..0adbbfed7 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -34,7 +34,7 @@ MediaPlayerButton { cancelStroke: pixels; } -mediaPlayerActiveFg: #54b5ed; +mediaPlayerActiveFg: windowActiveBg; mediaPlayerInactiveFg: #dfebf2; mediaPlayerButton: MediaPlayerButton { diff --git a/Telegram/SourceFiles/media/player/media_player_panel.cpp b/Telegram/SourceFiles/media/player/media_player_panel.cpp index 2ec37694d..05f3862d1 100644 --- a/Telegram/SourceFiles/media/player/media_player_panel.cpp +++ b/Telegram/SourceFiles/media/player/media_player_panel.cpp @@ -172,7 +172,7 @@ void Panel::paintEvent(QPaintEvent *e) { if (_layout != Layout::Full) { shadowedSides |= (rtl() ? Side::Left : Side::Right) | Side::Top; } - _shadow.paint(p, shadowedRect, st::defaultInnerDropdown.shadowShift, shadowedSides); + _shadow.paint(p, shadowedRect, st::defaultDropdownShadowShift, shadowedSides); p.fillRect(shadowedRect, st::windowBg); } diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp index 249da8bfd..237127fa0 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp @@ -81,7 +81,7 @@ void VolumeController::applyVolumeChange(float64 volume) { } VolumeWidget::VolumeWidget(QWidget *parent) : TWidget(parent) -, _shadow(st::defaultInnerDropdown.shadow) +, _shadow(st::defaultDropdownShadow) , _controller(this) { hide(); _controller->setIsVertical(true); @@ -145,7 +145,7 @@ void VolumeWidget::paintEvent(QPaintEvent *e) { auto shadowedRect = rect().marginsRemoved(getMargin()); using ShadowSide = Ui::RectShadow::Side; auto shadowedSides = ShadowSide::Left | ShadowSide::Right | ShadowSide::Bottom; - _shadow.paint(p, shadowedRect, st::defaultInnerDropdown.shadowShift, shadowedSides); + _shadow.paint(p, shadowedRect, st::defaultDropdownShadowShift, shadowedSides); p.fillRect(shadowedRect.x(), 0, shadowedRect.width(), shadowedRect.y() + shadowedRect.height(), st::windowBg); } diff --git a/Telegram/SourceFiles/media/view/mediaview.style b/Telegram/SourceFiles/media/view/mediaview.style index 4390bd489..0d0b3ea6a 100644 --- a/Telegram/SourceFiles/media/view/mediaview.style +++ b/Telegram/SourceFiles/media/view/mediaview.style @@ -137,10 +137,24 @@ mediaviewMenu: Menu(defaultMenu) { separatorFg: mediaviewMenuFg; } +mediaviewMenuShadow: Shadow(defaultEmptyShadow) { + fallback: mediaviewMenuBg; +} +mediaviewPanelAnimation: PanelAnimation(defaultPanelAnimation) { + fadeBg: mediaviewMenuBg; + shadow: mediaviewMenuShadow; +} mediaviewPopupMenu: PopupMenu(defaultPopupMenu) { - shadow: icon {}; + shadow: mediaviewMenuShadow; menu: mediaviewMenu; + animation: mediaviewPanelAnimation; } mediaviewDropdownMenu: DropdownMenu(defaultDropdownMenu) { menu: mediaviewMenu; + wrap: InnerDropdown(defaultInnerDropdown) { + bg: mediaviewMenuBg; + animation: mediaviewPanelAnimation; + scrollPadding: margins(0px, 8px, 0px, 8px); + shadow: mediaviewMenuShadow; + } } diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 8b19be1c5..c28f02360 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -2542,7 +2542,7 @@ void MediaView::onDropdown() { _dropdown->addAction(action.text, this, action.member); } _dropdown->moveToRight(0, height() - _dropdown->height()); - _dropdown->showAnimated(); + _dropdown->showAnimated(Ui::PanelAnimation::Origin::BottomRight); _dropdown->setFocus(); } diff --git a/Telegram/SourceFiles/ui/animation.h b/Telegram/SourceFiles/ui/animation.h index f58e4b3b1..8b5ff5460 100644 --- a/Telegram/SourceFiles/ui/animation.h +++ b/Telegram/SourceFiles/ui/animation.h @@ -213,6 +213,25 @@ void startManager(); void stopManager(); void registerClipManager(Media::Clip::Manager *manager); +inline uint64 shifted(uint32 components) { + auto wide = static_cast(components); + return (wide & 0x00000000000000FFULL) + | ((wide & 0x000000000000FF00ULL) << 8) + | ((wide & 0x0000000000FF0000ULL) << 16) + | ((wide & 0x00000000FF000000ULL) << 24); +} + +inline uint32 unshifted(uint64 components) { + return static_cast((components & 0x000000000000FF00ULL) >> 8) + | static_cast((components & 0x00000000FF000000ULL) >> 16) + | static_cast((components & 0x0000FF0000000000ULL) >> 24) + | static_cast((components & 0xFF00000000000000ULL) >> 32); +} + +inline uint64 reshifted(uint64 components) { + return (components >> 8) & 0x00FF00FF00FF00FFULL; +} + inline int interpolate(int a, int b, float64 b_ratio) { return qRound(a + float64(b - a) * b_ratio); } @@ -220,11 +239,20 @@ inline int interpolate(int a, int b, float64 b_ratio) { inline QColor color(QColor a, QColor b, float64 b_ratio) { auto bOpacity = snap(interpolate(0, 255, b_ratio), 0, 255) + 1; auto aOpacity = (256 - bOpacity); + auto bBits = static_cast(b.alpha() & 0xFF) + | (static_cast(b.red() & 0xFF) << 16) + | (static_cast(b.green() & 0xFF) << 32) + | (static_cast(b.blue() & 0xFF) << 48); + auto aBits = static_cast(a.alpha() & 0xFF) + | (static_cast(a.red() & 0xFF) << 16) + | (static_cast(a.green() & 0xFF) << 32) + | (static_cast(a.blue() & 0xFF) << 48); + auto resultBits = (aBits * aOpacity + bBits * bOpacity) >> 8; return { - (a.red() * aOpacity + b.red() * bOpacity) >> 8, - (a.green() * aOpacity + b.green() * bOpacity) >> 8, - (a.blue() * aOpacity + b.blue() * bOpacity) >> 8, - (a.alpha() * aOpacity + b.alpha() * bOpacity) >> 8 + static_cast((resultBits >> 16) & 0xFF), + static_cast((resultBits >> 32) & 0xFF), + static_cast((resultBits >> 48) & 0xFF), + static_cast(resultBits & 0xFF), }; } diff --git a/Telegram/SourceFiles/ui/effects/panel_animation.cpp b/Telegram/SourceFiles/ui/effects/panel_animation.cpp new file mode 100644 index 000000000..ef783f5ff --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/panel_animation.cpp @@ -0,0 +1,492 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/effects/panel_animation.h" + +namespace Ui { + +void PanelAnimation::setFinalImage(QImage &&finalImage, QRect inner) { + t_assert(!started()); + _finalImage = std_::move(finalImage).convertToFormat(QImage::Format_ARGB32_Premultiplied); + + t_assert(!_finalImage.isNull()); + _finalWidth = _finalImage.width(); + _finalHeight = _finalImage.height(); + + setStartWidth(); + setStartHeight(); + setStartAlpha(); + setStartFadeTop(); + createFadeMask(); + setWidthDuration(); + setHeightDuration(); + setAlphaDuration(); + setShadow(); + + auto checkCorner = [this, inner](Corner &corner) { + if (!corner.valid()) return; + if ((_startWidth >= 0 && _startWidth < _finalWidth) + || (_startHeight >= 0 && _startHeight < _finalHeight)) { + t_assert(corner.width <= inner.width()); + t_assert(corner.height <= inner.height()); + } + }; + checkCorner(_topLeft); + checkCorner(_topRight); + checkCorner(_bottomLeft); + checkCorner(_bottomRight); + _finalInts = reinterpret_cast(_finalImage.constBits()); + _finalIntsPerLine = (_finalImage.bytesPerLine() >> 2); + _finalIntsPerLineAdded = _finalIntsPerLine - _finalWidth; + t_assert(_finalImage.depth() == static_cast(sizeof(uint32) << 3)); + t_assert(_finalImage.bytesPerLine() == (_finalIntsPerLine << 2)); + t_assert(_finalIntsPerLineAdded >= 0); + + _finalInnerLeft = inner.x(); + _finalInnerTop = inner.y(); + _finalInnerWidth = inner.width(); + _finalInnerHeight = inner.height(); + _finalInnerRight = _finalInnerLeft + _finalInnerWidth; + _finalInnerBottom = _finalInnerTop + _finalInnerHeight; + t_assert(QRect(0, 0, _finalWidth, _finalHeight).contains(inner)); +} + +void PanelAnimation::setShadow() { + if (_skipShadow) return; + + _shadow.extend = _st.shadow.extend; + _shadow.left = cloneImage(_st.shadow.left); + if (_shadow.valid()) { + _shadow.topLeft = cloneImage(_st.shadow.topLeft); + _shadow.top = cloneImage(_st.shadow.top); + _shadow.topRight = cloneImage(_st.shadow.topRight); + _shadow.right = cloneImage(_st.shadow.right); + _shadow.bottomRight = cloneImage(_st.shadow.bottomRight); + _shadow.bottom = cloneImage(_st.shadow.bottom); + _shadow.bottomLeft = cloneImage(_st.shadow.bottomLeft); + t_assert(!_shadow.topLeft.isNull() + && !_shadow.top.isNull() + && !_shadow.topRight.isNull() + && !_shadow.right.isNull() + && !_shadow.bottomRight.isNull() + && !_shadow.bottom.isNull() + && !_shadow.bottomLeft.isNull()); + } else { + _shadow.topLeft = + _shadow.top = + _shadow.topRight = + _shadow.right = + _shadow.bottomRight = + _shadow.bottom = + _shadow.bottomLeft = QImage(); + } +} + +void PanelAnimation::setStartWidth() { + _startWidth = qRound(_st.startWidth * _finalImage.width()); + if (_startWidth >= 0) t_assert(_startWidth <= _finalWidth); +} + +void PanelAnimation::setStartHeight() { + _startHeight = qRound(_st.startHeight * _finalImage.height()); + if (_startHeight >= 0) t_assert(_startHeight <= _finalHeight); +} + +void PanelAnimation::setStartAlpha() { + _startAlpha = qRound(_st.startOpacity * 255); + t_assert(_startAlpha >= 0 && _startAlpha < 256); +} + +void PanelAnimation::setStartFadeTop() { + _startFadeTop = qRound(_st.startFadeTop * _finalImage.height()); +} + +void PanelAnimation::createFadeMask() { + auto resultHeight = qRound(_finalImage.height() * _st.fadeHeight); + auto finalAlpha = qRound(_st.fadeOpacity * 255); + t_assert(finalAlpha >= 0 && finalAlpha < 256); + auto result = QImage(1, resultHeight, QImage::Format_ARGB32_Premultiplied); + auto ints = reinterpret_cast(result.bits()); + auto intsPerLine = (result.bytesPerLine() >> 2); + auto up = (_origin == PanelAnimation::Origin::BottomLeft || _origin == PanelAnimation::Origin::BottomRight); + auto from = up ? resultHeight : 0, to = resultHeight - from, delta = up ? -1 : 1; + for (auto y = from; y != to; y += delta) { + auto alpha = static_cast(finalAlpha * y) / resultHeight; + *ints = (0xFFU << 24) | (alpha << 16) | (alpha << 8) | alpha; + ints += intsPerLine; + } + _fadeMask = style::colorizeImage(result, _st.fadeBg); + _fadeHeight = _fadeMask.height(); + _fadeInts = reinterpret_cast(_fadeMask.constBits()); + _fadeIntsPerLine = (_fadeMask.bytesPerLine() >> 2); + t_assert(_fadeMask.bytesPerLine() == (_fadeIntsPerLine << 2)); +} + +void PanelAnimation::setCornerMasks(QImage &&topLeft, QImage &&topRight, QImage &&bottomLeft, QImage &&bottomRight) { + setCornerMask(_topLeft, std_::move(topLeft)); + setCornerMask(_topRight, std_::move(topRight)); + setCornerMask(_bottomLeft, std_::move(bottomLeft)); + setCornerMask(_bottomRight, std_::move(bottomRight)); +} + +void PanelAnimation::setSkipShadow(bool skipShadow) { + t_assert(!started()); + _skipShadow = skipShadow; +} + +void PanelAnimation::setCornerMask(Corner &corner, QImage &&image) { + t_assert(!started()); + corner.image = std_::move(image); + if (corner.valid()) { + corner.width = corner.image.width(); + corner.height = corner.image.height(); + corner.bytes = corner.image.constBits(); + corner.bytesPerPixel = (corner.image.depth() >> 3); + corner.bytesPerLineAdded = corner.image.bytesPerLine() - corner.width * corner.bytesPerPixel; + t_assert(corner.image.depth() == (corner.bytesPerPixel << 3)); + t_assert(corner.bytesPerLineAdded >= 0); + if (_startWidth >= 0) t_assert(corner.width <= _startWidth); + if (_startHeight >= 0) t_assert(corner.height <= _startHeight); + if (!_finalImage.isNull()) { + t_assert(corner.width <= _finalInnerWidth); + t_assert(corner.height <= _finalInnerHeight); + } + } else { + corner.width = corner.height = 0; + } +} + +QImage PanelAnimation::cloneImage(const style::icon &source) { + if (source.empty()) return QImage(); + + auto result = QImage(source.width(), source.height(), QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + { + Painter p(&result); + source.paint(p, 0, 0, source.width()); + } + return std_::move(result); +} + +void PanelAnimation::setWidthDuration() { + _widthDuration = _st.widthDuration; + t_assert(_widthDuration >= 0.); + t_assert(_widthDuration <= 1.); +} + +void PanelAnimation::setHeightDuration() { + t_assert(!started()); + _heightDuration = _st.heightDuration; + t_assert(_heightDuration >= 0.); + t_assert(_heightDuration <= 1.); +} + +void PanelAnimation::setAlphaDuration() { + t_assert(!started()); + _alphaDuration = _st.opacityDuration; + t_assert(_alphaDuration >= 0.); + t_assert(_alphaDuration <= 1.); +} + +void PanelAnimation::start() { + t_assert(!started()); + t_assert(!_finalImage.isNull()); + _frame = QImage(_finalWidth, _finalHeight, QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(_finalImage.devicePixelRatio()); + _frameIntsPerLine = (_frame.bytesPerLine() >> 2); + _frameInts = reinterpret_cast(_frame.bits()); + _frameIntsPerLineAdded = _frameIntsPerLine - _finalWidth; + t_assert(_frame.depth() == static_cast(sizeof(uint32) << 3)); + t_assert(_frame.bytesPerLine() == (_frameIntsPerLine << 2)); + t_assert(_frameIntsPerLineAdded >= 0); +} + +const QImage &PanelAnimation::getFrame(float64 dt, float64 opacity) { + t_assert(started()); + t_assert(dt >= 0.); + + auto &transition = anim::easeOutCirc; + constexpr auto finalAlpha = 256; + auto alpha = (dt >= _alphaDuration) ? finalAlpha : anim::interpolate(_startAlpha + 1, finalAlpha, transition(1., dt / _alphaDuration)); + _frameAlpha = anim::interpolate(0, alpha, opacity); + + auto frameWidth = (_startWidth < 0 || dt >= _widthDuration) ? _finalInnerWidth : anim::interpolate(_startWidth, _finalInnerWidth, transition(1., dt / _widthDuration)); + auto frameHeight = (_startHeight < 0 || dt >= _heightDuration) ? _finalInnerHeight : anim::interpolate(_startHeight, _finalInnerHeight, transition(1., dt / _heightDuration)); + auto frameLeft = (_origin == Origin::TopLeft || _origin == Origin::BottomLeft) ? _finalInnerLeft : (_finalInnerRight - frameWidth); + auto frameTop = (_origin == Origin::TopLeft || _origin == Origin::TopRight) ? _finalInnerTop : (_finalInnerBottom - frameHeight); + auto frameRight = frameLeft + frameWidth; + auto frameBottom = frameTop + frameHeight; + + auto fadeTop = (_fadeHeight > 0) ? snap(anim::interpolate(_startFadeTop, _finalInnerHeight, transition(1., dt)), 0, frameHeight) : frameHeight; + auto fadeBottom = (fadeTop < frameHeight) ? qMin(fadeTop + _fadeHeight, frameHeight) : frameHeight; + auto fadeSkipLines = 0; + if (_origin == Origin::BottomLeft || _origin == Origin::BottomRight) { + fadeTop = frameHeight - fadeTop; + fadeBottom = frameHeight - fadeBottom; + qSwap(fadeTop, fadeBottom); + fadeSkipLines = fadeTop + _fadeHeight - fadeBottom; + } + fadeTop += frameTop; + fadeBottom += frameTop; + + auto finalInts = _finalInts + frameLeft + frameTop * _finalIntsPerLine; + auto frameInts = _frameInts + frameLeft + frameTop * _frameIntsPerLine; + auto finalIntsPerLineAdd = (_finalWidth - frameWidth) + _finalIntsPerLineAdded; + auto frameIntsPerLineAdd = (_finalWidth - frameWidth) + _frameIntsPerLineAdded; + + // Draw frameWidth x fadeTop with fade first color. + auto fadeInts = _fadeInts + fadeSkipLines * _fadeIntsPerLine; + auto fadeWithMasterAlpha = [this](uint32 fade) { + auto fadeAlphaAddition = (256 - (fade >> 24)); + auto fadePattern = anim::shifted(fade); + return [this, fadeAlphaAddition, fadePattern](uint32 source) { + auto sourceAlpha = (source >> 24) + 1; + auto sourcePattern = anim::shifted(source); + auto mixedPattern = anim::reshifted(fadePattern * sourceAlpha + sourcePattern * fadeAlphaAddition); + return anim::unshifted(mixedPattern * _frameAlpha); + }; + }; + if (frameTop != fadeTop) { + // Take the fade components from the first line of the fade mask. + auto withMasterAlpha = fadeWithMasterAlpha(_fadeInts ? *_fadeInts : 0); + for (auto y = frameTop; y != fadeTop; ++y) { + for (auto x = frameLeft; x != frameRight; ++x) { + *frameInts++ = withMasterAlpha(*finalInts++); + } + finalInts += finalIntsPerLineAdd; + frameInts += frameIntsPerLineAdd; + } + } + + // Draw frameWidth x (fadeBottom - fadeTop) with fade gradient. + for (auto y = fadeTop; y != fadeBottom; ++y) { + auto withMasterAlpha = fadeWithMasterAlpha(*fadeInts); + for (auto x = frameLeft; x != frameRight; ++x) { + *frameInts++ = withMasterAlpha(*finalInts++); + } + finalInts += finalIntsPerLineAdd; + frameInts += frameIntsPerLineAdd; + fadeInts += _fadeIntsPerLine; + } + + // Draw frameWidth x (frameBottom - fadeBottom) with fade final color. + if (fadeBottom != frameBottom) { + // Take the fade components from the last line of the fade mask. + auto withMasterAlpha = fadeWithMasterAlpha(*(fadeInts - _fadeIntsPerLine)); + for (auto y = fadeBottom; y != frameBottom; ++y) { + for (auto x = frameLeft; x != frameRight; ++x) { + *frameInts++ = withMasterAlpha(*finalInts++); + } + finalInts += finalIntsPerLineAdd; + frameInts += frameIntsPerLineAdd; + } + } + + // Draw corners + auto innerLeft = qMax(_finalInnerLeft, frameLeft); + auto innerTop = qMax(_finalInnerTop, frameTop); + auto innerRight = qMin(_finalInnerRight, frameRight); + auto innerBottom = qMin(_finalInnerBottom, frameBottom); + if (innerLeft != _finalInnerLeft || innerTop != _finalInnerTop) { + paintCorner(_topLeft, innerLeft, innerTop); + } + if (innerRight != _finalInnerRight || innerTop != _finalInnerTop) { + paintCorner(_topRight, innerRight - _topRight.width, innerTop); + } + if (innerLeft != _finalInnerLeft || innerBottom != _finalInnerBottom) { + paintCorner(_bottomLeft, innerLeft, innerBottom - _bottomLeft.height); + } + if (innerRight != _finalInnerRight || innerBottom != _finalInnerBottom) { + paintCorner(_bottomRight, innerRight - _bottomRight.width, innerBottom - _bottomRight.height); + } + + // Fill the rest with transparent + if (frameTop) { + memset(_frameInts, 0, _frameIntsPerLine * frameTop * sizeof(uint32)); + } + auto widthLeft = (_finalWidth - frameRight); + if (frameLeft || widthLeft) { + auto frameInts = _frameInts + frameTop * _frameIntsPerLine; + for (auto y = frameTop; y != frameBottom; ++y) { + memset(frameInts, 0, frameLeft * sizeof(uint32)); + memset(frameInts + frameLeft + frameWidth, 0, widthLeft * sizeof(uint32)); + frameInts += _frameIntsPerLine; + } + } + if (auto heightLeft = (_finalHeight - frameBottom)) { + memset(_frameInts + frameBottom * _frameIntsPerLine, 0, _frameIntsPerLine * heightLeft * sizeof(uint32)); + } + + // Draw shadow + if (_shadow.valid()) { + paintShadow(innerLeft, innerTop, innerRight, innerBottom); + } + + // Debug + //frameInts = _frameInts; + //auto pattern = anim::shifted((static_cast(0xFF) << 24) | (static_cast(0xFF) << 16) | (static_cast(0xFF) << 8) | static_cast(0xFF)); + //for (auto y = 0; y != _finalHeight; ++y) { + // for (auto x = 0; x != _finalWidth; ++x) { + // auto source = *frameInts; + // auto sourceAlpha = (source >> 24); + // *frameInts = anim::unshifted(anim::shifted(source) * 256 + pattern * (256 - sourceAlpha)); + // ++frameInts; + // } + // frameInts += _frameIntsPerLineAdded; + //} + + return _frame; +} + +void PanelAnimation::paintCorner(Corner &corner, int left, int top) { + auto mask = corner.bytes; + auto bytesPerPixel = corner.bytesPerPixel; + auto bytesPerLineAdded = corner.bytesPerLineAdded; + auto frameInts = _frameInts + top * _frameIntsPerLine + left; + auto frameIntsPerLineAdd = _frameIntsPerLine - corner.width; + for (auto y = 0; y != corner.height; ++y) { + for (auto x = 0; x != corner.width; ++x) { + auto alpha = static_cast(*mask) + 1; + *frameInts = anim::unshifted(anim::shifted(*frameInts) * alpha); + ++frameInts; + mask += bytesPerPixel; + } + frameInts += frameIntsPerLineAdd; + mask += bytesPerLineAdded; + } +} + +void PanelAnimation::paintShadow(int left, int top, int right, int bottom) { + left -= _shadow.extend.left(); + top -= _shadow.extend.top(); + right += _shadow.extend.right(); + bottom += _shadow.extend.bottom(); + paintShadowCorner(left, top, _shadow.topLeft); + paintShadowCorner(right - _shadow.topRight.width(), top, _shadow.topRight); + paintShadowCorner(right - _shadow.bottomRight.width(), bottom - _shadow.bottomRight.height(), _shadow.bottomRight); + paintShadowCorner(left, bottom - _shadow.bottomLeft.height(), _shadow.bottomLeft); + paintShadowVertical(left, top + _shadow.topLeft.height(), bottom - _shadow.bottomLeft.height(), _shadow.left); + paintShadowVertical(right - _shadow.right.width(), top + _shadow.topRight.height(), bottom - _shadow.bottomRight.height(), _shadow.right); + paintShadowHorizontal(left + _shadow.topLeft.width(), right - _shadow.topRight.width(), top, _shadow.top); + paintShadowHorizontal(left + _shadow.bottomLeft.width(), right - _shadow.bottomRight.width(), bottom - _shadow.bottom.height(), _shadow.bottom); +} + +void PanelAnimation::paintShadowCorner(int left, int top, const QImage &image) { + auto imageWidth = image.width(); + auto imageHeight = image.height(); + auto imageInts = reinterpret_cast(image.constBits()); + auto imageIntsPerLine = (image.bytesPerLine() >> 2); + auto imageIntsPerLineAdded = imageIntsPerLine - imageWidth; + if (left < 0) { + auto shift = -base::take(left); + imageWidth -= shift; + imageInts += shift; + } + if (top < 0) { + auto shift = -base::take(top); + imageHeight -= shift; + imageInts += shift * imageIntsPerLine; + } + if (left + imageWidth > _finalWidth) { + imageWidth = _finalWidth - left; + } + if (top + imageHeight > _finalHeight) { + imageHeight = _finalHeight - top; + } + if (imageWidth < 0 || imageHeight < 0) return; + + auto frameInts = _frameInts + top * _frameIntsPerLine + left; + auto frameIntsPerLineAdd = _frameIntsPerLine - imageWidth; + for (auto y = 0; y != imageHeight; ++y) { + for (auto x = 0; x != imageWidth; ++x) { + auto source = *frameInts; + auto shadowAlpha = _frameAlpha - (source >> 24); + *frameInts = anim::unshifted(anim::shifted(source) * 256 + anim::shifted(*imageInts) * shadowAlpha); + ++frameInts; + ++imageInts; + } + frameInts += frameIntsPerLineAdd; + imageInts += imageIntsPerLineAdded; + } +} + +void PanelAnimation::paintShadowVertical(int left, int top, int bottom, const QImage &image) { + auto imageWidth = image.width(); + auto imageInts = reinterpret_cast(image.constBits()); + if (left < 0) { + auto shift = -base::take(left); + imageWidth -= shift; + imageInts += shift; + } + if (top < 0) top = 0; + if (bottom > _finalHeight) bottom = _finalHeight; + if (left + imageWidth > _finalWidth) { + imageWidth = _finalWidth - left; + } + if (imageWidth < 0 || bottom <= top) return; + + auto frameInts = _frameInts + top * _frameIntsPerLine + left; + auto frameIntsPerLineAdd = _frameIntsPerLine - imageWidth; + for (auto y = top; y != bottom; ++y) { + for (auto x = 0; x != imageWidth; ++x) { + auto source = *frameInts; + auto shadowAlpha = _frameAlpha - (source >> 24); + *frameInts = anim::unshifted(anim::shifted(source) * 256 + anim::shifted(*imageInts) * shadowAlpha); + ++frameInts; + ++imageInts; + } + frameInts += frameIntsPerLineAdd; + imageInts -= imageWidth; + } +} + +void PanelAnimation::paintShadowHorizontal(int left, int right, int top, const QImage &image) { + auto imageHeight = image.height(); + auto imageInts = reinterpret_cast(image.constBits()); + auto imageIntsPerLine = (image.bytesPerLine() >> 2); + if (top < 0) { + auto shift = -base::take(top); + imageHeight -= shift; + imageInts += shift * imageIntsPerLine; + } + if (left < 0) left = 0; + if (right > _finalWidth) right = _finalWidth; + if (top + imageHeight > _finalHeight) { + imageHeight = _finalHeight - top; + } + if (imageHeight < 0 || right <= left) return; + + auto frameInts = _frameInts + top * _frameIntsPerLine + left; + auto frameIntsPerLineAdd = _frameIntsPerLine - (right - left); + for (auto y = 0; y != imageHeight; ++y) { + auto imagePattern = anim::shifted(*imageInts); + for (auto x = left; x != right; ++x) { + auto source = *frameInts; + auto shadowAlpha = _frameAlpha - (source >> 24); + *frameInts = anim::unshifted(anim::shifted(source) * 256 + imagePattern * shadowAlpha); + ++frameInts; + } + frameInts += frameIntsPerLineAdd; + imageInts += imageIntsPerLine; + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/panel_animation.h b/Telegram/SourceFiles/ui/effects/panel_animation.h new file mode 100644 index 000000000..988315483 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/panel_animation.h @@ -0,0 +1,134 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "styles/style_widgets.h" + +namespace Ui { + +class PanelAnimation { +public: + enum class Origin { + TopLeft, + TopRight, + BottomLeft, + BottomRight, + }; + PanelAnimation(const style::PanelAnimation &st, Origin origin) : _st(st), _origin(origin) { + } + + void setFinalImage(QImage &&finalImage, QRect inner); + void setCornerMasks(QImage &&topLeft, QImage &&topRight, QImage &&bottomLeft, QImage &&bottomRight); + void setSkipShadow(bool skipShadow); + + void start(); + const QImage &getFrame(float64 dt, float64 opacity); + +private: + void setStartWidth(); + void setStartHeight(); + void setStartAlpha(); + void setStartFadeTop(); + void createFadeMask(); + void setWidthDuration(); + void setHeightDuration(); + void setAlphaDuration(); + void setShadow(); + + bool started() const { + return !_frame.isNull(); + } + + struct Corner { + QImage image; + int width = 0; + int height = 0; + const uchar *bytes = nullptr; + int bytesPerPixel = 0; + int bytesPerLineAdded = 0; + + bool valid() const { + return !image.isNull(); + } + }; + void setCornerMask(Corner &corner, QImage &&image); + void paintCorner(Corner &corner, int left, int top); + + struct Shadow { + style::margins extend; + QImage left, topLeft, top, topRight, right, bottomRight, bottom, bottomLeft; + + bool valid() const { + return !left.isNull(); + } + }; + QImage cloneImage(const style::icon &source); + void paintShadow(int left, int top, int right, int bottom); + void paintShadowCorner(int left, int top, const QImage &image); + void paintShadowVertical(int left, int top, int bottom, const QImage &image); + void paintShadowHorizontal(int left, int right, int top, const QImage &image); + + const style::PanelAnimation &_st; + Origin _origin = Origin::TopLeft; + + QImage _finalImage; + const uint32 *_finalInts = nullptr; + int _finalIntsPerLine = 0; + int _finalIntsPerLineAdded = 0; + int _finalWidth = 0; + int _finalHeight = 0; + int _finalInnerLeft = 0; + int _finalInnerTop = 0; + int _finalInnerRight = 0; + int _finalInnerBottom = 0; + int _finalInnerWidth = 0; + int _finalInnerHeight = 0; + + Shadow _shadow; + bool _skipShadow = false; + int _startWidth = -1; + int _startHeight = -1; + int _startAlpha = 0; + + int _startFadeTop = 0; + QImage _fadeMask; + int _fadeHeight = 0; + const uint32 *_fadeInts = nullptr; + int _fadeIntsPerLine = 0; + + Corner _topLeft; + Corner _topRight; + Corner _bottomLeft; + Corner _bottomRight; + + float64 _widthDuration = 1.; + float64 _heightDuration = 1.; + float64 _alphaDuration = 1.; + + QImage _frame; + uint32 *_frameInts = nullptr; + int _frameAlpha = 0; // recounted each getFrame() + int _frameIntsPerLine = 0; + int _frameIntsPerLineAdded = 0; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/rect_shadow.h b/Telegram/SourceFiles/ui/effects/rect_shadow.h index 6822046b9..af1e28b13 100644 --- a/Telegram/SourceFiles/ui/effects/rect_shadow.h +++ b/Telegram/SourceFiles/ui/effects/rect_shadow.h @@ -20,6 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "styles/style_widgets.h" + namespace Ui { class RectShadow { diff --git a/Telegram/SourceFiles/ui/flatinput.cpp b/Telegram/SourceFiles/ui/flatinput.cpp index 10b872d7b..ec04a0da6 100644 --- a/Telegram/SourceFiles/ui/flatinput.cpp +++ b/Telegram/SourceFiles/ui/flatinput.cpp @@ -240,11 +240,16 @@ void FlatInput::updatePlaceholderText() { void FlatInput::contextMenuEvent(QContextMenuEvent *e) { if (auto menu = createStandardContextMenu()) { - menu->addSeparator(); - auto action = menu->addAction(QString("test")); - action->setMenu(new QMenu(this)); - action->menu()->addAction(QString("test123")); - action->menu()->addAction(QString("test456")); + //menu->addSeparator(); + //auto action = menu->addAction(QString("test")); + //action->setMenu(new QMenu(this)); + //action->menu()->addAction(QString("test123")); + //action->menu()->addAction(QString("test456")); + //action->menu()->addAction(QString("test678")); + //auto second = action->menu()->addAction(QString("test90")); + //second->setMenu(new QMenu(this)); + //second->menu()->addAction(QString("testing111")); + //second->menu()->addAction(QString("testing222")); (new Ui::PopupMenu(menu))->popup(e->globalPos()); } } diff --git a/Telegram/SourceFiles/ui/style/style_core.cpp b/Telegram/SourceFiles/ui/style/style_core.cpp index 2e45ed8bd..98ff07b11 100644 --- a/Telegram/SourceFiles/ui/style/style_core.cpp +++ b/Telegram/SourceFiles/ui/style/style_core.cpp @@ -90,10 +90,10 @@ void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect auto green = (c.green() * initialAlpha) >> 8; auto blue = (c.blue() * initialAlpha) >> 8; auto alpha = (255 * initialAlpha) >> 8; - auto pattern = static_cast(alpha) - | (static_cast(red) << 16) - | (static_cast(green) << 32) - | (static_cast(blue) << 48); + auto pattern = static_cast(blue) + | (static_cast(green) << 16) + | (static_cast(red) << 32) + | (static_cast(alpha) << 48); auto resultBytesPerPixel = (src.depth() >> 3); auto resultIntsPerPixel = 1; @@ -113,12 +113,7 @@ void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect for (int y = 0; y != height; ++y) { for (int x = 0; x != width; ++x) { auto maskOpacity = static_cast(*maskBytes) + 1; - auto masked = (pattern * maskOpacity) >> 8; - auto alpha = static_cast(masked & 0xFF); - auto red = static_cast((masked >> 16) & 0xFF); - auto green = static_cast((masked >> 32) & 0xFF); - auto blue = static_cast((masked >> 48) & 0xFF); - *resultInts = blue | (green << 8) | (red << 16) | (alpha << 24); + *resultInts = anim::unshifted(pattern * maskOpacity); maskBytes += maskBytesPerPixel; resultInts += resultIntsPerPixel; } diff --git a/Telegram/SourceFiles/ui/twidget.cpp b/Telegram/SourceFiles/ui/twidget.cpp index 90c13f3dd..4eec33cda 100644 --- a/Telegram/SourceFiles/ui/twidget.cpp +++ b/Telegram/SourceFiles/ui/twidget.cpp @@ -72,7 +72,7 @@ QPixmap myGrab(TWidget *target, QRect rect) { result.fill(Qt::transparent); target->grabStart(); - target->render(&result, QPoint(), QRegion(rect), QWidget::DrawChildren | QWidget::IgnoreMask); + target->render(&result, QPoint(0, 0), rect, QWidget::DrawChildren | QWidget::IgnoreMask); target->grabFinish(); return result; diff --git a/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp index 0805d1a17..11b614bc3 100644 --- a/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp +++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.cpp @@ -23,13 +23,21 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "ui/scrollarea.h" +#include "ui/widgets/shadow.h" #include "profile/profile_members_widget.h" +#include "ui/effects/panel_animation.h" + +namespace { + +constexpr float64 kFadeHeight = 1. / 3; +constexpr int kFadeAlphaMax = 160; + +} // namespace namespace Ui { InnerDropdown::InnerDropdown(QWidget *parent, const style::InnerDropdown &st) : TWidget(parent) , _st(st) -, _shadow(_st.shadow) , _scroll(this, _st.scroll) { _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideAnimated())); @@ -99,35 +107,39 @@ void InnerDropdown::onScroll() { void InnerDropdown::paintEvent(QPaintEvent *e) { Painter p(this); - if (!_cache.isNull()) { - bool animating = _a_appearance.animating(getms()); - if (animating) { - p.setOpacity(_a_appearance.current(_hiding ? 0. : 1.)); - } else if (_hiding || isHidden()) { - hideFinished(); - return; + auto ms = getms(); + if (_a_show.animating(ms)) { + if (auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.)) { + p.drawImage(0, 0, _showAnimation->getFrame(_a_show.current(1.), opacity)); } + return; + } else if (_a_opacity.animating(ms)) { + p.setOpacity(_a_opacity.current(0.)); p.drawPixmap(0, 0, _cache); - if (!animating) { - showChildren(); - _cache = QPixmap(); - } + return; + } else if (_hiding || isHidden()) { + hideFinished(); + return; + } else if (_showAnimation) { + p.drawImage(0, 0, _showAnimation->getFrame(1., 1.)); + _showAnimation.reset(); + showChildren(); return; } - // draw shadow - auto shadowedRect = rect().marginsRemoved(_st.padding); - _shadow.paint(p, shadowedRect, _st.shadowShift); - p.fillRect(shadowedRect, st::windowBg); + auto inner = rect().marginsRemoved(_st.padding); + Shadow::paint(p, inner, width(), _st.shadow); + App::roundRect(p, inner, _st.bg, ImageRoundRadius::Small); } void InnerDropdown::enterEvent(QEvent *e) { - showAnimated(); + showAnimated(_origin); return TWidget::enterEvent(e); } void InnerDropdown::leaveEvent(QEvent *e) { - if (_a_appearance.animating(getms())) { + auto ms = getms(); + if (_a_show.animating(ms) || _a_opacity.animating(ms)) { hideAnimated(); } else { _hideTimer.start(300); @@ -136,18 +148,24 @@ void InnerDropdown::leaveEvent(QEvent *e) { } void InnerDropdown::otherEnter() { - showAnimated(); + showAnimated(_origin); } void InnerDropdown::otherLeave() { - if (_a_appearance.animating(getms())) { + auto ms = getms(); + if (_a_show.animating(ms) || _a_opacity.animating(ms)) { hideAnimated(); } else { _hideTimer.start(0); } } -void InnerDropdown::showAnimated() { +void InnerDropdown::setOrigin(PanelAnimation::Origin origin) { + _origin = origin; +} + +void InnerDropdown::showAnimated(PanelAnimation::Origin origin) { + setOrigin(origin); _hideTimer.stop(); showStarted(); } @@ -160,8 +178,7 @@ void InnerDropdown::hideAnimated(HideOption option) { if (_hiding) return; _hideTimer.stop(); - _hiding = true; - startAnimation(); + startOpacityAnimation(true); } void InnerDropdown::hideFast() { @@ -169,22 +186,12 @@ void InnerDropdown::hideFast() { _hideTimer.stop(); _hiding = false; - _a_appearance.finish(); + _a_opacity.finish(); hideFinished(); } -void InnerDropdown::startAnimation() { - auto from = _hiding ? 1. : 0.; - auto to = _hiding ? 0. : 1.; - if (!_a_appearance.animating()) { - showChildren(); - _cache = myGrab(this); - } - hideChildren(); - _a_appearance.start([this] { repaintCallback(); }, from, to, _st.duration); -} - void InnerDropdown::hideFinished() { + _a_show.finish(); _cache = QPixmap(); _ignoreShowEvents = false; if (!isHidden()) { @@ -195,25 +202,83 @@ void InnerDropdown::hideFinished() { } } +void InnerDropdown::prepareCache() { + if (_a_opacity.animating()) return; + + auto showAnimation = base::take(_a_show); + auto showAnimationData = base::take(_showAnimation); + showChildren(); + _cache = myGrab(this); + _showAnimation = base::take(showAnimationData); + _a_show = base::take(showAnimation); +} + +void InnerDropdown::startOpacityAnimation(bool hiding) { + _hiding = false; + prepareCache(); + _hiding = hiding; + hideChildren(); + _a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., _st.duration); +} + void InnerDropdown::showStarted() { if (_ignoreShowEvents) return; if (isHidden()) { show(); + startShowAnimation(); + return; } else if (!_hiding) { return; } - _hiding = false; - startAnimation(); + startOpacityAnimation(false); } -void InnerDropdown::repaintCallback() { +void InnerDropdown::startShowAnimation() { + if (!_a_show.animating()) { + auto opacityAnimation = base::take(_a_opacity); + showChildren(); + auto cache = grabForPanelAnimation(); + _a_opacity = base::take(opacityAnimation); + + _showAnimation = std_::make_unique(_st.animation, _origin); + _showAnimation->setFinalImage(std_::move(cache), rect().marginsRemoved(_st.padding)); + auto corners = App::cornersMask(ImageRoundRadius::Small); + _showAnimation->setCornerMasks(QImage(*corners[0]), QImage(*corners[1]), QImage(*corners[2]), QImage(*corners[3])); + _showAnimation->start(); + } + hideChildren(); + _a_show.start([this] { showAnimationCallback(); }, 0., 1., _st.showDuration); +} + +QImage InnerDropdown::grabForPanelAnimation() { + myEnsureResized(this); + auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + App::roundRect(p, rect().marginsRemoved(_st.padding), _st.bg, ImageRoundRadius::Small); + for (auto child : children()) { + if (auto widget = qobject_cast(child)) { + widget->render(&p, widget->pos(), widget->rect(), QWidget::DrawChildren | QWidget::IgnoreMask); + } + } + } + return std_::move(result); +} + +void InnerDropdown::opacityAnimationCallback() { update(); - if (!_a_appearance.animating() && _hiding) { + if (_hiding && !_a_opacity.animating()) { _hiding = false; hideFinished(); } } +void InnerDropdown::showAnimationCallback() { + update(); +} + bool InnerDropdown::eventFilter(QObject *obj, QEvent *e) { if (e->type() == QEvent::Enter) { otherEnter(); diff --git a/Telegram/SourceFiles/ui/widgets/inner_dropdown.h b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h index b955f43f5..7f7dfe988 100644 --- a/Telegram/SourceFiles/ui/widgets/inner_dropdown.h +++ b/Telegram/SourceFiles/ui/widgets/inner_dropdown.h @@ -20,8 +20,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/effects/rect_shadow.h" #include "styles/style_widgets.h" +#include "ui/effects/panel_animation.h" class ScrollArea; @@ -36,7 +36,7 @@ public: void setOwnedWidget(TWidget *widget); bool overlaps(const QRect &globalRect) { - if (isHidden() || _a_appearance.animating()) return false; + if (isHidden() || _a_show.animating() || _a_opacity.animating()) return false; return rect().marginsRemoved(_st.padding).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } @@ -46,7 +46,6 @@ public: void otherEnter(); void otherLeave(); - void showFast(); void hideFast(); void setHiddenCallback(base::lambda_unique callback) { @@ -54,10 +53,11 @@ public: } bool isHiding() const { - return _hiding && _a_appearance.animating(); + return _hiding && _a_opacity.animating(); } - void showAnimated(); + void setOrigin(PanelAnimation::Origin origin); + void showAnimated(PanelAnimation::Origin origin); enum class HideOption { Default, IgnoreShow, @@ -84,28 +84,34 @@ private slots: } private: + QImage grabForPanelAnimation(); + void startShowAnimation(); + void startOpacityAnimation(bool hiding); + void prepareCache(); + class Container; - void repaintCallback(); + void showAnimationCallback(); + void opacityAnimationCallback(); void hideFinished(); void showStarted(); - void startAnimation(); - void updateHeight(); const style::InnerDropdown &_st; - bool _hiding = false; + PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft; + std_::unique_ptr _showAnimation; + FloatAnimation _a_show; + bool _hiding = false; QPixmap _cache; - FloatAnimation _a_appearance; + FloatAnimation _a_opacity; QTimer _hideTimer; bool _ignoreShowEvents = false; base::lambda_unique _hiddenCallback; - RectShadow _shadow; ChildWidget _scroll; int _maxHeight = 0; diff --git a/Telegram/SourceFiles/ui/widgets/popup_menu.cpp b/Telegram/SourceFiles/ui/widgets/popup_menu.cpp index 6dc106def..6f9985e9a 100644 --- a/Telegram/SourceFiles/ui/widgets/popup_menu.cpp +++ b/Telegram/SourceFiles/ui/widgets/popup_menu.cpp @@ -18,6 +18,7 @@ #include "stdafx.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/shadow.h" #include "pspecific.h" #include "application.h" #include "lang.h" @@ -26,19 +27,13 @@ namespace Ui { PopupMenu::PopupMenu(const style::PopupMenu &st) : TWidget(nullptr) , _st(st) -, _menu(this, _st.menu) -, _shadow(_st.shadow) -, a_opacity(1) -, _a_hide(animation(this, &PopupMenu::step_hide)) { +, _menu(this, _st.menu) { init(); } PopupMenu::PopupMenu(QMenu *menu, const style::PopupMenu &st) : TWidget(nullptr) , _st(st) -, _menu(this, menu, _st.menu) -, _shadow(_st.shadow) -, a_opacity(1) -, _a_hide(animation(this, &PopupMenu::step_hide)) { +, _menu(this, menu, _st.menu) { init(); for (auto action : actions()) { @@ -50,8 +45,6 @@ PopupMenu::PopupMenu(QMenu *menu, const style::PopupMenu &st) : TWidget(nullptr) } void PopupMenu::init() { - _padding = _shadow.getDimensions(_st.shadowShift); - _menu->setResizedCallback([this] { handleMenuResize(); }); _menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) { handleActivated(action, actionTop, source); @@ -63,8 +56,7 @@ void PopupMenu::init() { _menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); }); _menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); }); - _menu->moveToLeft(_padding.left(), _padding.top()); - handleMenuResize(); + handleCompositingUpdate(); setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::Popup | Qt::NoDropShadowWindowHint); setMouseTracking(true); @@ -75,9 +67,17 @@ void PopupMenu::init() { setAttribute(Qt::WA_TranslucentBackground, true); } +void PopupMenu::handleCompositingUpdate() { + _padding = _compositing ? _st.shadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth); + _menu->moveToLeft(_padding.left() + _st.scrollPadding.left(), _padding.top() + _st.scrollPadding.top()); + handleMenuResize(); +} + void PopupMenu::handleMenuResize() { - resize(_padding.left() + _menu->width() + _padding.right(), _padding.top() + _menu->height() + _padding.bottom()); - _inner = QRect(_padding.left(), _padding.top(), width() - _padding.left() - _padding.right(), height() - _padding.top() - _padding.bottom()); + auto newWidth = _padding.left() + _st.scrollPadding.left() + _menu->width() + _st.scrollPadding.right() + _padding.right(); + auto newHeight = _padding.top() + _st.scrollPadding.top() + _menu->height() + _st.scrollPadding.bottom() + _padding.bottom(); + resize(newWidth, newHeight); + _inner = rect().marginsRemoved(_padding); } QAction *PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) { @@ -106,21 +106,36 @@ PopupMenu::Actions &PopupMenu::actions() { void PopupMenu::paintEvent(QPaintEvent *e) { Painter p(this); - auto clip = e->rect(); - p.setClipRect(clip); - auto compositionMode = p.compositionMode(); - p.setCompositionMode(QPainter::CompositionMode_Source); - if (_a_hide.animating()) { - p.setOpacity(a_opacity.current()); + auto ms = getms(); + if (_a_show.animating(ms)) { + if (auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.)) { + p.drawImage(0, 0, _showAnimation->getFrame(_a_show.current(1.), opacity)); + } + } else if (_a_opacity.animating(ms)) { + p.setOpacity(_a_opacity.current(0.)); p.drawPixmap(0, 0, _cache); - return; + } else if (_hiding || isHidden()) { + hideFinished(); + } else if (_showAnimation) { + p.drawImage(0, 0, _showAnimation->getFrame(1., 1.)); + _showAnimation.reset(); + showChildren(); + } else { + paintBg(p); } +} - // This is the minimal alpha value that allowed mouse tracking in OS X. - p.fillRect(clip, QColor(255, 255, 255, 13)); - p.setCompositionMode(compositionMode); - - _shadow.paint(p, _inner, _st.shadowShift); +void PopupMenu::paintBg(Painter &p) { + if (_compositing) { + Shadow::paint(p, _inner, width(), _st.shadow); + App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small); + } else { + p.fillRect(0, 0, width() - _padding.right(), _padding.top(), _st.shadow.fallback); + p.fillRect(width() - _padding.right(), 0, _padding.right(), height() - _padding.bottom(), _st.shadow.fallback); + p.fillRect(_padding.left(), height() - _padding.bottom(), width() - _padding.left(), _padding.bottom(), _st.shadow.fallback); + p.fillRect(0, _padding.top(), _padding.left(), height() - _padding.top(), _st.shadow.fallback); + p.fillRect(_inner, _st.menu.itemBg); + } } void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) { @@ -225,21 +240,13 @@ void PopupMenu::hideEvent(QHideEvent *e) { void PopupMenu::hideMenu(bool fast) { if (isHidden()) return; - if (_parent && !_a_hide.animating()) { + if (_parent && !_a_opacity.animating()) { _parent->childHiding(this); } if (fast) { - if (_a_hide.animating()) { - _a_hide.stop(); - } - a_opacity = anim::fvalue(0, 0); - hideFinish(); + hideFast(); } else { - if (!_a_hide.animating()) { - _cache = myGrab(this); - a_opacity.start(0); - _a_hide.start(); - } + hideAnimated(); if (_parent) { _parent->hideMenu(); } @@ -255,20 +262,134 @@ void PopupMenu::childHiding(PopupMenu *child) { } } -void PopupMenu::hideFinish() { - hide(); +void PopupMenu::setOrigin(PanelAnimation::Origin origin) { + _origin = origin; } -void PopupMenu::step_hide(float64 ms, bool timer) { - float64 dt = ms / _st.duration; - if (dt >= 1) { - _a_hide.stop(); - a_opacity.finish(); - hideFinish(); - } else { - a_opacity.update(dt, anim::linear); +void PopupMenu::showAnimated(PanelAnimation::Origin origin) { + setOrigin(origin); + showStarted(); +} + +void PopupMenu::hideAnimated() { + if (isHidden()) return; + if (_hiding) return; + + startOpacityAnimation(true); +} + +void PopupMenu::hideFast() { + if (isHidden()) return; + + _hiding = false; + _a_opacity.finish(); + hideFinished(); +} + +void PopupMenu::hideFinished() { + _a_show.finish(); + _cache = QPixmap(); + if (!isHidden()) { + hide(); } - if (timer) update(); +} + +void PopupMenu::prepareCache() { + if (_a_opacity.animating()) return; + + auto showAnimation = base::take(_a_show); + auto showAnimationData = base::take(_showAnimation); + showChildren(); + _cache = myGrab(this); + _showAnimation = base::take(showAnimationData); + _a_show = base::take(showAnimation); +} + +void PopupMenu::startOpacityAnimation(bool hiding) { + _hiding = false; + if (!_compositing) { + _a_opacity.finish(); + if (hiding) { + hideFinished(); + } else { + update(); + } + return; + } + prepareCache(); + _hiding = hiding; + hideChildren(); + _a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., _st.duration); +} + +void PopupMenu::showStarted() { + if (isHidden()) { + show(); + startShowAnimation(); + return; + } else if (!_hiding) { + return; + } + startOpacityAnimation(false); +} + +void PopupMenu::startShowAnimation() { + if (!_compositing) { + _a_show.finish(); + update(); + return; + } + if (!_a_show.animating()) { + auto opacityAnimation = base::take(_a_opacity); + showChildren(); + auto cache = grabForPanelAnimation(); + _a_opacity = base::take(opacityAnimation); + + _showAnimation = std_::make_unique(_st.animation, _origin); + _showAnimation->setFinalImage(std_::move(cache), _inner); + if (_compositing) { + auto corners = App::cornersMask(ImageRoundRadius::Small); + _showAnimation->setCornerMasks(QImage(*corners[0]), QImage(*corners[1]), QImage(*corners[2]), QImage(*corners[3])); + } else { + _showAnimation->setSkipShadow(true); + } + _showAnimation->start(); + } + hideChildren(); + _a_show.start([this] { showAnimationCallback(); }, 0., 1., _st.showDuration); +} + +void PopupMenu::opacityAnimationCallback() { + update(); + if (_hiding && !_a_opacity.animating()) { + _hiding = false; + hideFinished(); + } +} + +void PopupMenu::showAnimationCallback() { + update(); +} + +QImage PopupMenu::grabForPanelAnimation() { + myEnsureResized(this); + auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + if (_compositing) { + App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small); + } else { + p.fillRect(_inner, _st.menu.itemBg); + } + for (auto child : children()) { + if (auto widget = qobject_cast(child)) { + widget->render(&p, widget->pos(), widget->rect(), QWidget::DrawChildren | QWidget::IgnoreMask); + } + } + } + return std_::move(result); } void PopupMenu::deleteOnHide(bool del) { @@ -282,8 +403,15 @@ void PopupMenu::popup(const QPoint &p) { void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) { _parent = parent; - QPoint w = p - QPoint(0, _padding.top()); - QRect r = Sandbox::screenGeometry(p); + auto origin = PanelAnimation::Origin::TopLeft; + auto w = p - QPoint(0, _padding.top()); + auto r = Sandbox::screenGeometry(p); +#ifdef Q_OS_LINUX + _compositing = QX11Info::isCompositingManagerRunning(QApplication::desktop()->screenNumber(p)); +#else // Q_OS_LINUX + _compositing = true; +#endif // Q_OS_LINUX + handleCompositingUpdate(); if (rtl()) { if (w.x() - width() < r.x() - _padding.left()) { if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) { @@ -299,8 +427,9 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou if (_parent && w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left()) { w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right()); } else { - w.setX(r.x() + r.width() - width() + _padding.right()); + w.setX(p.x() - width() + _padding.right()); } + origin = PanelAnimation::Origin::TopRight; } } if (w.y() + height() - _padding.bottom() > r.y() + r.height()) { @@ -308,13 +437,18 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou w.setY(r.y() + r.height() - height() + _padding.bottom()); } else { w.setY(p.y() - height() + _padding.bottom()); + origin = (origin == PanelAnimation::Origin::TopRight) ? PanelAnimation::Origin::BottomRight : PanelAnimation::Origin::BottomLeft; } } + if (w.x() < r.x()) { + w.setX(r.x()); + } if (w.y() < r.y()) { w.setY(r.y()); } move(w); + setOrigin(origin); _menu->setShowSource(source); psUpdateOverlayed(this); @@ -323,11 +457,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou windowHandle()->requestActivate(); activateWindow(); - if (_a_hide.animating()) { - _a_hide.stop(); - _cache = QPixmap(); - } - a_opacity = anim::fvalue(1, 1); + startShowAnimation(); } PopupMenu::~PopupMenu() { diff --git a/Telegram/SourceFiles/ui/widgets/popup_menu.h b/Telegram/SourceFiles/ui/widgets/popup_menu.h index 6863a311e..b53255376 100644 --- a/Telegram/SourceFiles/ui/widgets/popup_menu.h +++ b/Telegram/SourceFiles/ui/widgets/popup_menu.h @@ -18,8 +18,8 @@ #pragma once #include "styles/style_widgets.h" -#include "ui/effects/rect_shadow.h" #include "ui/widgets/menu.h" +#include "ui/effects/panel_animation.h" namespace Ui { @@ -58,14 +58,28 @@ protected: } private: + void paintBg(Painter &p); + void hideFast(); + void setOrigin(PanelAnimation::Origin origin); + void showAnimated(PanelAnimation::Origin origin); + void hideAnimated(); + + QImage grabForPanelAnimation(); + void startShowAnimation(); + void startOpacityAnimation(bool hiding); + void prepareCache(); void childHiding(PopupMenu *child); - void step_hide(float64 ms, bool timer); + void showAnimationCallback(); + void opacityAnimationCallback(); void init(); - void hideFinish(); + + void hideFinished(); + void showStarted(); using TriggeredSource = Ui::Menu::TriggeredSource; + void handleCompositingUpdate(); void handleMenuResize(); void handleActivated(QAction *action, int actionTop, TriggeredSource source); void handleTriggered(QAction *action, int actionTop, TriggeredSource source); @@ -97,12 +111,16 @@ private: QRect _inner; style::margins _padding; - Ui::RectShadow _shadow; SubmenuPointer _activeSubmenu; + PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft; + std_::unique_ptr _showAnimation; + FloatAnimation _a_show; + + bool _compositing = true; + bool _hiding = false; QPixmap _cache; - anim::fvalue a_opacity; - Animation _a_hide; + FloatAnimation _a_opacity; bool _deleteOnHide = true; bool _triggering = false; diff --git a/Telegram/SourceFiles/ui/widgets/shadow.cpp b/Telegram/SourceFiles/ui/widgets/shadow.cpp index 38c49e1fe..13fd54eb3 100644 --- a/Telegram/SourceFiles/ui/widgets/shadow.cpp +++ b/Telegram/SourceFiles/ui/widgets/shadow.cpp @@ -53,4 +53,59 @@ void ToggleableShadow::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), _color); } +void Shadow::paint(Painter &p, const QRect &box, int outerWidth, const style::Shadow &st, Sides sides) { + auto left = (sides & Side::Left); + auto top = (sides & Side::Top); + auto right = (sides & Side::Right); + auto bottom = (sides & Side::Bottom); + if (left) { + auto from = box.y(); + auto to = from + box.height(); + if (top && !st.topLeft.empty()) { + st.topLeft.paint(p, box.x() - st.extend.left(), box.y() - st.extend.top(), outerWidth); + from += st.topLeft.height() - st.extend.top(); + } + if (bottom && !st.bottomLeft.empty()) { + st.bottomLeft.paint(p, box.x() - st.extend.left(), box.y() + box.height() + st.extend.bottom() - st.bottomLeft.height(), outerWidth); + to -= st.bottomLeft.height() - st.extend.bottom(); + } + if (to > from && !st.left.empty()) { + st.left.fill(p, rtlrect(box.x() - st.extend.left(), from, st.left.width(), to - from, outerWidth)); + } + } + if (right) { + auto from = box.y(); + auto to = from + box.height(); + if (top && !st.topRight.empty()) { + st.topRight.paint(p, box.x() + box.width() + st.extend.right() - st.topRight.width(), box.y() - st.extend.top(), outerWidth); + from += st.topRight.height() - st.extend.top(); + } + if (bottom && !st.bottomRight.empty()) { + st.bottomRight.paint(p, box.x() + box.width() + st.extend.right() - st.bottomRight.width(), box.y() + box.height() + st.extend.bottom() - st.bottomRight.height(), outerWidth); + to -= st.bottomRight.height() - st.extend.bottom(); + } + if (to > from && !st.right.empty()) { + st.right.fill(p, rtlrect(box.x() + box.width() + st.extend.right() - st.right.width(), from, st.right.width(), to - from, outerWidth)); + } + } + if (top && !st.top.empty()) { + auto from = box.x(); + auto to = from + box.width(); + if (left && !st.topLeft.empty()) from += st.topLeft.width() - st.extend.left(); + if (right && !st.topRight.empty()) to -= st.topRight.width() - st.extend.right(); + if (to > from) { + st.top.fill(p, rtlrect(from, box.y() - st.extend.top(), to - from, st.top.height(), outerWidth)); + } + } + if (bottom && !st.bottom.empty()) { + auto from = box.x(); + auto to = from + box.width(); + if (left && !st.bottomLeft.empty()) from += st.bottomLeft.width() - st.extend.left(); + if (right && !st.bottomRight.empty()) to -= st.bottomRight.width() - st.extend.right(); + if (to > from) { + st.bottom.fill(p, rtlrect(from, box.y() + box.height() + st.extend.bottom() - st.bottom.height(), to - from, st.bottom.height(), outerWidth)); + } + } +} + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/shadow.h b/Telegram/SourceFiles/ui/widgets/shadow.h index 798a45dd5..bf1ef00b6 100644 --- a/Telegram/SourceFiles/ui/widgets/shadow.h +++ b/Telegram/SourceFiles/ui/widgets/shadow.h @@ -1,5 +1,24 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ #pragma once +#include "styles/style_widgets.h" + namespace Ui { class PlainShadow : public TWidget { @@ -63,4 +82,35 @@ private: }; +class Shadow : public TWidget { +public: + enum class Side { + Left = 0x01, + Top = 0x02, + Right = 0x04, + Bottom = 0x08, + }; + Q_DECLARE_FLAGS(Sides, Side); + Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(Sides); + + Shadow(QWidget *parent, const style::Shadow &st, Sides sides = Side::Left | Side::Top | Side::Right | Side::Bottom) : TWidget(parent) + , _st(st) + , _sides(sides) { + } + + static void paint(Painter &p, const QRect &box, int outerWidth, const style::Shadow &st, Sides sides = Side::Left | Side::Top | Side::Right | Side::Bottom); + +protected: + void paintEvent(QPaintEvent *e) override { + Painter p(this); + paint(p, rect().marginsRemoved(_st.extend), width(), _st, _sides); + } + +private: + const style::Shadow &_st; + Sides _sides; + +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(Shadow::Sides); + } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 7a4fbebdf..db60dba29 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -40,6 +40,19 @@ IconButton { duration: int; } +Shadow { + left: icon; + topLeft: icon; + top: icon; + topRight: icon; + right: icon; + bottomRight: icon; + bottom: icon; + bottomLeft: icon; + extend: margins; + fallback: color; +} + MaskButton { width: pixels; height: pixels; @@ -148,23 +161,41 @@ Menu { widthMax: pixels; } +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: icon; - shadowShift: pixels; + shadow: Shadow; + scrollPadding: margins; + animation: PanelAnimation; menu: Menu; duration: int; + showDuration: int; } InnerDropdown { padding: margins; - shadow: icon; - shadowShift: pixels; + shadow: Shadow; + animation: PanelAnimation; duration: int; + showDuration: int; width: pixels; + bg: color; scroll: flatScroll; scrollMargin: margins; scrollPadding: margins; @@ -209,9 +240,39 @@ discreteSliderLabelFont: normalFont; discreteSliderLabelFg: #1485c2; discreteSliderDuration: 200; +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", #373737 }}; defaultMenu: Menu { - skip: 8px; + skip: 0px; itemBg: windowBg; itemBgOver: windowOverBg; @@ -235,23 +296,30 @@ defaultMenu: Menu { widthMax: 300px; } defaultPopupMenu: PopupMenu { - shadow: defaultDropdownShadow; - shadowShift: defaultDropdownShadowShift; + shadow: defaultRoundShadow; + animation: defaultPanelAnimation; + + scrollPadding: margins(0px, 8px, 0px, 8px); menu: defaultMenu; - duration: 120; + duration: 150; + showDuration: 200; } defaultInnerDropdown: InnerDropdown { padding: margins(10px, 10px, 10px, 10px); - shadow: defaultDropdownShadow; - shadowShift: defaultDropdownShadowShift; + shadow: defaultRoundShadow; + animation: defaultPanelAnimation; duration: 150; + showDuration: 200; + bg: menuBg; scroll: solidScroll; } defaultDropdownMenu: DropdownMenu { - wrap: defaultInnerDropdown; + wrap: InnerDropdown(defaultInnerDropdown) { + scrollPadding: margins(0px, 8px, 0px, 8px); + } menu: defaultMenu; } diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index f58b25a74..4bcedc72d 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -105,7 +105,7 @@ void TopBarWidget::showMenu() { _menu.destroyDelayed(); }); _menu->moveToRight(0, 0); - _menu->showAnimated(); + _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } } } diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 141f4a54c..93504aea6 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -461,6 +461,8 @@ '<(src_loc)/ui/buttons/peer_avatar_button.h', '<(src_loc)/ui/buttons/round_button.cpp', '<(src_loc)/ui/buttons/round_button.h', + '<(src_loc)/ui/effects/panel_animation.cpp', + '<(src_loc)/ui/effects/panel_animation.h', '<(src_loc)/ui/effects/radial_animation.cpp', '<(src_loc)/ui/effects/radial_animation.h', '<(src_loc)/ui/effects/rect_shadow.cpp',