Show tdesktop cloud themes in a box.

This commit is contained in:
John Preston 2019-09-03 18:24:51 +03:00
parent 95afcbb485
commit ac8f924909
28 changed files with 684 additions and 192 deletions

View File

@ -339,6 +339,7 @@ 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_tile" = "Tile background";
"lng_settings_adaptive_wide" = "Adaptive layout for wide screens";

View File

@ -5407,23 +5407,6 @@ FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
return FileLoadTo(peer->id, action.options, action.replyTo);
}
void ApiWrap::requestSupportContact(FnMut<void(const MTPUser &)> callback) {
_supportContactCallbacks.push_back(std::move(callback));
if (_supportContactCallbacks.size() > 1) {
return;
}
request(MTPhelp_GetSupport(
)).done([=](const MTPhelp_Support &result) {
result.match([&](const MTPDhelp_support &data) {
for (auto &handler : base::take(_supportContactCallbacks)) {
handler(data.vuser());
}
});
}).fail([=](const RPCError &error) {
_supportContactCallbacks.clear();
}).send();
}
void ApiWrap::uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image) {
peer = peer->migrateToOrMe();
const auto ready = PreparePeerPhoto(peer->id, std::move(image));

View File

@ -443,8 +443,6 @@ public:
not_null<PeerData*> peer,
FullMsgId itemId = FullMsgId());
void requestSupportContact(FnMut<void(const MTPUser&)> callback);
void uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image);
void clearPeerPhoto(not_null<PhotoData*> photo);

View File

@ -0,0 +1,74 @@
/*
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 "data/data_cloud_themes.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "apiwrap.h"
namespace Data {
QString CloudThemes::Format() {
static const auto kResult = QString::fromLatin1("tdesktop");
return kResult;
}
CloudThemes::CloudThemes(not_null<Main::Session*> session)
: _session(session) {
}
void CloudThemes::refresh() {
if (_requestId) {
return;
}
_requestId = _session->api().request(MTPaccount_GetThemes(
MTP_string(Format()),
MTP_int(_hash)
)).done([=](const MTPaccount_Themes &result) {
result.match([&](const MTPDaccount_themes &data) {
_hash = data.vhash().v;
parseThemes(data.vthemes().v);
_updates.fire({});
}, [](const MTPDaccount_themesNotModified &) {
});
}).fail([=](const RPCError &error) {
_requestId = 0;
}).send();
}
void CloudThemes::parseThemes(const QVector<MTPTheme> &list) {
_list.clear();
_list.reserve(list.size());
for (const auto &theme : list) {
theme.match([&](const MTPDtheme &data) {
const auto document = data.vdocument();
_list.push_back({
data.vid().v,
data.vaccess_hash().v,
qs(data.vslug()),
qs(data.vtitle()),
(document
? _session->data().processDocument(*document).get()
: nullptr),
data.is_creator()
});
}, [&](const MTPDthemeDocumentNotModified &data) {
LOG(("API Error: Unexpected themeDocumentNotModified."));
});
}
}
rpl::producer<> CloudThemes::updated() const {
return _updates.events();
}
const std::vector<CloudTheme> &CloudThemes::list() const {
return _list;
}
} // namespace Data

View File

@ -0,0 +1,46 @@
/*
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
*/
#pragma once
namespace Main {
class Session;
} // namespace Main
namespace Data {
struct CloudTheme {
uint64 id = 0;
uint64 accessHash = 0;
QString slug;
QString title;
DocumentData *document = nullptr;
bool creator = false;
};
class CloudThemes final {
public:
explicit CloudThemes(not_null<Main::Session*> session);
[[nodiscard]] static QString Format();
void refresh();
[[nodiscard]] rpl::producer<> updated() const;
[[nodiscard]] const std::vector<CloudTheme> &list() const;
private:
void parseThemes(const QVector<MTPTheme> &list);
const not_null<Main::Session*> _session;
int32 _hash = 0;
mtpRequestId _requestId = 0;
std::vector<CloudTheme> _list;
rpl::event_stream<> _updates;
};
} // namespace Data

View File

@ -693,7 +693,8 @@ bool DocumentData::saveToCache() const {
return (type == StickerDocument && size < Storage::kMaxStickerInMemory)
|| (isAnimation() && size < Storage::kMaxAnimationInMemory)
|| (isVoiceMessage() && size < Storage::kMaxVoiceInMemory)
|| (type == WallPaperDocument);
|| (type == WallPaperDocument)
|| (isTheme() && size < Storage::kMaxFileInMemory);
}
void DocumentData::unload() {

View File

@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_game.h"
#include "data/data_poll.h"
#include "data/data_scheduled_messages.h"
#include "data/data_cloud_themes.h"
#include "base/unixtime.h"
#include "styles/style_boxes.h" // st::backgroundSize
@ -209,7 +210,8 @@ Session::Session(not_null<Main::Session*> session)
})
, _unmuteByFinishedTimer([=] { unmuteByFinished(); })
, _groups(this)
, _scheduledMessages(std::make_unique<ScheduledMessages>(this)) {
, _scheduledMessages(std::make_unique<ScheduledMessages>(this))
, _cloudThemes(std::make_unique<CloudThemes>(session)) {
_cache->open(Local::cacheKey());
_bigFileCache->open(Local::cacheBigFileKey());

View File

@ -64,6 +64,7 @@ class Folder;
class LocationPoint;
class WallPaper;
class ScheduledMessages;
class CloudThemes;
class Session final {
public:
@ -87,10 +88,12 @@ public:
[[nodiscard]] const Groups &groups() const {
return _groups;
}
[[nodiscard]] ScheduledMessages &scheduledMessages() const {
return *_scheduledMessages;
}
[[nodiscard]] CloudThemes &cloudThemes() const {
return *_cloudThemes;
}
[[nodiscard]] MsgId nextNonHistoryEntryId() {
return ++_nonHistoryEntryId;
}
@ -994,6 +997,7 @@ private:
Groups _groups;
std::unique_ptr<ScheduledMessages> _scheduledMessages;
std::unique_ptr<CloudThemes> _cloudThemes;
MsgId _nonHistoryEntryId = ServerMaxMsgId;
rpl::lifetime _lifetime;

View File

@ -1221,7 +1221,7 @@ void Widget::dropEvent(QDropEvent *e) {
if (auto peer = _inner->updateFromParentDrag(mapToGlobal(e->pos()))) {
e->acceptProposedAction();
App::main()->onFilesOrForwardDrop(peer->id, e->mimeData());
controller()->window()->activateWindow();
controller()->widget()->activateWindow();
}
}
}

View File

@ -1319,8 +1319,8 @@ void InnerWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton butt
_dragStartPosition = mapPointToItem(
mapFromGlobal(screenPos),
_mouseActionItem);
_pressWasInactive = _controller->window()->wasInactivePress();
if (_pressWasInactive) _controller->window()->setInactivePress(false);
_pressWasInactive = _controller->widget()->wasInactivePress();
if (_pressWasInactive) _controller->widget()->setInactivePress(false);
if (ClickHandler::getPressed()) {
_mouseAction = MouseAction::PrepareDrag;

View File

@ -150,7 +150,7 @@ HistoryInner::HistoryInner(
update();
}
});
subscribe(_controller->window()->dragFinished(), [this] {
subscribe(_controller->widget()->dragFinished(), [this] {
mouseActionUpdate(QCursor::pos());
});
session().data().itemRemoved(
@ -1026,8 +1026,8 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
? mouseActionView->data().get()
: nullptr;
_dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), mouseActionView);
_pressWasInactive = _controller->window()->wasInactivePress();
if (_pressWasInactive) _controller->window()->setInactivePress(false);
_pressWasInactive = _controller->widget()->wasInactivePress();
if (_pressWasInactive) _controller->widget()->setInactivePress(false);
if (ClickHandler::getPressed()) {
_mouseAction = MouseAction::PrepareDrag;
@ -1234,7 +1234,7 @@ std::unique_ptr<QMimeData> HistoryInner::prepareDrag() {
void HistoryInner::performDrag() {
if (auto mimeData = prepareDrag()) {
// This call enters event loop and can destroy any QObject.
_controller->window()->launchDrag(std::move(mimeData));
_controller->widget()->launchDrag(std::move(mimeData));
}
}

View File

@ -126,7 +126,7 @@ ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
}
void ActivateWindow(not_null<Window::SessionController*> controller) {
const auto window = controller->window();
const auto window = controller->widget();
window->activateWindow();
Core::App().activateWindowDelayed(window);
}
@ -1749,7 +1749,7 @@ void HistoryWidget::showHistory(
_channel = peerToChannel(_peer->id);
_canSendMessages = _peer->canWrite();
_contactStatus = std::make_unique<HistoryView::ContactStatus>(
&controller()->window()->controller(),
&controller()->window(),
this,
_peer);
_contactStatus->heightValue() | rpl::start_with_next([=] {

View File

@ -1889,8 +1889,8 @@ void ListWidget::mouseActionStart(
const auto pressElement = _overElement;
_mouseAction = MouseAction::None;
_pressWasInactive = _controller->window()->wasInactivePress();
if (_pressWasInactive) _controller->window()->setInactivePress(false);
_pressWasInactive = _controller->widget()->wasInactivePress();
if (_pressWasInactive) _controller->widget()->setInactivePress(false);
if (ClickHandler::getPressed()) {
_mouseAction = MouseAction::PrepareDrag;
@ -2370,7 +2370,7 @@ std::unique_ptr<QMimeData> ListWidget::prepareDrag() {
void ListWidget::performDrag() {
if (auto mimeData = prepareDrag()) {
// This call enters event loop and can destroy any QObject.
_controller->window()->launchDrag(std::move(mimeData));
_controller->widget()->launchDrag(std::move(mimeData));
}
}

View File

@ -1818,8 +1818,8 @@ void ListWidget::mouseActionStart(
auto pressLayout = _overLayout;
_mouseAction = MouseAction::None;
_pressWasInactive = _controller->parentController()->window()->wasInactivePress();
if (_pressWasInactive) _controller->parentController()->window()->setInactivePress(false);
_pressWasInactive = _controller->parentController()->widget()->wasInactivePress();
if (_pressWasInactive) _controller->parentController()->widget()->setInactivePress(false);
if (ClickHandler::getPressed() && !hasSelected()) {
_mouseAction = MouseAction::PrepareDrag;

View File

@ -271,7 +271,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
UsernameValue(user),
tr::lng_context_copy_mention(tr::now));
const auto window = &_controller->parentController()->window()->controller();
const auto window = &_controller->parentController()->window();
AddMainButton(
result,
tr::lng_info_add_as_contact(),
@ -501,7 +501,7 @@ void ActionsFiller::addShareContactAction(not_null<UserData*> user) {
}
void ActionsFiller::addEditContactAction(not_null<UserData*> user) {
const auto window = &_controller->parentController()->window()->controller();
const auto window = &_controller->parentController()->window();
AddActionButton(
_wrap,
tr::lng_info_edit_contact(),
@ -590,7 +590,7 @@ void ActionsFiller::addReportAction() {
}
void ActionsFiller::addBlockAction(not_null<UserData*> user) {
const auto window = &_controller->parentController()->window()->controller();
const auto window = &_controller->parentController()->window();
auto text = Notify::PeerUpdateValue(
user,

View File

@ -2759,7 +2759,7 @@ void MainWidget::updateWindowAdaptiveLayout() {
// dialogs widget to provide a wide enough chat history column.
// Don't shrink the column on the first call, when window is inited.
if (layout.windowLayout == Adaptive::WindowLayout::ThreeColumn
&& _started && _controller->window()->positionInited()) {
&& _started && _controller->widget()->positionInited()) {
//auto chatWidth = layout.chatWidth;
//if (_history->willSwitchToTabbedSelectorWithWidth(chatWidth)) {
// auto thirdColumnWidth = _history->tabbedSelectorSectionWidth();

View File

@ -30,12 +30,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme.h"
#include "window/themes/window_themes_embedded.h"
#include "window/themes/window_theme_editor_box.h"
#include "window/themes/window_themes_cloud_list.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "info/profile/info_profile_button.h"
#include "storage/localstorage.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "data/data_session.h"
#include "data/data_cloud_themes.h"
#include "chat_helpers/emoji_sets_manager.h"
#include "platform/platform_info.h"
#include "support/support_common.h"
@ -385,33 +388,6 @@ private:
};
class DefaultTheme final : public Ui::AbstractCheckView {
public:
using Type = Window::Theme::EmbeddedType;
using Scheme = Window::Theme::EmbeddedScheme;
DefaultTheme(Scheme scheme, bool checked);
QSize getSize() const override;
void paint(
Painter &p,
int left,
int top,
int outerWidth) override;
QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override;
void setColorizer(const Window::Theme::Colorizer &colorizer);
private:
void checkedChangedHook(anim::type animated) override;
Scheme _scheme;
Scheme _colorized;
Ui::RadioView _radio;
};
void ChooseFromFile(
not_null<::Main::Session*> session,
not_null<QWidget*> parent);
@ -614,76 +590,6 @@ void BackgroundRow::updateImage() {
}
}
DefaultTheme::DefaultTheme(Scheme scheme, bool checked)
: AbstractCheckView(st::defaultRadio.duration, checked, nullptr)
, _scheme(scheme)
, _radio(st::defaultRadio, checked, [=] { update(); }) {
setColorizer({});
}
void DefaultTheme::setColorizer(const Window::Theme::Colorizer &colorizer) {
_colorized = _scheme;
if (colorizer) {
Window::Theme::Colorize(_colorized, colorizer);
}
_radio.setToggledOverride(_colorized.radiobuttonActive);
_radio.setUntoggledOverride(_colorized.radiobuttonInactive);
update();
}
QSize DefaultTheme::getSize() const {
return st::settingsThemePreviewSize;
}
void DefaultTheme::paint(
Painter &p,
int left,
int top,
int outerWidth) {
const auto received = QRect(
st::settingsThemeBubblePosition,
st::settingsThemeBubbleSize);
const auto sent = QRect(
outerWidth - received.width() - st::settingsThemeBubblePosition.x(),
received.y() + received.height() + st::settingsThemeBubbleSkip,
received.width(),
received.height());
const auto radius = st::settingsThemeBubbleRadius;
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(_colorized.background);
p.drawRoundedRect(
QRect(QPoint(), st::settingsThemePreviewSize),
radius,
radius);
p.setBrush(_colorized.received);
p.drawRoundedRect(rtlrect(received, outerWidth), radius, radius);
p.setBrush(_colorized.sent);
p.drawRoundedRect(rtlrect(sent, outerWidth), radius, radius);
const auto radio = _radio.getSize();
_radio.paint(
p,
(outerWidth - radio.width()) / 2,
getSize().height() - radio.height() - st::settingsThemeRadioBottom,
outerWidth);
}
QImage DefaultTheme::prepareRippleMask() const {
return QImage();
}
bool DefaultTheme::checkRippleStartPosition(QPoint position) const {
return false;
}
void DefaultTheme::checkedChangedHook(anim::type animated) {
_radio.setChecked(checked(), animated);
}
void ChooseFromFile(
not_null<::Main::Session*> session,
not_null<QWidget*> parent) {
@ -1080,8 +986,10 @@ void SetupChatBackground(
}
void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
using Type = DefaultTheme::Type;
using Scheme = DefaultTheme::Scheme;
using Type = Window::Theme::EmbeddedType;
using Scheme = Window::Theme::EmbeddedScheme;
using Check = Window::Theme::CloudListCheck;
using Window::Theme::ColorsFromScheme;
const auto block = container->add(object_ptr<Ui::FixedHeightWidget>(
container));
@ -1121,11 +1029,13 @@ void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
apply(scheme);
};
auto checks = base::flat_map<Type,not_null<DefaultTheme*>>();
auto checks = base::flat_map<Type,not_null<Check*>>();
auto buttons = ranges::view::all(
kSchemesList
) | ranges::view::transform([&](const Scheme &scheme) {
auto check = std::make_unique<DefaultTheme>(scheme, false);
auto check = std::make_unique<Check>(
ColorsFromScheme(scheme),
false);
const auto weak = check.get();
const auto result = Ui::CreateChild<Ui::Radioenum<Type>>(
block,
@ -1158,9 +1068,9 @@ void SetupDefaultThemes(not_null<Ui::VerticalLayout*> container) {
const auto colorizer = Window::Theme::ColorizerFrom(
*scheme,
*color);
i->second->setColorizer(colorizer);
i->second->setColors(ColorsFromScheme(*scheme, colorizer));
} else {
i->second->setColorizer({});
i->second->setColors(ColorsFromScheme(*scheme));
}
}
};
@ -1264,11 +1174,38 @@ void SetupThemeOptions(
&st::settingsIconThemes,
st::settingsChatIconLeft
)->addClickHandler([=] {
Ui::show(Box(
controller->window().show(Box(
Window::Theme::CreateBox,
&controller->window()->controller()));
&controller->window()));
});
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
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));
});
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));
AddSkip(container);
}

View File

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_cover.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_cloud_themes.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "main/main_session.h"
@ -240,14 +241,30 @@ void SetupHelp(
container,
tr::lng_settings_ask_question(),
st::settingsSectionButton);
const auto requestId = button->lifetime().make_state<mtpRequestId>();
button->lifetime().add([=] {
if (*requestId) {
controller->session().api().request(*requestId).cancel();
}
});
button->addClickHandler([=] {
const auto ready = crl::guard(button, [=](const MTPUser &data) {
if (const auto user = controller->session().data().processUser(data)) {
Ui::showPeerHistory(user, ShowAtUnreadMsgId);
}
});
const auto sure = crl::guard(button, [=] {
controller->session().api().requestSupportContact(ready);
if (*requestId) {
return;
}
*requestId = controller->session().api().request(
MTPhelp_GetSupport()
).done([=](const MTPhelp_Support &result) {
*requestId = 0;
result.match([&](const MTPDhelp_support &data) {
auto &owner = controller->session().data();
if (const auto user = owner.processUser(data.vuser())) {
Ui::showPeerHistory(user, ShowAtUnreadMsgId);
}
});
}).fail([=](const RPCError &error) {
*requestId = 0;
}).send();
});
auto box = Box<ConfirmBox>(
tr::lng_settings_ask_sure(tr::now),
@ -300,6 +317,7 @@ void Main::setupContent(not_null<Window::SessionController*> controller) {
// If we load this in advance it won't jump when we open its' section.
controller->session().api().reloadPasswordState();
controller->session().api().reloadContactSignupSilent();
controller->session().data().cloudThemes().refresh();
}
rpl::producer<Type> Main::sectionShowOther() {

View File

@ -1193,18 +1193,28 @@ void ToggleNightMode(const QString &path) {
Background()->toggleNightMode(path);
}
bool LoadFromFile(
const QString &path,
Instance *out,
QByteArray *outContent) {
*outContent = readThemeContent(path);
if (outContent->size() < 4) {
LOG(("Theme Error: Could not load theme from %1").arg(path));
bool LoadFromContent(
const QByteArray &content,
not_null<Instance*> out,
const Colorizer &colorizer) {
if (content.size() < 4) {
LOG(("Theme Error: Bad theme content size: %1").arg(content.size()));
return false;
}
const auto colorizer = ColorizerForTheme(path);
return loadTheme(*outContent, out->cached, colorizer, out);
return loadTheme(content, out->cached, colorizer, out);
}
bool LoadFromFile(
const QString &path,
not_null<Instance*> out,
not_null<QByteArray*> outContent) {
*outContent = readThemeContent(path);
return LoadFromContent(*outContent, out, ColorizerForTheme(path));
}
bool LoadFromContent(const QByteArray &content, not_null<Instance*> out) {
return LoadFromContent(content, out, {});
}
QString EditingPalettePath() {

View File

@ -66,8 +66,9 @@ void Revert();
bool LoadFromFile(
const QString &file,
Instance *out,
QByteArray *outContent);
not_null<Instance*> out,
not_null<QByteArray*> outContent);
bool LoadFromContent(const QByteArray &content, not_null<Instance*> out);
QColor CountAverageColor(const QImage &image);
QColor AdjustedColor(QColor original, QColor background);
QImage ProcessBackgroundImage(QImage image);

View File

@ -533,7 +533,7 @@ void CreateBox(
box->setFocusCallback([=] { name->setFocusFast(); });
box->addButton(tr::lng_box_done(), [=] {
const auto done = [=] {
const auto title = name->getLastText().trimmed();
if (title.isEmpty()) {
name->showError();
@ -541,7 +541,9 @@ void CreateBox(
}
box->closeBox();
StartEditor(window, title);
});
};
Ui::Connect(name, &Ui::InputField::submitted, done);
box->addButton(tr::lng_box_done(), done);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}

View File

@ -0,0 +1,332 @@
/*
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 "window/themes/window_themes_cloud_list.h"
#include "window/themes/window_themes_embedded.h"
#include "window/themes/window_theme.h"
#include "window/window_session_controller.h"
#include "data/data_cloud_themes.h"
#include "data/data_file_origin.h"
#include "data/data_document.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
namespace Window {
namespace Theme {
namespace {
[[nodiscard]] QImage ColorsBackgroundFromImage(const QImage &source) {
if (source.isNull()) {
return source;
}
const auto from = source.size();
const auto to = st::settingsThemePreviewSize * cIntRetinaFactor();
if (to.width() * from.height() > to.height() * from.width()) {
const auto small = (from.width() > to.width())
? source.scaledToWidth(to.width(), Qt::SmoothTransformation)
: source;
const auto takew = small.width();
const auto takeh = std::max(
takew * to.height() / to.width(),
1);
return (small.height() != takeh)
? small.copy(0, (small.height() - takeh) / 2, takew, takeh)
: small;
} else {
const auto small = (from.height() > to.height())
? source.scaledToHeight(to.height(), Qt::SmoothTransformation)
: source;
const auto takeh = small.height();
const auto takew = std::max(
takeh * to.width() / to.height(),
1);
return (small.width() != takew)
? small.copy(0, (small.width() - takew) / 2, takew, takeh)
: small;
}
}
[[nodiscard]] std::optional<CloudListColors> ColorsFromTheme(
const QString &path,
const QByteArray &theme) {
const auto content = [&] {
if (!theme.isEmpty()) {
return theme;
}
auto file = QFile(path);
return file.open(QIODevice::ReadOnly)
? file.readAll()
: QByteArray();
}();
if (content.isEmpty()) {
return std::nullopt;
}
auto instance = Instance();
if (!LoadFromContent(content, &instance)) {
return std::nullopt;
}
auto result = CloudListColors();
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;
}
} // namespace
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);
result.background.fill(scheme.background);
return result;
}
CloudListColors ColorsFromScheme(
const EmbeddedScheme &scheme,
const Colorizer &colorizer) {
if (!colorizer) {
return ColorsFromScheme(scheme);
}
auto copy = scheme;
Colorize(copy, colorizer);
return ColorsFromScheme(copy);
}
CloudListCheck::CloudListCheck(const Colors &colors, 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);
update();
}
QSize CloudListCheck::getSize() const {
return st::settingsThemePreviewSize;
}
void CloudListCheck::paint(
Painter &p,
int left,
int top,
int outerWidth) {
if (_colors.background.isNull()) {
return;
}
const auto received = QRect(
st::settingsThemeBubblePosition,
st::settingsThemeBubbleSize);
const auto sent = QRect(
outerWidth - received.width() - st::settingsThemeBubblePosition.x(),
received.y() + received.height() + st::settingsThemeBubbleSkip,
received.width(),
received.height());
const auto radius = st::settingsThemeBubbleRadius;
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.drawImage(
QRect(QPoint(), st::settingsThemePreviewSize),
_colors.background);
p.setBrush(_colors.received);
p.drawRoundedRect(rtlrect(received, outerWidth), radius, radius);
p.setBrush(_colors.sent);
p.drawRoundedRect(rtlrect(sent, outerWidth), radius, radius);
const auto skip = st::settingsThemeRadioBottom / 2;
const auto radio = _radio.getSize();
_radio.paint(
p,
(outerWidth - radio.width()) / 2,
getSize().height() - radio.height() - st::settingsThemeRadioBottom,
outerWidth);
}
QImage CloudListCheck::prepareRippleMask() const {
return QImage();
}
bool CloudListCheck::checkRippleStartPosition(QPoint position) const {
return false;
}
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 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>(-1);
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*> {
const auto document = theme.document;
if (!document) {
return Ui::CreateChild<Ui::RpWidget>(content);
}
if (document) {
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

@ -0,0 +1,70 @@
/*
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
*/
#pragma once
#include "boxes/generic_box.h"
#include "ui/widgets/checkbox.h"
namespace Data {
struct CloudTheme;
} // namespace Data
namespace Window {
class SessionController;
namespace Theme {
struct EmbeddedScheme;
struct Colorizer;
struct CloudListColors {
QImage background;
QColor sent;
QColor received;
QColor radiobuttonBg;
QColor radiobuttonInactive;
QColor radiobuttonActive;
};
[[nodiscard]] CloudListColors ColorsFromScheme(const EmbeddedScheme &scheme);
[[nodiscard]] CloudListColors ColorsFromScheme(
const EmbeddedScheme &scheme,
const Colorizer &colorizer);
class CloudListCheck final : public Ui::AbstractCheckView {
public:
using Colors = CloudListColors;
CloudListCheck(const Colors &colors, bool checked);
QSize getSize() const override;
void paint(
Painter &p,
int left,
int top,
int outerWidth) override;
QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override;
void setColors(const Colors &colors);
private:
void checkedChangedHook(anim::type animated) override;
Colors _colors;
Ui::RadioView _radio;
};
void CloudListBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
std::vector<Data::CloudTheme> list);
} // namespace Theme
} // namespace Window

View File

@ -23,7 +23,7 @@ Controller::Controller(not_null<Main::Account*> account)
_account->sessionValue(
) | rpl::start_with_next([=](Main::Session *session) {
_sessionController = session
? std::make_unique<SessionController>(session, &_widget)
? std::make_unique<SessionController>(session, this)
: nullptr;
_widget.updateWindowIcon();
}, _lifetime);

View File

@ -343,7 +343,7 @@ void Filler::addToggleArchive() {
}
void Filler::addBlockUser(not_null<UserData*> user) {
const auto window = &_controller->window()->controller();
const auto window = &_controller->window();
const auto blockText = [](not_null<UserData*> user) {
return user->isBlocked()
? ((user->isBot() && !user->isSupport())
@ -378,7 +378,7 @@ void Filler::addBlockUser(not_null<UserData*> user) {
void Filler::addUserActions(not_null<UserData*> user) {
const auto controller = _controller;
const auto window = &_controller->window()->controller();
const auto window = &_controller->window();
if (_source != PeerMenuSource::ChatsList) {
if (user->session().supportMode()) {
_addAction("Edit support info", [=] {

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "window/window_controller.h"
#include "window/main_window.h"
#include "info/info_memento.h"
#include "info/info_controller.h"
@ -100,11 +101,13 @@ void SessionNavigation::showSettings(const SectionShow &params) {
SessionController::SessionController(
not_null<Main::Session*> session,
not_null<::MainWindow*> window)
not_null<Controller*> window)
: SessionNavigation(session)
, _window(window)
, _tabbedSelector(
std::make_unique<ChatHelpers::TabbedSelector>(window, this)) {
std::make_unique<ChatHelpers::TabbedSelector>(
_window->widget(),
this)) {
init();
subscribe(session->api().fullPeerUpdated(), [=](PeerData *peer) {
@ -125,6 +128,10 @@ SessionController::SessionController(
}, lifetime());
}
not_null<::MainWindow*> SessionController::widget() const {
return _window->widget();
}
auto SessionController::tabbedSelector() const
-> not_null<ChatHelpers::TabbedSelector*> {
return _tabbedSelector.get();
@ -133,18 +140,18 @@ auto SessionController::tabbedSelector() const
void SessionController::takeTabbedSelectorOwnershipFrom(
not_null<QWidget*> parent) {
if (_tabbedSelector->parent() == parent) {
if (const auto chats = _window->chatsWidget()) {
if (const auto chats = widget()->chatsWidget()) {
chats->returnTabbedSelector();
}
if (_tabbedSelector->parent() == parent) {
_tabbedSelector->hide();
_tabbedSelector->setParent(window());
_tabbedSelector->setParent(widget());
}
}
}
bool SessionController::hasTabbedSelectorOwnership() const {
return (_tabbedSelector->parent() == window());
return (_tabbedSelector->parent() == widget());
}
void SessionController::showEditPeerBox(PeerData *peer) {
@ -295,9 +302,9 @@ void SessionController::disableGifPauseReason(GifPauseReason reason) {
bool SessionController::isGifPausedAtLeastFor(GifPauseReason reason) const {
if (reason == GifPauseReason::Any) {
return (_gifPauseReasons != 0) || !window()->isActive();
return (_gifPauseReasons != 0) || !widget()->isActive();
}
return (static_cast<int>(_gifPauseReasons) >= 2 * static_cast<int>(reason)) || !window()->isActive();
return (static_cast<int>(_gifPauseReasons) >= 2 * static_cast<int>(reason)) || !widget()->isActive();
}
int SessionController::dialogsSmallColumnWidth() const {
@ -322,7 +329,7 @@ bool SessionController::forceWideDialogs() const {
SessionController::ColumnLayout SessionController::computeColumnLayout() const {
auto layout = Adaptive::WindowLayout::OneColumn;
auto bodyWidth = window()->bodyWidget()->width();
auto bodyWidth = widget()->bodyWidget()->width();
auto dialogsWidth = 0, chatWidth = 0, thirdWidth = 0;
auto useOneColumnLayout = [&] {
@ -411,7 +418,7 @@ bool SessionController::canShowThirdSection() const {
auto currentLayout = computeColumnLayout();
auto minimalExtendBy = minimalThreeColumnWidth()
- currentLayout.bodyWidth;
return (minimalExtendBy <= window()->maximalExtendBy());
return (minimalExtendBy <= widget()->maximalExtendBy());
}
bool SessionController::canShowThirdSectionWithoutResize() const {
@ -444,15 +451,15 @@ void SessionController::resizeForThirdSection() {
// Next - extend by minimal third column without moving.
// Next - show third column inside the window without moving.
// Last - extend with moving.
if (window()->canExtendNoMove(wanted)) {
return window()->tryToExtendWidthBy(wanted);
} else if (window()->canExtendNoMove(minimal)) {
if (widget()->canExtendNoMove(wanted)) {
return widget()->tryToExtendWidthBy(wanted);
} else if (widget()->canExtendNoMove(minimal)) {
extendBy = minimal;
return window()->tryToExtendWidthBy(minimal);
return widget()->tryToExtendWidthBy(minimal);
} else if (layout.bodyWidth >= minimalThreeColumnWidth()) {
return 0;
}
return window()->tryToExtendWidthBy(minimal);
return widget()->tryToExtendWidthBy(minimal);
}();
if (extendedBy) {
if (extendBy != session().settings().thirdColumnWidth()) {
@ -473,11 +480,11 @@ void SessionController::resizeForThirdSection() {
}
void SessionController::closeThirdSection() {
auto newWindowSize = window()->size();
auto newWindowSize = widget()->size();
auto layout = computeColumnLayout();
if (layout.windowLayout == Adaptive::WindowLayout::ThreeColumn) {
auto noResize = window()->isFullScreen()
|| window()->isMaximized();
auto noResize = widget()->isFullScreen()
|| widget()->isMaximized();
auto savedValue = session().settings().thirdSectionExtendedBy();
auto extendedBy = (savedValue == -1)
? layout.thirdWidth
@ -489,14 +496,14 @@ void SessionController::closeThirdSection() {
session().settings().setDialogsWidthRatio(
(currentRatio * layout.bodyWidth) / newBodyWidth);
newWindowSize = QSize(
window()->width() + (newBodyWidth - layout.bodyWidth),
window()->height());
widget()->width() + (newBodyWidth - layout.bodyWidth),
widget()->height());
}
session().settings().setTabbedSelectorSectionEnabled(false);
session().settings().setThirdSectionInfoEnabled(false);
session().saveSettingsDelayed();
if (window()->size() != newWindowSize) {
window()->resize(newWindowSize);
if (widget()->size() != newWindowSize) {
widget()->resize(newWindowSize);
} else {
updateColumnLayout();
}

View File

@ -46,6 +46,7 @@ namespace Window {
class LayerWidget;
class MainWindow;
class SectionMemento;
class Controller;
enum class GifPauseReason {
Any = 0,
@ -151,11 +152,12 @@ class SessionController
public:
SessionController(
not_null<Main::Session*> session,
not_null<::MainWindow*> window);
not_null<Controller*> window);
[[nodiscard]] not_null<::MainWindow*> window() const {
return _window;
[[nodiscard]] Controller &window() const {
return *_window;
}
[[nodiscard]] not_null<::MainWindow*> widget() const;
[[nodiscard]] auto tabbedSelector() const
-> not_null<ChatHelpers::TabbedSelector*>;
@ -302,7 +304,7 @@ private:
void pushToChatEntryHistory(Dialogs::RowDescriptor row);
bool chatEntryHistoryMove(int steps);
const not_null<::MainWindow*> _window;
const not_null<Controller*> _window;
std::unique_ptr<Passport::FormController> _passportForm;

View File

@ -184,6 +184,8 @@
<(src_loc)/data/data_channel.h
<(src_loc)/data/data_channel_admins.cpp
<(src_loc)/data/data_channel_admins.h
<(src_loc)/data/data_cloud_themes.cpp
<(src_loc)/data/data_cloud_themes.h
<(src_loc)/data/data_countries.cpp
<(src_loc)/data/data_countries.h
<(src_loc)/data/data_document.cpp
@ -899,6 +901,8 @@
<(src_loc)/window/themes/window_theme_preview.h
<(src_loc)/window/themes/window_theme_warning.cpp
<(src_loc)/window/themes/window_theme_warning.h
<(src_loc)/window/themes/window_themes_cloud_list.cpp
<(src_loc)/window/themes/window_themes_cloud_list.h
<(src_loc)/window/themes/window_themes_embedded.cpp
<(src_loc)/window/themes/window_themes_embedded.h
<(src_loc)/apiwrap.cpp