2
0
mirror of https://github.com/kotatogram/kotatogram-desktop synced 2025-08-31 14:45:14 +00:00

Closed beta 10019008: Some more ripple animations added.

This commit is contained in:
John Preston
2016-11-16 19:04:25 +03:00
parent cdef9fa14f
commit 07689476a6
66 changed files with 845 additions and 680 deletions

View File

@@ -25,7 +25,111 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
namespace Ui {
FlatButton::FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st) : AbstractButton(parent)
LinkButton::LinkButton(QWidget *parent, const QString &text, const style::LinkButton &st) : AbstractButton(parent)
, _text(text)
, _textWidth(st.font->width(_text))
, _st(st) {
resize(_textWidth, _st.font->height);
setCursor(style::cur_pointer);
}
int LinkButton::naturalWidth() const {
return _textWidth;
}
void LinkButton::paintEvent(QPaintEvent *e) {
Painter p(this);
auto &font = ((_state & StateOver) ? _st.overFont : _st.font);
auto &pen = ((_state & StateDown) ? _st.downColor : ((_state & StateOver) ? _st.overColor : _st.color));
p.setFont(font);
p.setPen(pen);
if (_textWidth > width()) {
p.drawText(0, font->ascent, font->elided(_text, width()));
} else {
p.drawText(0, font->ascent, _text);
}
}
void LinkButton::setText(const QString &text) {
_text = text;
_textWidth = _st.font->width(_text);
resize(_textWidth, _st.font->height);
update();
}
void LinkButton::onStateChanged(int oldState, StateChangeSource source) {
update();
}
RippleButton::RippleButton(QWidget *parent, const style::RippleAnimation &st) : AbstractButton(parent)
, _st(st) {
}
void RippleButton::setForceRippled(bool rippled, SetForceRippledWay way) {
if (_forceRippled != rippled) {
_forceRippled = rippled;
if (_forceRippled) {
ensureRipple();
if (_ripple->empty()) {
_ripple->addFading();
} else {
_ripple->lastUnstop();
}
} else if (_ripple) {
_ripple->lastStop();
}
}
if (way == SetForceRippledWay::SkipAnimation && _ripple) {
_ripple->lastFinish();
}
update();
}
void RippleButton::paintRipple(QPainter &p, int x, int y, uint64 ms) {
if (_ripple) {
_ripple->paint(p, x, y, width(), ms);
if (_ripple->empty()) {
_ripple.reset();
}
}
}
void RippleButton::onStateChanged(int oldState, StateChangeSource source) {
update();
auto wasDown = (oldState & StateDown);
auto down = (_state & StateDown);
if (!_st.showDuration || down == wasDown || _forceRippled) {
return;
}
if (down && (source == StateChangeSource::ByPress)) {
// Start a ripple only from mouse press.
ensureRipple();
_ripple->add(prepareRippleStartPosition());
} else if (!down && _ripple) {
// Finish ripple anyway.
_ripple->lastStop();
}
}
void RippleButton::ensureRipple() {
if (!_ripple) {
_ripple = std_::make_unique<RippleAnimation>(_st, prepareRippleMask(), [this] { update(); });
}
}
QImage RippleButton::prepareRippleMask() const {
return RippleAnimation::rectMask(size());
}
QPoint RippleButton::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos());
}
RippleButton::~RippleButton() = default;
FlatButton::FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st) : RippleButton(parent, st.ripple)
, _text(text)
, _st(st)
, a_over(0)
@@ -81,6 +185,8 @@ void FlatButton::step_appearance(float64 ms, bool timer) {
}
void FlatButton::onStateChanged(int oldState, StateChangeSource source) {
RippleButton::onStateChanged(oldState, source);
a_over.start((_state & StateOver) ? 1. : 0.);
if (source == StateChangeSource::ByUser || source == StateChangeSource::ByPress) {
_a_appearance.stop();
@@ -89,27 +195,6 @@ void FlatButton::onStateChanged(int oldState, StateChangeSource source) {
} else {
_a_appearance.start();
}
handleRipples(oldState & StateDown, (source == StateChangeSource::ByPress));
}
void FlatButton::handleRipples(bool wasDown, bool wasPress) {
auto down = static_cast<bool>(_state & StateDown);
if (!_st.ripple.showDuration || down == wasDown) {
return;
}
if (down && wasPress) {
// Start a ripple only from mouse press.
if (!_ripple) {
_ripple = std_::make_unique<Ui::RippleAnimation>(_st.ripple, prepareRippleMask(), [this] { update(); });
}
auto clickPosition = mapFromGlobal(QCursor::pos());
_ripple->add(clickPosition);
} else if (!down && _ripple) {
// Finish ripple anyway.
_ripple->lastStop();
}
}
void FlatButton::paintEvent(QPaintEvent *e) {
@@ -121,69 +206,17 @@ void FlatButton::paintEvent(QPaintEvent *e) {
p.fillRect(r, anim::brush(_st.bgColor, _st.overBgColor, a_over.current()));
auto ms = getms();
if (_ripple) {
_ripple->paint(p, 0, 0, width(), ms);
if (_ripple->empty()) {
_ripple.reset();
}
}
paintRipple(p, 0, 0, ms);
p.setFont((_state & StateOver) ? _st.overFont : _st.font);
p.setRenderHint(QPainter::TextAntialiasing);
p.setPen(anim::pen(_st.color, _st.overColor, a_over.current()));
auto top = (_state & StateDown) ? _st.downTextTop : ((_state & StateOver) ? _st.overTextTop : _st.textTop);
r.setTop(top);
r.setTop(_st.textTop);
p.drawText(r, _text, style::al_top);
}
QImage FlatButton::prepareRippleMask() const {
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(cRetinaFactor());
result.fill(QColor(255, 255, 255));
return std_::move(result);
}
FlatButton::~FlatButton() = default;
LinkButton::LinkButton(QWidget *parent, const QString &text, const style::LinkButton &st) : AbstractButton(parent)
, _text(text)
, _textWidth(st.font->width(_text))
, _st(st) {
resize(_textWidth, _st.font->height);
setCursor(style::cur_pointer);
}
int LinkButton::naturalWidth() const {
return _textWidth;
}
void LinkButton::paintEvent(QPaintEvent *e) {
Painter p(this);
auto &font = ((_state & StateOver) ? _st.overFont : _st.font);
auto &pen = ((_state & StateDown) ? _st.downColor : ((_state & StateOver) ? _st.overColor : _st.color));
p.setFont(font);
p.setPen(pen);
if (_textWidth > width()) {
p.drawText(0, font->ascent, font->elided(_text, width()));
} else {
p.drawText(0, font->ascent, _text);
}
}
void LinkButton::setText(const QString &text) {
_text = text;
_textWidth = _st.font->width(_text);
resize(_textWidth, _st.font->height);
update();
}
void LinkButton::onStateChanged(int oldState, StateChangeSource source) {
update();
}
RoundButton::RoundButton(QWidget *parent, const QString &text, const style::RoundButton &st) : AbstractButton(parent)
RoundButton::RoundButton(QWidget *parent, const QString &text, const style::RoundButton &st) : RippleButton(parent, st.ripple)
, _fullText(text)
, _st(st) {
setCursor(style::cur_pointer);
@@ -264,20 +297,14 @@ void RoundButton::paintEvent(QPaintEvent *e) {
}
auto ms = getms();
if (_ripple) {
_ripple->paint(p, rounded.x(), rounded.y(), width(), ms);
if (_ripple->empty()) {
_ripple.reset();
}
}
paintRipple(p, rounded.x(), rounded.y(), ms);
p.setFont(_st.font);
int textLeft = _st.padding.left() + ((width() - innerWidth - _st.padding.left() - _st.padding.right()) / 2);
if (_fullWidthOverride < 0) {
textLeft = -_fullWidthOverride / 2;
}
int textTopDelta = (_state & StateDown) ? (_st.downTextTop - _st.textTop) : 0;
int textTop = _st.padding.top() + _st.textTop + textTopDelta;
int textTop = _st.padding.top() + _st.textTop;
if (!_text.isEmpty()) {
p.setPen((over || down) ? _st.textFgOver : _st.textFg);
p.drawTextLeft(textLeft, textTop, width(), _text);
@@ -287,32 +314,7 @@ void RoundButton::paintEvent(QPaintEvent *e) {
p.setPen((over || down) ? _st.secondaryTextFgOver : _st.secondaryTextFg);
p.drawTextLeft(textLeft, textTop, width(), _secondaryText);
}
_st.icon.paint(p, QPoint(_st.padding.left(), _st.padding.right() + textTopDelta), width());
}
void RoundButton::onStateChanged(int oldState, StateChangeSource source) {
update();
handleRipples(oldState & StateDown, (source == StateChangeSource::ByPress));
}
void RoundButton::handleRipples(bool wasDown, bool wasPress) {
auto down = static_cast<bool>(_state & StateDown);
if (!_st.ripple.showDuration || down == wasDown) {
return;
}
if (down && wasPress) {
// Start a ripple only from mouse press.
if (!_ripple) {
_ripple = std_::make_unique<Ui::RippleAnimation>(_st.ripple, prepareRippleMask(), [this] { update(); });
}
auto clickPosition = mapFromGlobal(QCursor::pos()) - QPoint(_st.padding.left(), _st.padding.top());
_ripple->add(clickPosition);
} else if (!down && _ripple) {
// Finish ripple anyway.
_ripple->lastStop();
}
_st.icon.paint(p, QPoint(_st.padding.left(), _st.padding.right()), width());
}
QImage RoundButton::prepareRippleMask() const {
@@ -321,22 +323,14 @@ QImage RoundButton::prepareRippleMask() const {
if (_fullWidthOverride < 0) {
rounded = QRect(0, rounded.top(), innerWidth - _fullWidthOverride, rounded.height());
}
auto result = QImage(rounded.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(cRetinaFactor());
result.fill(Qt::transparent);
{
Painter p(&result);
p.setPen(Qt::NoPen);
p.setBrush(QColor(255, 255, 255));
p.drawRoundedRect(rounded.translated(-rounded.topLeft()), st::buttonRadius, st::buttonRadius);
}
return std_::move(result);
return RippleAnimation::roundRectMask(rounded.size(), st::buttonRadius);
}
RoundButton::~RoundButton() = default;
QPoint RoundButton::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos()) - QPoint(_st.padding.left(), _st.padding.top());
}
IconButton::IconButton(QWidget *parent, const style::IconButton &st) : AbstractButton(parent)
IconButton::IconButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple)
, _st(st) {
resize(_st.width, _st.height);
setCursor(style::cur_pointer);
@@ -348,40 +342,15 @@ void IconButton::setIcon(const style::icon *icon, const style::icon *iconOver) {
update();
}
void IconButton::setActiveState(bool activeState, SetStateWay way) {
if (_activeState != activeState) {
_activeState = activeState;
if (_activeState) {
ensureRipple();
if (_ripple->empty()) {
_ripple->addFading();
} else {
_ripple->lastUnstop();
}
} else if (_ripple) {
_ripple->lastStop();
}
}
if (way == SetStateWay::SkipAnimation && _ripple) {
_ripple->lastFinish();
}
update();
}
void IconButton::paintEvent(QPaintEvent *e) {
Painter p(this);
auto ms = getms();
if (_ripple) {
_ripple->paint(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), width(), ms);
if (_ripple->empty()) {
_ripple.reset();
}
}
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms);
auto down = (_state & StateDown);
auto overIconOpacity = (down || _activeState) ? 1. : _a_over.current(getms(), (_state & StateOver) ? 1. : 0.);
auto overIconOpacity = (down || forceRippled()) ? 1. : _a_over.current(getms(), (_state & StateOver) ? 1. : 0.);
auto overIcon = [this] {
if (_iconOverrideOver) {
return _iconOverrideOver;
@@ -399,7 +368,7 @@ void IconButton::paintEvent(QPaintEvent *e) {
return &_st.icon;
};
auto icon = (overIconOpacity == 1.) ? overIcon() : justIcon();
auto position = (_state & StateDown) ? _st.iconPositionDown : _st.iconPosition;
auto position = _st.iconPosition;
if (position.x() < 0) {
position.setX((width() - icon->width()) / 2);
}
@@ -417,6 +386,8 @@ void IconButton::paintEvent(QPaintEvent *e) {
}
void IconButton::onStateChanged(int oldState, StateChangeSource source) {
RippleButton::onStateChanged(oldState, source);
auto over = (_state & StateOver);
if (over != (oldState & StateOver)) {
if (_st.duration) {
@@ -427,54 +398,56 @@ void IconButton::onStateChanged(int oldState, StateChangeSource source) {
update();
}
}
handleRipples(oldState & StateDown, (source == StateChangeSource::ByPress));
}
void IconButton::handleRipples(bool wasDown, bool wasPress) {
auto down = static_cast<bool>(_state & StateDown);
if (!_st.ripple.showDuration || _st.rippleAreaSize <= 0 || down == wasDown) {
return;
}
if (down && wasPress && !_activeState) {
// Start a ripple only from mouse press.
ensureRipple();
auto clickPosition = mapFromGlobal(QCursor::pos());
auto rippleCenter = QRect(_st.rippleAreaPosition, QSize(_st.rippleAreaSize, _st.rippleAreaSize)).center();
auto clickRadiusSquare = style::point::dotProduct(clickPosition - rippleCenter, clickPosition - rippleCenter);
auto startRadius = 0;
if (clickRadiusSquare * 4 > _st.rippleAreaSize * _st.rippleAreaSize) {
startRadius = sqrt(clickRadiusSquare) - (_st.rippleAreaSize / 2);
}
_ripple->add(clickPosition - _st.rippleAreaPosition, startRadius);
} else if (!down && _ripple && !_activeState) {
// Finish ripple anyway.
_ripple->lastStop();
}
}
void IconButton::ensureRipple() {
if (!_ripple) {
_ripple = std_::make_unique<Ui::RippleAnimation>(_st.ripple, prepareRippleMask(), [this] { update(); });
}
QPoint IconButton::prepareRippleStartPosition() const {
return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition;
}
QImage IconButton::prepareRippleMask() const {
auto size = _st.rippleAreaSize * cIntRetinaFactor();
auto result = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(cRetinaFactor());
result.fill(Qt::transparent);
{
Painter p(&result);
p.setRenderHint(QPainter::HighQualityAntialiasing);
p.setPen(Qt::NoPen);
p.setBrush(QColor(255, 255, 255));
p.drawEllipse(0, 0, _st.rippleAreaSize, _st.rippleAreaSize);
}
return std_::move(result);
return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize));
}
IconButton::~IconButton() = default;
LeftOutlineButton::LeftOutlineButton(QWidget *parent, const QString &text, const style::OutlineButton &st) : RippleButton(parent, st.ripple)
, _text(text)
, _fullText(text)
, _textWidth(st.font->width(_text))
, _fullTextWidth(_textWidth)
, _st(st) {
resizeToWidth(_textWidth + _st.padding.left() + _st.padding.right());
setCursor(style::cur_pointer);
}
void LeftOutlineButton::setText(const QString &text) {
_text = text;
_fullText = text;
_fullTextWidth = _textWidth = _st.font->width(_text);
resizeToWidth(width());
update();
}
int LeftOutlineButton::resizeGetHeight(int newWidth) {
int availableWidth = qMax(newWidth - _st.padding.left() - _st.padding.right(), 1);
if ((availableWidth < _fullTextWidth) || (_textWidth < availableWidth)) {
_text = _st.font->elided(_fullText, availableWidth);
_textWidth = _st.font->width(_text);
}
return _st.padding.top() + _st.font->height + _st.padding.bottom();
}
void LeftOutlineButton::paintEvent(QPaintEvent *e) {
Painter p(this);
bool over = (_state & StateOver), down = (_state & StateDown);
if (width() > _st.outlineWidth) {
p.fillRect(rtlrect(_st.outlineWidth, 0, width() - _st.outlineWidth, height(), width()), (over || down) ? _st.textBgOver : _st.textBg);
paintRipple(p, 0, 0, getms());
p.fillRect(rtlrect(0, 0, _st.outlineWidth, height(), width()), (over || down) ? _st.outlineFgOver : _st.outlineFg);
}
p.setFont(_st.font);
p.setPen((over || down) ? _st.textFgOver : _st.textFg);
p.drawTextLeft(_st.padding.left(), _st.padding.top(), width(), _text, _textWidth);
}
} // namespace Ui

View File

@@ -27,44 +27,6 @@ namespace Ui {
class RippleAnimation;
class FlatButton : public AbstractButton {
public:
FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st);
void step_appearance(float64 ms, bool timer);
void setOpacity(float64 o);
float64 opacity() const;
void setText(const QString &text);
void setWidth(int32 w);
int32 textWidth() const;
~FlatButton();
protected:
void paintEvent(QPaintEvent *e) override;
void onStateChanged(int oldState, StateChangeSource source) override;
private:
QImage prepareRippleMask() const;
void handleRipples(bool wasDown, bool wasPress);
QString _text, _textForAutoSize;
int _width;
const style::FlatButton &_st;
anim::fvalue a_over;
Animation _a_appearance;
float64 _opacity = 1.;
std_::unique_ptr<RippleAnimation> _ripple;
};
class LinkButton : public AbstractButton {
public:
LinkButton(QWidget *parent, const QString &text, const style::LinkButton &st = st::defaultLinkButton);
@@ -85,7 +47,73 @@ private:
};
class RoundButton : public AbstractButton {
class RippleButton : public AbstractButton {
public:
RippleButton(QWidget *parent, const style::RippleAnimation &st);
// Displays full ripple circle constantly.
enum class SetForceRippledWay {
Default,
SkipAnimation,
};
void setForceRippled(bool rippled, SetForceRippledWay way = SetForceRippledWay::Default);
bool forceRippled() const {
return _forceRippled;
}
~RippleButton();
protected:
void paintRipple(QPainter &p, int x, int y, uint64 ms);
void onStateChanged(int oldState, StateChangeSource source) override;
virtual QImage prepareRippleMask() const;
virtual QPoint prepareRippleStartPosition() const;
private:
void ensureRipple();
void handleRipples(bool wasDown, bool wasPress);
const style::RippleAnimation &_st;
std_::unique_ptr<RippleAnimation> _ripple;
bool _forceRippled = false;
};
class FlatButton : public RippleButton {
public:
FlatButton(QWidget *parent, const QString &text, const style::FlatButton &st);
void step_appearance(float64 ms, bool timer);
void setText(const QString &text);
void setWidth(int32 w);
int32 textWidth() const;
protected:
void paintEvent(QPaintEvent *e) override;
void onStateChanged(int oldState, StateChangeSource source) override;
private:
void setOpacity(float64 o);
float64 opacity() const;
QString _text, _textForAutoSize;
int _width;
const style::FlatButton &_st;
anim::fvalue a_over;
Animation _a_appearance;
float64 _opacity = 1.;
};
class RoundButton : public RippleButton {
public:
RoundButton(QWidget *parent, const QString &text, const style::RoundButton &st);
@@ -102,17 +130,13 @@ public:
};
void setTextTransform(TextTransform transform);
~RoundButton();
protected:
void paintEvent(QPaintEvent *e) override;
void onStateChanged(int oldState, StateChangeSource source) override;
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
private:
QImage prepareRippleMask() const;
void handleRipples(bool wasDown, bool wasPress);
void updateText();
void resizeToText();
@@ -128,44 +152,48 @@ private:
TextTransform _transform = TextTransform::ToUpper;
std_::unique_ptr<RippleAnimation> _ripple;
};
class IconButton : public AbstractButton {
class IconButton : public RippleButton {
public:
IconButton(QWidget *parent, const style::IconButton &st);
// Pass nullptr to restore the default icon.
void setIcon(const style::icon *icon, const style::icon *iconOver = nullptr);
// Displays full ripple circle constantly.
enum class SetStateWay {
Default,
SkipAnimation,
};
void setActiveState(bool activeState, SetStateWay way = SetStateWay::Default);
~IconButton();
protected:
void paintEvent(QPaintEvent *e) override;
void onStateChanged(int oldState, StateChangeSource source) override;
private:
void ensureRipple();
QImage prepareRippleMask() const;
void handleRipples(bool wasDown, bool wasPress);
QImage prepareRippleMask() const override;
QPoint prepareRippleStartPosition() const override;
private:
const style::IconButton &_st;
const style::icon *_iconOverride = nullptr;
const style::icon *_iconOverrideOver = nullptr;
FloatAnimation _a_over;
std_::unique_ptr<RippleAnimation> _ripple;
bool _activeState = false;
};
class LeftOutlineButton : public RippleButton {
public:
LeftOutlineButton(QWidget *parent, const QString &text, const style::OutlineButton &st = st::defaultLeftOutlineButton);
void setText(const QString &text);
protected:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
private:
QString _text, _fullText;
int _textWidth, _fullTextWidth;
const style::OutlineButton &_st;
};

View File

@@ -58,6 +58,7 @@ void DropdownMenu::init() {
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
_menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); });
_menu->setMouseReleaseDelegate([this](QPoint globalPosition) { handleMouseRelease(globalPosition); });
setMouseTracking(true);
@@ -176,6 +177,14 @@ void DropdownMenu::handleMousePress(QPoint globalPosition) {
}
}
void DropdownMenu::handleMouseRelease(QPoint globalPosition) {
if (_parent) {
_parent->forwardMouseRelease(globalPosition);
} else {
hideMenu();
}
}
void DropdownMenu::focusOutEvent(QFocusEvent *e) {
hideMenu();
}

View File

@@ -88,6 +88,10 @@ private:
_menu->handleMousePress(globalPosition);
}
void handleMousePress(QPoint globalPosition);
void forwardMouseRelease(QPoint globalPosition) {
_menu->handleMouseRelease(globalPosition);
}
void handleMouseRelease(QPoint globalPosition);
using SubmenuPointer = QPointer<DropdownMenu>;
bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source);

View File

@@ -18,6 +18,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "stdafx.h"
#include "ui/widgets/menu.h"
#include "ui/effects/ripple_animation.h"
namespace Ui {
Menu::Menu(QWidget *parent, const style::Menu &st) : TWidget(parent)
@@ -150,6 +152,7 @@ void Menu::actionChanged() {
void Menu::paintEvent(QPaintEvent *e) {
Painter p(this);
auto ms = getms();
auto clip = e->rect();
auto topskip = QRect(0, 0, width(), _st.skip);
@@ -172,8 +175,15 @@ void Menu::paintEvent(QPaintEvent *e) {
p.fillRect(0, 0, width(), actionHeight, _st.itemBg);
p.fillRect(_st.separatorPadding.left(), _st.separatorPadding.top(), width() - _st.separatorPadding.left() - _st.separatorPadding.right(), _st.separatorWidth, _st.separatorFg);
} else {
auto enabled = action->isEnabled(), selected = (i == _selected && enabled);
auto enabled = action->isEnabled();
auto selected = ((i == _selected || i == _pressed) && enabled);
p.fillRect(0, 0, width(), actionHeight, selected ? _st.itemBgOver : _st.itemBg);
if (data.ripple) {
data.ripple->paint(p, 0, 0, width(), ms);
if (data.ripple->empty()) {
data.ripple.reset();
}
}
if (auto icon = (selected ? data.iconOver : data.icon)) {
icon->paint(p, _st.itemIconPosition, width());
}
@@ -207,9 +217,29 @@ void Menu::itemPressed(TriggeredSource source) {
return;
}
if (_selected >= 0 && _selected < _actions.size() && _actions[_selected]->isEnabled()) {
if (_triggeredCallback) {
auto actionTop = _st.skip + itemTop(_selected);
_triggeredCallback(_actions[_selected], actionTop, source);
_pressed = _selected;
if (source == TriggeredSource::Mouse) {
if (!_actionsData[_pressed].ripple) {
auto mask = RippleAnimation::rectMask(QSize(width(), _itemHeight));
_actionsData[_pressed].ripple = MakeShared<RippleAnimation>(_st.ripple, std_::move(mask), [this, selected = _pressed] {
updateItem(selected);
});
}
_actionsData[_pressed].ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, itemTop(_pressed)));
} else {
itemReleased(source);
}
}
}
void Menu::itemReleased(TriggeredSource source) {
auto pressed = base::take(_pressed, -1);
if (pressed >= 0 && pressed < _actions.size()) {
if (source == TriggeredSource::Mouse && _actionsData[pressed].ripple) {
_actionsData[pressed].ripple->lastStop();
}
if (pressed == _selected && _triggeredCallback) {
_triggeredCallback(_actions[_selected], itemTop(_selected), source);
}
}
}
@@ -290,9 +320,8 @@ void Menu::setSelected(int selected) {
_selected = selected;
updateSelectedItem();
if (_activatedCallback) {
auto actionTop = _st.skip + itemTop(_selected);
auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard;
_activatedCallback((_selected >= 0) ? _actions[_selected] : nullptr, actionTop, source);
_activatedCallback((_selected >= 0) ? _actions[_selected] : nullptr, itemTop(_selected), source);
}
}
}
@@ -301,19 +330,23 @@ int Menu::itemTop(int index) {
if (index > _actions.size()) {
index = _actions.size();
}
int top = 0;
int top = _st.skip;
for (int i = 0; i < index; ++i) {
top += _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight;
}
return top;
}
void Menu::updateSelectedItem() {
if (_selected >= 0) {
update(0, _st.skip + itemTop(_selected), width(), _actions.at(_selected)->isSeparator() ? _separatorHeight : _itemHeight);
void Menu::updateItem(int index) {
if (index >= 0 && index < _actions.size()) {
update(0, itemTop(index), width(), _actions[index]->isSeparator() ? _separatorHeight : _itemHeight);
}
}
void Menu::updateSelectedItem() {
updateItem(_selected);
}
void Menu::mouseMoveEvent(QMouseEvent *e) {
handleMouseMove(e->globalPos());
}
@@ -336,6 +369,10 @@ void Menu::mousePressEvent(QMouseEvent *e) {
handleMousePress(e->globalPos());
}
void Menu::mouseReleaseEvent(QMouseEvent *e) {
handleMouseRelease(e->globalPos());
}
void Menu::handleMousePress(QPoint globalPosition) {
handleMouseMove(globalPosition);
if (rect().contains(mapFromGlobal(globalPosition))) {
@@ -345,4 +382,12 @@ void Menu::handleMousePress(QPoint globalPosition) {
}
}
void Menu::handleMouseRelease(QPoint globalPosition) {
handleMouseMove(globalPosition);
itemReleased(TriggeredSource::Mouse);
if (!rect().contains(mapFromGlobal(globalPosition)) && _mouseReleaseDelegate) {
_mouseReleaseDelegate(globalPosition);
}
}
} // namespace Ui

View File

@@ -24,6 +24,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
namespace Ui {
class RippleAnimation;
class Menu : public TWidget {
Q_OBJECT
@@ -76,11 +78,17 @@ public:
}
void handleMousePress(QPoint globalPosition);
void setMouseReleaseDelegate(base::lambda_unique<void(QPoint globalPosition)> delegate) {
_mouseReleaseDelegate = std_::move(delegate);
}
void handleMouseRelease(QPoint globalPosition);
protected:
void paintEvent(QPaintEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
@@ -99,8 +107,10 @@ private:
void clearMouseSelection();
int itemTop(int index);
void updateItem(int index);
void updateSelectedItem();
void itemPressed(TriggeredSource source);
void itemReleased(TriggeredSource source);
const style::Menu &_st;
@@ -110,6 +120,7 @@ private:
base::lambda_unique<bool(int key)> _keyPressDelegate;
base::lambda_unique<void(QPoint globalPosition)> _mouseMoveDelegate;
base::lambda_unique<void(QPoint globalPosition)> _mousePressDelegate;
base::lambda_unique<void(QPoint globalPosition)> _mouseReleaseDelegate;
struct ActionData {
bool hasSubmenu = false;
@@ -117,6 +128,7 @@ private:
QString shortcut;
const style::icon *icon = nullptr;
const style::icon *iconOver = nullptr;
QSharedPointer<RippleAnimation> ripple;
};
using ActionsData = QList<ActionData>;
@@ -129,6 +141,7 @@ private:
bool _mouseSelection = false;
int _selected = -1;
int _pressed = -1;
bool _childShown = false;
};

View File

@@ -55,6 +55,7 @@ void PopupMenu::init() {
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
_menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); });
_menu->setMouseReleaseDelegate([this](QPoint globalPosition) { handleMouseRelease(globalPosition); });
handleCompositingUpdate();
@@ -224,6 +225,14 @@ void PopupMenu::handleMousePress(QPoint globalPosition) {
}
}
void PopupMenu::handleMouseRelease(QPoint globalPosition) {
if (_parent) {
_parent->forwardMouseRelease(globalPosition);
} else {
hideMenu();
}
}
void PopupMenu::focusOutEvent(QFocusEvent *e) {
hideMenu();
}

View File

@@ -93,6 +93,10 @@ private:
_menu->handleMousePress(globalPosition);
}
void handleMousePress(QPoint globalPosition);
void forwardMouseRelease(QPoint globalPosition) {
_menu->handleMouseRelease(globalPosition);
}
void handleMouseRelease(QPoint globalPosition);
using SubmenuPointer = QPointer<PopupMenu>;
bool popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source);

View File

@@ -61,8 +61,6 @@ FlatButton {
height: pixels;
textTop: pixels;
overTextTop: pixels;
downTextTop: pixels;
font: font;
overFont: font;
@@ -87,7 +85,6 @@ RoundButton {
padding: margins;
textTop: pixels;
downTextTop: pixels;
icon: icon;
@@ -272,6 +269,8 @@ OutlineButton {
font: font;
padding: margins;
ripple: RippleAnimation;
}
IconButton {
@@ -280,9 +279,7 @@ IconButton {
icon: icon;
iconOver: icon;
iconPosition: point;
iconPositionDown: point;
duration: int;
@@ -394,6 +391,8 @@ Menu {
widthMin: pixels;
widthMax: pixels;
ripple: RippleAnimation;
}
PanelAnimation {
@@ -487,7 +486,6 @@ defaultActiveButton: RoundButton {
padding: margins(0px, 0px, 0px, 0px);
textTop: 8px;
downTextTop: 8px;
font: semiboldFont;
@@ -589,14 +587,22 @@ defaultLeftOutlineButton: OutlineButton {
font: normalFont;
padding: margins(11px, 5px, 11px, 5px);
ripple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgRipple;
}
}
attentionLeftOutlineButton: OutlineButton(defaultLeftOutlineButton) {
outlineFgOver: attentionBoxButtonFg;
outlineFgOver: attentionButtonFg;
textBgOver: attentionBoxButtonBgOver;
textBgOver: attentionButtonBgOver;
textFg: attentionBoxButtonFg;
textFgOver: attentionBoxButtonFg;
textFg: attentionButtonFg;
textFgOver: attentionButtonFg;
ripple: RippleAnimation(defaultRippleAnimation) {
color: attentionButtonBgRipple;
}
}
defaultInputArea: InputArea {
@@ -697,7 +703,6 @@ defaultRadiobutton: Radiobutton {
defaultIconButton: IconButton {
iconPosition: point(-1px, -1px);
iconPositionDown: point(-1px, -1px);
}
widgetSlideDuration: 200;
@@ -756,8 +761,8 @@ defaultMenu: Menu {
itemFg: windowFg;
itemFgOver: windowFgOver;
itemFgDisabled: #cccccc;
itemFgShortcut: #999999;
itemFgShortcutOver: #7c99b2;
itemFgShortcut: windowSubTextFg;
itemFgShortcutOver: windowSubTextFgOver;
itemFgShortcutDisabled: #cccccc;
itemIconPosition: point(0px, 0px);
itemPadding: margins(17px, 8px, 17px, 7px);
@@ -771,6 +776,8 @@ defaultMenu: Menu {
widthMin: 180px;
widthMax: 300px;
ripple: defaultRippleAnimation;
}
defaultPopupMenu: PopupMenu {
shadow: defaultRoundShadow;
@@ -801,18 +808,42 @@ defaultDropdownMenu: DropdownMenu {
menu: defaultMenu;
}
historyToDown: icon {
historyToDownBelow: icon {
{ "history_down_shadow", #00000040 },
{ "history_down_circle", #ffffff, point(4px, 4px) },
{ "history_down_circle", windowBg, point(4px, 4px) },
};
historyToDownBelowOver: icon {
{ "history_down_shadow", #00000040 },
{ "history_down_circle", windowBgOver, point(4px, 4px) },
};
contactsAddIconBelow: icon {
{ "history_down_shadow", #00000040 },
{ "history_down_circle", activeButtonBg, point(4px, 4px) },
};
contactsAddIconBelowOver: icon {
{ "history_down_shadow", #00000040 },
{ "history_down_circle", activeButtonBgOver, point(4px, 4px) },
};
contactsAddIcon: icon {
{ "history_down_shadow", #00000020 },
{ "history_down_circle", activeButtonBg, point(4px, 4px) },
{ "contacts_add", activeButtonFg, point(18px, 18px) },
};
contactsAddIconOver: icon {
{ "history_down_shadow", #00000020 },
{ "history_down_circle", activeButtonBgOver, point(4px, 4px) },
{ "contacts_add", activeButtonFg, point(18px, 18px) },
};
BotKeyboardButton {
margin: pixels;
padding: pixels;
height: pixels;
textTop: pixels;
ripple: RippleAnimation;
}
TwoIconButton {
width: pixels;
height: pixels;
iconBelow: icon;
iconAbove: icon;
iconBelowOver: icon;
iconAboveOver: icon;
iconPosition: point;
rippleAreaPosition: point;
rippleAreaSize: pixels;
ripple: RippleAnimation;
}