Move fingerprint / signal bars to a separate widget.

This commit is contained in:
John Preston 2020-08-14 16:51:47 +04:00
parent 1aaf7df54a
commit 34840766b2
10 changed files with 222 additions and 168 deletions

View File

@ -14,6 +14,8 @@ CallSignalBars {
width: pixels;
radius: pixels;
skip: pixels;
min: pixels;
max: pixels;
color: color;
inactiveOpacity: double;
}
@ -79,9 +81,12 @@ callOutgoingDefaultSize: size(160px, 110px);
callInnerPadding: 12px;
callFingerprintPadding: margins(9px, 4px, 9px, 5px);
callFingerprintTop: 11px;
callFingerprintSkip: 3px;
callFingerprintPadding: margins(10px, 4px, 8px, 5px);
callFingerprintSkip: 4px;
callFingerprintSignalBarsSkip: 2px;
callSignalBarsPadding: margins(8px, 9px, 11px, 5px);
callFingerprintTop: 8px;
callFingerprintBottom: -16px;
callTooltipMutedIcon: icon{{ "calls_mute_tooltip", videoPlayIconFg }};
@ -295,17 +300,21 @@ callDebugLabel: FlatLabel(defaultFlatLabel) {
callPanelDuration: 150;
callPanelSignalBars: CallSignalBars {
width: 3px;
width: 2px;
radius: 1px;
skip: 1px;
skip: 2px;
min: 4px;
max: 10px;
color: callNameFg;
inactiveOpacity: 0.5;
}
callBarSignalBars: CallSignalBars(callPanelSignalBars) {
width: 3px;
skip: 1px;
min: 3px;
max: 12px;
color: callBarFg;
}
callSignalMargin: 8px;
callSignalPadding: 4px;
callTitleButton: IconButton {
width: 34px;

View File

@ -8,11 +8,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_emoji_fingerprint.h"
#include "calls/calls_call.h"
#include "calls/calls_signal_bars.h"
#include "lang/lang_keys.h"
#include "data/data_user.h"
#include "ui/widgets/tooltip.h"
#include "ui/emoji_config.h"
#include "ui/rp_widget.h"
#include "styles/style_calls.h"
namespace Calls {
namespace {
constexpr auto kTooltipShowTimeoutMs = 1000;
const ushort Data[] = {
0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21,
0xd83d, 0xde0e, 0xd83d, 0xde34, 0xd83d, 0xde35, 0xd83d, 0xde08, 0xd83d, 0xde2c, 0xd83d, 0xde07,
@ -143,7 +151,146 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
}
}
return result;
}
object_ptr<Ui::RpWidget> CreateFingerprintAndSignalBars(
not_null<QWidget*> parent,
not_null<Call*> call) {
class EmojiTooltipShower final : public Ui::AbstractTooltipShower {
public:
EmojiTooltipShower(not_null<QWidget*> window, const QString &text)
: _window(window)
, _text(text) {
}
QString tooltipText() const override {
return _text;
}
QPoint tooltipPos() const override {
return QCursor::pos();
}
bool tooltipWindowActive() const override {
return _window->isActiveWindow();
}
private:
const not_null<QWidget*> _window;
const QString _text;
};
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
// Emoji tooltip.
const auto shower = raw->lifetime().make_state<EmojiTooltipShower>(
parent->window(),
tr::lng_call_fingerprint_tooltip(
tr::now,
lt_user,
call->user()->name));
raw->setMouseTracking(true);
raw->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::MouseMove) {
Ui::Tooltip::Show(kTooltipShowTimeoutMs, shower);
} else if (e->type() == QEvent::Leave) {
Ui::Tooltip::Hide();
}
}, raw->lifetime());
// Signal bars.
const auto bars = Ui::CreateChild<SignalBars>(
raw,
call,
st::callPanelSignalBars);
bars->setAttribute(Qt::WA_TransparentForMouseEvents);
// Geometry.
const auto print = ComputeEmojiFingerprint(call);
auto realSize = Ui::Emoji::GetSizeNormal();
auto size = realSize / cIntRetinaFactor();
auto count = print.size();
const auto printSize = QSize(
count * size + (count - 1) * st::callFingerprintSkip,
size);
const auto fullPrintSize = QRect(
QPoint(),
printSize
).marginsAdded(st::callFingerprintPadding).size();
const auto fullBarsSize = bars->rect().marginsAdded(
st::callSignalBarsPadding
).size();
const auto fullSize = QSize(
(fullPrintSize.width()
+ st::callFingerprintSignalBarsSkip
+ fullBarsSize.width()),
fullPrintSize.height());
raw->resize(fullSize);
bars->moveToRight(
st::callSignalBarsPadding.right(),
st::callSignalBarsPadding.top());
// Paint.
const auto background = raw->lifetime().make_state<QImage>(
fullSize * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
rpl::merge(
rpl::single(rpl::empty_value()),
Ui::Emoji::Updated(),
style::PaletteChanged()
) | rpl::start_with_next([=] {
background->fill(Qt::transparent);
// Prepare.
auto p = QPainter(background);
const auto height = fullSize.height();
const auto fullPrintRect = QRect(QPoint(), fullPrintSize);
const auto fullBarsRect = QRect(
fullSize.width() - fullBarsSize.width(),
0,
fullBarsSize.width(),
height);
const auto bigRadius = height / 2;
const auto smallRadius = st::buttonRadius;
const auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::callBgButton);
// Fingerprint part.
p.setClipRect(0, 0, fullPrintSize.width() / 2, height);
p.drawRoundedRect(fullPrintRect, bigRadius, bigRadius);
p.setClipRect(fullPrintSize.width() / 2, 0, fullSize.width(), height);
p.drawRoundedRect(fullPrintRect, smallRadius, smallRadius);
// Signal bars part.
const auto middle = fullBarsRect.center().x();
p.setClipRect(0, 0, middle, height);
p.drawRoundedRect(fullBarsRect, smallRadius, smallRadius);
p.setClipRect(middle, 0, fullBarsRect.width(), height);
p.drawRoundedRect(fullBarsRect, bigRadius, bigRadius);
// Emoji.
const auto realSize = Ui::Emoji::GetSizeNormal();
const auto size = realSize / cIntRetinaFactor();
auto left = st::callFingerprintPadding.left();
const auto top = st::callFingerprintPadding.top();
p.setClipping(false);
for (const auto emoji : print) {
Ui::Emoji::Draw(p, emoji, realSize, left, top);
left += st::callFingerprintSkip + size;
}
raw->update();
}, raw->lifetime());
raw->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
QPainter(raw).drawImage(raw->rect(), *background);
}, raw->lifetime());
raw->show();
return result;
}
} // namespace Calls

View File

@ -7,10 +7,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/object_ptr.h"
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Calls {
class Call;
std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call);
[[nodiscard]] std::vector<EmojiPtr> ComputeEmojiFingerprint(
not_null<Call*> call);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateFingerprintAndSignalBars(
not_null<QWidget*> parent,
not_null<Call*> call);
} // namespace Calls

View File

@ -52,11 +52,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QWindow>
namespace Calls {
namespace {
constexpr auto kTooltipShowTimeoutMs = 1000;
} // namespace
class Panel::Button final : public Ui::RippleButton {
public:
@ -352,7 +347,8 @@ void Panel::initWindow() {
#endif // Q_OS_WIN
const auto buttonWidth = st::callCancel.button.width;
const auto buttonsWidth = buttonWidth * 4;
const auto inControls = _fingerprintArea.contains(widgetPoint)
const auto inControls = (_fingerprint
&& _fingerprint->geometry().contains(widgetPoint))
|| QRect(
(widget()->width() - buttonsWidth) / 2,
_answerHangupRedial->y(),
@ -397,13 +393,9 @@ void Panel::initWidget() {
paint(clip);
}, widget()->lifetime());
widget()->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::MouseMove) {
handleMouseMove(static_cast<QMouseEvent*>(e.get()));
} else if (e->type() == QEvent::Resize) {
updateControlsGeometry();
}
widget()->sizeValue(
) | rpl::skip(1) | rpl::start_with_next([=] {
updateControlsGeometry();
}, widget()->lifetime());
}
@ -483,12 +475,6 @@ void Panel::reinitWithCall(Call *call) {
_user = _call->user();
_signalBars.create(
widget(),
_call,
st::callPanelSignalBars,
[=] { widget()->rtlupdate(signalBarsRect()); });
auto remoteMuted = _call->remoteAudioStateValue(
) | rpl::map([=](Call::RemoteAudioState state) {
return (state == Call::RemoteAudioState::Muted);
@ -585,6 +571,7 @@ void Panel::createRemoteAudioMute() {
rpl::single(_user->shortName())),
st::callRemoteAudioMute),
st::callTooltipPadding);
_remoteAudioMute->setAttribute(Qt::WA_TransparentForMouseEvents);
_remoteAudioMute->paintRequest(
) | rpl::start_with_next([=] {
@ -763,30 +750,27 @@ QRect Panel::outgoingFrameGeometry() const {
return _outgoingVideoBubble->geometry();
}
void Panel::updateFingerprintGeometry() {
auto realSize = Ui::Emoji::GetSizeNormal();
auto size = realSize / cIntRetinaFactor();
auto count = _fingerprint.size();
auto rectWidth = count * size + (count - 1) * st::callFingerprintSkip;
auto rectHeight = size;
auto left = (widget()->width() - rectWidth) / 2;
_fingerprintArea = QRect(
left,
st::callFingerprintTop + st::callFingerprintPadding.top(),
rectWidth,
rectHeight
).marginsAdded(st::callFingerprintPadding);
_fingerprintHeight = st::callFingerprintTop + _fingerprintArea.height() + st::callFingerprintBottom;
}
void Panel::updateControlsGeometry() {
if (widget()->size().isEmpty()) {
return;
}
updateFingerprintGeometry();
if (_fingerprint) {
#ifdef Q_OS_WIN
const auto minRight = _controls->geometry().width()
+ st::callFingerprintTop;
#else // Q_OS_WIN
const auto minRight = 0;
#endif // _controls
const auto desired = (widget()->width() - _fingerprint->width()) / 2;
_fingerprint->moveToRight(
std::max(desired, minRight),
st::callFingerprintTop);
}
const auto innerHeight = std::max(widget()->height(), st::callHeightMin);
const auto innerWidth = widget()->width() - 2 * st::callInnerPadding;
const auto availableTop = _fingerprintHeight;
const auto availableTop = st::callFingerprintTop
+ (_fingerprint ? _fingerprint->height() : 0)
+ st::callFingerprintBottom;
const auto available = widget()->height()
- st::callBottomControlsHeight
- availableTop;
@ -851,10 +835,6 @@ void Panel::updateControlsGeometry() {
_cancel->moveToLeft((widget()->width() - bothWidth) / 2, _buttonsTop);
updateHangupGeometry();
const auto skip = st::callSignalMargin + st::callSignalPadding;
const auto delta = (_signalBars->width() - _signalBars->height());
_signalBars->moveToLeft(skip, skip + delta / 2);
}
void Panel::updateOutgoingVideoBubbleGeometry() {
@ -910,44 +890,6 @@ void Panel::paint(QRect clip) {
fillTopShadow(p, incoming);
}
_call->videoIncoming()->markFrameShown();
if (_signalBars->isDisplayed()) {
paintSignalBarsBg(p);
}
if (!_fingerprint.empty() && clip.intersects(_fingerprintArea)) {
const auto radius = _fingerprintArea.height() / 2;
auto hq = PainterHighQualityEnabler(p);
p.setBrush(st::callBgButton);
p.setPen(Qt::NoPen);
p.drawRoundedRect(_fingerprintArea, radius, radius);
const auto realSize = Ui::Emoji::GetSizeNormal();
const auto size = realSize / cIntRetinaFactor();
auto left = _fingerprintArea.left() + st::callFingerprintPadding.left();
const auto top = _fingerprintArea.top() + st::callFingerprintPadding.top();
for (const auto emoji : _fingerprint) {
Ui::Emoji::Draw(p, emoji, realSize, left, top);
left += st::callFingerprintSkip + size;
}
}
}
QRect Panel::signalBarsRect() const {
const auto size = 2 * st::callSignalPadding + _signalBars->width();
return QRect(
st::callSignalMargin,
st::callSignalMargin,
size,
size);
}
void Panel::paintSignalBarsBg(Painter &p) {
App::roundRect(
p,
signalBarsRect(),
st::callBgButton,
ImageRoundRadius::Small);
}
void Panel::handleClose() {
@ -956,30 +898,10 @@ void Panel::handleClose() {
}
}
void Panel::handleMouseMove(not_null<QMouseEvent*> e) {
if (_fingerprintArea.contains(e->pos())) {
Ui::Tooltip::Show(kTooltipShowTimeoutMs, this);
} else {
Ui::Tooltip::Hide();
}
}
not_null<Ui::RpWidget*> Panel::widget() const {
return _window->body();
}
QString Panel::tooltipText() const {
return tr::lng_call_fingerprint_tooltip(tr::now, lt_user, _user->name);
}
QPoint Panel::tooltipPos() const {
return QCursor::pos();
}
bool Panel::tooltipWindowActive() const {
return _window->isActiveWindow();
}
void Panel::stateChanged(State state) {
Expects(_call != nullptr);
@ -990,7 +912,7 @@ void Panel::stateChanged(State state) {
&& (state != State::EndedByOtherDevice)
&& (state != State::FailedHangingUp)
&& (state != State::Failed)) {
auto toggleButton = [this](auto &&button, bool visible) {
auto toggleButton = [&](auto &&button, bool visible) {
button->toggle(
visible,
_window->isHidden()
@ -1018,8 +940,11 @@ void Panel::stateChanged(State state) {
_answerHangupRedialState = answerHangupRedialState;
refreshAnswerHangupRedialLabel();
}
if (_fingerprint.empty() && _call->isKeyShaForFingerprintReady()) {
fillFingerprint();
if (!_call->isKeyShaForFingerprintReady()) {
_fingerprint.destroy();
} else if (!_fingerprint) {
_fingerprint = CreateFingerprintAndSignalBars(widget(), _call);
updateControlsGeometry();
}
}
}
@ -1037,14 +962,6 @@ void Panel::refreshAnswerHangupRedialLabel() {
}());
}
void Panel::fillFingerprint() {
Expects(_call != nullptr);
_fingerprint = ComputeEmojiFingerprint(_call);
updateControlsGeometry();
widget()->update();
}
void Panel::updateStatusText(State state) {
auto statusText = [this, state]() -> QString {
switch (state) {

View File

@ -9,8 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h"
#include "base/timer.h"
#include "base/object_ptr.h"
#include "calls/calls_call.h"
#include "ui/widgets/tooltip.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
@ -41,12 +41,11 @@ struct CallBodyLayout;
namespace Calls {
class Tooltip;
class Userpic;
class SignalBars;
class VideoBubble;
class Panel final : private Ui::AbstractTooltipShower {
class Panel final {
public:
Panel(not_null<Call*> call);
~Panel();
@ -68,11 +67,6 @@ private:
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
// AbstractTooltipShower interface
QString tooltipText() const override;
QPoint tooltipPos() const override;
bool tooltipWindowActive() const override;
void paint(QRect clip);
void initWindow();
@ -84,12 +78,10 @@ private:
void initBottomShadow();
void handleClose();
void handleMouseMove(not_null<QMouseEvent*> e);
QRect signalBarsRect() const;
void paintSignalBarsBg(Painter &p);
void updateFingerprintGeometry();
void updateControlsGeometry();
void updateHangupGeometry();
void updateStatusGeometry();
@ -98,7 +90,6 @@ private:
void showControls();
void updateStatusText(State state);
void startDurationUpdateTimer(crl::time currentDuration);
void fillFingerprint();
void setIncomingSize(QSize size);
void fillTopShadow(QPainter &p, QRect incoming);
void fillBottomShadow(QPainter &p, QRect incoming);
@ -136,16 +127,13 @@ private:
object_ptr<Button> _mute;
object_ptr<Ui::FlatLabel> _name;
object_ptr<Ui::FlatLabel> _status;
object_ptr<SignalBars> _signalBars = { nullptr };
object_ptr<Ui::RpWidget> _fingerprint = { nullptr };
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _remoteAudioMute = { nullptr };
std::unique_ptr<Userpic> _userpic;
std::unique_ptr<VideoBubble> _outgoingVideoBubble;
std::vector<EmojiPtr> _fingerprint;
QRect _fingerprintArea;
QPixmap _bottomShadow;
int _bodyTop = 0;
int _buttonsTop = 0;
int _fingerprintHeight = 0;
base::Timer _updateDurationTimer;
base::Timer _updateOuterRippleTimer;

View File

@ -15,30 +15,20 @@ namespace Calls {
SignalBars::SignalBars(
QWidget *parent,
not_null<Call*> call,
const style::CallSignalBars &st,
Fn<void()> displayedChangedCallback)
const style::CallSignalBars &st)
: RpWidget(parent)
, _st(st)
, _count(Call::kSignalBarStarting)
, _displayedChangedCallback(std::move(displayedChangedCallback)) {
, _count(Call::kSignalBarStarting) {
resize(
_st.width + (_st.width + _st.skip) * (Call::kSignalBarCount - 1),
_st.width * Call::kSignalBarCount);
_st.max);
call->signalBarCountValue(
) | rpl::start_with_next([=](int count) {
changed(count);
}, lifetime());
}
bool SignalBars::isDisplayed() const {
return (_count >= 0);
}
void SignalBars::paintEvent(QPaintEvent *e) {
if (!isDisplayed()) {
return;
}
Painter p(this);
PainterHighQualityEnabler hq(p);
@ -46,14 +36,16 @@ void SignalBars::paintEvent(QPaintEvent *e) {
p.setBrush(_st.color);
for (auto i = 0; i < Call::kSignalBarCount; ++i) {
p.setOpacity((i < _count) ? 1. : _st.inactiveOpacity);
const auto barHeight = (i + 1) * _st.width;
const auto barHeight = _st.min
+ (_st.max - _st.min) * (i / float64(Call::kSignalBarCount - 1));
const auto barLeft = i * (_st.width + _st.skip);
const auto barTop = height() - barHeight;
p.drawRoundedRect(
barLeft,
barTop,
_st.width,
barHeight,
QRectF(
barLeft,
barTop,
_st.width,
barHeight),
_st.radius,
_st.radius);
}
@ -63,13 +55,8 @@ void SignalBars::paintEvent(QPaintEvent *e) {
void SignalBars::changed(int count) {
if (_count == Call::kSignalBarFinished) {
return;
}
if (_count != count) {
const auto wasDisplayed = isDisplayed();
} else if (_count != count) {
_count = count;
if (isDisplayed() != wasDisplayed && _displayedChangedCallback) {
_displayedChangedCallback();
}
update();
}
}

View File

@ -22,20 +22,15 @@ public:
SignalBars(
QWidget *parent,
not_null<Call*> call,
const style::CallSignalBars &st,
Fn<void()> displayedChangedCallback = nullptr);
bool isDisplayed() const;
protected:
void paintEvent(QPaintEvent *e) override;
const style::CallSignalBars &st);
private:
void paintEvent(QPaintEvent *e) override;
void changed(int count);
const style::CallSignalBars &_st;
int _count = 0;
Fn<void()> _displayedChangedCallback;
};

View File

@ -143,7 +143,7 @@ Colorizer ColorizerFrom(const EmbeddedScheme &scheme, const QColor &color) {
{ qstr("activeButtonFg"), Pair{ cColor("2da192"), cColor("282e33") } }, // activeButtonBg, windowBg
{ qstr("profileVerifiedCheckFg"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // profileVerifiedCheckBg, windowBg
{ qstr("overviewCheckFgActive"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // overviewCheckBgActive
{ qstr("callIconFg"), Pair{ cColor("5ad1c1"), cColor("26282c") } }, // callAnswerBg, callBg
{ qstr("callIconFg"), Pair{ cColor("5ad1c1"), cColor("1b1f23") } }, // callAnswerBg, callBgOpaque
} };
result.lightnessMin = 64;
break;

@ -1 +1 @@
Subproject commit 160c1046c18bcf91fc212d9ea042889437189bac
Subproject commit 3e4c5e2c23b644fe9b2444e291c32d7efa31db41

@ -1 +1 @@
Subproject commit 50aa8497c290f23c496b62882921a6143e9d8321
Subproject commit f2f592be817edc289eaff0248f3e41c945b736bd