mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Added button to voice record bar for ttl voice messages.
This commit is contained in:
parent
5cf0b6b50e
commit
ca86dce760
3 changed files with 218 additions and 62 deletions
|
@ -1072,6 +1072,7 @@ historyRecordCancelActive: windowActiveTextFg;
|
||||||
historyRecordFont: font(13px);
|
historyRecordFont: font(13px);
|
||||||
historyRecordDurationSkip: 12px;
|
historyRecordDurationSkip: 12px;
|
||||||
historyRecordDurationFg: historyComposeAreaFg;
|
historyRecordDurationFg: historyComposeAreaFg;
|
||||||
|
historyRecordTTLLineWidth: 2px;
|
||||||
|
|
||||||
historyRecordMainBlobMinRadius: 23px;
|
historyRecordMainBlobMinRadius: 23px;
|
||||||
historyRecordMainBlobMaxRadius: 37px;
|
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
|
} // namespace
|
||||||
|
|
||||||
class ListenWrap final {
|
class ListenWrap final {
|
||||||
|
@ -704,68 +873,8 @@ void RecordLock::init() {
|
||||||
void RecordLock::drawProgress(QPainter &p) {
|
void RecordLock::drawProgress(QPainter &p) {
|
||||||
const auto progress = _progress.current();
|
const auto progress = _progress.current();
|
||||||
|
|
||||||
const auto &originTop = _st.originTop;
|
const auto inner = DrawLockCircle(p, rect(), _st, progress);
|
||||||
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,
|
|
||||||
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()) {
|
if (isLocked()) {
|
||||||
Ui::RippleButton::paintRipple(p, _rippleRect.x(), _rippleRect.y());
|
Ui::RippleButton::paintRipple(p, _rippleRect.x(), _rippleRect.y());
|
||||||
}
|
}
|
||||||
|
@ -800,7 +909,7 @@ void RecordLock::drawProgress(QPainter &p) {
|
||||||
|
|
||||||
const auto lockTranslation = QPoint(
|
const auto lockTranslation = QPoint(
|
||||||
(inner.width() - size.width()) / 2,
|
(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);
|
const auto xRadius = anim::interpolate(2, 3, _lockToStopProgress);
|
||||||
|
|
||||||
if (_lockToStopProgress == 1.) {
|
if (_lockToStopProgress == 1.) {
|
||||||
|
@ -1015,6 +1124,7 @@ VoiceRecordBar::VoiceRecordBar(
|
||||||
, _show(std::move(descriptor.show))
|
, _show(std::move(descriptor.show))
|
||||||
, _send(std::move(descriptor.send))
|
, _send(std::move(descriptor.send))
|
||||||
, _lock(std::make_unique<RecordLock>(_outerContainer, _st.lock))
|
, _lock(std::make_unique<RecordLock>(_outerContainer, _st.lock))
|
||||||
|
, _ttlButton(std::make_unique<TTLButton>(_outerContainer, _st))
|
||||||
, _level(std::make_unique<VoiceRecordButton>(_outerContainer, _st))
|
, _level(std::make_unique<VoiceRecordButton>(_outerContainer, _st))
|
||||||
, _cancel(std::make_unique<CancelButton>(this, _st, descriptor.recorderHeight))
|
, _cancel(std::make_unique<CancelButton>(this, _st, descriptor.recorderHeight))
|
||||||
, _startTimer([=] { startRecording(); })
|
, _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() {
|
void VoiceRecordBar::init() {
|
||||||
if (_st.radius > 0) {
|
if (_st.radius > 0) {
|
||||||
_backgroundRect.emplace(_st.radius, _st.bg);
|
_backgroundRect.emplace(_st.radius, _st.bg);
|
||||||
|
@ -1213,6 +1350,7 @@ void VoiceRecordBar::init() {
|
||||||
if (to == value) {
|
if (to == value) {
|
||||||
_recordingLifetime.destroy();
|
_recordingLifetime.destroy();
|
||||||
}
|
}
|
||||||
|
updateTTLGeometry(TTLAnimationType::TopBottom, 1. - value);
|
||||||
};
|
};
|
||||||
_showListenAnimation.start(std::move(callback), 0., to, duration);
|
_showListenAnimation.start(std::move(callback), 0., to, duration);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
@ -1222,6 +1360,9 @@ void VoiceRecordBar::init() {
|
||||||
|
|
||||||
_lock->locks(
|
_lock->locks(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
|
_ttlButton->show();
|
||||||
|
updateTTLGeometry(TTLAnimationType::RightTopStatic, 0);
|
||||||
|
|
||||||
_level->setType(VoiceRecordButton::Type::Send);
|
_level->setType(VoiceRecordButton::Type::Send);
|
||||||
|
|
||||||
_level->clicks(
|
_level->clicks(
|
||||||
|
@ -1243,6 +1384,7 @@ void VoiceRecordBar::init() {
|
||||||
auto callback = [=](float64 value) {
|
auto callback = [=](float64 value) {
|
||||||
_lock->requestPaintLockToStopProgress(value);
|
_lock->requestPaintLockToStopProgress(value);
|
||||||
update();
|
update();
|
||||||
|
updateTTLGeometry(TTLAnimationType::RightLeft, value);
|
||||||
};
|
};
|
||||||
_lockToStopAnimation.start(std::move(callback), from, to, duration);
|
_lockToStopAnimation.start(std::move(callback), from, to, duration);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
@ -1318,6 +1460,9 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn<void()> &&callback) {
|
||||||
_listen->requestPaintProgress(value);
|
_listen->requestPaintProgress(value);
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
|
if (!show) {
|
||||||
|
updateTTLGeometry(TTLAnimationType::RightLeft, value);
|
||||||
|
}
|
||||||
if ((show && value == 1.) || (!show && value == 0.)) {
|
if ((show && value == 1.) || (!show && value == 0.)) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
|
@ -1465,6 +1610,7 @@ void VoiceRecordBar::hideFast() {
|
||||||
hide();
|
hide();
|
||||||
_lock->hide();
|
_lock->hide();
|
||||||
_level->hide();
|
_level->hide();
|
||||||
|
_ttlButton->clearState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceRecordBar::stopRecording(StopType type) {
|
void VoiceRecordBar::stopRecording(StopType type) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ struct RecordBar;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
class AbstractButton;
|
||||||
class SendButton;
|
class SendButton;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
@ -103,12 +104,19 @@ private:
|
||||||
Listen,
|
Listen,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class TTLAnimationType {
|
||||||
|
RightLeft,
|
||||||
|
TopBottom,
|
||||||
|
RightTopStatic,
|
||||||
|
};
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void initLockGeometry();
|
void initLockGeometry();
|
||||||
void initLevelGeometry();
|
void initLevelGeometry();
|
||||||
|
|
||||||
void updateMessageGeometry();
|
void updateMessageGeometry();
|
||||||
void updateLockGeometry();
|
void updateLockGeometry();
|
||||||
|
void updateTTLGeometry(TTLAnimationType type, float64 progress);
|
||||||
|
|
||||||
void recordUpdated(quint16 level, int samples);
|
void recordUpdated(quint16 level, int samples);
|
||||||
|
|
||||||
|
@ -143,6 +151,7 @@ private:
|
||||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||||
const std::shared_ptr<Ui::SendButton> _send;
|
const std::shared_ptr<Ui::SendButton> _send;
|
||||||
const std::unique_ptr<RecordLock> _lock;
|
const std::unique_ptr<RecordLock> _lock;
|
||||||
|
const std::unique_ptr<Ui::AbstractButton> _ttlButton;
|
||||||
const std::unique_ptr<VoiceRecordButton> _level;
|
const std::unique_ptr<VoiceRecordButton> _level;
|
||||||
const std::unique_ptr<CancelButton> _cancel;
|
const std::unique_ptr<CancelButton> _cancel;
|
||||||
std::unique_ptr<ListenWrap> _listen;
|
std::unique_ptr<ListenWrap> _listen;
|
||||||
|
|
Loading…
Add table
Reference in a new issue