Lukáš Tinkl 630da7caaa fix: 150% and 200% zoom levels prevent the user from logging in
- TLDR: we were scaling twice, resulting in ginourmous pixel values

The long story:
- since Qt treats the various scale factors in a multiplicative way (see
https://www.qt.io/blog/2016/01/26/high-dpi-support-in-qt-5-6 for
explanation) and there's no way to get the screen's baseline scale
factor programatically, we also have to export `QT_SCREEN_SCALE_FACTORS`
to something that's not equal to `0` or `1` to force the monitor scale
factor to `100%` and then compensate for it when exporting our own scale
value using `QT_SCALE_FACTOR`
- make the UI slider values go in `25%` steps, allowing for more fine
grained control; with `100%` we fallback to the Qt's native handling of
highdpi
- raised the maximum to `300%` since on highres displays, one wouldn't
be able to go over the implicit maximum of `200%` (due to the internal
scaling being 2x)
- scale our main window's minimum width/height so that we don't overflow
the monitor's available space
- modernize the `ConfirmAppRestartModal` to use `StatusDialog`
- use the new `Utils.restartApplication()` when changing the UI language
as well
- remove some dead code

In the (very) long term, we should take a different approach of scaling
our app independently of Qt, just taking the monitor
`Screen.devicePixelRatio` into account, similar to what other apps like
Telegram do

Fixes #13484
2024-02-27 10:39:05 +01:00

238 lines
8.4 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import QtQuick.Controls.Universal 2.15
import utils 1.0
import shared 1.0
import shared.views 1.0
import shared.status 1.0
import shared.views.chat 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 as StatusQ
import "../popups"
import "../stores"
SettingsContentBase {
id: appearanceView
property AppearanceStore appearanceStore
property var systemPalette
function updateTheme(theme) {
localAppSettings.theme = theme
Style.changeTheme(theme, systemPalette.isCurrentSystemThemeDark())
}
function updateFontSize(fontSize) {
Style.changeFontSize(fontSize)
Theme.updateFontSize(fontSize)
}
Component.onCompleted: {
appearanceView.updateFontSize(localAccountSensitiveSettings.fontSize)
}
readonly property var priv: QtObject {
id: priv
readonly property real savedDpr: {
const scaleFactorStr = appearanceView.appearanceStore.readTextFile(uiScaleFilePath)
if (scaleFactorStr === "") {
return 1
}
const scaleFactor = parseFloat(scaleFactorStr)
if (isNaN(scaleFactor)) {
return 1
}
return scaleFactor
}
}
Item {
id: appearanceContainer
anchors.left: !!parent ? parent.left : undefined
anchors.leftMargin: Style.current.padding
width: appearanceView.contentWidth - 2 * Style.current.padding
height: childrenRect.height
Rectangle {
id: preview
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: placeholderMessage.implicitHeight +
placeholderMessage.anchors.leftMargin +
placeholderMessage.anchors.rightMargin
radius: Style.current.radius
border.color: Style.current.border
color: Style.current.transparent
MessageView {
id: placeholderMessage
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Style.current.smallPadding
isMessage: true
shouldRepeatHeader: true
messageTimestamp: Date.now()
senderDisplayName: "vitalik.eth"
senderIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAb0lEQVR4Ae3UQQqAIBRF0Wj9ba9Bq6l5JBQqfn/ngDMH3YS3AAB/tO3H+XRG3b9bR/+gVoREI2RapVXpfd5+X5oXERKNkHS+rk3tOpWkeREh0QiZVu91ql2zNC8iJBoh0yqtSqt1slpCghICANDPBc0ESPh0bHkHAAAAAElFTkSuQmCC"
messageText: qsTr("Blockchains will drop search costs, causing a kind of decomposition that allows you to have markets of entities that are horizontally segregated and vertically segregated.")
messageContentType: Constants.messageContentType.messageType
placeholderMessage: true
}
}
StatusSectionHeadline {
id: sectionHeadlineFontSize
text: qsTr("Text size")
anchors.top: preview.bottom
anchors.topMargin: Style.current.bigPadding*2
anchors.left: parent.left
anchors.right: parent.right
}
StatusQ.StatusLabeledSlider {
id: fontSizeSlider
anchors.top: sectionHeadlineFontSize.bottom
anchors.topMargin: Style.current.padding
width: parent.width
textRole: "name"
valueRole: "value"
model: ListModel {
ListElement { name: qsTr("XS"); value: Theme.FontSizeXS }
ListElement { name: qsTr("S"); value: Theme.FontSizeS }
ListElement { name: qsTr("M"); value: Theme.FontSizeM }
ListElement { name: qsTr("L"); value: Theme.FontSizeL }
ListElement { name: qsTr("XL"); value: Theme.FontSizeXL }
ListElement { name: qsTr("XXL"); value: Theme.FontSizeXXL }
}
value: localAccountSensitiveSettings.fontSize
onCurrentValueChanged: {
const fontSize = currentValue
if (localAccountSensitiveSettings.fontSize !== fontSize) {
localAccountSensitiveSettings.fontSize = fontSize
appearanceView.updateFontSize(fontSize)
}
}
}
StatusSectionHeadline {
id: labelZoom
anchors.top: fontSizeSlider.bottom
anchors.topMargin: Style.current.bigPadding*2
anchors.left: parent.left
anchors.right: parent.right
text: qsTr("Zoom (requires restart)")
}
StatusQ.StatusLabeledSlider {
id: zoomSlider
readonly property int initialValue: priv.savedDpr * 100
readonly property bool dirty: value !== initialValue
anchors.top: labelZoom.bottom
anchors.topMargin: Style.current.padding
width: parent.width
from: 50
to: 300
stepSize: 25
model: [ qsTr("50%"), qsTr("75%"), qsTr("100%"), qsTr("125%"), qsTr("150%"), qsTr("175%"), qsTr("200%"),
qsTr("225%"), qsTr("250%"), qsTr("275%"), qsTr("300%")]
value: initialValue
onMoved: {
const uiScale = zoomSlider.value === 100 ? "" // reset to native highdpi
: zoomSlider.value / 100.0
appearanceView.appearanceStore.writeTextFile(uiScaleFilePath, uiScale)
}
onPressedChanged: {
if (!pressed && dirty) {
confirmAppRestartModal.open()
}
}
ConfirmAppRestartModal {
id: confirmAppRestartModal
onAccepted: Utils.restartApplication();
onClosed: zoomSlider.value = zoomSlider.initialValue
}
}
Rectangle {
id: modeSeparator
anchors.top: zoomSlider.bottom
anchors.topMargin: Style.current.padding*3
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: Style.current.separator
}
StatusSectionHeadline {
id: sectionHeadlineAppearance
text: qsTr("Mode")
anchors.top: modeSeparator.bottom
anchors.topMargin: Style.current.padding*3
anchors.left: parent.left
anchors.right: parent.right
}
RowLayout {
id: appearanceSection
anchors.top: sectionHeadlineAppearance.bottom
anchors.topMargin: Style.current.padding
anchors.left: parent.left
anchors.right: parent.right
spacing: Style.current.halfPadding
StatusImageRadioButton {
Layout.preferredWidth: parent.width/3 - parent.spacing
Layout.preferredHeight: implicitHeight
image.source: Style.png("appearance-light")
control.text: qsTr("Light")
control.checked: localAppSettings.theme === Universal.Light
onRadioCheckedChanged: {
if (checked) {
appearanceView.updateTheme(Universal.Light)
}
}
}
StatusImageRadioButton {
Layout.preferredWidth: parent.width/3 - parent.spacing
image.source: Style.png("appearance-dark")
control.text: qsTr("Dark")
control.checked: localAppSettings.theme === Universal.Dark
onRadioCheckedChanged: {
if (checked) {
appearanceView.updateTheme(Universal.Dark)
}
}
}
StatusImageRadioButton {
Layout.preferredWidth: parent.width/3 - parent.spacing
image.source: Style.png("appearance-system")
control.text: qsTr("System")
control.checked: localAppSettings.theme === Universal.System
onRadioCheckedChanged: {
if (checked) {
appearanceView.updateTheme(Universal.System)
}
}
}
}
}
}