mirror of
https://github.com/vale981/tdesktop
synced 2025-03-05 09:41:41 -05:00
Display full themes list in Settings.
This commit is contained in:
parent
534772722e
commit
dd74f57a66
10 changed files with 641 additions and 288 deletions
|
@ -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";
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue