Fix the scale of the external webapp shell.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Ilya Fedin
2026-09-13 22:17:10 +04:00
committed by John Preston
parent db3405699f
commit 0bf3e7e9ad
3 changed files with 88 additions and 51 deletions

View File

@@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QUrl>
#include <QtCore/QtMath>
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
#include <QtGui/QWindow>
@@ -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 &params) {
? 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();

View File

@@ -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());
}

View File

@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/style/style_core_scale.h"
#include <QtCore/QByteArray>
#include <QtCore/QJsonObject>
#include <QtCore/QSize>
@@ -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 <typename T>
[[nodiscard]] T Unscaled(T value) {
const auto factor = 100. / style::Scale();
if constexpr (std::is_arithmetic_v<T>) {
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);