Applying color themes with confirmation / reverting (15 seconds).

This commit is contained in:
John Preston 2016-11-02 17:44:33 +03:00
parent af9edc17d2
commit 5d10c02b5b
18 changed files with 517 additions and 73 deletions

View File

@ -292,6 +292,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_settings_adaptive_wide" = "Adaptive layout for wide screens";
"lng_backgrounds_header" = "Choose your new chat background";
"lng_theme_sure_keep" = "Keep this color theme?";
"lng_theme_reverting" = "Reverting to previous color theme in {count:_not_used_|# second|# seconds}.";
"lng_theme_keep_changes" = "Keep changes";
"lng_theme_revert" = "Revert";
"lng_download_path_dont_ask" = "Don't ask download path for each file";
"lng_download_path_label" = "Download path:";

View File

@ -283,3 +283,9 @@ newGroupDescription: InputArea(defaultInputArea) {
newGroupPublicLinkPadding: margins(0px, 20px, 0px, 5px);
newGroupLinkFadeDuration: 5000;
themeWarningWidth: boxWideWidth;
themeWarningHeight: 150px;
themeWarningShadow: boxShadow;
themeWarningShadowShift: boxShadowShift;
themeWarningTextTop: 60px;

View File

@ -828,7 +828,11 @@ QByteArray save() {\n\
}\n\
\n\
bool load(const QByteArray &cache) {\n\
return _palette.load(cache);\n\
if (_palette.load(cache)) {\n\
style::internal::resetIcons();\n\
return true;\n\
}\n\
return false;\n\
}\n\
\n\
bool setColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a) {\n\

View File

@ -1626,7 +1626,7 @@ void _writeUserSettings() {
EncryptedDescriptor data(size);
data.stream << quint32(dbiSendKey) << qint32(cCtrlEnter() ? dbiskCtrlEnter : dbiskEnter);
data.stream << quint32(dbiTileBackground) << qint32(Window::Theme::Background()->tile() ? 1 : 0);
data.stream << quint32(dbiTileBackground) << qint32(Window::Theme::Background()->tileForSave() ? 1 : 0);
data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0);
data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock());
data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0);

View File

@ -43,8 +43,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "apiwrap.h"
#include "settings/settings_widget.h"
#include "window/notifications_manager.h"
#include "platform/platform_notifications_manager.h"
#include "window/notifications_manager.h"
#include "window/window_theme.h"
#include "window/window_theme_warning.h"
ConnectingWidget::ConnectingWidget(QWidget *parent, const QString &text, const QString &reconnect) : QWidget(parent)
, _shadow(st::boxShadow)
@ -125,6 +127,9 @@ MainWindow::MainWindow() {
connect(&_autoLockTimer, SIGNAL(timeout()), this, SLOT(checkAutoLock()));
subscribe(Global::RefSelfChanged(), [this]() { updateGlobalMenu(); });
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) {
themeUpdated(data);
});
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_OpaquePaintEvent);
@ -594,6 +599,20 @@ void MainWindow::hideConnecting() {
}
}
void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) {
using Type = Window::Theme::BackgroundUpdate::Type;
if (data.type == Type::TestingTheme) {
if (!_testingThemeWarning) {
_testingThemeWarning.create(this);
_testingThemeWarning->setGeometry(rect());
_testingThemeWarning->setHiddenCallback([this] { _testingThemeWarning.destroyDelayed(); });
}
_testingThemeWarning->showAnimated();
} else if (data.type == Type::RevertingTheme || data.type == Type::ApplyingTheme) {
_testingThemeWarning->hideAnimated();
}
}
bool MainWindow::doWeReadServerHistory() const {
return isActive(false) && main && !Ui::isLayerShown() && main->doWeReadServerHistory();
}
@ -645,7 +664,9 @@ bool MainWindow::contentOverlapped(const QRect &globalRect) {
}
void MainWindow::setInnerFocus() {
if (layerBg && layerBg->canSetFocus()) {
if (_testingThemeWarning) {
_testingThemeWarning->setFocus();
} else if (layerBg && layerBg->canSetFocus()) {
layerBg->setInnerFocus();
} else if (_passcode) {
_passcode->setInnerFocus();
@ -951,6 +972,7 @@ void MainWindow::fixOrder() {
if (layerBg) layerBg->raise();
if (_mediaPreview) _mediaPreview->raise();
if (_connecting) _connecting->raise();
if (_testingThemeWarning) _testingThemeWarning->raise();
}
void MainWindow::showFromTray(QSystemTrayIcon::ActivationReason reason) {
@ -1045,6 +1067,7 @@ void MainWindow::updateControlsGeometry() {
if (layerBg) layerBg->resize(width(), height());
if (_mediaPreview) _mediaPreview->setGeometry(0, title->height(), width(), height() - title->height());
if (_connecting) _connecting->setGeometry(0, height() - _connecting->height(), _connecting->width(), _connecting->height());
if (_testingThemeWarning) _testingThemeWarning->setGeometry(rect());
}
MainWindow::TempDirState MainWindow::tempDirState() {

View File

@ -42,6 +42,13 @@ namespace Settings {
class Widget;
} // namespace Settings
namespace Window {
namespace Theme {
struct BackgroundUpdate;
class WarningWidget;
} // namespace Theme
} // namespace Window
class ConnectingWidget : public QWidget {
Q_OBJECT
@ -228,6 +235,8 @@ private:
void showConnecting(const QString &text, const QString &reconnect = QString());
void hideConnecting();
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
void updateControlsGeometry();
QPixmap grabInner();
@ -253,6 +262,7 @@ private:
bool _isActive = false;
ChildWidget<ConnectingWidget> _connecting = { nullptr };
ChildWidget<Window::Theme::WarningWidget> _testingThemeWarning = { nullptr };
Local::ClearManager *_clearManager = nullptr;

View File

@ -229,18 +229,7 @@ void BackgroundWidget::notifyFileQueryUpdated(const FileDialog::QueryUpdate &upd
auto filePath = update.filePaths.front();
if (filePath.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) {
QByteArray themeContent;
Window::Theme::Instance theme;
if (Window::Theme::LoadFromFile(filePath, &theme, &themeContent)) {
Local::writeTheme(QDir().relativeFilePath(filePath), QFileInfo(filePath).absoluteFilePath(), themeContent, theme.cached);
if (Window::Theme::Background()->tile() != theme.cached.tiled) {
Window::Theme::Background()->setTile(theme.cached.tiled);
}
if (!theme.cached.background.isEmpty()) {
Local::writeBackground(Window::Theme::kThemeBackground, QImage());
}
style::main_palette::apply(theme.palette);
}
Window::Theme::Apply(filePath);
return;
}

View File

@ -35,6 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "history/history_media_types.h"
#include "styles/style_history.h"
#include "window/window_theme.h"
namespace {
@ -54,7 +55,7 @@ struct ColorReferenceWrap {
};
ImagePtr generateUserpicImage(const style::icon &icon) {
auto data = QImage(icon.width() * cIntRetinaFactor(), icon.height() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
auto data = QImage(icon.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
data.setDevicePixelRatio(cRetinaFactor());
{
Painter p(&data);
@ -984,6 +985,15 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
bool playVideo = data->isVideo() && audioPlayer();
bool playAnimation = data->isAnimation();
auto &location = data->location(true);
if (auto applyTheme = data->name.endsWith(qstr(".tdesktop-theme"))) {
if (!location.isEmpty() && location.accessEnable()) {
if (Window::Theme::Apply(location.name())) {
location.accessDisable();
return;
}
location.accessDisable();
}
}
if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) {
if (playVoice) {
AudioMsgId playing;
@ -1277,13 +1287,22 @@ void DocumentData::automaticLoadSettingsChanged() {
void DocumentData::performActionOnLoad() {
if (_actionOnLoad == ActionOnLoadNone) return;
const FileLocation &loc(location(true));
QString already = loc.name();
HistoryItem *item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : nullptr;
auto loc = location(true);
auto already = loc.name();
auto item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : nullptr;
bool showImage = !isVideo() && (size < MediaViewImageSizeLimit);
bool playVoice = voice() && audioPlayer() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen);
bool playMusic = song() && audioPlayer() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen);
bool playAnimation = isAnimation() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && showImage && item && item->getMedia();
if (auto applyTheme = name.endsWith(qstr(".tdesktop-theme"))) {
if (!loc.isEmpty() && loc.accessEnable()) {
if (Window::Theme::Apply(loc.name())) {
loc.accessDisable();
return;
}
loc.accessDisable();
}
}
if (playVoice) {
if (loaded()) {
AudioMsgId playing;

View File

@ -75,43 +75,49 @@ void stopManager() {
internal::destroyIcons();
}
QImage colorizeImage(const QImage &src, QColor color, const QRect &r) {
t_assert(r.x() >= 0 && src.width() >= r.x() + r.width());
t_assert(r.y() >= 0 && src.height() >= r.y() + r.height());
void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect, QPoint dstPoint) {
if (srcRect.isNull()) {
srcRect = src.rect();
} else {
t_assert(src.rect().contains(srcRect));
}
auto width = srcRect.width();
auto height = srcRect.height();
t_assert(outResult && outResult->rect().contains(QRect(dstPoint, srcRect.size())));
auto initialAlpha = color.alpha() + 1;
auto red = color.red() * initialAlpha;
auto green = color.green() * initialAlpha;
auto blue = color.blue() * initialAlpha;
auto alpha = 255 * initialAlpha;
auto alpha_red = static_cast<uint64>(alpha) | (static_cast<uint64>(red) << 32);
auto green_blue = static_cast<uint64>(green) | (static_cast<uint64>(blue) << 32);
auto initialAlpha = c.alpha() + 1;
auto red = (c.red() * initialAlpha) >> 8;
auto green = (c.green() * initialAlpha) >> 8;
auto blue = (c.blue() * initialAlpha) >> 8;
auto alpha = (255 * initialAlpha) >> 8;
auto pattern = static_cast<uint64>(alpha)
| (static_cast<uint64>(red) << 16)
| (static_cast<uint64>(green) << 32)
| (static_cast<uint64>(blue) << 48);
auto result = QImage(r.width(), r.height(), QImage::Format_ARGB32_Premultiplied);
auto resultBytesPerPixel = (src.depth() >> 3);
auto resultIntsPerPixel = 1;
auto resultIntsPerLine = (result.bytesPerLine() >> 2);
auto resultIntsAdded = resultIntsPerLine - r.width() * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(result.bits());
auto resultIntsPerLine = (outResult->bytesPerLine() >> 2);
auto resultIntsAdded = resultIntsPerLine - width * resultIntsPerPixel;
auto resultInts = reinterpret_cast<uint32*>(outResult->bits()) + dstPoint.y() * resultIntsPerLine + dstPoint.x() * resultIntsPerPixel;
t_assert(resultIntsAdded >= 0);
t_assert(result.depth() == ((resultIntsPerPixel * sizeof(uint32)) << 3));
t_assert(result.bytesPerLine() == (resultIntsPerLine << 2));
t_assert(outResult->depth() == ((resultIntsPerPixel * sizeof(uint32)) << 3));
t_assert(outResult->bytesPerLine() == (resultIntsPerLine << 2));
auto maskBytesPerPixel = (src.depth() >> 3);
auto maskBytesPerLine = src.bytesPerLine();
auto maskBytesAdded = maskBytesPerLine - r.width() * maskBytesPerPixel;
auto maskBytes = src.constBits() + r.y() * maskBytesPerLine + r.x() * maskBytesPerPixel;
auto maskBytesAdded = maskBytesPerLine - width * maskBytesPerPixel;
auto maskBytes = src.constBits() + srcRect.y() * maskBytesPerLine + srcRect.x() * maskBytesPerPixel;
t_assert(maskBytesAdded >= 0);
t_assert(src.depth() == (maskBytesPerPixel << 3));
for (int y = 0; y != r.height(); ++y) {
for (int x = 0; x != r.width(); ++x) {
for (int y = 0; y != height; ++y) {
for (int x = 0; x != width; ++x) {
auto maskOpacity = static_cast<uint64>(*maskBytes) + 1;
auto alpha_red_masked = (alpha_red * maskOpacity) >> 16;
auto green_blue_masked = (green_blue * maskOpacity) >> 16;
auto alpha = static_cast<uint32>(alpha_red_masked & 0xFF);
auto red = static_cast<uint32>((alpha_red_masked >> 32) & 0xFF);
auto green = static_cast<uint32>(green_blue_masked & 0xFF);
auto blue = static_cast<uint32>((green_blue_masked >> 32) & 0xFF);
auto masked = (pattern * maskOpacity) >> 8;
auto alpha = static_cast<uint32>(masked & 0xFF);
auto red = static_cast<uint32>((masked >> 16) & 0xFF);
auto green = static_cast<uint32>((masked >> 32) & 0xFF);
auto blue = static_cast<uint32>((masked >> 48) & 0xFF);
*resultInts = blue | (green << 8) | (red << 16) | (alpha << 24);
maskBytes += maskBytesPerPixel;
resultInts += resultIntsPerPixel;
@ -120,8 +126,7 @@ QImage colorizeImage(const QImage &src, QColor color, const QRect &r) {
resultInts += resultIntsAdded;
}
result.setDevicePixelRatio(src.devicePixelRatio());
return std_::move(result);
outResult->setDevicePixelRatio(src.devicePixelRatio());
}
namespace internal {

View File

@ -63,10 +63,19 @@ bool setPaletteColor(QLatin1String name, uchar r, uchar g, uchar b, uchar a);
void startManager();
void stopManager();
QImage colorizeImage(const QImage &src, QColor c, const QRect &r);
// *outResult must be r.width() x r.height(), ARGB32_Premultiplied.
// QRect(0, 0, src.width(), src.height()) must contain r.
void colorizeImage(const QImage &src, QColor c, QImage *outResult, QRect srcRect = QRect(), QPoint dstPoint = QPoint(0, 0));
inline QImage colorizeImage(const QImage &src, const color &c, const QRect &r) {
return colorizeImage(src, c->c, r);
inline QImage colorizeImage(const QImage &src, QColor c, QRect srcRect = QRect()) {
if (srcRect.isNull()) srcRect = src.rect();
auto result = QImage(srcRect.size(), QImage::Format_ARGB32_Premultiplied);
colorizeImage(src, c, &result, srcRect);
return std_::move(result);
}
inline QImage colorizeImage(const QImage &src, const color &c, QRect srcRect = QRect()) {
return colorizeImage(src, c->c, srcRect);
}
namespace internal {

View File

@ -100,12 +100,12 @@ inline Color::operator const QPen &() const {
} // namespace internal
inline QColor interpolate(QColor a, QColor b, float64 opacity_b) {
auto bOpacity = static_cast<int>(opacity_b * 255), aOpacity = (255 - bOpacity);
auto bOpacity = static_cast<int>(opacity_b * 255) + 1, aOpacity = (256 - bOpacity);
return {
(a.red() * aOpacity + b.red() * bOpacity + 1) >> 8,
(a.green() * aOpacity + b.green() * bOpacity + 1) >> 8,
(a.blue() * aOpacity + b.blue() * bOpacity + 1) >> 8,
(a.alpha() * aOpacity + b.alpha() * bOpacity + 1) >> 8
(a.red() * aOpacity + b.red() * bOpacity) >> 8,
(a.green() * aOpacity + b.green() * bOpacity) >> 8,
(a.blue() * aOpacity + b.blue() * bOpacity) >> 8,
(a.alpha() * aOpacity + b.alpha() * bOpacity) >> 8
};
}

View File

@ -72,10 +72,6 @@ QImage createIconMask(const IconMask *mask) {
return maskImage.copy(r);
}
QImage createIconImage(const QImage &mask, QColor color) {
return colorizeImage(mask, color, QRect(0, 0, mask.width(), mask.height()));
}
} // namespace
MonoIcon::MonoIcon(const IconMask *mask, const Color &color, QPoint offset)
@ -141,7 +137,8 @@ void MonoIcon::paint(QPainter &p, const QPoint &pos, int outerw, QColor colorOve
if (_pixmap.isNull()) {
p.fillRect(partPosX, partPosY, w, h, colorOverride);
} else {
p.drawImage(partPosX, partPosY, createIconImage(_maskImage, colorOverride));
ensureColorizedImage(colorOverride);
p.drawImage(partPosX, partPosY, _colorizedImage);
}
}
@ -150,7 +147,8 @@ void MonoIcon::fill(QPainter &p, const QRect &rect, QColor colorOverride) const
if (_pixmap.isNull()) {
p.fillRect(rect, colorOverride);
} else {
p.drawImage(rect, createIconImage(_maskImage, colorOverride), QRect(0, 0, _pixmap.width(), _pixmap.height()));
ensureColorizedImage(colorOverride);
p.drawImage(rect, _colorizedImage, _colorizedImage.rect());
}
}
@ -206,12 +204,18 @@ void MonoIcon::ensureLoaded() const {
}
}
void MonoIcon::ensureColorizedImage(QColor color) const {
if (_colorizedImage.isNull()) _colorizedImage = QImage(_maskImage.size(), QImage::Format_ARGB32_Premultiplied);
colorizeImage(_maskImage, color, &_colorizedImage);
}
void MonoIcon::createCachedPixmap() const {
iconPixmaps.createIfNull();
auto key = qMakePair(_mask, colorKey(_color->c));
auto j = iconPixmaps->constFind(key);
if (j == iconPixmaps->cend()) {
j = iconPixmaps->insert(key, App::pixmapFromImageInPlace(createIconImage(_maskImage, _color->c)));
auto image = colorizeImage(_maskImage, _color);
j = iconPixmaps->insert(key, App::pixmapFromImageInPlace(std_::move(image)));
}
_pixmap = j.value();
_size = _pixmap.size() / cIntRetinaFactor();

View File

@ -68,11 +68,12 @@ public:
private:
void ensureLoaded() const;
void createCachedPixmap() const;
void ensureColorizedImage(QColor color) const;
const IconMask *_mask = nullptr;
Color _color;
QPoint _offset = { 0, 0 };
mutable QImage _maskImage;
mutable QImage _maskImage, _colorizedImage;
mutable QPixmap _pixmap; // for pixmaps
mutable QSize _size; // for rects

View File

@ -35,7 +35,15 @@ constexpr int kThemeBackgroundSizeLimit = 4 * 1024 * 1024;
constexpr int kThemeSchemeSizeLimit = 1024 * 1024;
struct Data {
struct Applying {
QString path;
QByteArray content;
QByteArray paletteForRevert;
Cached cached;
};
ChatBackground background;
Applying applying;
};
NeverFreedPointer<Data> instance;
@ -137,11 +145,11 @@ bool loadColorScheme(const QByteArray &content, Instance *out = nullptr) {
auto size = value.size();
auto error = false;
if (value[0] == '#' && (size == 7 || size == 8)) {
if (value[0] == '#' && (size == 7 || size == 9)) {
auto r = readHexUchar(value[1], value[2], error);
auto g = readHexUchar(value[3], value[4], error);
auto b = readHexUchar(value[5], value[6], error);
auto a = (size == 8) ? readHexUchar(value[7], value[8], error) : uchar(255);
auto a = (size == 9) ? readHexUchar(value[7], value[8], error) : uchar(255);
if (!error) {
if (out) {
error = !out->palette.setColor(QLatin1String(name), r, g, b, a);
@ -157,7 +165,7 @@ bool loadColorScheme(const QByteArray &content, Instance *out = nullptr) {
}
}
if (error) {
LOG(("Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme."));
LOG(("Error: Expected a color value in #rrggbb or #rrggbbaa format in the color scheme (while applying '%1: %2')").arg(QLatin1String(name)).arg(QLatin1String(value)));
return false;
}
}
@ -320,6 +328,12 @@ void ChatBackground::setImage(int32 id, QImage &&image) {
if (_id == kThemeBackground) {
_tile = _themeTile;
setPreparedImage(QImage(_themeImage));
} else if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) {
if (_id == internal::kTestingDefaultBackground || image.isNull()) {
image.load(qsl(":/gui/art/bg.jpg"));
_id = internal::kTestingDefaultBackground;
}
setPreparedImage(std_::move(image));
} else {
if (_id == kDefaultBackground) {
image.load(qsl(":/gui/art/bg.jpg"));
@ -356,19 +370,98 @@ bool ChatBackground::tile() const {
return _tile;
}
void ChatBackground::setTile(bool tile) {
bool ChatBackground::tileForSave() const {
if (_id == internal::kTestingThemeBackground ||
_id == internal::kTestingDefaultBackground) {
return _tileForRevert;
}
return tile();
}
void ChatBackground::ensureStarted() {
if (_image.isNull()) {
// We should start first, otherwise the default call
// to start() will reset this value to _themeTile.
start();
}
}
void ChatBackground::setTile(bool tile) {
ensureStarted();
if (_tile != tile) {
_tile = tile;
Local::writeUserSettings();
if (_id != internal::kTestingThemeBackground && _id != internal::kTestingDefaultBackground) {
Local::writeUserSettings();
}
notify(BackgroundUpdate(BackgroundUpdate::Type::Changed, _tile));
}
}
void ChatBackground::reset() {
if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) {
if (_themeImage.isNull()) {
_idForRevert = kDefaultBackground;
_imageForRevert = QImage();
_tileForRevert = false;
} else {
_idForRevert = kThemeBackground;
_imageForRevert = _themeImage;
_tileForRevert = _themeTile;
}
} else {
setImage(kThemeBackground);
}
}
void ChatBackground::saveForRevert() {
ensureStarted();
if (_id != internal::kTestingThemeBackground && _id != internal::kTestingDefaultBackground) {
_idForRevert = _id;
_imageForRevert = std_::move(_image).toImage();
_tileForRevert = _tile;
}
}
void ChatBackground::setTestingTheme(Instance &&theme) {
style::main_palette::apply(theme.palette);
if (!theme.background.isNull() || _id == kThemeBackground) {
saveForRevert();
setImage(internal::kTestingThemeBackground, std_::move(theme.background));
setTile(theme.tiled);
}
notify(BackgroundUpdate(BackgroundUpdate::Type::TestingTheme, _tile), true);
}
void ChatBackground::keepApplied() {
if (_id == internal::kTestingThemeBackground) {
_id = kThemeBackground;
_themeImage = _image.toImage();
_themeTile = _tile;
} else if (_id == internal::kTestingDefaultBackground) {
_id = kDefaultBackground;
_themeImage = QImage();
_themeTile = false;
writeNewBackgroundSettings();
}
notify(BackgroundUpdate(BackgroundUpdate::Type::ApplyingTheme, _tile), true);
}
void ChatBackground::writeNewBackgroundSettings() {
if (_tile != _tileForRevert) {
Local::writeUserSettings();
}
Local::writeBackground(_id, QImage());
}
void ChatBackground::revert() {
if (_id == internal::kTestingThemeBackground || _id == internal::kTestingDefaultBackground) {
setTile(_tileForRevert);
setImage(_idForRevert, std_::move(_imageForRevert));
}
notify(BackgroundUpdate(BackgroundUpdate::Type::RevertingTheme, _tile), true);
}
ChatBackground *Background() {
instance.createIfNull();
return &instance->background;
@ -396,6 +489,43 @@ void Unload() {
instance.clear();
}
bool Apply(const QString &filepath) {
QByteArray content;
Instance theme;
if (!LoadFromFile(filepath, &theme, &content)) {
return false;
}
instance.createIfNull();
instance->applying.path = filepath;
instance->applying.content = content;
instance->applying.cached = theme.cached;
if (instance->applying.paletteForRevert.isEmpty()) {
instance->applying.paletteForRevert = style::main_palette::save();
}
Background()->setTestingTheme(std_::move(theme));
return true;
}
void KeepApplied() {
auto filepath = instance ? instance->applying.path : QString();
if (filepath.isEmpty()) {
return;
}
auto pathRelative = QDir().relativeFilePath(filepath);
auto pathAbsolute = QFileInfo(filepath).absoluteFilePath();
Local::writeTheme(pathRelative, pathAbsolute, instance->applying.content, instance->applying.cached);
instance->applying = Data::Applying();
Background()->keepApplied();
}
void Revert() {
if (!instance->applying.paletteForRevert.isEmpty()) {
style::main_palette::load(instance->applying.paletteForRevert);
}
instance->applying = Data::Applying();
Background()->revert();
}
bool LoadFromFile(const QString &path, Instance *out, QByteArray *outContent) {
*outContent = readThemeContent(path);
if (outContent->size() < 4) {

View File

@ -24,7 +24,9 @@ namespace Window {
namespace Theme {
namespace internal {
constexpr int32 kUninitializedBackground = -3;
constexpr int32 kUninitializedBackground = -999;
constexpr int32 kTestingThemeBackground = -666;
constexpr int32 kTestingDefaultBackground = -665;
} // namespace internal
@ -43,6 +45,10 @@ struct Cached {
bool Load(const QString &pathRelative, const QString &pathAbsolute, const QByteArray &content, Cached &cache);
void Unload();
bool Apply(const QString &filepath);
void KeepApplied();
void Revert();
struct Instance {
style::palette palette;
QImage background;
@ -56,6 +62,9 @@ struct BackgroundUpdate {
New,
Changed,
Start,
TestingTheme,
RevertingTheme,
ApplyingTheme,
};
BackgroundUpdate(Type type, bool tiled) : type(type), tiled(tiled) {
@ -73,16 +82,22 @@ public:
void start();
void setImage(int32 id, QImage &&image = QImage());
void setTile(bool tile);
void reset() {
setImage(kThemeBackground);
}
void reset();
void setTestingTheme(Instance &&theme);
void keepApplied();
void revert();
int32 id() const;
const QPixmap &image() const;
bool tile() const;
bool tileForSave() const;
private:
void ensureStarted();
void saveForRevert();
void setPreparedImage(QImage &&image);
void writeNewBackgroundSettings();
int32 _id = internal::kUninitializedBackground;
QPixmap _image;
@ -91,6 +106,10 @@ private:
QImage _themeImage;
bool _themeTile = false;
int32 _idForRevert = internal::kUninitializedBackground;
QImage _imageForRevert;
bool _tileForRevert = false;
};
ChatBackground *Background();

View File

@ -0,0 +1,148 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "window/window_theme_warning.h"
#include "styles/style_boxes.h"
#include "ui/buttons/round_button.h"
#include "window/window_theme.h"
#include "lang.h"
namespace Window {
namespace Theme {
namespace {
constexpr int kWaitBeforeRevertMs = 15999;
} // namespace
WarningWidget::WarningWidget(QWidget *parent) : TWidget(parent)
, _secondsLeft(kWaitBeforeRevertMs / 1000)
, _shadow(st::themeWarningShadow)
, _keepChanges(this, lang(lng_theme_keep_changes), st::defaultBoxButton)
, _revert(this, lang(lng_theme_revert), st::cancelBoxButton) {
_keepChanges->setClickedCallback([] { Window::Theme::KeepApplied(); });
_revert->setClickedCallback([] { Window::Theme::Revert(); });
_timer.setTimeoutHandler([this] { handleTimer(); });
updateText();
}
void WarningWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
Window::Theme::Revert();
}
}
void WarningWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
if (!_cache.isNull()) {
if (!_animation.animating(getms())) {
if (isHidden()) {
return;
}
}
p.setOpacity(_animation.current(_hiding ? 0. : 1.));
p.drawPixmap(_outer.topLeft(), _cache);
if (!_animation.animating()) {
_cache = QPixmap();
showChildren();
_started = getms(true);
_timer.start(100);
}
return;
}
_shadow.paint(p, _inner, st::themeWarningShadowShift);
p.fillRect(_inner, st::boxBg);
p.setFont(st::boxTitleFont);
p.setPen(st::boxTitleFg);
p.drawTextLeft(_inner.x() + st::boxTitlePosition.x(), _inner.y() + st::boxTitlePosition.y(), width(), lang(lng_theme_sure_keep));
p.setFont(st::boxTextFont);
p.setPen(st::boxTextFg);
p.drawTextLeft(_inner.x() + st::boxTitlePosition.x(), _inner.y() + st::themeWarningTextTop, width(), _text);
}
void WarningWidget::resizeEvent(QResizeEvent *e) {
_inner = QRect((width() - st::themeWarningWidth) / 2, (height() - st::themeWarningHeight) / 2, st::themeWarningWidth, st::themeWarningHeight);
_outer = _inner.marginsAdded(_shadow.getDimensions(st::themeWarningShadowShift));
auto left = _inner.x() + _inner.width() - st::boxButtonPadding.right() - _keepChanges->width();
_keepChanges->moveToLeft(left, _inner.y() + _inner.height() - st::boxButtonPadding.bottom() - _keepChanges->height());
_revert->moveToLeft(left - st::boxButtonPadding.left() - _revert->width(), _keepChanges->y());
update();
}
void WarningWidget::handleTimer() {
auto msPassed = getms(true) - _started;
setSecondsLeft((kWaitBeforeRevertMs - msPassed) / 1000);
}
void WarningWidget::setSecondsLeft(int secondsLeft) {
if (secondsLeft <= 0) {
Window::Theme::Revert();
} else {
if (_secondsLeft != secondsLeft) {
_secondsLeft = secondsLeft;
updateText();
update();
}
_timer.start(100);
}
}
void WarningWidget::updateText() {
_text = lng_theme_reverting(lt_count, _secondsLeft);
}
void WarningWidget::showAnimated() {
startAnimation(false);
show();
setFocus();
}
void WarningWidget::hideAnimated() {
startAnimation(true);
}
void WarningWidget::startAnimation(bool hiding) {
_timer.stop();
_hiding = hiding;
if (_cache.isNull()) {
showChildren();
myEnsureResized(this);
_cache = myGrab(this, _outer);
}
hideChildren();
_animation.start([this] {
update();
if (_hiding) {
hide();
if (_hiddenCallback) {
_hiddenCallback();
}
}
}, _hiding ? 1. : 0., _hiding ? 0. : 1., st::layerSlideDuration);
}
} // namespace Theme
} // namespace Window

View File

@ -0,0 +1,71 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/effects/rect_shadow.h"
class BoxButton;
namespace Window {
namespace Theme {
class WarningWidget : public TWidget {
public:
WarningWidget(QWidget *parent);
void setHiddenCallback(base::lambda_unique<void()> callback) {
_hiddenCallback = std_::move(callback);
}
void showAnimated();
void hideAnimated();
protected:
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void setSecondsLeft(int secondsLeft);
void startAnimation(bool hiding);
void updateText();
void handleTimer();
bool _hiding = false;
FloatAnimation _animation;
QPixmap _cache;
QRect _inner, _outer;
SingleTimer _timer;
uint64 _started = 0;
int _secondsLeft = 0;
QString _text;
Ui::RectShadow _shadow;
ChildWidget<BoxButton> _keepChanges;
ChildWidget<BoxButton> _revert;
base::lambda_unique<void()> _hiddenCallback;
};
} // namespace Theme
} // namespace Window

View File

@ -559,6 +559,8 @@
'<(src_loc)/window/top_bar_widget.h',
'<(src_loc)/window/window_theme.cpp',
'<(src_loc)/window/window_theme.h',
'<(src_loc)/window/window_theme_warning.cpp',
'<(src_loc)/window/window_theme_warning.h',
'<(sp_media_key_tap_loc)/SPMediaKeyTap.m',
'<(sp_media_key_tap_loc)/SPMediaKeyTap.h',