diff --git a/Telegram/Resources/iv_html/page.css b/Telegram/Resources/iv_html/page.css index 9c0ab832f..cdbfc3108 100644 --- a/Telegram/Resources/iv_html/page.css +++ b/Telegram/Resources/iv_html/page.css @@ -12,6 +12,7 @@ body { margin: 0; background-color: var(--td-window-bg); color: var(--td-window-fg); + zoom: var(--td-zoom-percentage); } html.custom_scroll ::-webkit-scrollbar { diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 5f304356d..71d9ae0e8 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -222,7 +222,7 @@ QByteArray Settings::serialize() const { + Serialize::stringSize(_customFontFamily) + sizeof(qint32) * 3 + Serialize::bytearraySize(_tonsiteStorageToken) - + sizeof(qint32); + + sizeof(qint32) * 2; auto result = QByteArray(); result.reserve(size); @@ -377,7 +377,8 @@ QByteArray Settings::serialize() const { << qint32(_systemUnlockEnabled ? 1 : 0) << qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2) << _tonsiteStorageToken - << qint32(_includeMutedCounterFolders ? 1 : 0); + << qint32(_includeMutedCounterFolders ? 1 : 0) + << qint32(_ivZoom.current()); } Ensures(result.size() == size); @@ -501,6 +502,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0; qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2; QByteArray tonsiteStorageToken = _tonsiteStorageToken; + qint32 ivZoom = _ivZoom.current(); stream >> themesAccentColors; if (!stream.atEnd()) { @@ -810,6 +812,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> includeMutedCounterFolders; } + if (!stream.atEnd()) { + stream >> ivZoom; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -1021,6 +1026,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { ? std::optional<bool>() : (weatherInCelsius == 1); _tonsiteStorageToken = tonsiteStorageToken; + _ivZoom = ivZoom; } QString Settings::getSoundPath(const QString &key) const { @@ -1408,6 +1414,7 @@ void Settings::resetOnLastLogout() { _hiddenGroupCallTooltips = 0; _storiesClickTooltipHidden = false; _ttlVoiceClickTooltipHidden = false; + _ivZoom = 100; _recentEmojiPreload.clear(); _recentEmoji.clear(); @@ -1547,4 +1554,16 @@ bool Settings::rememberedDeleteMessageOnlyForYou() const { return _rememberedDeleteMessageOnlyForYou; } +int Settings::ivZoom() const { + return _ivZoom.current(); +} +rpl::producer<int> Settings::ivZoomValue() const { + return _ivZoom.value(); +} +void Settings::setIvZoom(int value) { + constexpr auto kMin = 30; + constexpr auto kMax = 200; + _ivZoom = std::clamp(value, kMin, kMax); +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index ab5f999a6..54199d8e5 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -915,6 +915,10 @@ public: _tonsiteStorageToken = value; } + [[nodiscard]] int ivZoom() const; + [[nodiscard]] rpl::producer<int> ivZoomValue() const; + void setIvZoom(int value); + [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); @@ -1050,6 +1054,7 @@ private: bool _systemUnlockEnabled = false; std::optional<bool> _weatherInCelsius; QByteArray _tonsiteStorageToken; + rpl::variable<int> _ivZoom = 100; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/iv/iv.style b/Telegram/SourceFiles/iv/iv.style index 6a5757276..cea363566 100644 --- a/Telegram/SourceFiles/iv/iv.style +++ b/Telegram/SourceFiles/iv/iv.style @@ -29,6 +29,38 @@ ivBack: IconButton(ivMenuToggle) { iconOver: ivBackIcon; rippleAreaPosition: point(12px, 6px); } +ivZoomButtonsSize: 26px; +ivPlusMinusZoom: IconButton(ivMenuToggle) { + width: ivZoomButtonsSize; + height: ivZoomButtonsSize; + + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: ivZoomButtonsSize; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +ivResetZoomStyle: TextStyle(defaultTextStyle) { + font: font(12px); +} +ivResetZoom: RoundButton(defaultActiveButton) { + textFg: windowFg; + textFgOver: windowFgOver; + textBg: windowBg; + textBgOver: windowBgOver; + + height: ivZoomButtonsSize; + padding: margins(0px, 0px, 0px, 0px); + + style: ivResetZoomStyle; + + ripple: defaultRippleAnimation; +} +ivResetZoomLabel: FlatLabel(defaultFlatLabel) { + textFg: windowFg; + style: ivResetZoomStyle; +} +ivResetZoomInnerPadding: 20px; ivBackIconDisabled: icon {{ "box_button_back", menuIconFg }}; ivForwardIcon: icon {{ "box_button_back-flip_horizontal", menuIconColor }}; ivForward: IconButton(ivBack) { diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index b864833dd..486778ef7 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/platform/ui_platform_window_title.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/widgets/menu/menu_action.h" #include "ui/widgets/rp_window.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/fade_wrap.h" @@ -50,7 +51,145 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Iv { namespace { -[[nodiscard]] QByteArray ComputeStyles() { +constexpr auto kZoomStep = int(10); +constexpr auto kDefaultZoom = int(100); + +class ItemZoom final : public Ui::Menu::Action { +public: + ItemZoom( + not_null<RpWidget*> parent, + const not_null<Delegate*> delegate, + const style::Menu &st) + : Ui::Menu::Action( + parent, + st, + Ui::CreateChild<QAction>(parent), + nullptr, + nullptr) + , _delegate(delegate) + , _st(st) { + init(); + } + + void init() { + enableMouseSelecting(); + + AbstractButton::setDisabled(true); + + class SmallButton final : public Ui::IconButton { + public: + SmallButton( + not_null<Ui::RpWidget*> parent, + QChar c, + float64 skip, + const style::color &color) + : Ui::IconButton(parent, st::ivPlusMinusZoom) + , _color(color) + , _skip(style::ConvertFloatScale(skip)) + , _c(c) { + } + + void paintEvent(QPaintEvent *event) override { + auto p = Painter(this); + Ui::RippleButton::paintRipple( + p, + st::ivPlusMinusZoom.rippleAreaPosition); + p.setPen(_color); + p.setFont(st::normalFont); + p.drawText( + QRectF(rect()).translated(0, _skip), + _c, + style::al_center); + } + + private: + const style::color _color; + const float64 _skip; + const QChar _c; + + }; + + const auto reset = Ui::CreateChild<Ui::RoundButton>( + this, + rpl::single<QString>(QString()), + st::ivResetZoom); + const auto resetLabel = Ui::CreateChild<Ui::FlatLabel>( + reset, + tr::lng_background_reset_default(), + st::ivResetZoomLabel); + resetLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + reset->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + reset->setClickedCallback([this] { + _delegate->ivSetZoom(kDefaultZoom); + }); + reset->show(); + const auto plus = Ui::CreateChild<SmallButton>( + this, + '+', + 0, + _st.itemFg); + plus->setClickedCallback([this] { + _delegate->ivSetZoom(_delegate->ivZoom() + kZoomStep); + }); + plus->show(); + const auto minus = Ui::CreateChild<SmallButton>( + this, + QChar(0x2013), + -1, + _st.itemFg); + minus->setClickedCallback([this] { + _delegate->ivSetZoom(_delegate->ivZoom() - kZoomStep); + }); + minus->show(); + + _delegate->ivZoomValue( + ) | rpl::start_with_next([this](int value) { + _text.setText(_st.itemStyle, QString::number(value) + '%'); + update(); + }, lifetime()); + + rpl::combine( + sizeValue(), + reset->sizeValue() + ) | rpl::start_with_next([=, this](const QSize &size, const QSize &) { + reset->setFullWidth(0 + + resetLabel->width() + + st::ivResetZoomInnerPadding); + resetLabel->moveToLeft( + (reset->width() - resetLabel->width()) / 2, + (reset->height() - resetLabel->height()) / 2); + reset->moveToRight( + _st.itemPadding.right(), + (size.height() - reset->height()) / 2); + plus->moveToRight( + _st.itemPadding.right() + reset->width(), + (size.height() - plus->height()) / 2); + minus->moveToRight( + _st.itemPadding.right() + plus->width() + reset->width(), + (size.height() - minus->height()) / 2); + }, lifetime()); + } + + void paintEvent(QPaintEvent *event) override { + auto p = QPainter(this); + p.setPen(_st.itemFg); + _text.draw(p, { + .position = QPoint( + _st.itemIconPosition.x(), + (height() - _text.minHeight()) / 2), + .outerWidth = width(), + .availableWidth = width(), + }); + } + +private: + const not_null<Delegate*> _delegate; + const style::Menu &_st; + Ui::Text::String _text; + +}; + +[[nodiscard]] QByteArray ComputeStyles(int zoom) { static const auto map = base::flat_map<QByteArray, const style::color*>{ { "shadow-fg", &st::shadowFg }, { "scroll-bg", &st::scrollBg }, @@ -85,7 +224,7 @@ namespace { static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{ { "iv-join-channel", tr::lng_iv_join_channel }, }; - return Ui::ComputeStyles(map, phrases) + return Ui::ComputeStyles(map, phrases, zoom) + ';' + Ui::ComputeSemiTransparentOverStyle( "light-button-bg-over", @@ -93,7 +232,7 @@ namespace { st::windowBg); } -[[nodiscard]] QByteArray WrapPage(const Prepared &page) { +[[nodiscard]] QByteArray WrapPage(const Prepared &page, int zoom) { #ifdef Q_OS_MAC const auto classAttribute = ""_q; #else // Q_OS_MAC @@ -110,7 +249,7 @@ namespace { <html)"_q + classAttribute + R"( style=")" - + Ui::EscapeForAttribute(ComputeStyles()) + + Ui::EscapeForAttribute(ComputeStyles(zoom)) + R"("> <head> <meta charset="utf-8"> @@ -206,7 +345,8 @@ Controller::Controller( Fn<ShareBoxResult(ShareBoxDescriptor)> showShareBox) : _delegate(delegate) , _updateStyles([=] { - const auto str = Ui::EscapeForScriptString(ComputeStyles()); + const auto zoom = _delegate->ivZoom(); + const auto str = Ui::EscapeForScriptString(ComputeStyles(zoom)); if (_webview) { _webview->eval("IV.updateStyles('" + str + "');"); } @@ -484,6 +624,16 @@ void Controller::createWebview(const Webview::StorageId &storageId) { if (event->key() == Qt::Key_Escape) { escape(); } + if (event->modifiers() & Qt::ControlModifier) { + if (event->key() == Qt::Key_Plus + || event->key() == Qt::Key_Equal) { + _delegate->ivSetZoom(_delegate->ivZoom() + kZoomStep); + } else if (event->key() == Qt::Key_Minus) { + _delegate->ivSetZoom(_delegate->ivZoom() - kZoomStep); + } else if (event->key() == Qt::Key_0) { + _delegate->ivSetZoom(kDefaultZoom); + } + } } }, window->lifetime()); @@ -595,7 +745,8 @@ void Controller::createWebview(const Webview::StorageId &storageId) { rpl::merge( Lang::Updated(), - style::PaletteChanged() + style::PaletteChanged(), + _delegate->ivZoomValue() | rpl::to_empty ) | rpl::start_with_next([=] { _updateStyles.call(); }, _webview->lifetime()); @@ -611,7 +762,8 @@ void Controller::createWebview(const Webview::StorageId &storageId) { return Webview::DataResult::Failed; } return finishWith( - WrapPage(_pages[index]), "text/html; charset=utf-8"); + WrapPage(_pages[index], _delegate->ivZoom()), + "text/html; charset=utf-8"); } else if (id.starts_with("page") && id.ends_with(".json")) { auto index = 0; const auto result = std::from_chars( @@ -897,6 +1049,10 @@ void Controller::showMenu() { showShareMenu(); }, &st::menuIconShare); + _menu->addSeparator(); + _menu->addAction( + base::make_unique_q<ItemZoom>(_menu, _delegate, _menu->menu()->st())); + _menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight); _menu->popup(_window->body()->mapToGlobal( QPoint(_window->body()->width(), 0) + st::ivMenuPosition)); diff --git a/Telegram/SourceFiles/iv/iv_delegate.h b/Telegram/SourceFiles/iv/iv_delegate.h index 09374fa17..d722d19f0 100644 --- a/Telegram/SourceFiles/iv/iv_delegate.h +++ b/Telegram/SourceFiles/iv/iv_delegate.h @@ -18,6 +18,10 @@ public: virtual void ivSetLastSourceWindow(not_null<QWidget*> window) = 0; [[nodiscard]] virtual QRect ivGeometry() const = 0; virtual void ivSaveGeometry(not_null<Ui::RpWindow*> window) = 0; + + [[nodiscard]] virtual int ivZoom() const = 0; + [[nodiscard]] virtual rpl::producer<int> ivZoomValue() const = 0; + virtual void ivSetZoom(int value) = 0; }; } // namespace Iv diff --git a/Telegram/SourceFiles/iv/iv_delegate_impl.cpp b/Telegram/SourceFiles/iv/iv_delegate_impl.cpp index 4de0fa03c..e97de87bf 100644 --- a/Telegram/SourceFiles/iv/iv_delegate_impl.cpp +++ b/Telegram/SourceFiles/iv/iv_delegate_impl.cpp @@ -117,4 +117,15 @@ void DelegateImpl::ivSaveGeometry(not_null<Ui::RpWindow*> window) { } } +int DelegateImpl::ivZoom() const { + return Core::App().settings().ivZoom(); +} +rpl::producer<int> DelegateImpl::ivZoomValue() const { + return Core::App().settings().ivZoomValue(); +} +void DelegateImpl::ivSetZoom(int value) { + Core::App().settings().setIvZoom(value); + Core::App().saveSettingsDelayed(); +} + } // namespace Iv diff --git a/Telegram/SourceFiles/iv/iv_delegate_impl.h b/Telegram/SourceFiles/iv/iv_delegate_impl.h index 9c7c0fb9d..f564f237a 100644 --- a/Telegram/SourceFiles/iv/iv_delegate_impl.h +++ b/Telegram/SourceFiles/iv/iv_delegate_impl.h @@ -19,6 +19,10 @@ public: [[nodiscard]] QRect ivGeometry() const override; void ivSaveGeometry(not_null<Ui::RpWindow*> window) override; + [[nodiscard]] int ivZoom() const; + [[nodiscard]] rpl::producer<int> ivZoomValue() const; + void ivSetZoom(int value); + private: QPointer<QWidget> _lastSourceWindow; diff --git a/Telegram/SourceFiles/ui/controls/location_picker.cpp b/Telegram/SourceFiles/ui/controls/location_picker.cpp index 945e5cf8f..71b3c6f42 100644 --- a/Telegram/SourceFiles/ui/controls/location_picker.cpp +++ b/Telegram/SourceFiles/ui/controls/location_picker.cpp @@ -379,7 +379,7 @@ void VenuesController::rowPaintIcon( static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{ { "maps-places-in-area", tr::lng_maps_places_in_area }, }; - return Ui::ComputeStyles(map, phrases, Window::Theme::IsNightMode()); + return Ui::ComputeStyles(map, phrases, 100, Window::Theme::IsNightMode()); } [[nodiscard]] QByteArray ReadResource(const QString &name) { diff --git a/Telegram/SourceFiles/ui/webview_helpers.cpp b/Telegram/SourceFiles/ui/webview_helpers.cpp index 3be7a6359..a0369421b 100644 --- a/Telegram/SourceFiles/ui/webview_helpers.cpp +++ b/Telegram/SourceFiles/ui/webview_helpers.cpp @@ -31,6 +31,7 @@ namespace { QByteArray ComputeStyles( const base::flat_map<QByteArray, const style::color*> &colors, const base::flat_map<QByteArray, tr::phrase<>> &phrases, + int zoom, bool nightTheme) { static const auto serialize = [](const style::color *color) { return Serialize((*color)->c); @@ -66,6 +67,9 @@ QByteArray ComputeStyles( result += "--td-"_q + name + ':' + serialize(color) + ';'; } result += "--td-night:"_q + (nightTheme ? "1" : "0") + ';'; + result += "--td-zoom-percentage:"_q + + (QString::number(zoom).toUtf8() + '%') + + ';'; return result; } diff --git a/Telegram/SourceFiles/ui/webview_helpers.h b/Telegram/SourceFiles/ui/webview_helpers.h index 518096479..c75f69e89 100644 --- a/Telegram/SourceFiles/ui/webview_helpers.h +++ b/Telegram/SourceFiles/ui/webview_helpers.h @@ -19,6 +19,7 @@ namespace Ui { [[nodiscard]] QByteArray ComputeStyles( const base::flat_map<QByteArray, const style::color*> &colors, const base::flat_map<QByteArray, tr::phrase<>> &phrases, + int zoom, bool nightTheme = false); [[nodiscard]] QByteArray ComputeSemiTransparentOverStyle( const QByteArray &name,