mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-21 00:23:56 +08:00
221 lines
5.6 KiB
C++
221 lines
5.6 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "ui/widgets/marquee_label.h"
|
|
|
|
#include "ui/painter.h"
|
|
#include "ui/text/text_entity.h"
|
|
#include "ui/text/text_options.h"
|
|
#include "ui/widgets/popup_menu.h"
|
|
#include "styles/style_marquee_label.h"
|
|
#include "styles/style_widgets.h"
|
|
|
|
namespace Ui {
|
|
namespace {
|
|
|
|
constexpr auto kMarqueeDelay = crl::time(500);
|
|
constexpr auto kMaxFrameDelta = crl::time(17);
|
|
|
|
} // namespace
|
|
|
|
MarqueeLabel::MarqueeLabel(
|
|
QWidget *parent,
|
|
rpl::producer<QString> text,
|
|
const style::FlatLabel &st)
|
|
: RpWidget(parent)
|
|
, _st(st)
|
|
, _marquee([=](crl::time now) { return marqueeStep(now); }) {
|
|
std::move(
|
|
text
|
|
) | rpl::on_next([=](const QString &value) {
|
|
setText(value);
|
|
}, lifetime());
|
|
}
|
|
|
|
void MarqueeLabel::setTextColorOverride(std::optional<QColor> color) {
|
|
_textColorOverride = color;
|
|
update();
|
|
}
|
|
|
|
void MarqueeLabel::setContextCopyText(const QString ©Text) {
|
|
_contextCopyText = copyText;
|
|
}
|
|
|
|
QAccessible::Role MarqueeLabel::accessibilityRole() {
|
|
return QAccessible::Role::StaticText;
|
|
}
|
|
|
|
QString MarqueeLabel::accessibilityName() {
|
|
return _text.toString();
|
|
}
|
|
|
|
void MarqueeLabel::setText(const QString &text) {
|
|
_text.setText(_st.style, text, NameTextOptions());
|
|
accessibilityNameChanged();
|
|
_offset = 0.;
|
|
_delay = kMarqueeDelay;
|
|
_lastUpdate = crl::now();
|
|
setNaturalWidth(_text.maxWidth());
|
|
refreshMarqueeState();
|
|
update();
|
|
}
|
|
|
|
int MarqueeLabel::resizeGetHeight(int newWidth) {
|
|
_availableTextWidth = newWidth;
|
|
refreshMarqueeState();
|
|
const auto full = _text.isEmpty()
|
|
? _st.style.font->height
|
|
: _text.countHeight(_text.maxWidth());
|
|
return _st.maxHeight ? std::min(full, _st.maxHeight) : full;
|
|
}
|
|
|
|
void MarqueeLabel::refreshMarqueeState() {
|
|
const auto was = _overflown;
|
|
_overflown = (_availableTextWidth > 0)
|
|
&& (_text.maxWidth() > _availableTextWidth);
|
|
if (_overflown && !_marquee.animating()) {
|
|
_offset = 0.;
|
|
_delay = kMarqueeDelay;
|
|
_lastUpdate = crl::now();
|
|
_marquee.start();
|
|
} else if (!_overflown && _marquee.animating()) {
|
|
_marquee.stop();
|
|
_offset = 0.;
|
|
}
|
|
if (was != _overflown) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
bool MarqueeLabel::marqueeStep(crl::time now) {
|
|
const auto dt = std::min(now - _lastUpdate, kMaxFrameDelta);
|
|
_lastUpdate = now;
|
|
if (_delay > 0) {
|
|
_delay -= dt;
|
|
return true;
|
|
}
|
|
const auto total = float64(_text.maxWidth() + st::marqueeLabelGap);
|
|
const auto slowdown = float64(st::marqueeLabelSlowdown);
|
|
const auto fast = float64(st::marqueeLabelSpeed);
|
|
const auto slow = float64(st::marqueeLabelSpeedSlow);
|
|
auto speed = fast;
|
|
if (_offset < slowdown) {
|
|
speed = slow + (fast - slow) * (_offset / slowdown);
|
|
} else if (_offset >= total - slowdown) {
|
|
const auto dist = _offset - (total - slowdown);
|
|
speed = fast - (fast - slow) * (dist / slowdown);
|
|
}
|
|
_offset += (dt / 1000.) * speed;
|
|
if (_offset > total) {
|
|
_offset = 0.;
|
|
_delay = kMarqueeDelay;
|
|
}
|
|
update();
|
|
return true;
|
|
}
|
|
|
|
void MarqueeLabel::paintEvent(QPaintEvent *e) {
|
|
if (_text.isEmpty()) {
|
|
return;
|
|
}
|
|
Painter p(this);
|
|
if (_overflown) {
|
|
paintMarquee(p);
|
|
return;
|
|
}
|
|
if (_textColorOverride) {
|
|
p.setPen(*_textColorOverride);
|
|
} else {
|
|
p.setPen(_st.textFg);
|
|
}
|
|
p.setTextPalette(_st.palette);
|
|
_text.draw(p, {
|
|
.position = { 0, 0 },
|
|
.availableWidth = width(),
|
|
.align = _st.align,
|
|
.clip = e->rect(),
|
|
.palette = &_st.palette,
|
|
.now = crl::now(),
|
|
});
|
|
}
|
|
|
|
void MarqueeLabel::paintMarquee(Painter &p) {
|
|
const auto ratio = style::DevicePixelRatio();
|
|
const auto full = size() * ratio;
|
|
if (_frame.size() != full) {
|
|
_frame = QImage(full, QImage::Format_ARGB32_Premultiplied);
|
|
_frame.setDevicePixelRatio(ratio);
|
|
}
|
|
_frame.fill(Qt::transparent);
|
|
{
|
|
auto q = Painter(&_frame);
|
|
if (_textColorOverride) {
|
|
q.setPen(*_textColorOverride);
|
|
} else {
|
|
q.setPen(_st.textFg);
|
|
}
|
|
q.setTextPalette(_st.palette);
|
|
const auto natural = _text.maxWidth();
|
|
const auto gap = st::marqueeLabelGap;
|
|
const auto now = crl::now();
|
|
q.save();
|
|
q.translate(-_offset, 0);
|
|
_text.draw(q, {
|
|
.position = { 0, 0 },
|
|
.availableWidth = natural,
|
|
.palette = &_st.palette,
|
|
.now = now,
|
|
});
|
|
if (_offset > 0.) {
|
|
q.translate(natural + gap, 0);
|
|
_text.draw(q, {
|
|
.position = { 0, 0 },
|
|
.availableWidth = natural,
|
|
.palette = &_st.palette,
|
|
.now = now,
|
|
});
|
|
}
|
|
q.restore();
|
|
q.setCompositionMode(QPainter::CompositionMode_DestinationOut);
|
|
const auto fade = st::marqueeLabelFade;
|
|
const auto ramp = float64(st::marqueeLabelFadeRamp);
|
|
const auto total = float64(natural + gap);
|
|
const auto leftOpacity = (_offset < ramp)
|
|
? (_offset / ramp)
|
|
: (_offset > total - ramp)
|
|
? (1. - (_offset - (total - ramp)) / ramp)
|
|
: 1.;
|
|
if (leftOpacity > 0.) {
|
|
auto gradient = QLinearGradient(0, 0, fade, 0);
|
|
gradient.setColorAt(
|
|
0.,
|
|
QColor(255, 255, 255, anim::interpolate(0, 255, leftOpacity)));
|
|
gradient.setColorAt(1., QColor(255, 255, 255, 0));
|
|
q.fillRect(QRect(0, 0, fade, height()), gradient);
|
|
}
|
|
auto gradient = QLinearGradient(width() - fade, 0, width(), 0);
|
|
gradient.setColorAt(0., QColor(255, 255, 255, 0));
|
|
gradient.setColorAt(1., QColor(255, 255, 255, 255));
|
|
q.fillRect(QRect(width() - fade, 0, fade, height()), gradient);
|
|
}
|
|
p.drawImage(0, 0, _frame);
|
|
}
|
|
|
|
void MarqueeLabel::contextMenuEvent(QContextMenuEvent *e) {
|
|
if (_contextCopyText.isEmpty() || _text.isEmpty()) {
|
|
return;
|
|
}
|
|
_menu = base::make_unique_q<PopupMenu>(this);
|
|
_menu->addAction(_contextCopyText, [text = _text.toString()] {
|
|
TextUtilities::SetClipboardText(TextForMimeData::Simple(text));
|
|
});
|
|
_menu->popup(e->globalPos());
|
|
e->accept();
|
|
}
|
|
|
|
} // namespace Ui
|