Display full themes list in Settings.

This commit is contained in:
John Preston 2019-09-04 18:59:43 +03:00
parent 534772722e
commit dd74f57a66
10 changed files with 641 additions and 288 deletions

View File

@ -339,7 +339,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_bg_from_file" = "Choose from file";
"lng_settings_bg_edit_theme" = "Launch theme editor";
"lng_settings_bg_create_theme" = "Create new theme";
"lng_settings_bg_cloud_themes" = "Cloud themes";
"lng_settings_bg_cloud_themes" = "Custom themes";
"lng_settings_bg_show_all" = "Show all themes";
"lng_settings_bg_tile" = "Tile background";
"lng_settings_adaptive_wide" = "Adaptive layout for wide screens";

View File

@ -165,7 +165,7 @@ not_null<Ui::RpWidget*> UrlAuthBox::setupContent(
st::boxPadding.bottom(),
st::boxPadding.right(),
st::boxPadding.bottom()));
checkbox->setAllowMultiline(true);
checkbox->setAllowTextLines();
checkbox->setText(text, true);
return checkbox;
};

View File

@ -2219,6 +2219,7 @@ void OverlayWidget::initThemePreview() {
_doc->id,
&Data::CloudTheme::documentId);
const auto cloud = (i != end(cloudList)) ? *i : Data::CloudTheme();
const auto isTrusted = (cloud.documentId != 0);
const auto path = _doc->location().name();
const auto id = _themePreviewId = rand_value<uint64>();
@ -2241,10 +2242,17 @@ void OverlayWidget::initThemePreview() {
tr::lng_theme_preview_apply(),
st::themePreviewApplyButton);
_themeApply->show();
_themeApply->setClickedCallback([this] {
_themeApply->setClickedCallback([=] {
const auto &object = Window::Theme::Background()->themeObject();
const auto currentlyIsCustom = !object.pathAbsolute.isEmpty()
&& !object.pathAbsolute.startsWith(qstr(":/gui/"))
&& !object.cloud.id;
auto preview = std::move(_themePreview);
close();
Window::Theme::Apply(std::move(preview));
if (isTrusted && !currentlyIsCustom) {
Window::Theme::KeepApplied();
}
});
_themeCancel.create(
this,

View File

@ -1178,6 +1178,8 @@ void SetupThemeOptions(
void SetupCloudThemes(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
using namespace rpl::mappers;
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
@ -1187,35 +1189,49 @@ void SetupCloudThemes(
AddDivider(inner);
AddSkip(inner, st::settingsPrivacySkip);
AddSubsectionTitle(inner, tr::lng_settings_bg_cloud_themes());
const auto title = AddSubsectionTitle(
inner,
tr::lng_settings_bg_cloud_themes());
const auto showAll = Ui::CreateChild<Ui::LinkButton>(
inner,
tr::lng_settings_bg_show_all(tr::now));
rpl::combine(
title->topValue(),
inner->widthValue(),
showAll->widthValue()
) | rpl::start_with_next([=](int top, int outerWidth, int width) {
showAll->moveToRight(
st::settingsSubsectionTitlePadding.left(),
top,
outerWidth);
}, showAll->lifetime());
AddSkip(inner, st::settingsThemesTopSkip);
const auto list = std::make_shared<std::vector<Data::CloudTheme>>();
AddButton(
wrap->entity(),
tr::lng_settings_bg_cloud_themes(),
st::settingsChatButton,
&st::settingsIconThemes,
st::settingsChatIconLeft
)->addClickHandler([=] {
controller->window().show(Box(
Window::Theme::CloudListBox,
controller,
*list));
const auto list = inner->lifetime().make_state<Window::Theme::CloudList>(
inner,
controller);
inner->add(
list->takeWidget(),
style::margins(
st::settingsButton.padding.left(),
0,
st::settingsButton.padding.right(),
0));
list->allShown(
) | rpl::start_with_next([=](bool shown) {
showAll->setVisible(!shown);
}, showAll->lifetime());
showAll->addClickHandler([=] {
list->showAll();
});
AddSkip(inner, st::settingsThemesTopSkip);
auto shown = rpl::single(
rpl::empty_value()
) | rpl::then(
controller->session().data().cloudThemes().updated()
) | rpl::map([=] {
*list = controller->session().data().cloudThemes().list();
return !list->empty();
});
wrap->setDuration(0)->toggleOn(std::move(shown));
wrap->setDuration(0)->toggleOn(list->empty() | rpl::map(!_1));
}
void SetupSupportSwitchSettings(

View File

@ -158,10 +158,10 @@ not_null<Button*> AddButtonWithLabel(
return button;
}
void AddSubsectionTitle(
not_null<Ui::FlatLabel*> AddSubsectionTitle(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> text) {
container->add(
return container->add(
object_ptr<Ui::FlatLabel>(
container,
std::move(text),

View File

@ -15,6 +15,7 @@ class Session;
namespace Ui {
class VerticalLayout;
class FlatLabel;
} // namespace Ui
namespace Window {
@ -90,7 +91,7 @@ void CreateRightLabel(
rpl::producer<QString> label,
const style::InfoProfileButton &st,
rpl::producer<QString> buttonText);
void AddSubsectionTitle(
not_null<Ui::FlatLabel*> AddSubsectionTitle(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> text);

View File

@ -452,15 +452,21 @@ void Checkbox::setText(const QString &text, bool rich) {
void Checkbox::setCheckAlignment(style::align alignment) {
if (_checkAlignment != alignment) {
_checkAlignment = alignment;
resizeToText();
update();
}
}
void Checkbox::setAllowMultiline(bool allow) {
_allowMultiline = allow;
void Checkbox::setAllowTextLines(int lines) {
_allowTextLines = lines;
resizeToText();
update();
}
void Checkbox::setTextBreakEverywhere(bool allow) {
_textBreakEverywhere = allow;
}
bool Checkbox::checked() const {
return _check->checked();
}
@ -530,67 +536,88 @@ void Checkbox::paintEvent(QPaintEvent *e) {
_check->paint(p, check.left(), check.top(), width());
}
}
if (realCheckRect.contains(e->rect())) return;
if (realCheckRect.contains(e->rect()) || _text.isEmpty()) {
return;
}
auto leftSkip = _st.checkPosition.x()
const auto alignLeft = (_checkAlignment & Qt::AlignLeft);
const auto alignRight = (_checkAlignment & Qt::AlignRight);
const auto textSkip = _st.checkPosition.x()
+ check.width()
+ _st.textPosition.x();
auto availableTextWidth = qMax(width() - leftSkip, 1);
const auto availableTextWidth = (alignLeft || alignRight)
? std::max(width() - textSkip, 1)
: std::max(width() - _st.margin.left() - _st.margin.right(), 1);
const auto textTop = _st.margin.top() + _st.textPosition.y();
if (!_text.isEmpty()) {
p.setPen(anim::pen(_st.textFg, _st.textFgActive, active));
auto textSkip = _st.checkPosition.x()
+ check.width()
+ _st.textPosition.x();
auto textTop = _st.margin.top() + _st.textPosition.y();
if (_checkAlignment & Qt::AlignLeft) {
if (_allowMultiline) {
_text.drawLeft(
p,
textSkip,
textTop,
availableTextWidth,
width());
} else {
_text.drawLeftElided(
p,
textSkip,
textTop,
availableTextWidth,
width());
}
} else if (_checkAlignment & Qt::AlignRight) {
if (_allowMultiline) {
_text.drawRight(
p,
textSkip,
textTop,
availableTextWidth,
width());
} else {
_text.drawRightElided(
p,
textSkip,
textTop,
availableTextWidth,
width());
}
} else if (_allowMultiline || _text.countHeight(width() - _st.margin.left() - _st.margin.right()) < 2 * _st.style.font->height) {
p.setPen(anim::pen(_st.textFg, _st.textFgActive, active));
if (alignLeft) {
if (!_allowTextLines) {
_text.drawLeft(
p,
_st.margin.left(),
textSkip,
textTop,
width() - _st.margin.left() - _st.margin.right(),
width(),
style::al_top);
availableTextWidth,
width());
} else {
_text.drawLeftElided(
p,
_st.margin.left(),
textSkip,
textTop,
width() - _st.margin.left() - _st.margin.right(),
width());
availableTextWidth,
width(),
_allowTextLines,
style::al_left,
0,
-1,
0,
_textBreakEverywhere);
}
} else if (alignRight) {
if (!_allowTextLines) {
_text.drawRight(
p,
textSkip,
textTop,
availableTextWidth,
width());
} else {
_text.drawRightElided(
p,
textSkip,
textTop,
availableTextWidth,
width(),
_allowTextLines,
style::al_left,
0,
-1,
0,
_textBreakEverywhere);
}
} else if (!_allowTextLines
|| (_text.countHeight(availableTextWidth)
< (_allowTextLines + 1) * _st.style.font->height)) {
_text.drawLeft(
p,
_st.margin.left(),
textTop,
width() - _st.margin.left() - _st.margin.right(),
width(),
style::al_top);
} else {
_text.drawLeftElided(
p,
_st.margin.left(),
textTop,
width() - _st.margin.left() - _st.margin.right(),
width(),
_allowTextLines,
style::al_top,
0,
-1,
0,
_textBreakEverywhere);
}
}
@ -633,7 +660,7 @@ void Checkbox::handlePress() {
int Checkbox::resizeGetHeight(int newWidth) {
const auto result = _check->getSize().height();
const auto centered = ((_checkAlignment & Qt::AlignHCenter) != 0);
if (!centered && !_allowMultiline) {
if (!centered && _allowTextLines == 1) {
return result;
}
const auto leftSkip = _st.checkPosition.x()
@ -641,11 +668,12 @@ int Checkbox::resizeGetHeight(int newWidth) {
+ _st.textPosition.x();
const auto availableTextWidth = centered
? (newWidth - _st.margin.left() - _st.margin.right())
: qMax(width() - leftSkip, 1);
: std::max(width() - leftSkip, 1);
const auto textHeight = _text.countHeight(availableTextWidth);
const auto textBottom = _st.textPosition.y()
+ ((centered && !_allowMultiline)
? _st.style.font->height
: _text.countHeight(availableTextWidth));
+ (_allowTextLines
? std::min(textHeight, _allowTextLines * _st.style.font->height)
: textHeight);
return std::max(result, textBottom);
}

View File

@ -152,7 +152,8 @@ public:
void setText(const QString &text, bool rich = false);
void setCheckAlignment(style::align alignment);
void setAllowMultiline(bool allow);
void setAllowTextLines(int lines = 0);
void setTextBreakEverywhere(bool allow = true);
bool checked() const;
rpl::producer<bool> checkedChanges() const;
@ -200,7 +201,8 @@ private:
Text::String _text;
style::align _checkAlignment = style::al_left;
bool _allowMultiline = false;
int _allowTextLines = 1;
bool _textBreakEverywhere = false;
};

View File

@ -19,11 +19,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_history.h"
namespace Window {
namespace Theme {
namespace {
constexpr auto kFakeCloudThemeId = 0xFFFFFFFFFFFFFFFAULL;
constexpr auto kShowPerRow = 4;
[[nodiscard]] Data::CloudTheme FakeCloudTheme(const Object &object) {
auto result = Data::CloudTheme();
result.id = result.documentId = kFakeCloudThemeId;
result.slug = object.pathAbsolute;
return result;
}
[[nodiscard]] QImage ColorsBackgroundFromImage(const QImage &source) {
if (source.isNull()) {
return source;
@ -78,22 +89,403 @@ namespace {
result.background = ColorsBackgroundFromImage(instance.background);
result.sent = st::msgOutBg[instance.palette]->c;
result.received = st::msgInBg[instance.palette]->c;
result.radiobuttonBg = st::msgServiceBg[instance.palette]->c;
result.radiobuttonActive
= result.radiobuttonInactive
= st::msgServiceFg[instance.palette]->c;
return result;
}
[[nodiscard]] CloudListColors ColorsFromCurrentTheme() {
auto result = CloudListColors();
auto background = Background()->createCurrentImage();
result.background = ColorsBackgroundFromImage(background);
result.sent = st::msgOutBg->c;
result.received = st::msgInBg->c;
result.radiobuttonActive
= result.radiobuttonInactive
= st::msgServiceFg->c;
return result;
}
} // namespace
CloudList::CloudList(
not_null<QWidget*> parent,
not_null<Window::SessionController*> window)
: _window(window)
, _owned(parent)
, _outer(_owned.data())
, _group(std::make_shared<Ui::RadiobuttonGroup>()) {
setup();
}
void CloudList::showAll() {
_showAll = true;
}
object_ptr<Ui::RpWidget> CloudList::takeWidget() {
return std::move(_owned);
}
rpl::producer<bool> CloudList::empty() const {
using namespace rpl::mappers;
return _count.value() | rpl::map(_1 == 0);
}
rpl::producer<bool> CloudList::allShown() const {
using namespace rpl::mappers;
return rpl::combine(
_showAll.value(),
_count.value(),
_1 || (_2 <= kShowPerRow));
}
void CloudList::setup() {
_group->setChangedCallback([=](int selected) {
const auto &object = Background()->themeObject();
_group->setValue(groupValueForId(
object.cloud.id ? object.cloud.id : kFakeCloudThemeId));
});
auto cloudListChanges = rpl::single(
rpl::empty_value()
) | rpl::then(
_window->session().data().cloudThemes().updated()
);
auto themeChanges = rpl::single(
BackgroundUpdate(BackgroundUpdate::Type::New, Background()->tile())
) | rpl::then(base::ObservableViewer(
*Background()
)) | rpl::filter([](const BackgroundUpdate &update) {
return (update.type == BackgroundUpdate::Type::ApplyingTheme)
|| (update.type == BackgroundUpdate::Type::New);
});
rpl::combine(
std::move(cloudListChanges),
std::move(themeChanges),
allShown()
) | rpl::map([=] {
return collectAll();
}) | rpl::start_with_next([=](std::vector<Data::CloudTheme> &&list) {
rebuildUsing(std::move(list));
}, _outer->lifetime());
_outer->widthValue(
) | rpl::start_with_next([=](int width) {
updateGeometry();
}, _outer->lifetime());
}
std::vector<Data::CloudTheme> CloudList::collectAll() const {
const auto &object = Background()->themeObject();
const auto isDefault = object.pathAbsolute.isEmpty()
|| object.pathAbsolute.startsWith(qstr(":/gui/"));
auto result = _window->session().data().cloudThemes().list();
if (!isDefault) {
const auto i = ranges::find(
result,
object.cloud.id,
&Data::CloudTheme::id);
if (i == end(result)) {
if (object.cloud.id) {
result.push_back(object.cloud);
} else {
result.push_back(FakeCloudTheme(object));
}
}
}
return result;
}
void CloudList::rebuildUsing(std::vector<Data::CloudTheme> &&list) {
const auto fullCount = int(list.size());
const auto changed = applyChangesFrom(std::move(list));
_count = fullCount;
if (changed) {
updateGeometry();
}
}
bool CloudList::applyChangesFrom(std::vector<Data::CloudTheme> &&list) {
if (list.empty()) {
if (_elements.empty()) {
return false;
}
_elements.clear();
return true;
}
auto changed = false;
const auto limit = _showAll.current() ? list.size() : kShowPerRow;
const auto &object = Background()->themeObject();
const auto id = object.cloud.id ? object.cloud.id : kFakeCloudThemeId;
ranges::stable_sort(list, std::less<>(), [&](const Data::CloudTheme &t) {
if (t.id == id) {
return 0;
} else if (t.documentId) {
return 1;
} else {
return 2;
}
});
if (list.front().id == id) {
const auto j = ranges::find(_elements, id, &Element::id);
if (j == end(_elements)) {
insert(0, list.front());
changed = true;
} else if (j - begin(_elements) >= limit) {
std::rotate(
begin(_elements) + limit - 1,
j,
j + 1);
changed = true;
}
}
if (removeStaleUsing(list)) {
changed = true;
}
if (insertTillLimit(list, limit)) {
changed = true;
}
_group->setValue(groupValueForId(id));
return changed;
}
bool CloudList::removeStaleUsing(const std::vector<Data::CloudTheme> &list) {
const auto check = [&](Element &element) {
const auto j = ranges::find(
list,
element.theme.id,
&Data::CloudTheme::id);
if (j == end(list)) {
return true;
}
refreshElementUsing(element, *j);
return false;
};
const auto from = ranges::remove_if(_elements, check);
if (from == end(_elements)) {
return false;
}
_elements.erase(from, end(_elements));
return true;
}
bool CloudList::insertTillLimit(
const std::vector<Data::CloudTheme> &list,
int limit) {
const auto insertCount = (limit - int(_elements.size()));
if (insertCount < 0) {
_elements.erase(end(_elements) + insertCount, end(_elements));
return true;
} else if (!insertCount) {
return false;
}
const auto isGood = [](const Data::CloudTheme &theme) {
return (theme.documentId != 0);
};
auto positionForGood = ranges::find_if(_elements, [&](const Element &e) {
return !isGood(e.theme);
}) - begin(_elements);
auto positionForBad = end(_elements) - begin(_elements);
auto insertElements = ranges::view::all(
list
) | ranges::view::filter([&](const Data::CloudTheme &theme) {
const auto i = ranges::find(_elements, theme.id, &Element::id);
return (i == end(_elements));
}) | ranges::view::take(insertCount);
for (const auto &theme : insertElements) {
auto &index = isGood(theme) ? positionForGood : positionForBad;
insert(index, theme);
++index;
}
return true;
}
void CloudList::insert(int index, const Data::CloudTheme &theme) {
const auto id = theme.id;
const auto value = groupValueForId(id);
const auto checked = _group->hasValue() && (_group->value() == value);
auto check = std::make_unique<CloudListCheck>(checked);
const auto raw = check.get();
auto button = std::make_unique<Ui::Radiobutton>(
_outer,
_group,
value,
theme.title,
st::settingsTheme,
std::move(check));
button->setCheckAlignment(style::al_top);
button->setAllowTextLines(2);
button->setTextBreakEverywhere();
button->show();
button->setClickedCallback([=] {
const auto i = ranges::find(_elements, id, &Element::id);
if (i == end(_elements)
|| id == kFakeCloudThemeId
|| i->waiting) {
return;
}
const auto documentId = i->theme.documentId;
if (!documentId) {
// #TODO themes
return;
}
const auto document = _window->session().data().document(documentId);
DocumentOpenClickHandler::Open(
Data::FileOrigin(),
document,
nullptr);
});
auto &element = *_elements.insert(
begin(_elements) + index,
Element{ theme, raw, std::move(button) });
refreshColors(element);
}
void CloudList::refreshElementUsing(
Element &element,
const Data::CloudTheme &data) {
const auto colorsChanged = (element.theme.documentId != data.documentId)
|| ((element.id() == kFakeCloudThemeId)
&& (element.theme.slug != data.slug));
const auto titleChanged = (element.theme.title != data.title);
element.theme = data;
if (colorsChanged) {
setWaiting(element, false);
refreshColors(element);
}
if (titleChanged) {
element.button->setText(data.title);
}
}
void CloudList::refreshColors(Element &element) {
if (element.id() == kFakeCloudThemeId) {
element.check->setColors(ColorsFromCurrentTheme());
} else if (const auto documentId = element.theme.documentId) {
const auto document = _window->session().data().document(documentId);
document->save(Data::FileOrigin(), QString()); // #TODO themes
if (document->loaded()) {
refreshColorsFromDocument(element, document);
} else {
setWaiting(element, true);
subscribeToDownloadFinished();
}
} else {
element.check->setColors(CloudListColors());
}
}
void CloudList::setWaiting(Element &element, bool waiting) {
element.waiting = waiting;
element.button->setPointerCursor(!waiting);
}
void CloudList::refreshColorsFromDocument(
Element &element,
not_null<DocumentData*> document) {
auto colors = ColorsFromTheme(
document->filepath(),
document->data());
if (!colors) {
return;
}
if (colors->background.isNull()) {
colors->background = ColorsFromCurrentTheme().background;
}
element.check->setColors(*colors);
}
void CloudList::subscribeToDownloadFinished() {
if (_downloadFinishedLifetime) {
return;
}
base::ObservableViewer(
_window->session().downloaderTaskFinished()
) | rpl::start_with_next([=] {
auto &&waiting = _elements | ranges::view::filter(&Element::waiting);
const auto still = ranges::count_if(waiting, [&](Element &element) {
const auto id = element.theme.documentId;
const auto document = _window->session().data().document(id);
if (!document->loaded()) {
return true;
}
refreshColorsFromDocument(element, document);
element.waiting = false;
return false;
});
if (!still) {
_downloadFinishedLifetime.destroy();
}
}, _downloadFinishedLifetime);
}
int CloudList::groupValueForId(uint64 id) {
const auto i = _groupValueById.find(id);
if (i != end(_groupValueById)) {
return i->second;
}
const auto result = int(_idByGroupValue.size());
_groupValueById.emplace(id, result);
_idByGroupValue.push_back(id);
return result;
}
void CloudList::updateGeometry() {
const auto width = _outer->width();
if (!width) {
return;
}
const auto height = resizeGetHeight(width);
if (height != _outer->height()) {
_outer->resize(width, height);
}
}
int CloudList::resizeGetHeight(int newWidth) {
const auto desired = st::settingsThemePreviewSize.width();
const auto minSkip = st::settingsThemeMinSkip;
const auto single = std::min(
st::settingsThemePreviewSize.width(),
(newWidth - minSkip * (kShowPerRow - 1)) / kShowPerRow);
const auto skip = (newWidth - kShowPerRow * single)
/ float64(kShowPerRow - 1);
auto x = 0.;
auto y = 0;
auto index = 0;
auto rowHeight = 0;
for (const auto &element : _elements) {
const auto button = element.button.get();
button->moveToLeft(int(std::round(x)), y);
accumulate_max(rowHeight, button->height());
x += single + skip;
if (++index == kShowPerRow) {
x = 0.;
index = 0;
y += rowHeight + st::themesSmallSkip;
rowHeight = 0;
}
}
return rowHeight
? (y + rowHeight)
: (y > 0)
? (y - st::themesSmallSkip)
: 0;
}
CloudListColors ColorsFromScheme(const EmbeddedScheme &scheme) {
auto result = CloudListColors();
result.sent = scheme.sent;
result.received = scheme.received;
result.radiobuttonActive = scheme.radiobuttonActive;
result.radiobuttonInactive = scheme.radiobuttonInactive;
result.radiobuttonBg = QColor(255, 255, 255, 0);
result.background = QImage(
QSize(1, 1) * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
@ -113,19 +505,23 @@ CloudListColors ColorsFromScheme(
}
CloudListCheck::CloudListCheck(const Colors &colors, bool checked)
: CloudListCheck(checked) {
setColors(colors);
}
CloudListCheck::CloudListCheck(bool checked)
: AbstractCheckView(st::defaultRadio.duration, checked, nullptr)
, _radio(st::defaultRadio, checked, [=] { update(); }) {
setColors(colors);
}
void CloudListCheck::setColors(const Colors &colors) {
_colors = colors;
_radio.setToggledOverride(_colors.radiobuttonActive);
_radio.setUntoggledOverride(_colors.radiobuttonInactive);
_radio.setToggledOverride(_colors->radiobuttonActive);
_radio.setUntoggledOverride(_colors->radiobuttonInactive);
const auto size = st::settingsThemePreviewSize * cIntRetinaFactor();
_backgroundFull = (_colors.background.size() == size)
? _colors.background
: _colors.background.scaled(
_backgroundFull = (_colors->background.size() == size)
? _colors->background
: _colors->background.scaled(
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
@ -153,14 +549,42 @@ void CloudListCheck::validateBackgroundCache(int width) {
Images::prepareRound(_backgroundCache, ImageRoundRadius::Large);
}
void CloudListCheck::paint(
void CloudListCheck::paint(Painter &p, int left, int top, int outerWidth) {
if (!_colors) {
return;
} else if (_colors->background.isNull()) {
paintNotSupported(p, left, top, outerWidth);
} else {
paintWithColors(p, left, top, outerWidth);
}
}
void CloudListCheck::paintNotSupported(
Painter &p,
int left,
int top,
int outerWidth) {
if (_colors.background.isNull()) {
return;
}
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
p.drawRoundedRect(
QRect(0, 0, outerWidth, st::settingsThemePreviewSize.height()),
st::historyMessageRadius,
st::historyMessageRadius);
}
void CloudListCheck::paintWithColors(
Painter &p,
int left,
int top,
int outerWidth) {
Expects(_colors.has_value());
validateBackgroundCache(outerWidth);
p.drawImage(
QRect(0, 0, outerWidth, st::settingsThemePreviewSize.height()),
_backgroundCache);
const auto received = QRect(
st::settingsThemeBubblePosition,
@ -175,14 +599,9 @@ void CloudListCheck::paint(
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
validateBackgroundCache(outerWidth);
p.drawImage(
QRect(0, 0, outerWidth, st::settingsThemePreviewSize.height()),
_backgroundCache);
p.setBrush(_colors.received);
p.setBrush(_colors->received);
p.drawRoundedRect(rtlrect(received, outerWidth), radius, radius);
p.setBrush(_colors.sent);
p.setBrush(_colors->sent);
p.drawRoundedRect(rtlrect(sent, outerWidth), radius, radius);
const auto skip = st::settingsThemeRadioBottom / 2;
@ -207,178 +626,5 @@ void CloudListCheck::checkedChangedHook(anim::type animated) {
_radio.setChecked(checked(), animated);
}
int CountButtonsHeight(const std::vector<not_null<Ui::RpWidget*>> &buttons) {
constexpr auto kPerRow = 4;
const auto skip = (st::boxWideWidth
- st::settingsSubsectionTitlePadding.left()
- st::settingsSubsectionTitlePadding.right()
- kPerRow * st::settingsThemePreviewSize.width())
/ float64(kPerRow - 1);
auto x = 0.;
auto y = 0;
auto index = 0;
for (const auto button : buttons) {
button->moveToLeft(int(std::round(x)), y);
x += st::settingsThemePreviewSize.width() + skip;
if (++index == kPerRow) {
x = 0.;
index = 0;
y += st::settingsTheme.textPosition.y()
+ st::settingsTheme.style.font->height
+ st::themesSmallSkip;
}
}
if (index) {
return y
+ st::settingsTheme.textPosition.y()
+ st::settingsTheme.style.font->height;
} else if (y) {
return y - st::themesSmallSkip;
}
return 0;
}
void CloudListBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
std::vector<Data::CloudTheme> list) {
using WaitingPair = std::pair<
not_null<DocumentData*>,
not_null<CloudListCheck*>>;
box->setTitle(tr::lng_settings_bg_cloud_themes());
box->setWidth(st::boxWideWidth);
const auto currentId = Background()->themeObject().cloud.documentId;
ranges::stable_sort(list, std::less<>(), [&](const Data::CloudTheme &t) {
return !t.documentId ? 2 : (t.documentId == currentId) ? 0 : 1;
});
const auto content = box->addRow(
object_ptr<Ui::RpWidget>(box),
style::margins(
st::settingsSubsectionTitlePadding.left(),
0,
st::settingsSubsectionTitlePadding.right(),
0));
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
const auto resolveCurrent = [=] {
const auto currentId = Background()->themeObject().cloud.id;
const auto i = currentId
? ranges::find(list, currentId, &Data::CloudTheme::id)
: end(list);
group->setValue(i - begin(list));
};
resolveCurrent();
auto checker = Background()->add_subscription([=](const BackgroundUpdate &update) {
if (update.type == BackgroundUpdate::Type::ApplyingTheme
|| update.type == BackgroundUpdate::Type::New) {
resolveCurrent();
}
});
group->setChangedCallback([=](int selected) {
resolveCurrent();
});
Ui::AttachAsChild(box, std::move(checker));
const auto waiting = std::make_shared<std::vector<WaitingPair>>();
const auto fallback = std::make_shared<QImage>();
const auto buttonsMap = std::make_shared<base::flat_map<
not_null<CloudListCheck*>,
not_null<Ui::Radiobutton*>>>();
const auto getFallbackImage = [=] {
if (fallback->isNull()) {
*fallback = ColorsBackgroundFromImage(
Background()->createCurrentImage());
}
return *fallback;
};
auto index = 0;
auto buttons = ranges::view::all(
list
) | ranges::view::transform([&](const Data::CloudTheme &theme)
-> not_null<Ui::RpWidget*> {
if (!theme.documentId) {
index++;
return Ui::CreateChild<Ui::RpWidget>(content);
}
const auto document = window->session().data().document(
theme.documentId);
document->save(Data::FileOrigin(), QString()); // #TODO themes
auto colors = ColorsFromTheme(
document->filepath(),
document->data());
if (colors && colors->background.isNull()) {
colors->background = getFallbackImage();
}
auto check = std::make_unique<CloudListCheck>(
colors.value_or(CloudListColors()),
false);
const auto weak = check.get();
const auto result = Ui::CreateChild<Ui::Radiobutton>(
content,
group,
index++,
theme.title,
st::settingsTheme,
std::move(check));
result->addClickHandler([=] {
if (result->isDisabled()) {
return;
}
DocumentOpenClickHandler::Open(
Data::FileOrigin(),
document,
nullptr);
});
if (!document->loaded()) {
waiting->emplace_back(document, weak);
buttonsMap->emplace(weak, result);
}
if (!colors) {
result->setDisabled(true);
result->setPointerCursor(false);
}
result->setCheckAlignment(style::al_top);
result->resizeToWidth(st::settingsThemePreviewSize.width());
weak->setUpdateCallback([=] { result->update(); });
return result;
}) | ranges::to_vector;
const auto check = [=](WaitingPair pair) {
const auto &[document, check] = pair;
if (!document->loaded()) {
return false;
}
auto colors = ColorsFromTheme(
document->filepath(),
document->data());
if (colors) {
if (colors->background.isNull()) {
colors->background = getFallbackImage();
}
check->setColors(*colors);
const auto i = buttonsMap->find(check);
Assert(i != end(*buttonsMap));
i->second->setDisabled(false);
i->second->setPointerCursor(true);
}
return true;
};
auto &finished = window->session().downloaderTaskFinished();
auto subscription = finished.add_subscription([=] {
waiting->erase(ranges::remove_if(*waiting, check), end(*waiting));
});
Ui::AttachAsChild(box, std::move(subscription));
const auto height = CountButtonsHeight(buttons);
content->resize(content->width(), height);
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
}
} // namespace Theme
} // namespace Window

View File

@ -8,12 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/generic_box.h"
#include "data/data_cloud_themes.h"
#include "ui/widgets/checkbox.h"
namespace Data {
struct CloudTheme;
} // namespace Data
namespace Window {
class SessionController;
@ -27,7 +24,6 @@ struct CloudListColors {
QImage background;
QColor sent;
QColor received;
QColor radiobuttonBg;
QColor radiobuttonInactive;
QColor radiobuttonActive;
};
@ -40,6 +36,8 @@ struct CloudListColors {
class CloudListCheck final : public Ui::AbstractCheckView {
public:
using Colors = CloudListColors;
explicit CloudListCheck(bool checked);
CloudListCheck(const Colors &colors, bool checked);
QSize getSize() const override;
@ -54,10 +52,12 @@ public:
void setColors(const Colors &colors);
private:
void paintNotSupported(Painter &p, int left, int top, int outerWidth);
void paintWithColors(Painter &p, int left, int top, int outerWidth);
void checkedChangedHook(anim::type animated) override;
void validateBackgroundCache(int width);
Colors _colors;
std::optional<Colors> _colors;
Ui::RadioView _radio;
QImage _backgroundFull;
QImage _backgroundCache;
@ -65,10 +65,61 @@ private:
};
void CloudListBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
std::vector<Data::CloudTheme> list);
class CloudList final {
public:
CloudList(
not_null<QWidget*> parent,
not_null<Window::SessionController*> window);
void showAll();
[[nodiscard]] rpl::producer<bool> empty() const;
[[nodiscard]] rpl::producer<bool> allShown() const;
[[nodiscard]] object_ptr<Ui::RpWidget> takeWidget();
private:
struct Element {
Data::CloudTheme theme;
not_null<CloudListCheck*> check;
std::unique_ptr<Ui::Radiobutton> button;
bool waiting = false;
uint64 id() const {
return theme.id;
}
};
void setup();
[[nodiscard]] std::vector<Data::CloudTheme> collectAll() const;
void rebuildUsing(std::vector<Data::CloudTheme> &&list);
bool applyChangesFrom(std::vector<Data::CloudTheme> &&list);
bool removeStaleUsing(const std::vector<Data::CloudTheme> &list);
bool insertTillLimit(
const std::vector<Data::CloudTheme> &list,
int limit);
void refreshElementUsing(Element &element, const Data::CloudTheme &data);
void insert(int index, const Data::CloudTheme &theme);
void refreshColors(Element &element);
void refreshColorsFromDocument(
Element &element,
not_null<DocumentData*> document);
void setWaiting(Element &element, bool waiting);
void subscribeToDownloadFinished();
int resizeGetHeight(int newWidth);
void updateGeometry();
[[nodiscard]] int groupValueForId(uint64 id);
const not_null<Window::SessionController*> _window;
object_ptr<Ui::RpWidget> _owned;
const not_null<Ui::RpWidget*> _outer;
const std::shared_ptr<Ui::RadiobuttonGroup> _group;
rpl::variable<bool> _showAll = false;
rpl::variable<int> _count = 0;
std::vector<Element> _elements;
std::vector<uint64> _idByGroupValue;
base::flat_map<uint64, int> _groupValueById;
rpl::lifetime _downloadFinishedLifetime;
};
} // namespace Theme
} // namespace Window