mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 06:26:18 +00:00
Closed beta 10019008: Some more ripple animations added.
This commit is contained in:
@@ -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
|
||||
|
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
||||
};
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user