From 0bf3e7e9ad9dae1affd796bfac60a930129994e0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 13 Sep 2026 22:17:10 +0400 Subject: [PATCH] Fix the scale of the external webapp shell. Co-Authored-By: Claude Opus 5 --- .../ui/chat/attach/attach_bot_webview.cpp | 84 ++++++++++++------- .../attach/attach_bot_webview_linux_shell.cpp | 41 ++++----- .../attach/attach_bot_webview_linux_shell.h | 14 ++++ 3 files changed, 88 insertions(+), 51 deletions(-) diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index e3660b2752..65be9196b7 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include #include #include @@ -779,28 +780,12 @@ void FillNativeSharedPanelMenu( } } -[[nodiscard]] QImage RasterizeStyleIcon(const style::icon &icon) { - const auto size = icon.size(); - const auto ratio = style::DevicePixelRatio(); - auto image = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied); - image.setDevicePixelRatio(ratio); - image.fill(Qt::transparent); - auto painter = Painter(&image); - icon.paintInCenter(painter, QRect(QPoint(), size)); - return image; -} - -[[nodiscard]] QImage RasterizeVerifiedBadge() { - const auto size = st::infoVerifiedStar.size() + QSize(0, st::lineWidth); - const auto ratio = style::DevicePixelRatio(); - auto image = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied); - image.setDevicePixelRatio(ratio); - image.fill(Qt::transparent); - auto painter = Painter(&image); - const auto width = size.width(); - st::infoVerifiedStar.paint(painter, st::lineWidth, 0, width); - st::infoPeerBadge.verifiedCheck.paint(painter, st::lineWidth, 0, width); - return image; +// WebKit maps CSS pixels to the screen by itself, so rasterize above any +// screen density (Qt floors it on X11) and let it downscale. +[[nodiscard]] int ExternalShellAssetRatio() { + return std::min( + style::DevicePixelRatio() + 1, + style::kScaleMax / 100); } [[nodiscard]] QString PngDataUrl(const QImage &image) { @@ -826,15 +811,41 @@ void FillNativeSharedPanelMenu( return result; } -[[nodiscard]] QJsonObject SerializeStyleIconAsset(const style::icon &icon) { - return SerializeRasterAsset(RasterizeStyleIcon(icon), icon.size()); +[[nodiscard]] QJsonObject SerializeStyleIconAsset( + const style::icon &icon, + const style::color &color) { + const auto ratio = ExternalShellAssetRatio(); + const auto image = icon.instance( + color->c, + ratio * 100, + true); + return SerializeRasterAsset(image, image.size() / ratio); } [[nodiscard]] QJsonObject SerializeVerifiedBadgeAsset() { - const auto size = st::infoVerifiedStar.size() + QSize(0, st::lineWidth); + const auto ratio = ExternalShellAssetRatio(); + const auto scale = ratio * 100; + const auto star = st::infoVerifiedStar.instance( + st::profileVerifiedCheckBg->c, + scale, + true); + const auto check = st::infoPeerBadge.verifiedCheck.instance( + st::profileVerifiedCheckFg->c, + scale, + true); + const auto line = LinuxShell::Unscaled(st::lineWidth) * ratio; + auto image = QImage( + star.size() + QSize(0, line), + QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + { + auto p = QPainter(&image); + p.drawImage(line, 0, star); + p.drawImage(line, 0, check); + } return SerializeRasterAsset( - RasterizeVerifiedBadge(), - size, + image, + image.size() / ratio, tr::lng_sr_verified_badge(tr::now)); } @@ -845,7 +856,9 @@ void CollectSharedPanelMenuIcons( if (!item.iconKey.isEmpty() && item.icon && !result.contains(item.iconKey)) { - result.insert(item.iconKey, SerializeStyleIconAsset(*item.icon)); + result.insert( + item.iconKey, + SerializeStyleIconAsset(*item.icon, st::menuIconColor)); } if (!item.children.empty()) { CollectSharedPanelMenuIcons(item.children, result); @@ -1889,7 +1902,10 @@ void Panel::requestExternalShellButtonEmoji(const QString &name) { _delegate->botResolveButtonEmoji({ .customEmojiId = state->args.iconCustomEmojiId, .textColor = state->textColor, - .size = kExternalShellButtonIconSize, + // Custom emoji take a logical size, rasterize them as other assets. + .size = qCeil(kExternalShellButtonIconSize + * ExternalShellAssetRatio() + / double(style::DevicePixelRatio())), .callback = std::move(send), }); } @@ -1918,7 +1934,9 @@ void Panel::sendExternalShellAssets() { sendExternalShellMethod("setAssets", { { u"icons"_q, icons }, { u"titleMenuIcon"_q, - SerializeStyleIconAsset(st::separatePanelMenu.icon) }, + SerializeStyleIconAsset( + st::separatePanelMenu.icon, + st::boxTitleCloseFg) }, { u"verifiedBadge"_q, SerializeVerifiedBadgeAsset() }, { u"menuPalette"_q, LinuxShell::MenuPalette() }, }); @@ -2162,7 +2180,7 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { ? Webview::WindowStyle::Frameless : Webview::WindowStyle::Default, .windowMargins = _externalShell - ? st::botWebViewShellShadowPadding + ? LinuxShell::Unscaled(st::botWebViewShellShadowPadding) : QMargins(), .initialSize = _externalShell ? LinuxShell::WindowSize(st::botWebViewPanelSize) @@ -2596,7 +2614,9 @@ void Panel::sendContentSafeArea() { : 0; const auto scaled = top * style::DevicePixelRatio(); auto report = 0; - if (const auto screen = QGuiApplication::primaryScreen()) { + if (_externalShell) { + report = LinuxShell::Unscaled(top); + } else if (const auto screen = QGuiApplication::primaryScreen()) { const auto dpi = screen->logicalDotsPerInch(); const auto ratio = screen->devicePixelRatio(); const auto basePair = screen->handle()->logicalBaseDpi(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.cpp index 11a1cb0a5f..ba4c830c3d 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.cpp @@ -158,17 +158,17 @@ QByteArray EventScript( } QJsonObject Metrics() { - const auto &shellPadding = st::botWebViewShellPadding; - const auto &shadowPadding = st::botWebViewShellShadowPadding; - const auto &titlePadding = st::botWebViewShellTitlePadding; - const auto &menuButtonSize = st::botWebViewShellMenuButtonSize; - const auto fullscreenButtonSize = QSize( + const auto shellPadding = Unscaled(st::botWebViewShellPadding); + const auto shadowPadding = Unscaled(st::botWebViewShellShadowPadding); + const auto titlePadding = Unscaled(st::botWebViewShellTitlePadding); + const auto menuButtonSize = Unscaled(st::botWebViewShellMenuButtonSize); + const auto fullscreenButtonSize = Unscaled(QSize( st::fullScreenPanelClose.width, - st::fullScreenPanelClose.height); + st::fullScreenPanelClose.height)); const auto fullscreenControlShift - = st::separatePanelClose.rippleAreaPosition; + = Unscaled(st::separatePanelClose.rippleAreaPosition); return { - { u"shellRadius"_q, st::botWebViewShellRadius }, + { u"shellRadius"_q, Unscaled(st::botWebViewShellRadius) }, { u"shellPaddingTop"_q, shellPadding.top() }, { u"shellPaddingRight"_q, shellPadding.right() }, { u"shellPaddingBottom"_q, shellPadding.bottom() }, @@ -177,20 +177,23 @@ QJsonObject Metrics() { { u"shadowPaddingRight"_q, shadowPadding.right() }, { u"shadowPaddingBottom"_q, shadowPadding.bottom() }, { u"shadowPaddingLeft"_q, shadowPadding.left() }, - { u"headerHeight"_q, st::botWebViewShellHeaderHeight }, + { u"headerHeight"_q, Unscaled(st::botWebViewShellHeaderHeight) }, { u"titlePaddingTop"_q, titlePadding.top() }, { u"titlePaddingRight"_q, titlePadding.right() }, { u"titlePaddingBottom"_q, titlePadding.bottom() }, { u"titlePaddingLeft"_q, titlePadding.left() }, - { u"badgeSkip"_q, st::botWebViewShellBadgeSkip }, - { u"frameRadius"_q, st::botWebViewShellFrameRadius }, + { u"badgeSkip"_q, Unscaled(st::botWebViewShellBadgeSkip) }, + { u"frameRadius"_q, Unscaled(st::botWebViewShellFrameRadius) }, { u"controlWidth"_q, menuButtonSize.width() }, { u"controlHeight"_q, menuButtonSize.height() }, - { u"buttonHeight"_q, st::botWebViewBottomButton.height }, - { u"buttonGapX"_q, st::botWebViewBottomSkip.x() }, - { u"buttonGapY"_q, st::botWebViewBottomSkip.y() }, - { u"disclosureSkip"_q, st::botWebViewShellDisclosureSkip }, - { u"footerButtonSkip"_q, st::botWebViewShellFooterButtonSkip }, + { u"buttonHeight"_q, Unscaled(st::botWebViewBottomButton.height) }, + { u"buttonGapX"_q, Unscaled(st::botWebViewBottomSkip.x()) }, + { u"buttonGapY"_q, Unscaled(st::botWebViewBottomSkip.y()) }, + { u"disclosureSkip"_q, Unscaled(st::botWebViewShellDisclosureSkip) }, + { + u"footerButtonSkip"_q, + Unscaled(st::botWebViewShellFooterButtonSkip), + }, { u"fullscreenControlWidth"_q, fullscreenButtonSize.width() }, { u"fullscreenControlHeight"_q, fullscreenButtonSize.height() }, { u"fullscreenControlTop"_q, fullscreenControlShift.y() }, @@ -200,11 +203,11 @@ QJsonObject Metrics() { } QSize WindowSize(QSize contentSize) { - const auto &shadowPadding = st::botWebViewShellShadowPadding; - return contentSize + QSize( + const auto shadowPadding = Unscaled(st::botWebViewShellShadowPadding); + return Unscaled(contentSize) + QSize( shadowPadding.left() + shadowPadding.right(), shadowPadding.top() - + st::botWebViewShellHeaderHeight + + Unscaled(st::botWebViewShellHeaderHeight) + shadowPadding.bottom()); } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.h index fe2892cf5d..323da70999 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview_linux_shell.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "ui/style/style_core_scale.h" + #include #include #include @@ -21,6 +23,18 @@ struct ResolvedColors { QColor bottomBg; }; +// WebKit applies the system scale to CSS pixels by itself, +// so the shell gets style values without the application scale. +template +[[nodiscard]] T Unscaled(T value) { + const auto factor = 100. / style::Scale(); + if constexpr (std::is_arithmetic_v) { + return T(base::SafeRound(value * factor)); + } else { + return value * factor; + } +} + #if !defined Q_OS_WIN && !defined Q_OS_MAC [[nodiscard]] QByteArray InstallScript(const QString &shellToken);