mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-25 04:38:23 +00:00
Added button to voice record bar for ttl voice messages.
This commit is contained in:
parent
5cf0b6b50e
commit
ca86dce760
@ -1072,6 +1072,7 @@ historyRecordCancelActive: windowActiveTextFg;
|
||||
historyRecordFont: font(13px);
|
||||
historyRecordDurationSkip: 12px;
|
||||
historyRecordDurationFg: historyComposeAreaFg;
|
||||
historyRecordTTLLineWidth: 2px;
|
||||
|
||||
historyRecordMainBlobMinRadius: 23px;
|
||||
historyRecordMainBlobMaxRadius: 37px;
|
||||
|
@ -199,6 +199,175 @@ void PaintWaveform(
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] QRect DrawLockCircle(
|
||||
QPainter &p,
|
||||
const QRect &widgetRect,
|
||||
const style::RecordBarLock &st,
|
||||
float64 progress) {
|
||||
const auto &originTop = st.originTop;
|
||||
const auto &originBottom = st.originBottom;
|
||||
const auto &originBody = st.originBody;
|
||||
const auto &shadowTop = st.shadowTop;
|
||||
const auto &shadowBottom = st.shadowBottom;
|
||||
const auto &shadowBody = st.shadowBody;
|
||||
const auto &shadowMargins = st::historyRecordLockMargin;
|
||||
|
||||
const auto bottomMargin = anim::interpolate(
|
||||
0,
|
||||
widgetRect.height() - shadowTop.height() - shadowBottom.height(),
|
||||
progress);
|
||||
|
||||
const auto topMargin = anim::interpolate(
|
||||
widgetRect.height() / 4,
|
||||
0,
|
||||
progress);
|
||||
|
||||
const auto full = widgetRect - QMargins(0, topMargin, 0, bottomMargin);
|
||||
const auto inner = full - shadowMargins;
|
||||
const auto content = inner
|
||||
- style::margins(0, originTop.height(), 0, originBottom.height());
|
||||
const auto contentShadow = full
|
||||
- style::margins(0, shadowTop.height(), 0, shadowBottom.height());
|
||||
|
||||
const auto w = full.width();
|
||||
{
|
||||
shadowTop.paint(p, full.topLeft(), w);
|
||||
originTop.paint(p, inner.topLeft(), w);
|
||||
}
|
||||
{
|
||||
const auto shadowPos = QPoint(
|
||||
full.x(),
|
||||
contentShadow.y() + contentShadow.height());
|
||||
const auto originPos = QPoint(
|
||||
inner.x(),
|
||||
content.y() + content.height());
|
||||
shadowBottom.paint(p, shadowPos, w);
|
||||
originBottom.paint(p, originPos, w);
|
||||
}
|
||||
{
|
||||
shadowBody.fill(p, contentShadow);
|
||||
originBody.fill(p, content);
|
||||
}
|
||||
if (progress < 1.) {
|
||||
const auto &arrow = st.arrow;
|
||||
const auto arrowRect = QRect(
|
||||
inner.x(),
|
||||
content.y() + content.height() - arrow.height() / 2,
|
||||
inner.width(),
|
||||
arrow.height());
|
||||
p.setOpacity(1. - progress);
|
||||
arrow.paintInCenter(p, arrowRect);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
return inner;
|
||||
}
|
||||
|
||||
class TTLButton final : public Ui::RippleButton {
|
||||
public:
|
||||
TTLButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::RecordBar &st);
|
||||
|
||||
void clearState() override;
|
||||
|
||||
protected:
|
||||
QImage prepareRippleMask() const override;
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
|
||||
private:
|
||||
const style::RecordBar &_st;
|
||||
const QRect _rippleRect;
|
||||
const QString _text;
|
||||
|
||||
Ui::Animations::Simple _activeAnimation;
|
||||
|
||||
};
|
||||
|
||||
TTLButton::TTLButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::RecordBar &st)
|
||||
: RippleButton(parent, st.lock.ripple)
|
||||
, _st(st)
|
||||
, _rippleRect(Rect(Size(st::historyRecordLockTopShadow.width()))
|
||||
- (st::historyRecordLockRippleMargin))
|
||||
, _text(u"1"_q) {
|
||||
resize(Size(st::historyRecordLockTopShadow.width()));
|
||||
|
||||
setClickedCallback([=] {
|
||||
Ui::AbstractButton::setDisabled(!Ui::AbstractButton::isDisabled());
|
||||
const auto isActive = !Ui::AbstractButton::isDisabled();
|
||||
_activeAnimation.start(
|
||||
[=] { update(); },
|
||||
isActive ? 0. : 1.,
|
||||
isActive ? 1. : 0.,
|
||||
st::historyRecordVoiceShowDuration);
|
||||
});
|
||||
|
||||
paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &clip) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto inner = DrawLockCircle(p, rect(), _st.lock, 1.);
|
||||
|
||||
Ui::RippleButton::paintRipple(p, _rippleRect.x(), _rippleRect.y());
|
||||
|
||||
const auto innerRect = inner - st::historyRecordLockMargin * 2;
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(_st.lock.fg);
|
||||
p.drawText(inner, _text, style::al_center);
|
||||
|
||||
const auto penWidth = st::historyRecordTTLLineWidth;
|
||||
auto pen = QPen(_st.lock.fg);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(penWidth);
|
||||
|
||||
p.setPen(pen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawArc(innerRect, arc::kQuarterLength, arc::kHalfLength);
|
||||
|
||||
{
|
||||
p.setClipRect(innerRect
|
||||
- QMargins(innerRect.width() / 2, 0, -penWidth, -penWidth));
|
||||
pen.setStyle(Qt::DotLine);
|
||||
p.setPen(pen);
|
||||
p.drawEllipse(innerRect);
|
||||
p.setClipping(false);
|
||||
}
|
||||
|
||||
const auto activeProgress = _activeAnimation.value(
|
||||
!Ui::AbstractButton::isDisabled() ? 1 : 0);
|
||||
if (activeProgress) {
|
||||
p.setOpacity(activeProgress);
|
||||
pen.setStyle(Qt::SolidLine);
|
||||
pen.setBrush(st::windowBgActive);
|
||||
p.setPen(pen);
|
||||
p.setBrush(pen.brush());
|
||||
p.drawEllipse(innerRect);
|
||||
|
||||
p.setPen(st::windowFgActive);
|
||||
p.drawText(innerRect, _text, style::al_center);
|
||||
}
|
||||
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void TTLButton::clearState() {
|
||||
Ui::AbstractButton::setDisabled(true);
|
||||
update();
|
||||
}
|
||||
|
||||
QImage TTLButton::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::EllipseMask(_rippleRect.size());
|
||||
}
|
||||
|
||||
QPoint TTLButton::prepareRippleStartPosition() const {
|
||||
return mapFromGlobal(QCursor::pos()) - _rippleRect.topLeft();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ListenWrap final {
|
||||
@ -704,68 +873,8 @@ void RecordLock::init() {
|
||||
void RecordLock::drawProgress(QPainter &p) {
|
||||
const auto progress = _progress.current();
|
||||
|
||||
const auto &originTop = _st.originTop;
|
||||
const auto &originBottom = _st.originBottom;
|
||||
const auto &originBody = _st.originBody;
|
||||
const auto &shadowTop = _st.shadowTop;
|
||||
const auto &shadowBottom = _st.shadowBottom;
|
||||
const auto &shadowBody = _st.shadowBody;
|
||||
const auto &shadowMargins = st::historyRecordLockMargin;
|
||||
const auto inner = DrawLockCircle(p, rect(), _st, progress);
|
||||
|
||||
const auto bottomMargin = anim::interpolate(
|
||||
0,
|
||||
rect().height() - shadowTop.height() - shadowBottom.height(),
|
||||
progress);
|
||||
|
||||
const auto topMargin = anim::interpolate(
|
||||
rect().height() / 4,
|
||||
0,
|
||||
progress);
|
||||
|
||||
const auto full = rect().marginsRemoved(
|
||||
style::margins(0, topMargin, 0, bottomMargin));
|
||||
const auto inner = full.marginsRemoved(shadowMargins);
|
||||
const auto content = inner.marginsRemoved(style::margins(
|
||||
0,
|
||||
originTop.height(),
|
||||
0,
|
||||
originBottom.height()));
|
||||
const auto contentShadow = full.marginsRemoved(style::margins(
|
||||
0,
|
||||
shadowTop.height(),
|
||||
0,
|
||||
shadowBottom.height()));
|
||||
|
||||
const auto w = full.width();
|
||||
{
|
||||
shadowTop.paint(p, full.topLeft(), w);
|
||||
originTop.paint(p, inner.topLeft(), w);
|
||||
}
|
||||
{
|
||||
const auto shadowPos = QPoint(
|
||||
full.x(),
|
||||
contentShadow.y() + contentShadow.height());
|
||||
const auto originPos = QPoint(
|
||||
inner.x(),
|
||||
content.y() + content.height());
|
||||
shadowBottom.paint(p, shadowPos, w);
|
||||
originBottom.paint(p, originPos, w);
|
||||
}
|
||||
{
|
||||
shadowBody.fill(p, contentShadow);
|
||||
originBody.fill(p, content);
|
||||
}
|
||||
{
|
||||
const auto &arrow = _st.arrow;
|
||||
const auto arrowRect = QRect(
|
||||
inner.x(),
|
||||
content.y() + content.height() - arrow.height() / 2,
|
||||
inner.width(),
|
||||
arrow.height());
|
||||
p.setOpacity(1. - progress);
|
||||
arrow.paintInCenter(p, arrowRect);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
if (isLocked()) {
|
||||
Ui::RippleButton::paintRipple(p, _rippleRect.x(), _rippleRect.y());
|
||||
}
|
||||
@ -800,7 +909,7 @@ void RecordLock::drawProgress(QPainter &p) {
|
||||
|
||||
const auto lockTranslation = QPoint(
|
||||
(inner.width() - size.width()) / 2,
|
||||
(originTop.height() * 2 - size.height()) / 2);
|
||||
(_st.originTop.height() * 2 - size.height()) / 2);
|
||||
const auto xRadius = anim::interpolate(2, 3, _lockToStopProgress);
|
||||
|
||||
if (_lockToStopProgress == 1.) {
|
||||
@ -1015,6 +1124,7 @@ VoiceRecordBar::VoiceRecordBar(
|
||||
, _show(std::move(descriptor.show))
|
||||
, _send(std::move(descriptor.send))
|
||||
, _lock(std::make_unique<RecordLock>(_outerContainer, _st.lock))
|
||||
, _ttlButton(std::make_unique<TTLButton>(_outerContainer, _st))
|
||||
, _level(std::make_unique<VoiceRecordButton>(_outerContainer, _st))
|
||||
, _cancel(std::make_unique<CancelButton>(this, _st, descriptor.recorderHeight))
|
||||
, _startTimer([=] { startRecording(); })
|
||||
@ -1090,6 +1200,33 @@ void VoiceRecordBar::updateLockGeometry() {
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceRecordBar::updateTTLGeometry(
|
||||
TTLAnimationType type,
|
||||
float64 progress) {
|
||||
const auto parent = parentWidget();
|
||||
const auto me = Ui::MapFrom(_outerContainer, parent, geometry());
|
||||
const auto anyTop = me.y() - st::historyRecordLockPosition.y();
|
||||
if (type == TTLAnimationType::RightLeft) {
|
||||
const auto finalRight = _outerContainer->width()
|
||||
- rect::right(me)
|
||||
+ st::historyRecordLockPosition.x();
|
||||
|
||||
const auto from = -_ttlButton->width();
|
||||
const auto right = anim::interpolate(from, finalRight, progress);
|
||||
_ttlButton->moveToRight(right, _ttlButton->y());
|
||||
} else if (type == TTLAnimationType::TopBottom) {
|
||||
const auto ttlFrom = anyTop - _ttlButton->height() * 2;
|
||||
const auto ttlTo = anyTop - _lock->height();
|
||||
_ttlButton->moveToLeft(
|
||||
_ttlButton->x(),
|
||||
anim::interpolate(ttlFrom, ttlTo, 1. - progress));
|
||||
} else if (type == TTLAnimationType::RightTopStatic) {
|
||||
_ttlButton->moveToRight(
|
||||
-_ttlButton->width(),
|
||||
anyTop - _ttlButton->height() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceRecordBar::init() {
|
||||
if (_st.radius > 0) {
|
||||
_backgroundRect.emplace(_st.radius, _st.bg);
|
||||
@ -1213,6 +1350,7 @@ void VoiceRecordBar::init() {
|
||||
if (to == value) {
|
||||
_recordingLifetime.destroy();
|
||||
}
|
||||
updateTTLGeometry(TTLAnimationType::TopBottom, 1. - value);
|
||||
};
|
||||
_showListenAnimation.start(std::move(callback), 0., to, duration);
|
||||
}, lifetime());
|
||||
@ -1222,6 +1360,9 @@ void VoiceRecordBar::init() {
|
||||
|
||||
_lock->locks(
|
||||
) | rpl::start_with_next([=] {
|
||||
_ttlButton->show();
|
||||
updateTTLGeometry(TTLAnimationType::RightTopStatic, 0);
|
||||
|
||||
_level->setType(VoiceRecordButton::Type::Send);
|
||||
|
||||
_level->clicks(
|
||||
@ -1243,6 +1384,7 @@ void VoiceRecordBar::init() {
|
||||
auto callback = [=](float64 value) {
|
||||
_lock->requestPaintLockToStopProgress(value);
|
||||
update();
|
||||
updateTTLGeometry(TTLAnimationType::RightLeft, value);
|
||||
};
|
||||
_lockToStopAnimation.start(std::move(callback), from, to, duration);
|
||||
}, lifetime());
|
||||
@ -1318,6 +1460,9 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn<void()> &&callback) {
|
||||
_listen->requestPaintProgress(value);
|
||||
}
|
||||
update();
|
||||
if (!show) {
|
||||
updateTTLGeometry(TTLAnimationType::RightLeft, value);
|
||||
}
|
||||
if ((show && value == 1.) || (!show && value == 0.)) {
|
||||
if (callback) {
|
||||
callback();
|
||||
@ -1465,6 +1610,7 @@ void VoiceRecordBar::hideFast() {
|
||||
hide();
|
||||
_lock->hide();
|
||||
_level->hide();
|
||||
_ttlButton->clearState();
|
||||
}
|
||||
|
||||
void VoiceRecordBar::stopRecording(StopType type) {
|
||||
|
@ -21,6 +21,7 @@ struct RecordBar;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class SendButton;
|
||||
} // namespace Ui
|
||||
|
||||
@ -103,12 +104,19 @@ private:
|
||||
Listen,
|
||||
};
|
||||
|
||||
enum class TTLAnimationType {
|
||||
RightLeft,
|
||||
TopBottom,
|
||||
RightTopStatic,
|
||||
};
|
||||
|
||||
void init();
|
||||
void initLockGeometry();
|
||||
void initLevelGeometry();
|
||||
|
||||
void updateMessageGeometry();
|
||||
void updateLockGeometry();
|
||||
void updateTTLGeometry(TTLAnimationType type, float64 progress);
|
||||
|
||||
void recordUpdated(quint16 level, int samples);
|
||||
|
||||
@ -143,6 +151,7 @@ private:
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const std::shared_ptr<Ui::SendButton> _send;
|
||||
const std::unique_ptr<RecordLock> _lock;
|
||||
const std::unique_ptr<Ui::AbstractButton> _ttlButton;
|
||||
const std::unique_ptr<VoiceRecordButton> _level;
|
||||
const std::unique_ptr<CancelButton> _cancel;
|
||||
std::unique_ptr<ListenWrap> _listen;
|
||||
|
Loading…
Reference in New Issue
Block a user