Rich dropdown animations.

This commit is contained in:
John Preston 2016-11-09 16:40:51 +03:00
parent 45ce0ecb1f
commit 3366e05b77
39 changed files with 1234 additions and 160 deletions

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

View File

@ -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;

View File

@ -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);

View File

@ -147,8 +147,9 @@ struct lambda_wrap_helper_move_impl<Lambda, std_::false_type, Return, Args...> :
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<JustLambda*>(source);
new (lambda) JustLambda(static_cast<JustLambda&&>(*source_lambda));
}
@ -222,8 +223,9 @@ struct lambda_wrap_helper_copy_impl<Lambda, std_::false_type, Return, Args...> :
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<const JustLambda*>(source);
new (lambda) JustLambda(static_cast<const JustLambda &>(*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_;
};

View File

@ -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) {

View File

@ -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);

View File

@ -36,6 +36,7 @@ enum RoundCorners {
SmallMaskCorners = 0x00, // for images
LargeMaskCorners,
MenuCorners,
BotKbOverCorners,
StickerCorners,
StickerSelectedCorners,

View File

@ -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();

View File

@ -34,7 +34,7 @@ MediaPlayerButton {
cancelStroke: pixels;
}
mediaPlayerActiveFg: #54b5ed;
mediaPlayerActiveFg: windowActiveBg;
mediaPlayerInactiveFg: #dfebf2;
mediaPlayerButton: MediaPlayerButton {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -213,6 +213,25 @@ void startManager();
void stopManager();
void registerClipManager(Media::Clip::Manager *manager);
inline uint64 shifted(uint32 components) {
auto wide = static_cast<uint64>(components);
return (wide & 0x00000000000000FFULL)
| ((wide & 0x000000000000FF00ULL) << 8)
| ((wide & 0x0000000000FF0000ULL) << 16)
| ((wide & 0x00000000FF000000ULL) << 24);
}
inline uint32 unshifted(uint64 components) {
return static_cast<uint32>((components & 0x000000000000FF00ULL) >> 8)
| static_cast<uint32>((components & 0x00000000FF000000ULL) >> 16)
| static_cast<uint32>((components & 0x0000FF0000000000ULL) >> 24)
| static_cast<uint32>((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<uint64>(b.alpha() & 0xFF)
| (static_cast<uint64>(b.red() & 0xFF) << 16)
| (static_cast<uint64>(b.green() & 0xFF) << 32)
| (static_cast<uint64>(b.blue() & 0xFF) << 48);
auto aBits = static_cast<uint64>(a.alpha() & 0xFF)
| (static_cast<uint64>(a.red() & 0xFF) << 16)
| (static_cast<uint64>(a.green() & 0xFF) << 32)
| (static_cast<uint64>(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<int>((resultBits >> 16) & 0xFF),
static_cast<int>((resultBits >> 32) & 0xFF),
static_cast<int>((resultBits >> 48) & 0xFF),
static_cast<int>(resultBits & 0xFF),
};
}

View File

@ -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<const uint32*>(_finalImage.constBits());
_finalIntsPerLine = (_finalImage.bytesPerLine() >> 2);
_finalIntsPerLineAdded = _finalIntsPerLine - _finalWidth;
t_assert(_finalImage.depth() == static_cast<int>(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<uint32*>(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<uint32>(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<const uint32*>(_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<uint32*>(_frame.bits());
_frameIntsPerLineAdded = _frameIntsPerLine - _finalWidth;
t_assert(_frame.depth() == static_cast<int>(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<uint32>(0xFF) << 24) | (static_cast<uint32>(0xFF) << 16) | (static_cast<uint32>(0xFF) << 8) | static_cast<uint32>(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<uint32>(*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<const uint32*>(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<const uint32*>(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<const uint32*>(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

View File

@ -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

View File

@ -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 {

View File

@ -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());
}
}

View File

@ -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<uint64>(alpha)
| (static_cast<uint64>(red) << 16)
| (static_cast<uint64>(green) << 32)
| (static_cast<uint64>(blue) << 48);
auto pattern = static_cast<uint64>(blue)
| (static_cast<uint64>(green) << 16)
| (static_cast<uint64>(red) << 32)
| (static_cast<uint64>(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<uint64>(*maskBytes) + 1;
auto masked = (pattern * maskOpacity) >> 8;
auto alpha = static_cast<uint32>(masked & 0xFF);
auto red = static_cast<uint32>((masked >> 16) & 0xFF);
auto green = static_cast<uint32>((masked >> 32) & 0xFF);
auto blue = static_cast<uint32>((masked >> 48) & 0xFF);
*resultInts = blue | (green << 8) | (red << 16) | (alpha << 24);
*resultInts = anim::unshifted(pattern * maskOpacity);
maskBytes += maskBytesPerPixel;
resultInts += resultIntsPerPixel;
}

View File

@ -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;

View File

@ -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<PanelAnimation>(_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<QWidget*>(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();

View File

@ -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<void()> 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<PanelAnimation> _showAnimation;
FloatAnimation _a_show;
bool _hiding = false;
QPixmap _cache;
FloatAnimation _a_appearance;
FloatAnimation _a_opacity;
QTimer _hideTimer;
bool _ignoreShowEvents = false;
base::lambda_unique<void()> _hiddenCallback;
RectShadow _shadow;
ChildWidget<ScrollArea> _scroll;
int _maxHeight = 0;

View File

@ -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<PanelAnimation>(_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<QWidget*>(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() {

View File

@ -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<PanelAnimation> _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;

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -105,7 +105,7 @@ void TopBarWidget::showMenu() {
_menu.destroyDelayed();
});
_menu->moveToRight(0, 0);
_menu->showAnimated();
_menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
}
}
}

View File

@ -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',