mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-26 19:14:02 +02:00
966 lines
24 KiB
C++
966 lines
24 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/boxes/choose_font_box.h"
|
|
|
|
#include "base/event_filter.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "ui/boxes/confirm_box.h"
|
|
#include "ui/chat/chat_style.h"
|
|
#include "ui/effects/ripple_animation.h"
|
|
#include "ui/layers/generic_box.h"
|
|
#include "ui/style/style_core_font.h"
|
|
#include "ui/widgets/checkbox.h"
|
|
#include "ui/widgets/multi_select.h"
|
|
#include "ui/widgets/scroll_area.h"
|
|
#include "ui/cached_round_corners.h"
|
|
#include "ui/painter.h"
|
|
#include "styles/style_boxes.h"
|
|
#include "styles/style_chat.h"
|
|
#include "styles/style_settings.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_window.h"
|
|
|
|
#include <QtGui/QFontDatabase>
|
|
|
|
namespace Ui {
|
|
namespace {
|
|
|
|
constexpr auto kMinTextWidth = 120;
|
|
constexpr auto kMaxTextWidth = 320;
|
|
constexpr auto kMaxTextLines = 3;
|
|
|
|
struct PreviewRequest {
|
|
QString family;
|
|
QColor msgBg;
|
|
QColor msgShadow;
|
|
QColor replyBar;
|
|
QColor replyNameFg;
|
|
QColor textFg;
|
|
QImage bubbleTail;
|
|
};
|
|
|
|
class PreviewPainter {
|
|
public:
|
|
PreviewPainter(const QImage &bg, PreviewRequest request);
|
|
|
|
QImage takeResult();
|
|
|
|
private:
|
|
void layout();
|
|
|
|
void paintBubble(Painter &p);
|
|
void paintContent(Painter &p);
|
|
void paintReply(Painter &p);
|
|
void paintMessage(Painter &p);
|
|
|
|
void validateBubbleCache();
|
|
|
|
const PreviewRequest _request;
|
|
const style::owned_color _msgBg;
|
|
const style::owned_color _msgShadow;
|
|
|
|
style::owned_font _nameFontOwned;
|
|
style::font _nameFont;
|
|
style::TextStyle _nameStyle;
|
|
style::owned_font _textFontOwned;
|
|
style::font _textFont;
|
|
style::TextStyle _textStyle;
|
|
|
|
Ui::Text::String _nameText;
|
|
Ui::Text::String _replyText;
|
|
Ui::Text::String _messageText;
|
|
|
|
QRect _replyRect;
|
|
QRect _name;
|
|
QRect _reply;
|
|
QRect _message;
|
|
QRect _content;
|
|
QRect _bubble;
|
|
QSize _outer;
|
|
|
|
Ui::CornersPixmaps _bubbleCorners;
|
|
QPixmap _bubbleShadowBottomRight;
|
|
|
|
QImage _result;
|
|
|
|
};
|
|
|
|
class Selector final : public Ui::RpWidget {
|
|
public:
|
|
Selector(
|
|
not_null<QWidget*> parent,
|
|
const QString &now,
|
|
rpl::producer<QString> filter,
|
|
rpl::producer<> submits,
|
|
Fn<void(QString)> chosen,
|
|
Fn<void(Ui::ScrollToRequest, anim::type)> scrollTo);
|
|
|
|
void initScroll(anim::type animated);
|
|
void setMinHeight(int height);
|
|
void selectSkip(Qt::Key direction);
|
|
|
|
private:
|
|
struct Entry {
|
|
QString id;
|
|
QString key;
|
|
QString text;
|
|
QStringList keywords;
|
|
QImage cache;
|
|
std::unique_ptr<Ui::RadioView> check;
|
|
std::unique_ptr<Ui::RippleAnimation> ripple;
|
|
int paletteVersion = 0;
|
|
};
|
|
[[nodiscard]] static std::vector<Entry> FullList(const QString &now);
|
|
|
|
int resizeGetHeight(int newWidth) override;
|
|
void paintEvent(QPaintEvent *e) override;
|
|
void leaveEventHook(QEvent *e) override;
|
|
void mouseMoveEvent(QMouseEvent *e) override;
|
|
void mousePressEvent(QMouseEvent *e) override;
|
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
|
|
|
[[nodiscard]] bool searching() const;
|
|
[[nodiscard]] int shownRowsCount() const;
|
|
[[nodiscard]] Entry &shownRowAt(int index);
|
|
|
|
void applyFilter(const QString &query);
|
|
void updateSelected(int selected);
|
|
void updatePressed(int pressed);
|
|
void updateRow(int index);
|
|
void updateRow(not_null<Entry*> row, int hint);
|
|
void addRipple(int index, QPoint position);
|
|
void validateCache(Entry &row);
|
|
void choose(Entry &row);
|
|
|
|
const style::SettingsButton &_st;
|
|
std::vector<Entry> _rows;
|
|
std::vector<not_null<Entry*>> _filtered;
|
|
QString _chosen;
|
|
int _selected = -1;
|
|
int _pressed = -1;
|
|
|
|
std::optional<QPoint> _lastGlobalPoint;
|
|
bool _selectedByKeyboard = false;
|
|
|
|
Fn<void(QString)> _callback;
|
|
Fn<void(Ui::ScrollToRequest, anim::type)> _scrollTo;
|
|
|
|
int _rowsSkip = 0;
|
|
int _rowHeight = 0;
|
|
int _minHeight = 0;
|
|
|
|
QString _query;
|
|
QStringList _queryWords;
|
|
|
|
rpl::lifetime _lifetime;
|
|
|
|
};
|
|
|
|
Selector::Selector(
|
|
not_null<QWidget*> parent,
|
|
const QString &now,
|
|
rpl::producer<QString> filter,
|
|
rpl::producer<> submits,
|
|
Fn<void(QString)> chosen,
|
|
Fn<void(Ui::ScrollToRequest, anim::type)> scrollTo)
|
|
: RpWidget(parent)
|
|
, _st(st::settingsButton)
|
|
, _rows(FullList(now))
|
|
, _chosen(now)
|
|
, _callback(std::move(chosen))
|
|
, _scrollTo(std::move(scrollTo))
|
|
, _rowsSkip(st::settingsInfoPhotoSkip)
|
|
, _rowHeight(_st.height + _st.padding.top() + _st.padding.bottom()) {
|
|
setMouseTracking(true);
|
|
|
|
std::move(filter) | rpl::start_with_next([=](const QString &query) {
|
|
applyFilter(query);
|
|
}, _lifetime);
|
|
|
|
std::move(
|
|
submits
|
|
) | rpl::start_with_next([=] {
|
|
if (_selected >= 0) {
|
|
choose(shownRowAt(_selected));
|
|
} else if (searching() && !_filtered.empty()) {
|
|
choose(*_filtered.front());
|
|
}
|
|
}, _lifetime);
|
|
}
|
|
|
|
void Selector::applyFilter(const QString &query) {
|
|
if (_query == query) {
|
|
return;
|
|
}
|
|
_query = query;
|
|
|
|
updateSelected(-1);
|
|
updatePressed(-1);
|
|
|
|
_queryWords = TextUtilities::PrepareSearchWords(_query);
|
|
|
|
const auto skip = [](
|
|
const QStringList &haystack,
|
|
const QStringList &needles) {
|
|
const auto find = [](
|
|
const QStringList &haystack,
|
|
const QString &needle) {
|
|
for (const auto &item : haystack) {
|
|
if (item.startsWith(needle)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
for (const auto &needle : needles) {
|
|
if (!find(haystack, needle)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
_filtered.clear();
|
|
if (!_queryWords.isEmpty()) {
|
|
_filtered.reserve(_rows.size());
|
|
for (auto &row : _rows) {
|
|
if (!skip(row.keywords, _queryWords)) {
|
|
_filtered.push_back(&row);
|
|
} else {
|
|
row.ripple = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
resizeToWidth(width());
|
|
Ui::SendPendingMoveResizeEvents(this);
|
|
update();
|
|
}
|
|
|
|
void Selector::updateSelected(int selected) {
|
|
if (_selected == selected) {
|
|
return;
|
|
}
|
|
const auto was = (_selected >= 0);
|
|
updateRow(_selected);
|
|
_selected = selected;
|
|
updateRow(_selected);
|
|
const auto now = (_selected >= 0);
|
|
if (was != now) {
|
|
setCursor(now ? style::cur_pointer : style::cur_default);
|
|
}
|
|
if (_selectedByKeyboard) {
|
|
const auto top = (_selected > 0)
|
|
? (_rowsSkip + _selected * _rowHeight)
|
|
: 0;
|
|
const auto bottom = (_selected > 0)
|
|
? (top + _rowHeight)
|
|
: _selected
|
|
? 0
|
|
: _rowHeight;
|
|
_scrollTo({ top, bottom }, anim::type::instant);
|
|
}
|
|
}
|
|
|
|
void Selector::updatePressed(int pressed) {
|
|
if (_pressed == pressed) {
|
|
return;
|
|
} else if (_pressed >= 0) {
|
|
if (auto &ripple = shownRowAt(_pressed).ripple) {
|
|
ripple->lastStop();
|
|
}
|
|
}
|
|
updateRow(_pressed);
|
|
_pressed = pressed;
|
|
updateRow(_pressed);
|
|
}
|
|
|
|
void Selector::updateRow(int index) {
|
|
if (index >= 0) {
|
|
update(0, _rowsSkip + index * _rowHeight, width(), _rowHeight);
|
|
}
|
|
}
|
|
|
|
void Selector::updateRow(not_null<Entry*> row, int hint) {
|
|
if (hint >= 0 && hint < shownRowsCount() && &shownRowAt(hint) == row) {
|
|
updateRow(hint);
|
|
} else if (searching()) {
|
|
const auto i = ranges::find(_filtered, row);
|
|
if (i != end(_filtered)) {
|
|
updateRow(int(i - begin(_filtered)));
|
|
}
|
|
} else {
|
|
const auto index = int(row.get() - &_rows[0]);
|
|
Assert(index >= 0 && index < _rows.size());
|
|
updateRow(index);
|
|
}
|
|
}
|
|
|
|
void Selector::validateCache(Entry &row) {
|
|
const auto version = style::PaletteVersion();
|
|
if (row.cache.isNull()) {
|
|
const auto ratio = style::DevicePixelRatio();
|
|
row.cache = QImage(
|
|
QSize(width(), _rowHeight) * ratio,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
row.cache.setDevicePixelRatio(ratio);
|
|
} else if (row.paletteVersion == version) {
|
|
return;
|
|
}
|
|
row.paletteVersion = version;
|
|
row.cache.fill(Qt::transparent);
|
|
auto owned = style::owned_font(row.id, 0, st::boxFontSize);
|
|
const auto font = owned.font();
|
|
auto p = QPainter(&row.cache);
|
|
p.setFont(font);
|
|
p.setPen(st::windowFg);
|
|
|
|
const auto textw = width() - _st.padding.left() - _st.padding.right();
|
|
const auto textt = (_rowHeight - font->height) / 2.;
|
|
p.drawText(
|
|
_st.padding.left(),
|
|
textt + font->ascent,
|
|
font->elided(row.text, textw));
|
|
}
|
|
|
|
bool Selector::searching() const {
|
|
return !_queryWords.isEmpty();
|
|
}
|
|
|
|
int Selector::shownRowsCount() const {
|
|
return searching() ? int(_filtered.size()) : int(_rows.size());
|
|
}
|
|
|
|
Selector::Entry &Selector::shownRowAt(int index) {
|
|
return searching() ? *_filtered[index] : _rows[index];
|
|
}
|
|
|
|
void Selector::setMinHeight(int height) {
|
|
_minHeight = height;
|
|
if (_minHeight > 0) {
|
|
resizeToWidth(width());
|
|
}
|
|
}
|
|
|
|
void Selector::selectSkip(Qt::Key key) {
|
|
const auto count = shownRowsCount();
|
|
if (key == Qt::Key_Down) {
|
|
if (_selected + 1 < count) {
|
|
_selectedByKeyboard = true;
|
|
updateSelected(_selected + 1);
|
|
}
|
|
} else if (key == Qt::Key_Up) {
|
|
if (_selected >= 0) {
|
|
_selectedByKeyboard = true;
|
|
updateSelected(_selected - 1);
|
|
}
|
|
} else if (key == Qt::Key_PageDown) {
|
|
const auto change = _minHeight / _rowHeight;
|
|
if (_selected + 1 < count) {
|
|
_selectedByKeyboard = true;
|
|
updateSelected(std::min(_selected + change, count - 1));
|
|
}
|
|
} else if (key == Qt::Key_PageUp) {
|
|
const auto change = _minHeight / _rowHeight;
|
|
if (_selected > 0) {
|
|
_selectedByKeyboard = true;
|
|
updateSelected(std::max(_selected - change, 0));
|
|
} else if (!_selected) {
|
|
_selectedByKeyboard = true;
|
|
updateSelected(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Selector::initScroll(anim::type animated) {
|
|
const auto index = [&] {
|
|
if (searching()) {
|
|
const auto i = ranges::find(_filtered, _chosen, &Entry::id);
|
|
if (i != end(_filtered)) {
|
|
return int(i - begin(_filtered));
|
|
}
|
|
return -1;
|
|
}
|
|
const auto i = ranges::find(_rows, _chosen, &Entry::id);
|
|
Assert(i != end(_rows));
|
|
return int(i - begin(_rows));
|
|
}();
|
|
if (index >= 0) {
|
|
const auto top = _rowsSkip + index * _rowHeight;
|
|
const auto use = std::max(top - (_minHeight - _rowHeight) / 2, 0);
|
|
_scrollTo({ use, use + _minHeight }, animated);
|
|
}
|
|
}
|
|
|
|
int Selector::resizeGetHeight(int newWidth) {
|
|
const auto added = 2 * _rowsSkip;
|
|
return std::max(added + shownRowsCount() * _rowHeight, _minHeight);
|
|
}
|
|
|
|
void Selector::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
|
|
const auto rows = shownRowsCount();
|
|
if (!rows) {
|
|
p.setFont(st::normalFont);
|
|
p.setPen(st::windowSubTextFg);
|
|
p.drawText(
|
|
QRect(0, 0, width(), height() * 2 / 3),
|
|
tr::lng_font_not_found(tr::now),
|
|
style::al_center);
|
|
return;
|
|
}
|
|
const auto clip = e->rect();
|
|
const auto clipped = std::max(clip.y() - _rowsSkip, 0);
|
|
const auto from = std::min(clipped / _rowHeight, rows);
|
|
const auto till = std::min(
|
|
(clip.y() + clip.height() - _rowsSkip + _rowHeight - 1) / _rowHeight,
|
|
rows);
|
|
const auto active = (_pressed >= 0) ? _pressed : _selected;
|
|
for (auto i = from; i != till; ++i) {
|
|
auto &row = shownRowAt(i);
|
|
const auto y = _rowsSkip + i * _rowHeight;
|
|
const auto bg = (i == active) ? st::windowBgOver : st::windowBg;
|
|
const auto rect = QRect(0, y, width(), _rowHeight);
|
|
p.fillRect(rect, bg);
|
|
|
|
if (row.ripple) {
|
|
row.ripple->paint(p, 0, y, width());
|
|
if (row.ripple->empty()) {
|
|
row.ripple = nullptr;
|
|
}
|
|
}
|
|
|
|
validateCache(row);
|
|
p.drawImage(0, y, row.cache);
|
|
|
|
if (!row.check) {
|
|
row.check = std::make_unique<Ui::RadioView>(
|
|
st::langsRadio,
|
|
(row.id == _chosen),
|
|
[=, row = &row] { updateRow(row, i); });
|
|
}
|
|
row.check->paint(
|
|
p,
|
|
_st.iconLeft,
|
|
y + (_rowHeight - st::langsRadio.diameter) / 2,
|
|
width());
|
|
}
|
|
}
|
|
|
|
void Selector::leaveEventHook(QEvent *e) {
|
|
_lastGlobalPoint = std::nullopt;
|
|
if (!_selectedByKeyboard) {
|
|
updateSelected(-1);
|
|
}
|
|
}
|
|
|
|
void Selector::mouseMoveEvent(QMouseEvent *e) {
|
|
if (!_lastGlobalPoint) {
|
|
_lastGlobalPoint = e->globalPos();
|
|
if (_selectedByKeyboard) {
|
|
return;
|
|
}
|
|
} else if (*_lastGlobalPoint == e->globalPos() && _selectedByKeyboard) {
|
|
return;
|
|
} else {
|
|
_lastGlobalPoint = e->globalPos();
|
|
}
|
|
_selectedByKeyboard = false;
|
|
const auto y = e->y() - _rowsSkip;
|
|
const auto index = (y >= 0) ? (y / _rowHeight) : -1;
|
|
updateSelected((index >= 0 && index < shownRowsCount()) ? index : -1);
|
|
}
|
|
|
|
void Selector::mousePressEvent(QMouseEvent *e) {
|
|
updatePressed(_selected);
|
|
if (_pressed >= 0) {
|
|
addRipple(_pressed, e->pos());
|
|
}
|
|
}
|
|
|
|
void Selector::mouseReleaseEvent(QMouseEvent *e) {
|
|
const auto pressed = _pressed;
|
|
updatePressed(-1);
|
|
if (pressed == _selected) {
|
|
choose(shownRowAt(pressed));
|
|
}
|
|
}
|
|
|
|
void Selector::choose(Entry &row) {
|
|
const auto id = row.id;
|
|
if (_chosen != id) {
|
|
const auto i = ranges::find(_rows, _chosen, &Entry::id);
|
|
Assert(i != end(_rows));
|
|
if (i->check) {
|
|
i->check->setChecked(false, anim::type::normal);
|
|
}
|
|
_chosen = id;
|
|
if (row.check) {
|
|
row.check->setChecked(true, anim::type::normal);
|
|
}
|
|
}
|
|
const auto animated = searching()
|
|
? anim::type::instant
|
|
: anim::type::normal;
|
|
_callback(id);
|
|
initScroll(animated);
|
|
}
|
|
|
|
void Selector::addRipple(int index, QPoint position) {
|
|
Expects(index >= 0 && index < shownRowsCount());
|
|
|
|
const auto row = &shownRowAt(index);
|
|
if (!row->ripple) {
|
|
row->ripple = std::make_unique<Ui::RippleAnimation>(
|
|
st::defaultRippleAnimation,
|
|
Ui::RippleAnimation::RectMask({ width(), _rowHeight }),
|
|
[=] { updateRow(row, index); });
|
|
}
|
|
row->ripple->add(position - QPoint(0, _rowsSkip + index * _rowHeight));
|
|
}
|
|
|
|
std::vector<Selector::Entry> Selector::FullList(const QString &now) {
|
|
using namespace TextUtilities;
|
|
|
|
auto database = QFontDatabase();
|
|
auto families = database.families();
|
|
auto result = std::vector<Entry>();
|
|
result.reserve(families.size() + 3);
|
|
const auto add = [&](const QString &text, const QString &id = {}) {
|
|
result.push_back({
|
|
.id = id,
|
|
.text = text,
|
|
.keywords = PrepareSearchWords(text),
|
|
});
|
|
};
|
|
add(tr::lng_font_default(tr::now));
|
|
add(tr::lng_font_system(tr::now), style::SystemFontTag());
|
|
for (const auto &family : families) {
|
|
if (database.isScalable(family)) {
|
|
result.push_back({ .id = family });
|
|
}
|
|
}
|
|
auto nowIt = ranges::find(result, now, &Entry::id);
|
|
if (nowIt == end(result)) {
|
|
result.push_back({ .id = now });
|
|
nowIt = end(result) - 1;
|
|
}
|
|
for (auto i = begin(result) + 2; i != end(result); ++i) {
|
|
i->key = TextUtilities::RemoveAccents(i->id).toLower();
|
|
i->text = i->id;
|
|
i->keywords = TextUtilities::PrepareSearchWords(i->id);
|
|
}
|
|
auto skip = 2;
|
|
if (nowIt - begin(result) >= skip) {
|
|
std::swap(result[2], *nowIt);
|
|
++skip;
|
|
}
|
|
ranges::sort(
|
|
begin(result) + skip, end(result),
|
|
std::less<>(),
|
|
&Entry::key);
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] PreviewRequest PrepareRequest(const QString &family) {
|
|
return {
|
|
.family = family,
|
|
.msgBg = st::msgInBg->c,
|
|
.msgShadow = st::msgInShadow->c,
|
|
.replyBar = st::msgInReplyBarColor->c,
|
|
.replyNameFg = st::msgInServiceFg->c,
|
|
.textFg = st::historyTextInFg->c,
|
|
.bubbleTail = st::historyBubbleTailInLeft.instance(st::msgInBg->c),
|
|
};
|
|
}
|
|
|
|
PreviewPainter::PreviewPainter(const QImage &bg, PreviewRequest request)
|
|
: _request(request)
|
|
, _msgBg(_request.msgBg)
|
|
, _msgShadow(_request.msgShadow)
|
|
, _nameFontOwned(_request.family, style::FontFlag::Semibold, st::fsize)
|
|
, _nameFont(_nameFontOwned.font())
|
|
, _nameStyle(st::semiboldTextStyle)
|
|
, _textFontOwned(_request.family, 0, st::fsize)
|
|
, _textFont(_textFontOwned.font())
|
|
, _textStyle(st::defaultTextStyle) {
|
|
_nameStyle.font = _nameFont;
|
|
_textStyle.font = _textFont;
|
|
|
|
layout();
|
|
|
|
const auto ratio = style::DevicePixelRatio();
|
|
_result = QImage(
|
|
_outer * ratio,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
_result.setDevicePixelRatio(ratio);
|
|
|
|
auto p = Painter(&_result);
|
|
p.drawImage(0, 0, bg);
|
|
|
|
p.translate(_bubble.topLeft());
|
|
paintBubble(p);
|
|
}
|
|
|
|
void PreviewPainter::paintBubble(Painter &p) {
|
|
validateBubbleCache();
|
|
const auto bubble = QRect(QPoint(), _bubble.size());
|
|
const auto cornerShadow = _bubbleShadowBottomRight.size()
|
|
/ _bubbleShadowBottomRight.devicePixelRatio();
|
|
p.drawPixmap(
|
|
bubble.width() - cornerShadow.width(),
|
|
bubble.height() + st::msgShadow - cornerShadow.height(),
|
|
_bubbleShadowBottomRight);
|
|
Ui::FillRoundRect(p, bubble, _msgBg.color(), _bubbleCorners);
|
|
const auto &bubbleTail = _request.bubbleTail;
|
|
const auto tail = bubbleTail.size() / bubbleTail.devicePixelRatio();
|
|
p.drawImage(-tail.width(), bubble.height() - tail.height(), bubbleTail);
|
|
p.fillRect(
|
|
-tail.width(),
|
|
bubble.height(),
|
|
tail.width() + bubble.width() - cornerShadow.width(),
|
|
st::msgShadow,
|
|
_request.msgShadow);
|
|
p.translate(_content.topLeft());
|
|
const auto local = _content.translated(-_content.topLeft());
|
|
p.setClipRect(local);
|
|
paintContent(p);
|
|
}
|
|
|
|
void PreviewPainter::validateBubbleCache() {
|
|
if (!_bubbleCorners.p[0].isNull()) {
|
|
return;
|
|
}
|
|
const auto radius = st::bubbleRadiusLarge;
|
|
_bubbleCorners = Ui::PrepareCornerPixmaps(radius, _msgBg.color());
|
|
_bubbleCorners.p[2] = {};
|
|
_bubbleShadowBottomRight
|
|
= Ui::PrepareCornerPixmaps(radius, _msgShadow.color()).p[3];
|
|
}
|
|
|
|
void PreviewPainter::paintContent(Painter &p) {
|
|
paintReply(p);
|
|
|
|
p.translate(_message.topLeft());
|
|
const auto local = _message.translated(-_message.topLeft());
|
|
p.setClipRect(local);
|
|
paintMessage(p);
|
|
}
|
|
|
|
void PreviewPainter::paintReply(Painter &p) {
|
|
{
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(_request.replyBar);
|
|
|
|
const auto outline = st::messageTextStyle.blockquote.outline;
|
|
const auto radius = st::messageTextStyle.blockquote.radius;
|
|
p.setOpacity(Ui::kDefaultOutline1Opacity);
|
|
p.setClipRect(
|
|
_replyRect.x(),
|
|
_replyRect.y(),
|
|
outline,
|
|
_replyRect.height());
|
|
p.drawRoundedRect(_replyRect, radius, radius);
|
|
p.setOpacity(Ui::kDefaultBgOpacity);
|
|
p.setClipRect(
|
|
_replyRect.x() + outline,
|
|
_replyRect.y(),
|
|
_replyRect.width() - outline,
|
|
_replyRect.height());
|
|
p.drawRoundedRect(_replyRect, radius, radius);
|
|
}
|
|
p.setOpacity(1.);
|
|
p.setClipping(false);
|
|
|
|
p.setPen(_request.replyNameFg);
|
|
_nameText.drawLeftElided(
|
|
p,
|
|
_name.x(),
|
|
_name.y(),
|
|
_name.width(),
|
|
_outer.width());
|
|
|
|
p.setPen(_request.textFg);
|
|
_replyText.drawLeftElided(
|
|
p,
|
|
_reply.x(),
|
|
_reply.y(),
|
|
_reply.width(),
|
|
_outer.width());
|
|
}
|
|
|
|
void PreviewPainter::paintMessage(Painter &p) {
|
|
p.setPen(_request.textFg);
|
|
_messageText.drawLeft(p, 0, 0, _message.width(), _message.width());
|
|
}
|
|
|
|
QImage PreviewPainter::takeResult() {
|
|
return std::move(_result);
|
|
}
|
|
|
|
void PreviewPainter::layout() {
|
|
const auto skip = st::boxRowPadding.left();
|
|
const auto minTextWidth = style::ConvertScale(kMinTextWidth);
|
|
const auto maxTextWidth = st::boxWidth
|
|
- 2 * skip
|
|
- st::msgPadding.left()
|
|
- st::msgPadding.right();
|
|
|
|
_nameText = Ui::Text::String(
|
|
_nameStyle,
|
|
tr::lng_settings_chat_message_reply_from(tr::now));
|
|
_replyText = Ui::Text::String(
|
|
_textStyle,
|
|
tr::lng_background_text2(tr::now));
|
|
_messageText = Ui::Text::String(
|
|
_textStyle,
|
|
tr::lng_background_text1(tr::now),
|
|
kDefaultTextOptions,
|
|
st::msgMinWidth / 2);
|
|
|
|
const auto namePosition = QPoint(
|
|
st::historyReplyPadding.left(),
|
|
st::historyReplyPadding.top());
|
|
const auto replyPosition = QPoint(
|
|
st::historyReplyPadding.left(),
|
|
(st::historyReplyPadding.top() + _nameFont->height));
|
|
const auto paddingRight = st::historyReplyPadding.right();
|
|
|
|
const auto wantedWidth = std::max({
|
|
namePosition.x() + _nameText.maxWidth() + paddingRight,
|
|
replyPosition.x() + _replyText.maxWidth() + paddingRight,
|
|
_messageText.maxWidth()
|
|
});
|
|
|
|
const auto messageWidth = std::clamp(
|
|
wantedWidth,
|
|
minTextWidth,
|
|
maxTextWidth);
|
|
const auto messageHeight = _messageText.countHeight(messageWidth);
|
|
|
|
_replyRect = QRect(
|
|
st::msgReplyBarPos.x(),
|
|
st::historyReplyTop,
|
|
messageWidth,
|
|
(st::historyReplyPadding.top()
|
|
+ _nameFont->height
|
|
+ _textFont->height
|
|
+ st::historyReplyPadding.bottom()));
|
|
|
|
_name = QRect(
|
|
_replyRect.topLeft() + namePosition,
|
|
QSize(messageWidth - namePosition.x(), _nameFont->height));
|
|
_reply = QRect(
|
|
_replyRect.topLeft() + replyPosition,
|
|
QSize(messageWidth - replyPosition.x(), _textFont->height));
|
|
_message = QRect(0, 0, messageWidth, messageHeight);
|
|
|
|
const auto replySkip = _replyRect.y()
|
|
+ _replyRect.height()
|
|
+ st::historyReplyBottom;
|
|
_message.moveTop(replySkip);
|
|
|
|
_content = QRect(0, 0, messageWidth, replySkip + messageHeight);
|
|
|
|
const auto msgPadding = st::msgPadding;
|
|
_bubble = _content.marginsAdded(msgPadding);
|
|
_content.moveTopLeft(-_bubble.topLeft());
|
|
_bubble.moveTopLeft({});
|
|
|
|
_outer = QSize(st::boxWidth, st::boxWidth / 2);
|
|
|
|
_bubble.moveTopLeft({ skip, std::max(
|
|
(_outer.height() - _bubble.height()) / 2,
|
|
st::msgMargin.top()) });
|
|
}
|
|
|
|
[[nodiscard]] QImage GeneratePreview(
|
|
const QImage &bg,
|
|
PreviewRequest request) {
|
|
return PreviewPainter(bg, request).takeResult();
|
|
}
|
|
|
|
[[nodiscard]] object_ptr<Ui::RpWidget> MakePreview(
|
|
not_null<QWidget*> parent,
|
|
Fn<QImage()> generatePreviewBg,
|
|
rpl::producer<QString> family) {
|
|
auto result = object_ptr<Ui::RpWidget>(parent.get());
|
|
const auto raw = result.data();
|
|
|
|
struct State {
|
|
QImage preview;
|
|
QImage bg;
|
|
QString family;
|
|
};
|
|
const auto state = raw->lifetime().make_state<State>();
|
|
|
|
state->bg = generatePreviewBg();
|
|
style::PaletteChanged() | rpl::start_with_next([=] {
|
|
state->bg = generatePreviewBg();
|
|
}, raw->lifetime());
|
|
|
|
rpl::combine(
|
|
rpl::single(rpl::empty) | rpl::then(style::PaletteChanged()),
|
|
std::move(family)
|
|
) | rpl::start_with_next([=](const auto &, QString family) {
|
|
state->family = family;
|
|
if (state->preview.isNull()) {
|
|
state->preview = GeneratePreview(
|
|
state->bg,
|
|
PrepareRequest(family));
|
|
const auto ratio = state->preview.devicePixelRatio();
|
|
raw->resize(state->preview.size() / int(ratio));
|
|
} else {
|
|
const auto weak = Ui::MakeWeak(raw);
|
|
const auto request = PrepareRequest(family);
|
|
crl::async([=, bg = state->bg] {
|
|
crl::on_main([
|
|
weak,
|
|
state,
|
|
preview = GeneratePreview(bg, request)
|
|
]() mutable {
|
|
if (const auto strong = weak.data()) {
|
|
state->preview = std::move(preview);
|
|
const auto ratio = state->preview.devicePixelRatio();
|
|
strong->resize(
|
|
strong->width(),
|
|
(state->preview.height() / int(ratio)));
|
|
strong->update();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}, raw->lifetime());
|
|
|
|
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
|
QPainter(raw).drawImage(0, 0, state->preview);
|
|
}, raw->lifetime());
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void ChooseFontBox(
|
|
not_null<GenericBox*> box,
|
|
Fn<QImage()> generatePreviewBg,
|
|
const QString &family,
|
|
Fn<void(QString)> save) {
|
|
box->setTitle(tr::lng_font_box_title());
|
|
|
|
struct State {
|
|
rpl::variable<QString> family;
|
|
rpl::variable<QString> query;
|
|
rpl::event_stream<> submits;
|
|
};
|
|
const auto state = box->lifetime().make_state<State>(State{
|
|
.family = family,
|
|
});
|
|
|
|
const auto top = box->setPinnedToTopContent(
|
|
object_ptr<Ui::VerticalLayout>(box));
|
|
top->add(MakePreview(top, generatePreviewBg, state->family.value()));
|
|
const auto filter = top->add(object_ptr<Ui::MultiSelect>(
|
|
top,
|
|
st::defaultMultiSelect,
|
|
tr::lng_participant_filter()));
|
|
top->resizeToWidth(st::boxWidth);
|
|
|
|
filter->setSubmittedCallback([=](Qt::KeyboardModifiers) {
|
|
state->submits.fire({});
|
|
});
|
|
filter->setQueryChangedCallback([=](const QString &query) {
|
|
state->query = query;
|
|
});
|
|
filter->setCancelledCallback([=] {
|
|
filter->clearQuery();
|
|
});
|
|
|
|
const auto chosen = [=](const QString &value) {
|
|
state->family = value;
|
|
filter->clearQuery();
|
|
};
|
|
const auto scrollTo = [=](
|
|
Ui::ScrollToRequest request,
|
|
anim::type animated) {
|
|
box->scrollTo(request, animated);
|
|
};
|
|
const auto selector = box->addRow(
|
|
object_ptr<Selector>(
|
|
box,
|
|
state->family.current(),
|
|
state->query.value(),
|
|
state->submits.events(),
|
|
chosen,
|
|
scrollTo),
|
|
QMargins());
|
|
box->setMinHeight(st::boxMaxListHeight);
|
|
box->setMaxHeight(st::boxMaxListHeight);
|
|
|
|
base::install_event_filter(filter, [=](not_null<QEvent*> e) {
|
|
if (e->type() == QEvent::KeyPress) {
|
|
const auto key = static_cast<QKeyEvent*>(e.get())->key();
|
|
if (key == Qt::Key_Up
|
|
|| key == Qt::Key_Down
|
|
|| key == Qt::Key_PageUp
|
|
|| key == Qt::Key_PageDown) {
|
|
selector->selectSkip(Qt::Key(key));
|
|
return base::EventFilterResult::Cancel;
|
|
}
|
|
}
|
|
return base::EventFilterResult::Continue;
|
|
});
|
|
|
|
rpl::combine(
|
|
box->heightValue(),
|
|
top->heightValue()
|
|
) | rpl::start_with_next([=](int box, int top) {
|
|
selector->setMinHeight(box - top);
|
|
}, selector->lifetime());
|
|
|
|
const auto apply = [=](QString chosen) {
|
|
if (chosen == family) {
|
|
box->closeBox();
|
|
return;
|
|
}
|
|
box->getDelegate()->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_settings_need_restart(),
|
|
.confirmed = [=] { save(chosen); },
|
|
.confirmText = tr::lng_settings_restart_now(),
|
|
}));
|
|
};
|
|
const auto refreshButtons = [=](QString chosen) {
|
|
box->clearButtons();
|
|
// Doesn't fit in most languages.
|
|
//if (!chosen.isEmpty()) {
|
|
// box->addLeftButton(tr::lng_background_reset_default(), [=] {
|
|
// apply(QString());
|
|
// });
|
|
//}
|
|
box->addButton(tr::lng_settings_save(), [=] {
|
|
apply(chosen);
|
|
});
|
|
box->addButton(tr::lng_cancel(), [=] {
|
|
box->closeBox();
|
|
});
|
|
};
|
|
state->family.value(
|
|
) | rpl::start_with_next(refreshButtons, box->lifetime());
|
|
|
|
box->setFocusCallback([=] {
|
|
filter->setInnerFocus();
|
|
});
|
|
box->setInitScrollCallback([=] {
|
|
SendPendingMoveResizeEvents(box);
|
|
selector->initScroll(anim::type::instant);
|
|
});
|
|
}
|
|
|
|
} // namespace Ui
|