mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-01 00:08:02 +00:00
Extract TabbedSelector from TabbedPanel.
Now we can use TabbedSelector separately, not only inside the panel.
This commit is contained in:
parent
2c81014188
commit
647ea44881
@ -142,6 +142,7 @@ StickersBox::StickersBox(QWidget*, Section section)
|
||||
, _installed(0, this, Section::Installed)
|
||||
, _featured(1, this, Section::Featured)
|
||||
, _archived(2, this, Section::Archived) {
|
||||
_tabs->setRippleTopRoundRadius(st::boxRadius);
|
||||
}
|
||||
|
||||
StickersBox::StickersBox(QWidget*, const Stickers::Order &archivedIds)
|
||||
|
@ -67,9 +67,7 @@ stickersRemoveSkip: 4px;
|
||||
stickersReorderIcon: icon {{ "stickers_reorder", menuIconFg }};
|
||||
stickersReorderSkip: 13px;
|
||||
|
||||
stickersTabs: SettingsSlider(defaultTabsSlider) {
|
||||
rippleRoundRadius: boxRadius;
|
||||
}
|
||||
stickersTabs: defaultTabsSlider;
|
||||
|
||||
stickerEmojiSkip: 5px;
|
||||
|
||||
@ -109,9 +107,7 @@ stickersSettingsUnreadPosition: point(4px, 5px);
|
||||
|
||||
emojiPanMargins: margins(10px, 10px, 10px, 10px);
|
||||
|
||||
emojiTabs: SettingsSlider(defaultTabsSlider) {
|
||||
rippleRoundRadius: buttonRadius;
|
||||
}
|
||||
emojiTabs: defaultTabsSlider;
|
||||
emojiScroll: defaultSolidScroll;
|
||||
emojiRecent: icon {{ "emoji_recent", emojiIconFg }};
|
||||
emojiRecentActive: icon {{ "emoji_recent", emojiIconFgActive }};
|
||||
|
@ -34,7 +34,7 @@ constexpr auto kSaveRecentEmojiTimeout = 3000;
|
||||
|
||||
} // namespace
|
||||
|
||||
class EmojiListWidget::Footer : public TabbedPanel::InnerFooter {
|
||||
class EmojiListWidget::Footer : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
Footer(gsl::not_null<EmojiListWidget*> parent);
|
||||
|
||||
@ -330,7 +330,7 @@ void EmojiListWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) {
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<TabbedPanel::InnerFooter> EmojiListWidget::createFooter() {
|
||||
object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
|
||||
Expects(_footer == nullptr);
|
||||
auto result = object_ptr<Footer>(this);
|
||||
_footer = result;
|
||||
@ -664,7 +664,7 @@ Ui::Emoji::Section EmojiListWidget::currentSection(int yOffset) const {
|
||||
return static_cast<Section>(sectionInfoByOffset(yOffset).section);
|
||||
}
|
||||
|
||||
TabbedPanel::InnerFooter *EmojiListWidget::getFooter() const {
|
||||
TabbedSelector::InnerFooter *EmojiListWidget::getFooter() const {
|
||||
return _footer;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
@ -84,7 +84,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class EmojiListWidget : public TabbedPanel::Inner {
|
||||
class EmojiListWidget : public TabbedSelector::Inner {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -95,7 +95,7 @@ public:
|
||||
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
|
||||
void refreshRecent() override;
|
||||
void clearSelection() override;
|
||||
object_ptr<TabbedPanel::InnerFooter> createFooter() override;
|
||||
object_ptr<TabbedSelector::InnerFooter> createFooter() override;
|
||||
|
||||
void showEmojiSection(Section section);
|
||||
Section currentSection(int yOffset) const;
|
||||
@ -121,7 +121,7 @@ protected:
|
||||
void enterFromChildEvent(QEvent *e, QWidget *child) override;
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
TabbedPanel::InnerFooter *getFooter() const override;
|
||||
TabbedSelector::InnerFooter *getFooter() const override;
|
||||
void processHideFinished() override;
|
||||
int countHeight() override;
|
||||
|
||||
|
@ -39,11 +39,11 @@ constexpr auto kSaveChosenTabTimeout = 1000;
|
||||
constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kStickersPanelPerRow = Stickers::kPanelPerRow;
|
||||
constexpr auto kInlineItemsMaxPerRow = 5;
|
||||
constexpr auto kSearchBotUsername = "gif";
|
||||
constexpr auto kSearchBotUsername = str_const("gif");
|
||||
|
||||
} // namespace
|
||||
|
||||
class GifsListWidget::Footer : public TabbedPanel::InnerFooter {
|
||||
class GifsListWidget::Footer : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
Footer(gsl::not_null<GifsListWidget*> parent);
|
||||
|
||||
@ -145,7 +145,7 @@ GifsListWidget::GifsListWidget(QWidget *parent, gsl::not_null<Window::Controller
|
||||
});
|
||||
}
|
||||
|
||||
object_ptr<TabbedPanel::InnerFooter> GifsListWidget::createFooter() {
|
||||
object_ptr<TabbedSelector::InnerFooter> GifsListWidget::createFooter() {
|
||||
Expects(_footer == nullptr);
|
||||
auto result = object_ptr<Footer>(this);
|
||||
_footer = result;
|
||||
@ -383,7 +383,7 @@ void GifsListWidget::clearSelection() {
|
||||
update();
|
||||
}
|
||||
|
||||
TabbedPanel::InnerFooter *GifsListWidget::getFooter() const {
|
||||
TabbedSelector::InnerFooter *GifsListWidget::getFooter() const {
|
||||
return _footer;
|
||||
}
|
||||
|
||||
@ -803,7 +803,8 @@ void GifsListWidget::searchForGifs(const QString &query) {
|
||||
}
|
||||
|
||||
if (!_searchBot && !_searchBotRequestId) {
|
||||
_searchBotRequestId = request(MTPcontacts_ResolveUsername(MTP_string(kSearchBotUsername))).done([this](const MTPcontacts_ResolvedPeer &result) {
|
||||
auto username = str_const_toString(kSearchBotUsername);
|
||||
_searchBotRequestId = request(MTPcontacts_ResolveUsername(MTP_string(username))).done([this](const MTPcontacts_ResolvedPeer &result) {
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
auto &data = result.c_contacts_resolvedPeer();
|
||||
App::feedUsers(data.vusers);
|
||||
|
@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
|
||||
namespace InlineBots {
|
||||
@ -40,7 +40,7 @@ class Controller;
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
class GifsListWidget : public TabbedPanel::Inner, public InlineBots::Layout::Context, private base::Subscriber, private MTP::Sender {
|
||||
class GifsListWidget : public TabbedSelector::Inner, public InlineBots::Layout::Context, private base::Subscriber, private MTP::Sender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -49,7 +49,7 @@ public:
|
||||
void refreshRecent() override;
|
||||
void preloadImages() override;
|
||||
void clearSelection() override;
|
||||
object_ptr<TabbedPanel::InnerFooter> createFooter() override;
|
||||
object_ptr<TabbedSelector::InnerFooter> createFooter() override;
|
||||
|
||||
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
|
||||
|
||||
@ -77,7 +77,7 @@ protected:
|
||||
void leaveToChildEvent(QEvent *e, QWidget *child) override;
|
||||
void enterFromChildEvent(QEvent *e, QWidget *child) override;
|
||||
|
||||
TabbedPanel::InnerFooter *getFooter() const override;
|
||||
TabbedSelector::InnerFooter *getFooter() const override;
|
||||
void processHideFinished() override;
|
||||
void processPanelHideFinished() override;
|
||||
int countHeight() override;
|
||||
|
@ -54,7 +54,7 @@ struct StickerIcon {
|
||||
|
||||
};
|
||||
|
||||
class StickersListWidget::Footer : public TabbedPanel::InnerFooter {
|
||||
class StickersListWidget::Footer : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
Footer(gsl::not_null<StickersListWidget*> parent);
|
||||
|
||||
@ -433,7 +433,7 @@ StickersListWidget::StickersListWidget(QWidget *parent, gsl::not_null<Window::Co
|
||||
});
|
||||
}
|
||||
|
||||
object_ptr<TabbedPanel::InnerFooter> StickersListWidget::createFooter() {
|
||||
object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
|
||||
Expects(_footer == nullptr);
|
||||
auto result = object_ptr<Footer>(this);
|
||||
_footer = result;
|
||||
@ -984,7 +984,7 @@ void StickersListWidget::clearSelection() {
|
||||
update();
|
||||
}
|
||||
|
||||
TabbedPanel::InnerFooter *StickersListWidget::getFooter() const {
|
||||
TabbedSelector::InnerFooter *StickersListWidget::getFooter() const {
|
||||
return _footer;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "base/variant.h"
|
||||
|
||||
namespace Window {
|
||||
@ -35,7 +35,7 @@ namespace ChatHelpers {
|
||||
|
||||
struct StickerIcon;
|
||||
|
||||
class StickersListWidget : public TabbedPanel::Inner, private base::Subscriber, private MTP::Sender {
|
||||
class StickersListWidget : public TabbedSelector::Inner, private base::Subscriber, private MTP::Sender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -44,7 +44,7 @@ public:
|
||||
void refreshRecent() override;
|
||||
void preloadImages() override;
|
||||
void clearSelection() override;
|
||||
object_ptr<TabbedPanel::InnerFooter> createFooter() override;
|
||||
object_ptr<TabbedSelector::InnerFooter> createFooter() override;
|
||||
|
||||
void showStickerSet(uint64 setId);
|
||||
|
||||
@ -72,7 +72,7 @@ protected:
|
||||
void leaveToChildEvent(QEvent *e, QWidget *child) override;
|
||||
void enterFromChildEvent(QEvent *e, QWidget *child) override;
|
||||
|
||||
TabbedPanel::InnerFooter *getFooter() const override;
|
||||
TabbedSelector::InnerFooter *getFooter() const override;
|
||||
void processHideFinished() override;
|
||||
void processPanelHideFinished() override;
|
||||
int countHeight() override;
|
||||
|
@ -20,334 +20,52 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
|
||||
#include "chat_helpers/emoji_list_widget.h"
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
#include "chat_helpers/gifs_list_widget.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/discrete_sliders.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "lang.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSaveChosenTabTimeout = 1000;
|
||||
constexpr auto kHideTimeoutMs = 300;
|
||||
constexpr auto kDelayedHideTimeoutMs = 3000;
|
||||
|
||||
} // namespace
|
||||
|
||||
class TabbedPanel::SlideAnimation : public Ui::RoundShadowAnimation {
|
||||
public:
|
||||
enum class Direction {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
};
|
||||
void setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner);
|
||||
|
||||
void start();
|
||||
void paintFrame(QPainter &p, float64 dt, float64 opacity);
|
||||
|
||||
private:
|
||||
Direction _direction = Direction::LeftToRight;
|
||||
QPixmap _leftImage, _rightImage;
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
int _innerLeft = 0;
|
||||
int _innerTop = 0;
|
||||
int _innerRight = 0;
|
||||
int _innerBottom = 0;
|
||||
int _innerWidth = 0;
|
||||
int _innerHeight = 0;
|
||||
|
||||
int _painterInnerLeft = 0;
|
||||
int _painterInnerTop = 0;
|
||||
int _painterInnerWidth = 0;
|
||||
int _painterInnerBottom = 0;
|
||||
int _painterCategoriesTop = 0;
|
||||
int _painterInnerHeight = 0;
|
||||
int _painterInnerRight = 0;
|
||||
|
||||
int _frameIntsPerLineAdd = 0;
|
||||
|
||||
};
|
||||
|
||||
void TabbedPanel::SlideAnimation::setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner) {
|
||||
Expects(!started());
|
||||
_direction = direction;
|
||||
_leftImage = QPixmap::fromImage(std::move(left).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
|
||||
_rightImage = QPixmap::fromImage(std::move(right).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
|
||||
|
||||
t_assert(!_leftImage.isNull());
|
||||
t_assert(!_rightImage.isNull());
|
||||
_width = _leftImage.width();
|
||||
_height = _rightImage.height();
|
||||
t_assert(!(_width % cIntRetinaFactor()));
|
||||
t_assert(!(_height % cIntRetinaFactor()));
|
||||
t_assert(_leftImage.devicePixelRatio() == _rightImage.devicePixelRatio());
|
||||
t_assert(_rightImage.width() == _width);
|
||||
t_assert(_rightImage.height() == _height);
|
||||
t_assert(QRect(0, 0, _width, _height).contains(inner));
|
||||
_innerLeft = inner.x();
|
||||
_innerTop = inner.y();
|
||||
_innerWidth = inner.width();
|
||||
_innerHeight = inner.height();
|
||||
t_assert(!(_innerLeft % cIntRetinaFactor()));
|
||||
t_assert(!(_innerTop % cIntRetinaFactor()));
|
||||
t_assert(!(_innerWidth % cIntRetinaFactor()));
|
||||
t_assert(!(_innerHeight % cIntRetinaFactor()));
|
||||
_innerRight = _innerLeft + _innerWidth;
|
||||
_innerBottom = _innerTop + _innerHeight;
|
||||
|
||||
_painterInnerLeft = _innerLeft / cIntRetinaFactor();
|
||||
_painterInnerTop = _innerTop / cIntRetinaFactor();
|
||||
_painterInnerRight = _innerRight / cIntRetinaFactor();
|
||||
_painterInnerBottom = _innerBottom / cIntRetinaFactor();
|
||||
_painterInnerWidth = _innerWidth / cIntRetinaFactor();
|
||||
_painterInnerHeight = _innerHeight / cIntRetinaFactor();
|
||||
_painterCategoriesTop = _painterInnerBottom - st::emojiCategory.height;
|
||||
}
|
||||
|
||||
void TabbedPanel::SlideAnimation::start() {
|
||||
t_assert(!_leftImage.isNull());
|
||||
t_assert(!_rightImage.isNull());
|
||||
RoundShadowAnimation::start(_width, _height, _leftImage.devicePixelRatio());
|
||||
auto checkCorner = [this](const Corner &corner) {
|
||||
if (!corner.valid()) return;
|
||||
t_assert(corner.width <= _innerWidth);
|
||||
t_assert(corner.height <= _innerHeight);
|
||||
};
|
||||
checkCorner(_topLeft);
|
||||
checkCorner(_topRight);
|
||||
checkCorner(_bottomLeft);
|
||||
checkCorner(_bottomRight);
|
||||
_frameIntsPerLineAdd = (_width - _innerWidth) + _frameIntsPerLineAdded;
|
||||
}
|
||||
|
||||
void TabbedPanel::SlideAnimation::paintFrame(QPainter &p, float64 dt, float64 opacity) {
|
||||
t_assert(started());
|
||||
t_assert(dt >= 0.);
|
||||
|
||||
_frameAlpha = anim::interpolate(1, 256, opacity);
|
||||
|
||||
auto frameInts = _frameInts + _innerLeft + _innerTop * _frameIntsPerLine;
|
||||
|
||||
auto leftToRight = (_direction == Direction::LeftToRight);
|
||||
|
||||
auto easeOut = anim::easeOutCirc(1., dt);
|
||||
auto easeIn = anim::easeInCirc(1., dt);
|
||||
|
||||
auto arrivingCoord = anim::interpolate(_innerWidth, 0, easeOut);
|
||||
auto departingCoord = anim::interpolate(0, _innerWidth, easeIn);
|
||||
if (auto decrease = (arrivingCoord % cIntRetinaFactor())) {
|
||||
arrivingCoord -= decrease;
|
||||
}
|
||||
if (auto decrease = (departingCoord % cIntRetinaFactor())) {
|
||||
departingCoord -= decrease;
|
||||
}
|
||||
auto arrivingAlpha = easeIn;
|
||||
auto departingAlpha = 1. - easeOut;
|
||||
auto leftCoord = (leftToRight ? arrivingCoord : departingCoord) * -1;
|
||||
auto leftAlpha = (leftToRight ? arrivingAlpha : departingAlpha);
|
||||
auto rightCoord = (leftToRight ? departingCoord : arrivingCoord);
|
||||
auto rightAlpha = (leftToRight ? departingAlpha : arrivingAlpha);
|
||||
|
||||
// _innerLeft ..(left).. leftTo ..(both).. bothTo ..(none).. noneTo ..(right).. _innerRight
|
||||
auto leftTo = _innerLeft + snap(_innerWidth + leftCoord, 0, _innerWidth);
|
||||
auto rightFrom = _innerLeft + snap(rightCoord, 0, _innerWidth);
|
||||
auto painterRightFrom = rightFrom / cIntRetinaFactor();
|
||||
if (opacity < 1.) {
|
||||
_frame.fill(Qt::transparent);
|
||||
}
|
||||
{
|
||||
Painter p(&_frame);
|
||||
p.setOpacity(opacity);
|
||||
p.fillRect(_painterInnerLeft, _painterInnerTop, _painterInnerWidth, _painterCategoriesTop - _painterInnerTop, st::emojiPanBg);
|
||||
p.fillRect(_painterInnerLeft, _painterCategoriesTop, _painterInnerWidth, _painterInnerBottom - _painterCategoriesTop, st::emojiPanCategories);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
if (leftTo > _innerLeft) {
|
||||
p.setOpacity(opacity * leftAlpha);
|
||||
p.drawPixmap(_painterInnerLeft, _painterInnerTop, _leftImage, _innerLeft - leftCoord, _innerTop, leftTo - _innerLeft, _innerHeight);
|
||||
}
|
||||
if (rightFrom < _innerRight) {
|
||||
p.setOpacity(opacity * rightAlpha);
|
||||
p.drawPixmap(painterRightFrom, _painterInnerTop, _rightImage, _innerLeft, _innerTop, _innerRight - rightFrom, _innerHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw corners
|
||||
//paintCorner(_topLeft, _innerLeft, _innerTop);
|
||||
//paintCorner(_topRight, _innerRight - _topRight.width, _innerTop);
|
||||
paintCorner(_bottomLeft, _innerLeft, _innerBottom - _bottomLeft.height);
|
||||
paintCorner(_bottomRight, _innerRight - _bottomRight.width, _innerBottom - _bottomRight.height);
|
||||
|
||||
// Draw shadow upon the transparent
|
||||
auto outerLeft = _innerLeft;
|
||||
auto outerTop = _innerTop;
|
||||
auto outerRight = _innerRight;
|
||||
auto outerBottom = _innerBottom;
|
||||
if (_shadow.valid()) {
|
||||
outerLeft -= _shadow.extend.left();
|
||||
outerTop -= _shadow.extend.top();
|
||||
outerRight += _shadow.extend.right();
|
||||
outerBottom += _shadow.extend.bottom();
|
||||
}
|
||||
if (cIntRetinaFactor() > 1) {
|
||||
if (auto skipLeft = (outerLeft % cIntRetinaFactor())) {
|
||||
outerLeft -= skipLeft;
|
||||
}
|
||||
if (auto skipTop = (outerTop % cIntRetinaFactor())) {
|
||||
outerTop -= skipTop;
|
||||
}
|
||||
if (auto skipRight = (outerRight % cIntRetinaFactor())) {
|
||||
outerRight += (cIntRetinaFactor() - skipRight);
|
||||
}
|
||||
if (auto skipBottom = (outerBottom % cIntRetinaFactor())) {
|
||||
outerBottom += (cIntRetinaFactor() - skipBottom);
|
||||
}
|
||||
}
|
||||
|
||||
if (opacity == 1.) {
|
||||
// Fill above the frame top with transparent.
|
||||
auto fillTopInts = (_frameInts + outerTop * _frameIntsPerLine + outerLeft);
|
||||
auto fillWidth = (outerRight - outerLeft) * sizeof(uint32);
|
||||
for (auto fillTop = _innerTop - outerTop; fillTop != 0; --fillTop) {
|
||||
memset(fillTopInts, 0, fillWidth);
|
||||
fillTopInts += _frameIntsPerLine;
|
||||
}
|
||||
|
||||
// Fill to the left and to the right of the frame with transparent.
|
||||
auto fillLeft = (_innerLeft - outerLeft) * sizeof(uint32);
|
||||
auto fillRight = (outerRight - _innerRight) * sizeof(uint32);
|
||||
if (fillLeft || fillRight) {
|
||||
auto fillInts = _frameInts + _innerTop * _frameIntsPerLine;
|
||||
for (auto y = _innerTop; y != _innerBottom; ++y) {
|
||||
memset(fillInts + outerLeft, 0, fillLeft);
|
||||
memset(fillInts + _innerRight, 0, fillRight);
|
||||
fillInts += _frameIntsPerLine;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill below the frame bottom with transparent.
|
||||
auto fillBottomInts = (_frameInts + _innerBottom * _frameIntsPerLine + outerLeft);
|
||||
for (auto fillBottom = outerBottom - _innerBottom; fillBottom != 0; --fillBottom) {
|
||||
memset(fillBottomInts, 0, fillWidth);
|
||||
fillBottomInts += _frameIntsPerLine;
|
||||
}
|
||||
}
|
||||
if (_shadow.valid()) {
|
||||
paintShadow(outerLeft, outerTop, outerRight, outerBottom);
|
||||
}
|
||||
|
||||
// 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;
|
||||
//}
|
||||
|
||||
p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop);
|
||||
}
|
||||
|
||||
TabbedPanel::Tab::Tab(TabType type, object_ptr<Inner> widget)
|
||||
: _type(type)
|
||||
, _widget(std::move(widget))
|
||||
, _weak(_widget)
|
||||
, _footer(_widget->createFooter()) {
|
||||
_footer->setParent(_widget->parentWidget());
|
||||
}
|
||||
|
||||
object_ptr<TabbedPanel::Inner> TabbedPanel::Tab::takeWidget() {
|
||||
return std::move(_widget);
|
||||
}
|
||||
|
||||
void TabbedPanel::Tab::returnWidget(object_ptr<Inner> widget) {
|
||||
_widget = std::move(widget);
|
||||
Ensures(_widget == _weak);
|
||||
}
|
||||
|
||||
void TabbedPanel::Tab::saveScrollTop() {
|
||||
_scrollTop = widget()->getVisibleTop();
|
||||
}
|
||||
|
||||
TabbedPanel::TabbedPanel(QWidget *parent, gsl::not_null<Window::Controller*> controller) : TWidget(parent)
|
||||
, _tabsSlider(this, st::emojiTabs)
|
||||
, _topShadow(this, st::shadowFg)
|
||||
, _bottomShadow(this, st::shadowFg)
|
||||
, _scroll(this, st::emojiScroll)
|
||||
, _tabs { {
|
||||
Tab { TabType::Emoji, object_ptr<EmojiListWidget>(this, controller) },
|
||||
Tab { TabType::Stickers, object_ptr<StickersListWidget>(this, controller) },
|
||||
Tab { TabType::Gifs, object_ptr<GifsListWidget>(this, controller) },
|
||||
} }
|
||||
, _currentTabType(AuthSession::Current().data().emojiPanelTab()) {
|
||||
, _selector(this, controller) {
|
||||
_selector->setRoundRadius(st::buttonRadius);
|
||||
|
||||
resize(QRect(0, 0, st::emojiPanWidth, st::emojiPanMaxHeight).marginsAdded(innerPadding()).size());
|
||||
_width = width();
|
||||
_height = height();
|
||||
|
||||
createTabsSlider();
|
||||
|
||||
_contentMaxHeight = st::emojiPanMaxHeight - marginTop() - marginBottom();
|
||||
_contentMaxHeight = st::emojiPanMaxHeight;
|
||||
_contentHeight = _contentMaxHeight;
|
||||
|
||||
_scroll->resize(st::emojiPanWidth - st::buttonRadius, _contentHeight);
|
||||
_scroll->move(verticalRect().x(), verticalRect().y() + marginTop());
|
||||
setWidgetToScrollArea();
|
||||
_selector->resize(st::emojiPanWidth, _contentHeight);
|
||||
_selector->move(innerRect().topLeft());
|
||||
|
||||
_bottomShadow->setGeometry(_tabsSlider->x(), _scroll->y() + _scroll->height() - st::lineWidth, _tabsSlider->width(), st::lineWidth);
|
||||
_hideTimer.setCallback([this] { hideByTimerOrLeave(); });
|
||||
|
||||
_hideTimer.setSingleShot(true);
|
||||
connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideByTimerOrLeave()));
|
||||
|
||||
for (auto &tab : _tabs) {
|
||||
connect(tab.widget(), &Inner::scrollToY, this, [this, tab = &tab](int y) {
|
||||
if (tab == currentTab()) {
|
||||
scrollToY(y);
|
||||
} else {
|
||||
tab->saveScrollTop(y);
|
||||
}
|
||||
});
|
||||
connect(tab.widget(), &Inner::disableScroll, this, [this, tab = &tab](bool disabled) {
|
||||
if (tab == currentTab()) {
|
||||
_scroll->disableScroll(disabled);
|
||||
}
|
||||
});
|
||||
connect(tab.widget(), SIGNAL(saveConfigDelayed(int)), this, SLOT(onSaveConfigDelayed(int)));
|
||||
}
|
||||
|
||||
connect(stickers(), SIGNAL(scrollUpdated()), this, SLOT(onScroll()));
|
||||
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
|
||||
connect(emoji(), SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr)));
|
||||
connect(stickers(), SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
|
||||
connect(stickers(), SIGNAL(checkForHide()), this, SLOT(onCheckForHide()));
|
||||
connect(gifs(), SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
|
||||
connect(gifs(), SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*)));
|
||||
connect(gifs(), SIGNAL(selected(InlineBots::Result*,UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)));
|
||||
connect(gifs(), &GifsListWidget::cancelled, this, [this] {
|
||||
connect(_selector, SIGNAL(checkForHide()), this, SLOT(onCheckForHide()));
|
||||
connect(_selector, SIGNAL(emojiSelected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr)));
|
||||
connect(_selector, SIGNAL(stickerSelected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
|
||||
connect(_selector, SIGNAL(photoSelected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*)));
|
||||
connect(_selector, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)));
|
||||
connect(_selector, &TabbedSelector::cancelled, this, [this] {
|
||||
hideAnimated();
|
||||
});
|
||||
|
||||
_saveConfigTimer.setSingleShot(true);
|
||||
connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig()));
|
||||
connect(_selector, &TabbedSelector::slideFinished, this, [this] {
|
||||
InvokeQueued(this, [this] {
|
||||
if (_hideAfterSlide) {
|
||||
startOpacityAnimation(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
|
||||
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged()));
|
||||
}
|
||||
|
||||
_topShadow->raise();
|
||||
_bottomShadow->raise();
|
||||
_tabsSlider->raise();
|
||||
|
||||
// setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
|
||||
hideChildren();
|
||||
@ -359,7 +77,7 @@ void TabbedPanel::moveBottom(int bottom) {
|
||||
}
|
||||
|
||||
void TabbedPanel::updateContentHeight() {
|
||||
auto addedHeight = innerPadding().top() + marginTop() + marginBottom() + innerPadding().bottom();
|
||||
auto addedHeight = innerPadding().top() + innerPadding().bottom();
|
||||
auto wantedContentHeight = qRound(st::emojiPanHeightRatio * _bottom) - addedHeight;
|
||||
auto contentHeight = snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight);
|
||||
auto resultTop = _bottom - addedHeight - contentHeight;
|
||||
@ -371,25 +89,10 @@ void TabbedPanel::updateContentHeight() {
|
||||
auto was = _contentHeight;
|
||||
_contentHeight = contentHeight;
|
||||
|
||||
resize(QRect(0, 0, innerRect().width(), marginTop() + _contentHeight + marginBottom()).marginsAdded(innerPadding()).size());
|
||||
_height = height();
|
||||
resize(QRect(0, 0, innerRect().width(), _contentHeight).marginsAdded(innerPadding()).size());
|
||||
move(x(), resultTop);
|
||||
|
||||
if (was > _contentHeight) {
|
||||
_scroll->resize(_scroll->width(), _contentHeight);
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollTop + _contentHeight);
|
||||
} else {
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollTop + _contentHeight);
|
||||
_scroll->resize(_scroll->width(), _contentHeight);
|
||||
}
|
||||
_bottomShadow->setGeometry(_tabsSlider->x(), _scroll->y() + _scroll->height() - st::lineWidth, _tabsSlider->width(), st::lineWidth);
|
||||
|
||||
_footerTop = innerRect().y() + innerRect().height() - st::emojiCategory.height;
|
||||
for (auto &tab : _tabs) {
|
||||
tab.footer()->move(_tabsSlider->x(), _footerTop);
|
||||
}
|
||||
_selector->resize(innerRect().width(), _contentHeight);
|
||||
|
||||
update();
|
||||
}
|
||||
@ -400,14 +103,6 @@ void TabbedPanel::onWndActiveChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedPanel::onSaveConfig() {
|
||||
Local::writeUserSettings();
|
||||
}
|
||||
|
||||
void TabbedPanel::onSaveConfigDelayed(int delay) {
|
||||
_saveConfigTimer.start(delay);
|
||||
}
|
||||
|
||||
void TabbedPanel::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
@ -416,13 +111,12 @@ void TabbedPanel::paintEvent(QPaintEvent *e) {
|
||||
// This call can finish _a_show animation and destroy _showAnimation.
|
||||
auto opacityAnimating = _a_opacity.animating(ms);
|
||||
|
||||
auto switching = (_slideAnimation != nullptr);
|
||||
auto showAnimating = _a_show.animating(ms);
|
||||
if (_showAnimation && !showAnimating) {
|
||||
_showAnimation.reset();
|
||||
if (!switching && !opacityAnimating) {
|
||||
showAll();
|
||||
currentTab()->widget()->afterShown();
|
||||
if (!opacityAnimating) {
|
||||
showChildren();
|
||||
_selector->afterShown();
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,64 +130,12 @@ void TabbedPanel::paintEvent(QPaintEvent *e) {
|
||||
p.drawPixmap(0, 0, _cache);
|
||||
} else if (_hiding || isHidden()) {
|
||||
hideFinished();
|
||||
} else if (switching) {
|
||||
paintSlideFrame(p, ms);
|
||||
if (!_a_slide.animating()) {
|
||||
_slideAnimation.reset();
|
||||
if (!opacityAnimating) {
|
||||
showAll();
|
||||
currentTab()->widget()->afterShown();
|
||||
}
|
||||
InvokeQueued(this, [this] {
|
||||
if (_hideAfterSlide && !_a_slide.animating()) {
|
||||
startOpacityAnimation(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!_cache.isNull()) _cache = QPixmap();
|
||||
if (!_inComplrexGrab) Ui::Shadow::paint(p, innerRect(), width(), st::emojiPanAnimation.shadow);
|
||||
paintContent(p);
|
||||
Ui::Shadow::paint(p, innerRect(), width(), st::emojiPanAnimation.shadow);
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedPanel::paintSlideFrame(Painter &p, TimeMs ms) {
|
||||
Ui::Shadow::paint(p, innerRect(), width(), st::emojiPanAnimation.shadow);
|
||||
|
||||
auto inner = innerRect();
|
||||
auto topPart = QRect(inner.x(), inner.y(), inner.width(), _tabsSlider->height() + st::buttonRadius);
|
||||
App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, App::RectPart::TopFull | App::RectPart::NoTopBottom);
|
||||
|
||||
auto slideDt = _a_slide.current(ms, 1.);
|
||||
_slideAnimation->paintFrame(p, slideDt, _a_opacity.current(_hiding ? 0. : 1.));
|
||||
}
|
||||
|
||||
void TabbedPanel::paintContent(Painter &p) {
|
||||
auto inner = innerRect();
|
||||
auto topPart = QRect(inner.x(), inner.y(), inner.width(), _tabsSlider->height() + st::buttonRadius);
|
||||
App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, App::RectPart::TopFull | App::RectPart::NoTopBottom);
|
||||
|
||||
auto showSectionIcons = (_currentTabType != TabType::Gifs);
|
||||
auto bottomPart = QRect(inner.x(), _footerTop - st::buttonRadius, inner.width(), st::emojiCategory.height + st::buttonRadius);
|
||||
auto &bottomBg = showSectionIcons ? st::emojiPanCategories : st::emojiPanBg;
|
||||
auto bottomParts = App::RectPart::NoTopBottom | App::RectPart::BottomFull;
|
||||
App::roundRect(p, bottomPart, bottomBg, ImageRoundRadius::Small, bottomParts);
|
||||
|
||||
auto horizontal = horizontalRect();
|
||||
auto sidesTop = horizontal.y();
|
||||
auto sidesHeight = _scroll->y() + _scroll->height() - sidesTop;
|
||||
p.fillRect(myrtlrect(inner.x() + inner.width() - st::emojiScroll.width, sidesTop, st::emojiScroll.width, sidesHeight), st::emojiPanBg);
|
||||
p.fillRect(myrtlrect(inner.x(), sidesTop, st::buttonRadius, sidesHeight), st::emojiPanBg);
|
||||
}
|
||||
|
||||
int TabbedPanel::marginTop() const {
|
||||
return _tabsSlider->height() - st::lineWidth;
|
||||
}
|
||||
|
||||
int TabbedPanel::marginBottom() const {
|
||||
return st::emojiCategory.height;
|
||||
}
|
||||
|
||||
void TabbedPanel::moveByBottom() {
|
||||
moveToRight(0, y());
|
||||
updateContentHeight();
|
||||
@ -504,7 +146,7 @@ void TabbedPanel::enterEventHook(QEvent *e) {
|
||||
}
|
||||
|
||||
bool TabbedPanel::preventAutoHide() const {
|
||||
return stickers()->preventAutoHide();
|
||||
return _selector->preventAutoHide();
|
||||
}
|
||||
|
||||
void TabbedPanel::leaveEventHook(QEvent *e) {
|
||||
@ -515,7 +157,7 @@ void TabbedPanel::leaveEventHook(QEvent *e) {
|
||||
if (_a_show.animating(ms) || _a_opacity.animating(ms)) {
|
||||
hideAnimated();
|
||||
} else {
|
||||
_hideTimer.start(300);
|
||||
_hideTimer.callOnce(kHideTimeoutMs);
|
||||
}
|
||||
return TWidget::leaveEventHook(e);
|
||||
}
|
||||
@ -533,24 +175,21 @@ void TabbedPanel::otherLeave() {
|
||||
if (_a_opacity.animating(ms)) {
|
||||
hideByTimerOrLeave();
|
||||
} else {
|
||||
_hideTimer.start(0);
|
||||
_hideTimer.callOnce(0);
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedPanel::hideFast() {
|
||||
if (isHidden()) return;
|
||||
|
||||
_hideTimer.stop();
|
||||
_hideTimer.cancel();
|
||||
_hiding = false;
|
||||
_a_opacity.finish();
|
||||
hideFinished();
|
||||
}
|
||||
|
||||
void TabbedPanel::refreshStickers() {
|
||||
stickers()->refreshStickers();
|
||||
if (isHidden() || _currentTabType != TabType::Stickers) {
|
||||
stickers()->preloadImages();
|
||||
}
|
||||
_selector->refreshStickers();
|
||||
}
|
||||
|
||||
void TabbedPanel::opacityAnimationCallback() {
|
||||
@ -559,9 +198,9 @@ void TabbedPanel::opacityAnimationCallback() {
|
||||
if (_hiding) {
|
||||
_hiding = false;
|
||||
hideFinished();
|
||||
} else if (!_a_show.animating() && !_a_slide.animating()) {
|
||||
showAll();
|
||||
currentTab()->widget()->afterShown();
|
||||
} else if (!_a_show.animating()) {
|
||||
showChildren();
|
||||
_selector->afterShown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -577,10 +216,8 @@ void TabbedPanel::prepareCache() {
|
||||
|
||||
auto showAnimation = base::take(_a_show);
|
||||
auto showAnimationData = base::take(_showAnimation);
|
||||
auto slideAnimation = base::take(_slideAnimation);
|
||||
showAll();
|
||||
showChildren();
|
||||
_cache = myGrab(this);
|
||||
_slideAnimation = base::take(slideAnimation);
|
||||
_showAnimation = base::take(showAnimationData);
|
||||
_a_show = base::take(showAnimation);
|
||||
if (_a_show.animating()) {
|
||||
@ -589,8 +226,8 @@ void TabbedPanel::prepareCache() {
|
||||
}
|
||||
|
||||
void TabbedPanel::startOpacityAnimation(bool hiding) {
|
||||
if (!_scroll->isHidden()) {
|
||||
currentTab()->widget()->beforeHiding();
|
||||
if (!_selector->isHidden()) {
|
||||
_selector->beforeHiding();
|
||||
}
|
||||
_hiding = false;
|
||||
prepareCache();
|
||||
@ -601,7 +238,7 @@ void TabbedPanel::startOpacityAnimation(bool hiding) {
|
||||
|
||||
void TabbedPanel::startShowAnimation() {
|
||||
if (!_a_show.animating()) {
|
||||
auto image = grabForComplexAnimation(GrabType::Panel);
|
||||
auto image = grabForAnimation();
|
||||
|
||||
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight);
|
||||
auto inner = rect().marginsRemoved(st::emojiPanMargins);
|
||||
@ -614,32 +251,23 @@ void TabbedPanel::startShowAnimation() {
|
||||
_a_show.start([this] { update(); }, 0., 1., st::emojiPanShowDuration);
|
||||
}
|
||||
|
||||
QImage TabbedPanel::grabForComplexAnimation(GrabType type) {
|
||||
QImage TabbedPanel::grabForAnimation() {
|
||||
auto cache = base::take(_cache);
|
||||
auto opacityAnimation = base::take(_a_opacity);
|
||||
auto slideAnimationData = base::take(_slideAnimation);
|
||||
auto slideAnimation = base::take(_a_slide);
|
||||
auto showAnimationData = base::take(_showAnimation);
|
||||
auto showAnimation = base::take(_a_show);
|
||||
|
||||
showAll();
|
||||
if (type == GrabType::Slide) {
|
||||
_topShadow->hide();
|
||||
_tabsSlider->hide();
|
||||
}
|
||||
showChildren();
|
||||
myEnsureResized(this);
|
||||
myEnsureResized(_selector);
|
||||
|
||||
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
result.fill(Qt::transparent);
|
||||
_inComplrexGrab = true;
|
||||
render(&result);
|
||||
_inComplrexGrab = false;
|
||||
_selector->render(&result, _selector->geometry().topLeft());
|
||||
|
||||
_a_show = base::take(showAnimation);
|
||||
_showAnimation = base::take(showAnimationData);
|
||||
_a_slide = base::take(slideAnimation);
|
||||
_slideAnimation = base::take(slideAnimationData);
|
||||
_a_opacity = base::take(opacityAnimation);
|
||||
_cache = base::take(_cache);
|
||||
|
||||
@ -650,8 +278,8 @@ void TabbedPanel::hideAnimated() {
|
||||
if (isHidden()) return;
|
||||
if (_hiding) return;
|
||||
|
||||
_hideTimer.stop();
|
||||
if (_a_slide.animating()) {
|
||||
_hideTimer.cancel();
|
||||
if (_selector->isSliding()) {
|
||||
_hideAfterSlide = true;
|
||||
} else {
|
||||
startOpacityAnimation(true);
|
||||
@ -662,32 +290,22 @@ TabbedPanel::~TabbedPanel() = default;
|
||||
|
||||
void TabbedPanel::hideFinished() {
|
||||
hide();
|
||||
for (auto &tab : _tabs) {
|
||||
tab.widget()->panelHideFinished();
|
||||
}
|
||||
_selector->hideFinished();
|
||||
_a_show.finish();
|
||||
_showAnimation.reset();
|
||||
_a_slide.finish();
|
||||
_slideAnimation.reset();
|
||||
_cache = QPixmap();
|
||||
_hiding = false;
|
||||
|
||||
scrollToY(0);
|
||||
}
|
||||
|
||||
void TabbedPanel::showAnimated() {
|
||||
_hideTimer.stop();
|
||||
_hideTimer.cancel();
|
||||
_hideAfterSlide = false;
|
||||
showStarted();
|
||||
}
|
||||
|
||||
void TabbedPanel::showStarted() {
|
||||
if (isHidden()) {
|
||||
emit updateStickers();
|
||||
currentTab()->widget()->refreshRecent();
|
||||
currentTab()->widget()->preloadImages();
|
||||
_a_slide.finish();
|
||||
_slideAnimation.reset();
|
||||
_selector->showStarted();
|
||||
moveByBottom();
|
||||
show();
|
||||
startShowAnimation();
|
||||
@ -712,41 +330,18 @@ bool TabbedPanel::eventFilter(QObject *obj, QEvent *e) {
|
||||
}
|
||||
|
||||
void TabbedPanel::stickersInstalled(uint64 setId) {
|
||||
_tabsSlider->setActiveSection(static_cast<int>(TabType::Stickers));
|
||||
_selector->stickersInstalled(setId);
|
||||
if (isHidden()) {
|
||||
moveByBottom();
|
||||
startShowAnimation();
|
||||
show();
|
||||
}
|
||||
showAll();
|
||||
stickers()->showStickerSet(setId);
|
||||
updateContentHeight();
|
||||
showChildren();
|
||||
showAnimated();
|
||||
}
|
||||
|
||||
void TabbedPanel::setInlineQueryPeer(PeerData *peer) {
|
||||
gifs()->setInlineQueryPeer(peer);
|
||||
}
|
||||
|
||||
void TabbedPanel::showAll() {
|
||||
currentTab()->footer()->show();
|
||||
_scroll->show();
|
||||
_topShadow->show();
|
||||
_bottomShadow->setVisible(_currentTabType == TabType::Gifs);
|
||||
_tabsSlider->show();
|
||||
}
|
||||
|
||||
void TabbedPanel::hideForSliding() {
|
||||
hideChildren();
|
||||
_tabsSlider->show();
|
||||
_topShadow->show();
|
||||
currentTab()->widget()->clearSelection();
|
||||
}
|
||||
|
||||
void TabbedPanel::onScroll() {
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
auto scrollBottom = scrollTop + _scroll->height();
|
||||
currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollBottom);
|
||||
_selector->setInlineQueryPeer(peer);
|
||||
}
|
||||
|
||||
style::margins TabbedPanel::innerPadding() const {
|
||||
@ -765,101 +360,9 @@ QRect TabbedPanel::verticalRect() const {
|
||||
return innerRect().marginsRemoved(style::margins(st::buttonRadius, 0, st::buttonRadius, 0));
|
||||
}
|
||||
|
||||
void TabbedPanel::createTabsSlider() {
|
||||
auto sections = QStringList();
|
||||
sections.push_back(lang(lng_switch_emoji).toUpper());
|
||||
sections.push_back(lang(lng_switch_stickers).toUpper());
|
||||
sections.push_back(lang(lng_switch_gifs).toUpper());
|
||||
_tabsSlider->setSections(sections);
|
||||
|
||||
_tabsSlider->setActiveSectionFast(static_cast<int>(_currentTabType));
|
||||
_tabsSlider->setSectionActivatedCallback([this] {
|
||||
switchTab();
|
||||
});
|
||||
|
||||
_tabsSlider->resizeToWidth(innerRect().width());
|
||||
_tabsSlider->moveToLeft(innerRect().x(), innerRect().y());
|
||||
_topShadow->setGeometry(_tabsSlider->x(), _tabsSlider->bottomNoMargins() - st::lineWidth, _tabsSlider->width(), st::lineWidth);
|
||||
}
|
||||
|
||||
void TabbedPanel::switchTab() {
|
||||
auto tab = _tabsSlider->activeSection();
|
||||
t_assert(tab >= 0 && tab < Tab::kCount);
|
||||
auto newTabType = static_cast<TabType>(tab);
|
||||
if (_currentTabType == newTabType) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto wasTab = _currentTabType;
|
||||
currentTab()->saveScrollTop();
|
||||
|
||||
if (!_scroll->isHidden()) {
|
||||
currentTab()->widget()->beforeHiding();
|
||||
}
|
||||
|
||||
auto wasCache = grabForComplexAnimation(GrabType::Slide);
|
||||
|
||||
auto widget = _scroll->takeWidget<Inner>();
|
||||
widget->setParent(this);
|
||||
widget->hide();
|
||||
currentTab()->footer()->hide();
|
||||
currentTab()->returnWidget(std::move(widget));
|
||||
|
||||
_currentTabType = newTabType;
|
||||
currentTab()->widget()->refreshRecent();
|
||||
currentTab()->widget()->preloadImages();
|
||||
setWidgetToScrollArea();
|
||||
|
||||
auto nowCache = grabForComplexAnimation(GrabType::Slide);
|
||||
|
||||
auto direction = (wasTab > _currentTabType) ? SlideAnimation::Direction::LeftToRight : SlideAnimation::Direction::RightToLeft;
|
||||
if (direction == SlideAnimation::Direction::LeftToRight) {
|
||||
std::swap(wasCache, nowCache);
|
||||
}
|
||||
_slideAnimation = std::make_unique<SlideAnimation>();
|
||||
auto inner = rect().marginsRemoved(st::emojiPanMargins);
|
||||
auto slidingRect = QRect(_tabsSlider->x() * cIntRetinaFactor(), _scroll->y() * cIntRetinaFactor(), _tabsSlider->width() * cIntRetinaFactor(), (inner.y() + inner.height() - _scroll->y()) * cIntRetinaFactor());
|
||||
_slideAnimation->setFinalImages(direction, std::move(wasCache), std::move(nowCache), slidingRect);
|
||||
auto corners = App::cornersMask(ImageRoundRadius::Small);
|
||||
_slideAnimation->setCornerMasks(QImage(*corners[0]), QImage(*corners[1]), QImage(*corners[2]), QImage(*corners[3]));
|
||||
_slideAnimation->start();
|
||||
|
||||
hideForSliding();
|
||||
|
||||
getTab(wasTab)->widget()->hideFinished();
|
||||
|
||||
_a_slide.start([this] { update(); }, 0., 1., st::emojiPanSlideDuration, anim::linear);
|
||||
update();
|
||||
|
||||
AuthSession::Current().data().setEmojiPanelTab(_currentTabType);
|
||||
onSaveConfigDelayed(kSaveChosenTabTimeout);
|
||||
}
|
||||
|
||||
gsl::not_null<EmojiListWidget*> TabbedPanel::emoji() const {
|
||||
return static_cast<EmojiListWidget*>(getTab(TabType::Emoji)->widget().get());
|
||||
}
|
||||
|
||||
gsl::not_null<StickersListWidget*> TabbedPanel::stickers() const {
|
||||
return static_cast<StickersListWidget*>(getTab(TabType::Stickers)->widget().get());
|
||||
}
|
||||
|
||||
gsl::not_null<GifsListWidget*> TabbedPanel::gifs() const {
|
||||
return static_cast<GifsListWidget*>(getTab(TabType::Gifs)->widget().get());
|
||||
}
|
||||
|
||||
void TabbedPanel::setWidgetToScrollArea() {
|
||||
_scroll->setOwnedWidget(currentTab()->takeWidget());
|
||||
_scroll->disableScroll(false);
|
||||
currentTab()->widget()->moveToLeft(0, 0);
|
||||
currentTab()->widget()->show();
|
||||
scrollToY(currentTab()->getScrollTop());
|
||||
updateContentHeight();
|
||||
onScroll();
|
||||
}
|
||||
|
||||
void TabbedPanel::onCheckForHide() {
|
||||
if (!rect().contains(mapFromGlobal(QCursor::pos()))) {
|
||||
_hideTimer.start(3000);
|
||||
_hideTimer.callOnce(kDelayedHideTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -872,44 +375,4 @@ bool TabbedPanel::overlaps(const QRect &globalRect) const {
|
||||
|| inner.marginsRemoved(QMargins(0, st::buttonRadius, 0, st::buttonRadius)).contains(testRect);
|
||||
}
|
||||
|
||||
void TabbedPanel::scrollToY(int y) {
|
||||
_scroll->scrollToY(y);
|
||||
|
||||
// Qt render glitch workaround, shadow sometimes disappears if we just scroll to y.
|
||||
_topShadow->update();
|
||||
}
|
||||
|
||||
TabbedPanel::Inner::Inner(QWidget *parent, gsl::not_null<Window::Controller*> controller) : TWidget(parent)
|
||||
, _controller(controller) {
|
||||
}
|
||||
|
||||
void TabbedPanel::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
|
||||
auto oldVisibleHeight = getVisibleBottom() - getVisibleTop();
|
||||
_visibleTop = visibleTop;
|
||||
_visibleBottom = visibleBottom;
|
||||
auto visibleHeight = getVisibleBottom() - getVisibleTop();
|
||||
if (visibleHeight != oldVisibleHeight) {
|
||||
resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight());
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedPanel::Inner::hideFinished() {
|
||||
processHideFinished();
|
||||
if (auto footer = getFooter()) {
|
||||
footer->processHideFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedPanel::Inner::panelHideFinished() {
|
||||
hideFinished();
|
||||
processPanelHideFinished();
|
||||
if (auto footer = getFooter()) {
|
||||
footer->processPanelHideFinished();
|
||||
}
|
||||
}
|
||||
|
||||
TabbedPanel::InnerFooter::InnerFooter(QWidget *parent) : TWidget(parent) {
|
||||
resize(st::emojiPanWidth, st::emojiCategory.height);
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
@ -24,27 +24,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/effects/panel_animation.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "auth_session.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace InlineBots {
|
||||
class Result;
|
||||
} // namespace InlineBots
|
||||
|
||||
namespace Ui {
|
||||
class PlainShadow;
|
||||
class ScrollArea;
|
||||
class RippleAnimation;
|
||||
class SettingsSlider;
|
||||
} // namesapce Ui
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
class EmojiListWidget;
|
||||
class StickersListWidget;
|
||||
class GifsListWidget;
|
||||
class TabbedSelector;
|
||||
|
||||
class TabbedPanel : public TWidget {
|
||||
Q_OBJECT
|
||||
@ -67,10 +59,9 @@ public:
|
||||
void showAnimated();
|
||||
void hideAnimated();
|
||||
|
||||
~TabbedPanel();
|
||||
void refreshStickers();
|
||||
|
||||
class Inner;
|
||||
class InnerFooter;
|
||||
~TabbedPanel();
|
||||
|
||||
protected:
|
||||
void enterEventHook(QEvent *e) override;
|
||||
@ -81,20 +72,10 @@ protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
bool eventFilter(QObject *obj, QEvent *e) override;
|
||||
|
||||
public slots:
|
||||
void refreshStickers();
|
||||
|
||||
private slots:
|
||||
void hideByTimerOrLeave();
|
||||
|
||||
void onWndActiveChanged();
|
||||
void onScroll();
|
||||
|
||||
void onCheckForHide();
|
||||
|
||||
void onSaveConfig();
|
||||
void onSaveConfigDelayed(int delay);
|
||||
|
||||
signals:
|
||||
void emojiSelected(EmojiPtr emoji);
|
||||
void stickerSelected(DocumentData *sticker);
|
||||
@ -104,48 +85,8 @@ signals:
|
||||
void updateStickers();
|
||||
|
||||
private:
|
||||
using TabType = EmojiPanelTab;
|
||||
class Tab {
|
||||
public:
|
||||
static constexpr auto kCount = 3;
|
||||
|
||||
Tab(TabType type, object_ptr<Inner> widget);
|
||||
|
||||
object_ptr<Inner> takeWidget();
|
||||
void returnWidget(object_ptr<Inner> widget);
|
||||
|
||||
TabType type() const {
|
||||
return _type;
|
||||
}
|
||||
gsl::not_null<Inner*> widget() const {
|
||||
return _weak;
|
||||
}
|
||||
gsl::not_null<InnerFooter*> footer() const {
|
||||
return _footer;
|
||||
}
|
||||
|
||||
void saveScrollTop();
|
||||
void saveScrollTop(int scrollTop) {
|
||||
_scrollTop = scrollTop;
|
||||
}
|
||||
int getScrollTop() const {
|
||||
return _scrollTop;
|
||||
}
|
||||
|
||||
private:
|
||||
TabType _type = TabType::Emoji;
|
||||
object_ptr<Inner> _widget = { nullptr };
|
||||
QPointer<Inner> _weak;
|
||||
object_ptr<InnerFooter> _footer;
|
||||
int _scrollTop = 0;
|
||||
|
||||
};
|
||||
|
||||
int marginTop() const;
|
||||
int marginBottom() const;
|
||||
void hideByTimerOrLeave();
|
||||
void moveByBottom();
|
||||
void paintSlideFrame(Painter &p, TimeMs ms);
|
||||
void paintContent(Painter &p);
|
||||
|
||||
style::margins innerPadding() const;
|
||||
|
||||
@ -160,17 +101,11 @@ private:
|
||||
// This one is allowed to be not rounded.
|
||||
QRect verticalRect() const;
|
||||
|
||||
enum class GrabType {
|
||||
Panel,
|
||||
Slide,
|
||||
};
|
||||
QImage grabForComplexAnimation(GrabType type);
|
||||
QImage grabForAnimation();
|
||||
void startShowAnimation();
|
||||
void startOpacityAnimation(bool hiding);
|
||||
void prepareCache();
|
||||
|
||||
void scrollToY(int y);
|
||||
|
||||
void opacityAnimationCallback();
|
||||
|
||||
void hideFinished();
|
||||
@ -179,35 +114,11 @@ private:
|
||||
bool preventAutoHide() const;
|
||||
void updateContentHeight();
|
||||
|
||||
void showAll();
|
||||
void hideForSliding();
|
||||
|
||||
void setWidgetToScrollArea();
|
||||
void createTabsSlider();
|
||||
void switchTab();
|
||||
gsl::not_null<Tab*> getTab(TabType type) {
|
||||
return &_tabs[static_cast<int>(type)];
|
||||
}
|
||||
gsl::not_null<const Tab*> getTab(TabType type) const {
|
||||
return &_tabs[static_cast<int>(type)];
|
||||
}
|
||||
gsl::not_null<Tab*> currentTab() {
|
||||
return getTab(_currentTabType);
|
||||
}
|
||||
gsl::not_null<const Tab*> currentTab() const {
|
||||
return getTab(_currentTabType);
|
||||
}
|
||||
gsl::not_null<EmojiListWidget*> emoji() const;
|
||||
gsl::not_null<StickersListWidget*> stickers() const;
|
||||
gsl::not_null<GifsListWidget*> gifs() const;
|
||||
object_ptr<TabbedSelector> _selector;
|
||||
|
||||
int _contentMaxHeight = 0;
|
||||
int _contentHeight = 0;
|
||||
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
int _bottom = 0;
|
||||
int _footerTop = 0;
|
||||
|
||||
std::unique_ptr<Ui::PanelAnimation> _showAnimation;
|
||||
Animation _a_show;
|
||||
@ -216,88 +127,7 @@ private:
|
||||
bool _hideAfterSlide = false;
|
||||
QPixmap _cache;
|
||||
Animation _a_opacity;
|
||||
QTimer _hideTimer;
|
||||
bool _inComplrexGrab = false;
|
||||
|
||||
class SlideAnimation;
|
||||
std::unique_ptr<SlideAnimation> _slideAnimation;
|
||||
Animation _a_slide;
|
||||
|
||||
object_ptr<Ui::SettingsSlider> _tabsSlider = { nullptr };
|
||||
object_ptr<Ui::PlainShadow> _topShadow;
|
||||
object_ptr<Ui::PlainShadow> _bottomShadow;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
std::array<Tab, Tab::kCount> _tabs;
|
||||
TabType _currentTabType = TabType::Emoji;
|
||||
|
||||
QTimer _saveConfigTimer;
|
||||
|
||||
};
|
||||
|
||||
class TabbedPanel::Inner : public TWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Inner(QWidget *parent, gsl::not_null<Window::Controller*> controller);
|
||||
|
||||
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
|
||||
|
||||
int getVisibleTop() const {
|
||||
return _visibleTop;
|
||||
}
|
||||
int getVisibleBottom() const {
|
||||
return _visibleBottom;
|
||||
}
|
||||
|
||||
virtual void refreshRecent() = 0;
|
||||
virtual void preloadImages() {
|
||||
}
|
||||
void hideFinished();
|
||||
void panelHideFinished();
|
||||
virtual void clearSelection() = 0;
|
||||
|
||||
virtual void afterShown() {
|
||||
}
|
||||
virtual void beforeHiding() {
|
||||
}
|
||||
|
||||
virtual object_ptr<InnerFooter> createFooter() = 0;
|
||||
|
||||
signals:
|
||||
void scrollToY(int y);
|
||||
void disableScroll(bool disabled);
|
||||
void saveConfigDelayed(int delay);
|
||||
|
||||
protected:
|
||||
gsl::not_null<Window::Controller*> controller() const {
|
||||
return _controller;
|
||||
}
|
||||
|
||||
virtual int countHeight() = 0;
|
||||
virtual InnerFooter *getFooter() const = 0;
|
||||
virtual void processHideFinished() {
|
||||
}
|
||||
virtual void processPanelHideFinished() {
|
||||
}
|
||||
|
||||
private:
|
||||
gsl::not_null<Window::Controller*> _controller;
|
||||
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
|
||||
};
|
||||
|
||||
class TabbedPanel::InnerFooter : public TWidget {
|
||||
public:
|
||||
InnerFooter(QWidget *parent);
|
||||
|
||||
protected:
|
||||
virtual void processHideFinished() {
|
||||
}
|
||||
virtual void processPanelHideFinished() {
|
||||
}
|
||||
friend class Inner;
|
||||
base::Timer _hideTimer;
|
||||
|
||||
};
|
||||
|
||||
|
22
Telegram/SourceFiles/chat_helpers/tabbed_section.cpp
Normal file
22
Telegram/SourceFiles/chat_helpers/tabbed_section.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "chat_helpers/tabbed_section.h"
|
||||
|
21
Telegram/SourceFiles/chat_helpers/tabbed_section.h
Normal file
21
Telegram/SourceFiles/chat_helpers/tabbed_section.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
660
Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
Normal file
660
Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp
Normal file
@ -0,0 +1,660 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
|
||||
#include "chat_helpers/emoji_list_widget.h"
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
#include "chat_helpers/gifs_list_widget.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/discrete_sliders.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "lang.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSaveChosenTabTimeout = 1000;
|
||||
|
||||
} // namespace
|
||||
|
||||
class TabbedSelector::SlideAnimation : public Ui::RoundShadowAnimation {
|
||||
public:
|
||||
enum class Direction {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
};
|
||||
void setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner);
|
||||
|
||||
void start();
|
||||
void paintFrame(QPainter &p, float64 dt, float64 opacity);
|
||||
|
||||
private:
|
||||
Direction _direction = Direction::LeftToRight;
|
||||
QPixmap _leftImage, _rightImage;
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
int _innerLeft = 0;
|
||||
int _innerTop = 0;
|
||||
int _innerRight = 0;
|
||||
int _innerBottom = 0;
|
||||
int _innerWidth = 0;
|
||||
int _innerHeight = 0;
|
||||
|
||||
int _painterInnerLeft = 0;
|
||||
int _painterInnerTop = 0;
|
||||
int _painterInnerWidth = 0;
|
||||
int _painterInnerBottom = 0;
|
||||
int _painterCategoriesTop = 0;
|
||||
int _painterInnerHeight = 0;
|
||||
int _painterInnerRight = 0;
|
||||
|
||||
int _frameIntsPerLineAdd = 0;
|
||||
|
||||
};
|
||||
|
||||
void TabbedSelector::SlideAnimation::setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner) {
|
||||
Expects(!started());
|
||||
_direction = direction;
|
||||
_leftImage = QPixmap::fromImage(std::move(left).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
|
||||
_rightImage = QPixmap::fromImage(std::move(right).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly);
|
||||
|
||||
t_assert(!_leftImage.isNull());
|
||||
t_assert(!_rightImage.isNull());
|
||||
_width = _leftImage.width();
|
||||
_height = _rightImage.height();
|
||||
t_assert(!(_width % cIntRetinaFactor()));
|
||||
t_assert(!(_height % cIntRetinaFactor()));
|
||||
t_assert(_leftImage.devicePixelRatio() == _rightImage.devicePixelRatio());
|
||||
t_assert(_rightImage.width() == _width);
|
||||
t_assert(_rightImage.height() == _height);
|
||||
t_assert(QRect(0, 0, _width, _height).contains(inner));
|
||||
_innerLeft = inner.x();
|
||||
_innerTop = inner.y();
|
||||
_innerWidth = inner.width();
|
||||
_innerHeight = inner.height();
|
||||
t_assert(!(_innerLeft % cIntRetinaFactor()));
|
||||
t_assert(!(_innerTop % cIntRetinaFactor()));
|
||||
t_assert(!(_innerWidth % cIntRetinaFactor()));
|
||||
t_assert(!(_innerHeight % cIntRetinaFactor()));
|
||||
_innerRight = _innerLeft + _innerWidth;
|
||||
_innerBottom = _innerTop + _innerHeight;
|
||||
|
||||
_painterInnerLeft = _innerLeft / cIntRetinaFactor();
|
||||
_painterInnerTop = _innerTop / cIntRetinaFactor();
|
||||
_painterInnerRight = _innerRight / cIntRetinaFactor();
|
||||
_painterInnerBottom = _innerBottom / cIntRetinaFactor();
|
||||
_painterInnerWidth = _innerWidth / cIntRetinaFactor();
|
||||
_painterInnerHeight = _innerHeight / cIntRetinaFactor();
|
||||
_painterCategoriesTop = _painterInnerBottom - st::emojiCategory.height;
|
||||
}
|
||||
|
||||
void TabbedSelector::SlideAnimation::start() {
|
||||
t_assert(!_leftImage.isNull());
|
||||
t_assert(!_rightImage.isNull());
|
||||
RoundShadowAnimation::start(_width, _height, _leftImage.devicePixelRatio());
|
||||
auto checkCorner = [this](const Corner &corner) {
|
||||
if (!corner.valid()) return;
|
||||
t_assert(corner.width <= _innerWidth);
|
||||
t_assert(corner.height <= _innerHeight);
|
||||
};
|
||||
checkCorner(_topLeft);
|
||||
checkCorner(_topRight);
|
||||
checkCorner(_bottomLeft);
|
||||
checkCorner(_bottomRight);
|
||||
_frameIntsPerLineAdd = (_width - _innerWidth) + _frameIntsPerLineAdded;
|
||||
}
|
||||
|
||||
void TabbedSelector::SlideAnimation::paintFrame(QPainter &p, float64 dt, float64 opacity) {
|
||||
Expects(started());
|
||||
Expects(dt >= 0.);
|
||||
|
||||
_frameAlpha = anim::interpolate(1, 256, opacity);
|
||||
|
||||
auto frameInts = _frameInts + _innerLeft + _innerTop * _frameIntsPerLine;
|
||||
|
||||
auto leftToRight = (_direction == Direction::LeftToRight);
|
||||
|
||||
auto easeOut = anim::easeOutCirc(1., dt);
|
||||
auto easeIn = anim::easeInCirc(1., dt);
|
||||
|
||||
auto arrivingCoord = anim::interpolate(_innerWidth, 0, easeOut);
|
||||
auto departingCoord = anim::interpolate(0, _innerWidth, easeIn);
|
||||
if (auto decrease = (arrivingCoord % cIntRetinaFactor())) {
|
||||
arrivingCoord -= decrease;
|
||||
}
|
||||
if (auto decrease = (departingCoord % cIntRetinaFactor())) {
|
||||
departingCoord -= decrease;
|
||||
}
|
||||
auto arrivingAlpha = easeIn;
|
||||
auto departingAlpha = 1. - easeOut;
|
||||
auto leftCoord = (leftToRight ? arrivingCoord : departingCoord) * -1;
|
||||
auto leftAlpha = (leftToRight ? arrivingAlpha : departingAlpha);
|
||||
auto rightCoord = (leftToRight ? departingCoord : arrivingCoord);
|
||||
auto rightAlpha = (leftToRight ? departingAlpha : arrivingAlpha);
|
||||
|
||||
// _innerLeft ..(left).. leftTo ..(both).. bothTo ..(none).. noneTo ..(right).. _innerRight
|
||||
auto leftTo = _innerLeft + snap(_innerWidth + leftCoord, 0, _innerWidth);
|
||||
auto rightFrom = _innerLeft + snap(rightCoord, 0, _innerWidth);
|
||||
auto painterRightFrom = rightFrom / cIntRetinaFactor();
|
||||
if (opacity < 1.) {
|
||||
_frame.fill(Qt::transparent);
|
||||
}
|
||||
{
|
||||
Painter p(&_frame);
|
||||
p.setOpacity(opacity);
|
||||
p.fillRect(_painterInnerLeft, _painterInnerTop, _painterInnerWidth, _painterCategoriesTop - _painterInnerTop, st::emojiPanBg);
|
||||
p.fillRect(_painterInnerLeft, _painterCategoriesTop, _painterInnerWidth, _painterInnerBottom - _painterCategoriesTop, st::emojiPanCategories);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
if (leftTo > _innerLeft) {
|
||||
p.setOpacity(opacity * leftAlpha);
|
||||
p.drawPixmap(_painterInnerLeft, _painterInnerTop, _leftImage, _innerLeft - leftCoord, _innerTop, leftTo - _innerLeft, _innerHeight);
|
||||
}
|
||||
if (rightFrom < _innerRight) {
|
||||
p.setOpacity(opacity * rightAlpha);
|
||||
p.drawPixmap(painterRightFrom, _painterInnerTop, _rightImage, _innerLeft, _innerTop, _innerRight - rightFrom, _innerHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw corners
|
||||
//paintCorner(_topLeft, _innerLeft, _innerTop);
|
||||
//paintCorner(_topRight, _innerRight - _topRight.width, _innerTop);
|
||||
paintCorner(_bottomLeft, _innerLeft, _innerBottom - _bottomLeft.height);
|
||||
paintCorner(_bottomRight, _innerRight - _bottomRight.width, _innerBottom - _bottomRight.height);
|
||||
|
||||
// Draw shadow upon the transparent
|
||||
auto outerLeft = _innerLeft;
|
||||
auto outerTop = _innerTop;
|
||||
auto outerRight = _innerRight;
|
||||
auto outerBottom = _innerBottom;
|
||||
if (_shadow.valid()) {
|
||||
outerLeft -= _shadow.extend.left();
|
||||
outerTop -= _shadow.extend.top();
|
||||
outerRight += _shadow.extend.right();
|
||||
outerBottom += _shadow.extend.bottom();
|
||||
}
|
||||
if (cIntRetinaFactor() > 1) {
|
||||
if (auto skipLeft = (outerLeft % cIntRetinaFactor())) {
|
||||
outerLeft -= skipLeft;
|
||||
}
|
||||
if (auto skipTop = (outerTop % cIntRetinaFactor())) {
|
||||
outerTop -= skipTop;
|
||||
}
|
||||
if (auto skipRight = (outerRight % cIntRetinaFactor())) {
|
||||
outerRight += (cIntRetinaFactor() - skipRight);
|
||||
}
|
||||
if (auto skipBottom = (outerBottom % cIntRetinaFactor())) {
|
||||
outerBottom += (cIntRetinaFactor() - skipBottom);
|
||||
}
|
||||
}
|
||||
|
||||
if (opacity == 1.) {
|
||||
// Fill above the frame top with transparent.
|
||||
auto fillTopInts = (_frameInts + outerTop * _frameIntsPerLine + outerLeft);
|
||||
auto fillWidth = (outerRight - outerLeft) * sizeof(uint32);
|
||||
for (auto fillTop = _innerTop - outerTop; fillTop != 0; --fillTop) {
|
||||
memset(fillTopInts, 0, fillWidth);
|
||||
fillTopInts += _frameIntsPerLine;
|
||||
}
|
||||
|
||||
// Fill to the left and to the right of the frame with transparent.
|
||||
auto fillLeft = (_innerLeft - outerLeft) * sizeof(uint32);
|
||||
auto fillRight = (outerRight - _innerRight) * sizeof(uint32);
|
||||
if (fillLeft || fillRight) {
|
||||
auto fillInts = _frameInts + _innerTop * _frameIntsPerLine;
|
||||
for (auto y = _innerTop; y != _innerBottom; ++y) {
|
||||
memset(fillInts + outerLeft, 0, fillLeft);
|
||||
memset(fillInts + _innerRight, 0, fillRight);
|
||||
fillInts += _frameIntsPerLine;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill below the frame bottom with transparent.
|
||||
auto fillBottomInts = (_frameInts + _innerBottom * _frameIntsPerLine + outerLeft);
|
||||
for (auto fillBottom = outerBottom - _innerBottom; fillBottom != 0; --fillBottom) {
|
||||
memset(fillBottomInts, 0, fillWidth);
|
||||
fillBottomInts += _frameIntsPerLine;
|
||||
}
|
||||
}
|
||||
if (_shadow.valid()) {
|
||||
paintShadow(outerLeft, outerTop, outerRight, outerBottom);
|
||||
}
|
||||
|
||||
// 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;
|
||||
//}
|
||||
|
||||
p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop);
|
||||
}
|
||||
|
||||
TabbedSelector::Tab::Tab(TabType type, object_ptr<Inner> widget)
|
||||
: _type(type)
|
||||
, _widget(std::move(widget))
|
||||
, _weak(_widget)
|
||||
, _footer(_widget->createFooter()) {
|
||||
_footer->setParent(_widget->parentWidget());
|
||||
}
|
||||
|
||||
object_ptr<TabbedSelector::Inner> TabbedSelector::Tab::takeWidget() {
|
||||
return std::move(_widget);
|
||||
}
|
||||
|
||||
void TabbedSelector::Tab::returnWidget(object_ptr<Inner> widget) {
|
||||
_widget = std::move(widget);
|
||||
Ensures(_widget == _weak);
|
||||
}
|
||||
|
||||
void TabbedSelector::Tab::saveScrollTop() {
|
||||
_scrollTop = widget()->getVisibleTop();
|
||||
}
|
||||
|
||||
TabbedSelector::TabbedSelector(QWidget *parent, gsl::not_null<Window::Controller*> controller) : TWidget(parent)
|
||||
, _tabsSlider(this, st::emojiTabs)
|
||||
, _topShadow(this, st::shadowFg)
|
||||
, _bottomShadow(this, st::shadowFg)
|
||||
, _scroll(this, st::emojiScroll)
|
||||
, _tabs { {
|
||||
Tab { TabType::Emoji, object_ptr<EmojiListWidget>(this, controller) },
|
||||
Tab { TabType::Stickers, object_ptr<StickersListWidget>(this, controller) },
|
||||
Tab { TabType::Gifs, object_ptr<GifsListWidget>(this, controller) },
|
||||
} }
|
||||
, _currentTabType(AuthSession::Current().data().emojiPanelTab()) {
|
||||
resize(st::emojiPanWidth, st::emojiPanMaxHeight);
|
||||
|
||||
createTabsSlider();
|
||||
|
||||
_scroll->resize(st::emojiPanWidth - st::buttonRadius, height() - marginTop() - marginBottom());
|
||||
_scroll->move(st::buttonRadius, marginTop());
|
||||
setWidgetToScrollArea();
|
||||
|
||||
_bottomShadow->setGeometry(_tabsSlider->x(), _scroll->y() + _scroll->height() - st::lineWidth, _tabsSlider->width(), st::lineWidth);
|
||||
|
||||
for (auto &tab : _tabs) {
|
||||
connect(tab.widget(), &Inner::scrollToY, this, [this, tab = &tab](int y) {
|
||||
if (tab == currentTab()) {
|
||||
scrollToY(y);
|
||||
} else {
|
||||
tab->saveScrollTop(y);
|
||||
}
|
||||
});
|
||||
connect(tab.widget(), &Inner::disableScroll, this, [this, tab = &tab](bool disabled) {
|
||||
if (tab == currentTab()) {
|
||||
_scroll->disableScroll(disabled);
|
||||
}
|
||||
});
|
||||
connect(tab.widget(), SIGNAL(saveConfigDelayed(int)), this, SLOT(onSaveConfigDelayed(int)));
|
||||
}
|
||||
|
||||
connect(stickers(), SIGNAL(scrollUpdated()), this, SLOT(onScroll()));
|
||||
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
|
||||
connect(emoji(), SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr)));
|
||||
connect(stickers(), SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
|
||||
connect(stickers(), SIGNAL(checkForHide()), this, SIGNAL(checkForHide()));
|
||||
connect(gifs(), SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
|
||||
connect(gifs(), SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*)));
|
||||
connect(gifs(), SIGNAL(selected(InlineBots::Result*, UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*, UserData*)));
|
||||
connect(gifs(), SIGNAL(cancelled()), this, SIGNAL(cancelled()));
|
||||
|
||||
_saveConfigTimer.setCallback([this] { Local::writeUserSettings(); });
|
||||
|
||||
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
|
||||
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged()));
|
||||
}
|
||||
|
||||
_topShadow->raise();
|
||||
_bottomShadow->raise();
|
||||
_tabsSlider->raise();
|
||||
|
||||
// setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, false);
|
||||
|
||||
hideChildren();
|
||||
}
|
||||
|
||||
void TabbedSelector::resizeEvent(QResizeEvent *e) {
|
||||
auto contentHeight = height() - marginTop() - marginBottom();
|
||||
if (e->oldSize().height() > height()) {
|
||||
_scroll->resize(_scroll->width(), contentHeight);
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollTop + contentHeight);
|
||||
} else {
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollTop + contentHeight);
|
||||
_scroll->resize(_scroll->width(), contentHeight);
|
||||
}
|
||||
_bottomShadow->setGeometry(_tabsSlider->x(), _scroll->y() + _scroll->height() - st::lineWidth, _tabsSlider->width(), st::lineWidth);
|
||||
|
||||
_footerTop = height() - st::emojiCategory.height;
|
||||
for (auto &tab : _tabs) {
|
||||
tab.footer()->move(_tabsSlider->x(), _footerTop);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void TabbedSelector::onSaveConfigDelayed(int delay) {
|
||||
_saveConfigTimer.callOnce(delay);
|
||||
}
|
||||
|
||||
void TabbedSelector::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
auto ms = getms();
|
||||
|
||||
auto switching = (_slideAnimation != nullptr);
|
||||
if (switching) {
|
||||
paintSlideFrame(p, ms);
|
||||
if (!_a_slide.animating()) {
|
||||
_slideAnimation.reset();
|
||||
showAll();
|
||||
currentTab()->widget()->afterShown();
|
||||
emit slideFinished();
|
||||
}
|
||||
} else {
|
||||
paintContent(p);
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::paintSlideFrame(Painter &p, TimeMs ms) {
|
||||
if (_roundRadius > 0) {
|
||||
auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius);
|
||||
App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, App::RectPart::TopFull | App::RectPart::NoTopBottom);
|
||||
} else {
|
||||
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
|
||||
}
|
||||
|
||||
auto slideDt = _a_slide.current(ms, 1.);
|
||||
_slideAnimation->paintFrame(p, slideDt, 1.);
|
||||
}
|
||||
|
||||
void TabbedSelector::paintContent(Painter &p) {
|
||||
auto showSectionIcons = (_currentTabType != TabType::Gifs);
|
||||
auto &bottomBg = showSectionIcons ? st::emojiPanCategories : st::emojiPanBg;
|
||||
if (_roundRadius > 0) {
|
||||
auto topPart = QRect(0, 0, width(), _tabsSlider->height() + _roundRadius);
|
||||
App::roundRect(p, topPart, st::emojiPanBg, ImageRoundRadius::Small, App::RectPart::TopFull | App::RectPart::NoTopBottom);
|
||||
|
||||
auto bottomPart = QRect(0, _footerTop - _roundRadius, width(), st::emojiCategory.height + _roundRadius);
|
||||
auto bottomParts = App::RectPart::NoTopBottom | App::RectPart::BottomFull;
|
||||
App::roundRect(p, bottomPart, bottomBg, ImageRoundRadius::Small, bottomParts);
|
||||
} else {
|
||||
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
|
||||
p.fillRect(0, _footerTop, width(), st::emojiCategory.height, bottomBg);
|
||||
}
|
||||
|
||||
auto sidesTop = marginTop();
|
||||
auto sidesHeight = height() - sidesTop - marginBottom();
|
||||
p.fillRect(myrtlrect(width() - st::emojiScroll.width, sidesTop, st::emojiScroll.width, sidesHeight), st::emojiPanBg);
|
||||
p.fillRect(myrtlrect(0, sidesTop, st::buttonRadius, sidesHeight), st::emojiPanBg);
|
||||
}
|
||||
|
||||
int TabbedSelector::marginTop() const {
|
||||
return _tabsSlider->height() - st::lineWidth;
|
||||
}
|
||||
|
||||
int TabbedSelector::marginBottom() const {
|
||||
return st::emojiCategory.height;
|
||||
}
|
||||
|
||||
void TabbedSelector::refreshStickers() {
|
||||
stickers()->refreshStickers();
|
||||
if (isHidden() || _currentTabType != TabType::Stickers) {
|
||||
stickers()->preloadImages();
|
||||
}
|
||||
}
|
||||
|
||||
bool TabbedSelector::preventAutoHide() const {
|
||||
return stickers()->preventAutoHide();
|
||||
}
|
||||
|
||||
QImage TabbedSelector::grabForAnimation() {
|
||||
auto slideAnimationData = base::take(_slideAnimation);
|
||||
auto slideAnimation = base::take(_a_slide);
|
||||
|
||||
showAll();
|
||||
_topShadow->hide();
|
||||
_tabsSlider->hide();
|
||||
myEnsureResized(this);
|
||||
|
||||
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
result.fill(Qt::transparent);
|
||||
render(&result);
|
||||
|
||||
_a_slide = base::take(slideAnimation);
|
||||
_slideAnimation = base::take(slideAnimationData);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TabbedSelector::~TabbedSelector() = default;
|
||||
|
||||
void TabbedSelector::hideFinished() {
|
||||
for (auto &tab : _tabs) {
|
||||
tab.widget()->panelHideFinished();
|
||||
}
|
||||
_a_slide.finish();
|
||||
_slideAnimation.reset();
|
||||
|
||||
scrollToY(0);
|
||||
}
|
||||
|
||||
void TabbedSelector::showStarted() {
|
||||
emit updateStickers();
|
||||
currentTab()->widget()->refreshRecent();
|
||||
currentTab()->widget()->preloadImages();
|
||||
_a_slide.finish();
|
||||
_slideAnimation.reset();
|
||||
showAll();
|
||||
}
|
||||
|
||||
void TabbedSelector::beforeHiding() {
|
||||
if (!_scroll->isHidden()) {
|
||||
currentTab()->widget()->beforeHiding();
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::afterShown() {
|
||||
if (!_a_slide.animating()) {
|
||||
showAll();
|
||||
currentTab()->widget()->afterShown();
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::stickersInstalled(uint64 setId) {
|
||||
_tabsSlider->setActiveSection(static_cast<int>(TabType::Stickers));
|
||||
stickers()->showStickerSet(setId);
|
||||
}
|
||||
|
||||
void TabbedSelector::setInlineQueryPeer(PeerData *peer) {
|
||||
gifs()->setInlineQueryPeer(peer);
|
||||
}
|
||||
|
||||
void TabbedSelector::showAll() {
|
||||
currentTab()->footer()->show();
|
||||
_scroll->show();
|
||||
_topShadow->show();
|
||||
_bottomShadow->setVisible(_currentTabType == TabType::Gifs);
|
||||
_tabsSlider->show();
|
||||
}
|
||||
|
||||
void TabbedSelector::hideForSliding() {
|
||||
hideChildren();
|
||||
_tabsSlider->show();
|
||||
_topShadow->show();
|
||||
currentTab()->widget()->clearSelection();
|
||||
}
|
||||
|
||||
void TabbedSelector::onScroll() {
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
auto scrollBottom = scrollTop + _scroll->height();
|
||||
currentTab()->widget()->setVisibleTopBottom(scrollTop, scrollBottom);
|
||||
}
|
||||
|
||||
void TabbedSelector::setRoundRadius(int radius) {
|
||||
_roundRadius = radius;
|
||||
_tabsSlider->setRippleTopRoundRadius(_roundRadius);
|
||||
}
|
||||
|
||||
void TabbedSelector::createTabsSlider() {
|
||||
auto sections = QStringList();
|
||||
sections.push_back(lang(lng_switch_emoji).toUpper());
|
||||
sections.push_back(lang(lng_switch_stickers).toUpper());
|
||||
sections.push_back(lang(lng_switch_gifs).toUpper());
|
||||
_tabsSlider->setSections(sections);
|
||||
|
||||
_tabsSlider->setActiveSectionFast(static_cast<int>(_currentTabType));
|
||||
_tabsSlider->setSectionActivatedCallback([this] {
|
||||
switchTab();
|
||||
});
|
||||
|
||||
_tabsSlider->resizeToWidth(width());
|
||||
_tabsSlider->moveToLeft(0, 0);
|
||||
_topShadow->setGeometry(_tabsSlider->x(), _tabsSlider->bottomNoMargins() - st::lineWidth, _tabsSlider->width(), st::lineWidth);
|
||||
}
|
||||
|
||||
void TabbedSelector::switchTab() {
|
||||
auto tab = _tabsSlider->activeSection();
|
||||
t_assert(tab >= 0 && tab < Tab::kCount);
|
||||
auto newTabType = static_cast<TabType>(tab);
|
||||
if (_currentTabType == newTabType) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto wasTab = _currentTabType;
|
||||
currentTab()->saveScrollTop();
|
||||
|
||||
if (!_scroll->isHidden()) {
|
||||
currentTab()->widget()->beforeHiding();
|
||||
}
|
||||
|
||||
auto wasCache = grabForAnimation();
|
||||
|
||||
auto widget = _scroll->takeWidget<Inner>();
|
||||
widget->setParent(this);
|
||||
widget->hide();
|
||||
currentTab()->footer()->hide();
|
||||
currentTab()->returnWidget(std::move(widget));
|
||||
|
||||
_currentTabType = newTabType;
|
||||
currentTab()->widget()->refreshRecent();
|
||||
currentTab()->widget()->preloadImages();
|
||||
setWidgetToScrollArea();
|
||||
|
||||
auto nowCache = grabForAnimation();
|
||||
|
||||
auto direction = (wasTab > _currentTabType) ? SlideAnimation::Direction::LeftToRight : SlideAnimation::Direction::RightToLeft;
|
||||
if (direction == SlideAnimation::Direction::LeftToRight) {
|
||||
std::swap(wasCache, nowCache);
|
||||
}
|
||||
_slideAnimation = std::make_unique<SlideAnimation>();
|
||||
auto slidingRect = QRect(_tabsSlider->x() * cIntRetinaFactor(), _scroll->y() * cIntRetinaFactor(), _tabsSlider->width() * cIntRetinaFactor(), (height() - _scroll->y()) * cIntRetinaFactor());
|
||||
_slideAnimation->setFinalImages(direction, std::move(wasCache), std::move(nowCache), slidingRect);
|
||||
auto corners = App::cornersMask(ImageRoundRadius::Small);
|
||||
_slideAnimation->setCornerMasks(QImage(*corners[0]), QImage(*corners[1]), QImage(*corners[2]), QImage(*corners[3]));
|
||||
_slideAnimation->start();
|
||||
|
||||
hideForSliding();
|
||||
|
||||
getTab(wasTab)->widget()->hideFinished();
|
||||
|
||||
_a_slide.start([this] { update(); }, 0., 1., st::emojiPanSlideDuration, anim::linear);
|
||||
update();
|
||||
|
||||
AuthSession::Current().data().setEmojiPanelTab(_currentTabType);
|
||||
onSaveConfigDelayed(kSaveChosenTabTimeout);
|
||||
}
|
||||
|
||||
gsl::not_null<EmojiListWidget*> TabbedSelector::emoji() const {
|
||||
return static_cast<EmojiListWidget*>(getTab(TabType::Emoji)->widget().get());
|
||||
}
|
||||
|
||||
gsl::not_null<StickersListWidget*> TabbedSelector::stickers() const {
|
||||
return static_cast<StickersListWidget*>(getTab(TabType::Stickers)->widget().get());
|
||||
}
|
||||
|
||||
gsl::not_null<GifsListWidget*> TabbedSelector::gifs() const {
|
||||
return static_cast<GifsListWidget*>(getTab(TabType::Gifs)->widget().get());
|
||||
}
|
||||
|
||||
void TabbedSelector::setWidgetToScrollArea() {
|
||||
_scroll->setOwnedWidget(currentTab()->takeWidget());
|
||||
_scroll->disableScroll(false);
|
||||
currentTab()->widget()->moveToLeft(0, 0);
|
||||
currentTab()->widget()->show();
|
||||
scrollToY(currentTab()->getScrollTop());
|
||||
onScroll();
|
||||
}
|
||||
|
||||
void TabbedSelector::scrollToY(int y) {
|
||||
_scroll->scrollToY(y);
|
||||
|
||||
// Qt render glitch workaround, shadow sometimes disappears if we just scroll to y.
|
||||
_topShadow->update();
|
||||
}
|
||||
|
||||
TabbedSelector::Inner::Inner(QWidget *parent, gsl::not_null<Window::Controller*> controller) : TWidget(parent)
|
||||
, _controller(controller) {
|
||||
}
|
||||
|
||||
void TabbedSelector::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) {
|
||||
auto oldVisibleHeight = getVisibleBottom() - getVisibleTop();
|
||||
_visibleTop = visibleTop;
|
||||
_visibleBottom = visibleBottom;
|
||||
auto visibleHeight = getVisibleBottom() - getVisibleTop();
|
||||
if (visibleHeight != oldVisibleHeight) {
|
||||
resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight());
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::Inner::hideFinished() {
|
||||
processHideFinished();
|
||||
if (auto footer = getFooter()) {
|
||||
footer->processHideFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::Inner::panelHideFinished() {
|
||||
hideFinished();
|
||||
processPanelHideFinished();
|
||||
if (auto footer = getFooter()) {
|
||||
footer->processPanelHideFinished();
|
||||
}
|
||||
}
|
||||
|
||||
TabbedSelector::InnerFooter::InnerFooter(QWidget *parent) : TWidget(parent) {
|
||||
resize(st::emojiPanWidth, st::emojiCategory.height);
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
250
Telegram/SourceFiles/chat_helpers/tabbed_selector.h
Normal file
250
Telegram/SourceFiles/chat_helpers/tabbed_selector.h
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/twidget.h"
|
||||
#include "ui/effects/panel_animation.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "auth_session.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace InlineBots {
|
||||
class Result;
|
||||
} // namespace InlineBots
|
||||
|
||||
namespace Ui {
|
||||
class PlainShadow;
|
||||
class ScrollArea;
|
||||
class SettingsSlider;
|
||||
} // namesapce Ui
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
class EmojiListWidget;
|
||||
class StickersListWidget;
|
||||
class GifsListWidget;
|
||||
|
||||
class TabbedSelector : public TWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TabbedSelector(QWidget *parent, gsl::not_null<Window::Controller*> controller);
|
||||
|
||||
void setRoundRadius(int radius);
|
||||
void refreshStickers();
|
||||
void stickersInstalled(uint64 setId);
|
||||
void setInlineQueryPeer(PeerData *peer);
|
||||
|
||||
void hideFinished();
|
||||
void showStarted();
|
||||
void beforeHiding();
|
||||
void afterShown();
|
||||
|
||||
bool preventAutoHide() const;
|
||||
bool isSliding() const {
|
||||
return _a_slide.animating();
|
||||
}
|
||||
|
||||
~TabbedSelector();
|
||||
|
||||
class Inner;
|
||||
class InnerFooter;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void onScroll();
|
||||
|
||||
void onSaveConfigDelayed(int delay);
|
||||
|
||||
signals:
|
||||
void emojiSelected(EmojiPtr emoji);
|
||||
void stickerSelected(DocumentData *sticker);
|
||||
void photoSelected(PhotoData *photo);
|
||||
void inlineResultSelected(InlineBots::Result *result, UserData *bot);
|
||||
|
||||
void updateStickers();
|
||||
|
||||
void cancelled();
|
||||
void slideFinished();
|
||||
void checkForHide();
|
||||
|
||||
private:
|
||||
using TabType = EmojiPanelTab;
|
||||
class Tab {
|
||||
public:
|
||||
static constexpr auto kCount = 3;
|
||||
|
||||
Tab(TabType type, object_ptr<Inner> widget);
|
||||
|
||||
object_ptr<Inner> takeWidget();
|
||||
void returnWidget(object_ptr<Inner> widget);
|
||||
|
||||
TabType type() const {
|
||||
return _type;
|
||||
}
|
||||
gsl::not_null<Inner*> widget() const {
|
||||
return _weak;
|
||||
}
|
||||
gsl::not_null<InnerFooter*> footer() const {
|
||||
return _footer;
|
||||
}
|
||||
|
||||
void saveScrollTop();
|
||||
void saveScrollTop(int scrollTop) {
|
||||
_scrollTop = scrollTop;
|
||||
}
|
||||
int getScrollTop() const {
|
||||
return _scrollTop;
|
||||
}
|
||||
|
||||
private:
|
||||
TabType _type = TabType::Emoji;
|
||||
object_ptr<Inner> _widget = { nullptr };
|
||||
QPointer<Inner> _weak;
|
||||
object_ptr<InnerFooter> _footer;
|
||||
int _scrollTop = 0;
|
||||
|
||||
};
|
||||
|
||||
int marginTop() const;
|
||||
int marginBottom() const;
|
||||
void paintSlideFrame(Painter &p, TimeMs ms);
|
||||
void paintContent(Painter &p);
|
||||
|
||||
QImage grabForAnimation();
|
||||
|
||||
void scrollToY(int y);
|
||||
|
||||
void showAll();
|
||||
void hideForSliding();
|
||||
|
||||
void setWidgetToScrollArea();
|
||||
void createTabsSlider();
|
||||
void switchTab();
|
||||
gsl::not_null<Tab*> getTab(TabType type) {
|
||||
return &_tabs[static_cast<int>(type)];
|
||||
}
|
||||
gsl::not_null<const Tab*> getTab(TabType type) const {
|
||||
return &_tabs[static_cast<int>(type)];
|
||||
}
|
||||
gsl::not_null<Tab*> currentTab() {
|
||||
return getTab(_currentTabType);
|
||||
}
|
||||
gsl::not_null<const Tab*> currentTab() const {
|
||||
return getTab(_currentTabType);
|
||||
}
|
||||
gsl::not_null<EmojiListWidget*> emoji() const;
|
||||
gsl::not_null<StickersListWidget*> stickers() const;
|
||||
gsl::not_null<GifsListWidget*> gifs() const;
|
||||
|
||||
int _roundRadius = 0;
|
||||
int _footerTop = 0;
|
||||
|
||||
class SlideAnimation;
|
||||
std::unique_ptr<SlideAnimation> _slideAnimation;
|
||||
Animation _a_slide;
|
||||
|
||||
object_ptr<Ui::SettingsSlider> _tabsSlider = { nullptr };
|
||||
object_ptr<Ui::PlainShadow> _topShadow;
|
||||
object_ptr<Ui::PlainShadow> _bottomShadow;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
std::array<Tab, Tab::kCount> _tabs;
|
||||
TabType _currentTabType = TabType::Emoji;
|
||||
|
||||
base::Timer _saveConfigTimer;
|
||||
|
||||
};
|
||||
|
||||
class TabbedSelector::Inner : public TWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Inner(QWidget *parent, gsl::not_null<Window::Controller*> controller);
|
||||
|
||||
void setVisibleTopBottom(int visibleTop, int visibleBottom) override;
|
||||
|
||||
int getVisibleTop() const {
|
||||
return _visibleTop;
|
||||
}
|
||||
int getVisibleBottom() const {
|
||||
return _visibleBottom;
|
||||
}
|
||||
|
||||
virtual void refreshRecent() = 0;
|
||||
virtual void preloadImages() {
|
||||
}
|
||||
void hideFinished();
|
||||
void panelHideFinished();
|
||||
virtual void clearSelection() = 0;
|
||||
|
||||
virtual void afterShown() {
|
||||
}
|
||||
virtual void beforeHiding() {
|
||||
}
|
||||
|
||||
virtual object_ptr<InnerFooter> createFooter() = 0;
|
||||
|
||||
signals:
|
||||
void scrollToY(int y);
|
||||
void disableScroll(bool disabled);
|
||||
void saveConfigDelayed(int delay);
|
||||
|
||||
protected:
|
||||
gsl::not_null<Window::Controller*> controller() const {
|
||||
return _controller;
|
||||
}
|
||||
|
||||
virtual int countHeight() = 0;
|
||||
virtual InnerFooter *getFooter() const = 0;
|
||||
virtual void processHideFinished() {
|
||||
}
|
||||
virtual void processPanelHideFinished() {
|
||||
}
|
||||
|
||||
private:
|
||||
gsl::not_null<Window::Controller*> _controller;
|
||||
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
|
||||
};
|
||||
|
||||
class TabbedSelector::InnerFooter : public TWidget {
|
||||
public:
|
||||
InnerFooter(QWidget *parent);
|
||||
|
||||
protected:
|
||||
virtual void processHideFinished() {
|
||||
}
|
||||
virtual void processPanelHideFinished() {
|
||||
}
|
||||
friend class Inner;
|
||||
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
@ -169,6 +169,10 @@ SettingsSlider::SettingsSlider(QWidget *parent, const style::SettingsSlider &st)
|
||||
setSelectOnPress(_st.ripple.showDuration == 0);
|
||||
}
|
||||
|
||||
void SettingsSlider::setRippleTopRoundRadius(int radius) {
|
||||
_rippleTopRoundRadius = radius;
|
||||
}
|
||||
|
||||
const style::font &SettingsSlider::getLabelFont() const {
|
||||
return _st.labelFont;
|
||||
}
|
||||
@ -218,12 +222,12 @@ void SettingsSlider::startRipple(int sectionIndex) {
|
||||
|
||||
QImage SettingsSlider::prepareRippleMask(int sectionIndex, const Section §ion) {
|
||||
auto size = QSize(section.width, height() - _st.rippleBottomSkip);
|
||||
if (!_st.rippleRoundRadius || (sectionIndex > 0 && sectionIndex + 1 < getSectionsCount())) {
|
||||
if (!_rippleTopRoundRadius || (sectionIndex > 0 && sectionIndex + 1 < getSectionsCount())) {
|
||||
return RippleAnimation::rectMask(size);
|
||||
}
|
||||
return RippleAnimation::maskByDrawer(size, false, [this, sectionIndex, width = section.width](QPainter &p) {
|
||||
auto plusRadius = _st.rippleRoundRadius + 1;
|
||||
p.drawRoundedRect(0, 0, width, height() + plusRadius, _st.rippleRoundRadius, _st.rippleRoundRadius);
|
||||
auto plusRadius = _rippleTopRoundRadius + 1;
|
||||
p.drawRoundedRect(0, 0, width, height() + plusRadius, _rippleTopRoundRadius, _rippleTopRoundRadius);
|
||||
if (sectionIndex > 0) {
|
||||
p.fillRect(0, 0, plusRadius, plusRadius, p.brush());
|
||||
}
|
||||
|
@ -103,6 +103,8 @@ class SettingsSlider : public DiscreteSlider {
|
||||
public:
|
||||
SettingsSlider(QWidget *parent, const style::SettingsSlider &st = st::defaultSettingsSlider);
|
||||
|
||||
void setRippleTopRoundRadius(int radius);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
@ -118,6 +120,7 @@ private:
|
||||
void resizeSections(int newWidth);
|
||||
|
||||
const style::SettingsSlider &_st;
|
||||
int _rippleTopRoundRadius = 0;
|
||||
|
||||
|
||||
};
|
||||
|
@ -675,7 +675,6 @@ SettingsSlider {
|
||||
labelFgActive: color;
|
||||
duration: int;
|
||||
rippleBottomSkip: pixels;
|
||||
rippleRoundRadius: pixels;
|
||||
rippleBg: color;
|
||||
rippleBgActive: color;
|
||||
ripple: RippleAnimation;
|
||||
|
@ -95,6 +95,10 @@
|
||||
<(src_loc)/chat_helpers/stickers_list_widget.h
|
||||
<(src_loc)/chat_helpers/tabbed_panel.cpp
|
||||
<(src_loc)/chat_helpers/tabbed_panel.h
|
||||
<(src_loc)/chat_helpers/tabbed_section.cpp
|
||||
<(src_loc)/chat_helpers/tabbed_section.h
|
||||
<(src_loc)/chat_helpers/tabbed_selector.cpp
|
||||
<(src_loc)/chat_helpers/tabbed_selector.h
|
||||
<(src_loc)/core/basic_types.h
|
||||
<(src_loc)/core/click_handler.cpp
|
||||
<(src_loc)/core/click_handler.h
|
||||
|
Loading…
Reference in New Issue
Block a user