352 lines
11 KiB
C++
352 lines
11 KiB
C++
/*
|
|
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 "ui/special_buttons.h"
|
|
|
|
#include "styles/style_boxes.h"
|
|
#include "styles/style_history.h"
|
|
#include "dialogs/dialogs_layout.h"
|
|
#include "ui/effects/ripple_animation.h"
|
|
|
|
namespace Ui {
|
|
namespace {
|
|
|
|
constexpr int kWideScale = 5;
|
|
|
|
} // namespace
|
|
|
|
HistoryDownButton::HistoryDownButton(QWidget *parent, const style::TwoIconButton &st) : RippleButton(parent, st.ripple)
|
|
, _st(st) {
|
|
resize(_st.width, _st.height);
|
|
setCursor(style::cur_pointer);
|
|
|
|
hide();
|
|
}
|
|
|
|
QImage HistoryDownButton::prepareRippleMask() const {
|
|
return Ui::RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize));
|
|
}
|
|
|
|
QPoint HistoryDownButton::prepareRippleStartPosition() const {
|
|
return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition;
|
|
}
|
|
|
|
void HistoryDownButton::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
|
|
auto ms = getms();
|
|
auto over = isOver();
|
|
auto down = isDown();
|
|
((over || down) ? _st.iconBelowOver : _st.iconBelow).paint(p, _st.iconPosition, width());
|
|
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms);
|
|
((over || down) ? _st.iconAboveOver : _st.iconAbove).paint(p, _st.iconPosition, width());
|
|
if (_unreadCount > 0) {
|
|
auto unreadString = QString::number(_unreadCount);
|
|
if (unreadString.size() > 4) {
|
|
unreadString = qsl("..") + unreadString.mid(unreadString.size() - 4);
|
|
}
|
|
|
|
Dialogs::Layout::UnreadBadgeStyle st;
|
|
st.align = style::al_center;
|
|
st.font = st::historyToDownBadgeFont;
|
|
st.size = st::historyToDownBadgeSize;
|
|
st.sizeId = Dialogs::Layout::UnreadBadgeInHistoryToDown;
|
|
Dialogs::Layout::paintUnreadCount(p, unreadString, width(), 0, st, nullptr);
|
|
}
|
|
}
|
|
|
|
void HistoryDownButton::setUnreadCount(int unreadCount) {
|
|
if (_unreadCount != unreadCount) {
|
|
_unreadCount = unreadCount;
|
|
update();
|
|
}
|
|
}
|
|
|
|
EmojiButton::EmojiButton(QWidget *parent, const style::IconButton &st) : RippleButton(parent, st.ripple)
|
|
, _st(st)
|
|
, _a_loading(animation(this, &EmojiButton::step_loading)) {
|
|
resize(_st.width, _st.height);
|
|
setCursor(style::cur_pointer);
|
|
}
|
|
|
|
void EmojiButton::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
|
|
auto ms = getms();
|
|
|
|
p.fillRect(e->rect(), st::historyComposeAreaBg);
|
|
paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y(), ms, _rippleOverride ? &(*_rippleOverride)->c : nullptr);
|
|
|
|
auto loading = a_loading.current(ms, _loading ? 1 : 0);
|
|
p.setOpacity(1 - loading);
|
|
|
|
auto over = isOver();
|
|
auto icon = _iconOverride ? _iconOverride : &(over ? _st.iconOver : _st.icon);
|
|
icon->paint(p, _st.iconPosition, width());
|
|
|
|
p.setOpacity(1.);
|
|
auto pen = _colorOverride ? (*_colorOverride)->p : (over ? st::historyEmojiCircleFgOver : st::historyEmojiCircleFg)->p;
|
|
pen.setWidth(st::historyEmojiCircleLine);
|
|
pen.setCapStyle(Qt::RoundCap);
|
|
p.setPen(pen);
|
|
p.setBrush(Qt::NoBrush);
|
|
|
|
PainterHighQualityEnabler hq(p);
|
|
QRect inner(QPoint((width() - st::historyEmojiCircle.width()) / 2, st::historyEmojiCircleTop), st::historyEmojiCircle);
|
|
if (loading > 0) {
|
|
int32 full = FullArcLength;
|
|
int32 start = qRound(full * float64(ms % st::historyEmojiCirclePeriod) / st::historyEmojiCirclePeriod), part = qRound(loading * full / st::historyEmojiCirclePart);
|
|
p.drawArc(inner, start, full - part);
|
|
} else {
|
|
p.drawEllipse(inner);
|
|
}
|
|
}
|
|
|
|
void EmojiButton::setLoading(bool loading) {
|
|
if (_loading != loading) {
|
|
_loading = loading;
|
|
auto from = loading ? 0. : 1., to = loading ? 1. : 0.;
|
|
a_loading.start([this] { update(); }, from, to, st::historyEmojiCircleDuration);
|
|
if (loading) {
|
|
_a_loading.start();
|
|
} else {
|
|
_a_loading.stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void EmojiButton::setColorOverrides(const style::icon *iconOverride, const style::color *colorOverride, const style::color *rippleOverride) {
|
|
_iconOverride = iconOverride;
|
|
_colorOverride = colorOverride;
|
|
_rippleOverride = rippleOverride;
|
|
update();
|
|
}
|
|
|
|
void EmojiButton::onStateChanged(State was, StateChangeSource source) {
|
|
RippleButton::onStateChanged(was, source);
|
|
auto wasOver = static_cast<bool>(was & StateFlag::Over);
|
|
if (isOver() != wasOver) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
QPoint EmojiButton::prepareRippleStartPosition() const {
|
|
return mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition;
|
|
}
|
|
|
|
QImage EmojiButton::prepareRippleMask() const {
|
|
return RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize));
|
|
}
|
|
|
|
SendButton::SendButton(QWidget *parent) : RippleButton(parent, st::historyReplyCancel.ripple) {
|
|
resize(st::historySendSize);
|
|
}
|
|
|
|
void SendButton::setType(Type type) {
|
|
if (_type != type) {
|
|
_contentFrom = grabContent();
|
|
_type = type;
|
|
_a_typeChanged.finish();
|
|
_contentTo = grabContent();
|
|
_a_typeChanged.start([this] { update(); }, 0., 1., st::historyRecordVoiceDuration);
|
|
update();
|
|
}
|
|
if (_type != Type::Record) {
|
|
_recordActive = false;
|
|
_a_recordActive.finish();
|
|
}
|
|
}
|
|
|
|
void SendButton::setRecordActive(bool recordActive) {
|
|
if (_recordActive != recordActive) {
|
|
_recordActive = recordActive;
|
|
_a_recordActive.start([this] { recordAnimationCallback(); }, _recordActive ? 0. : 1., _recordActive ? 1. : 0, st::historyRecordVoiceDuration);
|
|
update();
|
|
}
|
|
}
|
|
|
|
void SendButton::finishAnimating() {
|
|
_a_typeChanged.finish();
|
|
_a_recordActive.finish();
|
|
update();
|
|
}
|
|
|
|
void SendButton::mouseMoveEvent(QMouseEvent *e) {
|
|
AbstractButton::mouseMoveEvent(e);
|
|
if (_recording) {
|
|
if (_recordUpdateCallback) {
|
|
_recordUpdateCallback(e->globalPos());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SendButton::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
|
|
auto ms = getms();
|
|
auto over = (isDown() || isOver());
|
|
auto changed = _a_typeChanged.current(ms, 1.);
|
|
if (changed < 1.) {
|
|
PainterHighQualityEnabler hq(p);
|
|
p.setOpacity(1. - changed);
|
|
auto targetRect = QRect((1 - kWideScale) / 2 * width(), (1 - kWideScale) / 2 * height(), kWideScale * width(), kWideScale * height());
|
|
auto hiddenWidth = anim::interpolate(0, (1 - kWideScale) / 2 * width(), changed);
|
|
auto hiddenHeight = anim::interpolate(0, (1 - kWideScale) / 2 * height(), changed);
|
|
p.drawPixmap(targetRect.marginsAdded(QMargins(hiddenWidth, hiddenHeight, hiddenWidth, hiddenHeight)), _contentFrom);
|
|
p.setOpacity(changed);
|
|
auto shownWidth = anim::interpolate((1 - kWideScale) / 2 * width(), 0, changed);
|
|
auto shownHeight = anim::interpolate((1 - kWideScale) / 2 * height(), 0, changed);
|
|
p.drawPixmap(targetRect.marginsAdded(QMargins(shownWidth, shownHeight, shownWidth, shownHeight)), _contentTo);
|
|
} else if (_type == Type::Record) {
|
|
auto recordActive = recordActiveRatio();
|
|
auto rippleColor = anim::color(st::historyAttachEmoji.ripple.color, st::historyRecordVoiceRippleBgActive, recordActive);
|
|
paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), ms, &rippleColor);
|
|
|
|
auto fastIcon = [recordActive, over, this] {
|
|
if (recordActive == 1.) {
|
|
return &st::historyRecordVoiceActive;
|
|
} else if (over) {
|
|
return &st::historyRecordVoiceOver;
|
|
}
|
|
return &st::historyRecordVoice;
|
|
};
|
|
fastIcon()->paintInCenter(p, rect());
|
|
if (recordActive > 0. && recordActive < 1.) {
|
|
p.setOpacity(recordActive);
|
|
st::historyRecordVoiceActive.paintInCenter(p, rect());
|
|
p.setOpacity(1.);
|
|
}
|
|
} else if (_type == Type::Save) {
|
|
auto &saveIcon = over ? st::historyEditSaveIconOver : st::historyEditSaveIcon;
|
|
saveIcon.paint(p, st::historySendIconPosition, width());
|
|
} else if (_type == Type::Cancel) {
|
|
paintRipple(p, (width() - st::historyAttachEmoji.rippleAreaSize) / 2, st::historyAttachEmoji.rippleAreaPosition.y(), ms);
|
|
|
|
auto &cancelIcon = over ? st::historyReplyCancelIconOver : st::historyReplyCancelIcon;
|
|
cancelIcon.paintInCenter(p, rect());
|
|
} else {
|
|
auto &sendIcon = over ? st::historySendIconOver : st::historySendIcon;
|
|
sendIcon.paint(p, st::historySendIconPosition, width());
|
|
}
|
|
}
|
|
|
|
void SendButton::onStateChanged(State was, StateChangeSource source) {
|
|
RippleButton::onStateChanged(was, source);
|
|
|
|
auto down = (state() & StateFlag::Down);
|
|
if ((was & StateFlag::Down) != down) {
|
|
if (down) {
|
|
if (_type == Type::Record) {
|
|
_recording = true;
|
|
if (_recordStartCallback) {
|
|
_recordStartCallback();
|
|
}
|
|
}
|
|
} else if (_recording) {
|
|
_recording = false;
|
|
if (_recordStopCallback) {
|
|
_recordStopCallback(_recordActive);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QPixmap SendButton::grabContent() {
|
|
auto result = QImage(kWideScale * size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
|
result.setDevicePixelRatio(cRetinaFactor());
|
|
result.fill(Qt::transparent);
|
|
{
|
|
Painter p(&result);
|
|
p.drawPixmap((kWideScale - 1) / 2 * width(), (kWideScale - 1) / 2 * height(), myGrab(this));
|
|
}
|
|
return App::pixmapFromImageInPlace(std::move(result));
|
|
}
|
|
|
|
QImage SendButton::prepareRippleMask() const {
|
|
auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize;
|
|
return Ui::RippleAnimation::ellipseMask(QSize(size, size));
|
|
}
|
|
|
|
QPoint SendButton::prepareRippleStartPosition() const {
|
|
auto real = mapFromGlobal(QCursor::pos());
|
|
auto size = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaSize : st::historyReplyCancel.rippleAreaSize;
|
|
auto y = (_type == Type::Record) ? st::historyAttachEmoji.rippleAreaPosition.y() : (height() - st::historyReplyCancel.rippleAreaSize) / 2;
|
|
return real - QPoint((width() - size) / 2, y);
|
|
}
|
|
|
|
void SendButton::recordAnimationCallback() {
|
|
update();
|
|
if (_recordAnimationCallback) {
|
|
_recordAnimationCallback();
|
|
}
|
|
}
|
|
|
|
PeerAvatarButton::PeerAvatarButton(QWidget *parent, PeerData *peer, const style::PeerAvatarButton &st) : AbstractButton(parent)
|
|
, _peer(peer)
|
|
, _st(st) {
|
|
resize(_st.size, _st.size);
|
|
}
|
|
|
|
void PeerAvatarButton::paintEvent(QPaintEvent *e) {
|
|
if (_peer) {
|
|
Painter p(this);
|
|
_peer->paintUserpic(p, (_st.size - _st.photoSize) / 2, (_st.size - _st.photoSize) / 2, _st.photoSize);
|
|
}
|
|
}
|
|
|
|
NewAvatarButton::NewAvatarButton(QWidget *parent, int size, QPoint position) : RippleButton(parent, st::defaultActiveButton.ripple)
|
|
, _position(position) {
|
|
resize(size, size);
|
|
}
|
|
|
|
void NewAvatarButton::paintEvent(QPaintEvent *e) {
|
|
Painter p(this);
|
|
|
|
if (!_image.isNull()) {
|
|
p.drawPixmap(0, 0, _image);
|
|
return;
|
|
}
|
|
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(isOver() ? st::defaultActiveButton.textBgOver : st::defaultActiveButton.textBg);
|
|
{
|
|
PainterHighQualityEnabler hq(p);
|
|
p.drawEllipse(rect());
|
|
}
|
|
|
|
paintRipple(p, 0, 0, getms());
|
|
|
|
st::newGroupPhotoIcon.paint(p, _position, width());
|
|
}
|
|
|
|
void NewAvatarButton::setImage(const QImage &image) {
|
|
auto small = image.scaled(size() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
Images::prepareCircle(small);
|
|
_image = App::pixmapFromImageInPlace(std::move(small));
|
|
_image.setDevicePixelRatio(cRetinaFactor());
|
|
update();
|
|
}
|
|
|
|
QImage NewAvatarButton::prepareRippleMask() const {
|
|
return Ui::RippleAnimation::ellipseMask(size());
|
|
}
|
|
|
|
} // namespace Ui
|