Skip to content
Snippets Groups Projects
Verified Commit 5a024465 authored by Sebastian's avatar Sebastian Committed by Mikolai Gütschow
Browse files

Add setting for ui scale using LocalDensity

parent 2f365ee0
No related branches found
No related tags found
1 merge request!226Add setting for ui scale using LocalDensity
Showing
with 324 additions and 98 deletions
......@@ -115,6 +115,7 @@ private class Main : CliktCommand(
DaggerBriarDesktopApp.builder().desktopCoreModule(
DesktopCoreModule(dataDir, socksPort, controlPort)
).build()
// We need to load the eager singletons directly after making the
// dependency graphs
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app)
......
......@@ -56,6 +56,7 @@ import androidx.compose.material.icons.filled.SouthWest
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
......@@ -67,6 +68,7 @@ import androidx.compose.ui.input.pointer.PointerIconDefaults
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.platform.ClipboardManager
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
......@@ -104,8 +106,8 @@ import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF
import org.briarproject.briar.desktop.utils.PreviewUtils
import org.briarproject.briar.desktop.utils.PreviewUtils.preview
import org.briarproject.briar.desktop.utils.UiUtils.DensityDimension
import org.briarproject.briar.desktop.viewmodel.viewModel
import java.awt.Dimension
@Suppress("HardCodedStringLiteral")
const val link = "briar://ady23gvb2r76afe5zhxh5kvnh4b22zrcnxibn63tfknrdcwrw7zrs"
......@@ -196,6 +198,7 @@ fun AddContactDialog(
if (!visible) {
return
}
val density = LocalDensity.current
Dialog(
title = i18n("contact.add.title_dialog"),
onCloseRequest = onClose,
......@@ -204,67 +207,70 @@ fun AddContactDialog(
size = DpSize(width = 560.dp, height = 520.dp),
),
) {
window.minimumSize = Dimension(360, 512)
val clipboardManager = LocalClipboardManager.current
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
val aliasFocusRequester = remember { FocusRequester() }
Surface {
Scaffold(
modifier = Modifier.padding(horizontal = 24.dp).padding(top = 24.dp, bottom = 12.dp),
topBar = {
Box(Modifier.fillMaxWidth()) {
Text(
i18n("contact.add.remote.title"),
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 12.dp)
)
}
},
scaffoldState = scaffoldState,
content = {
Column(Modifier.fillMaxSize()) {
if (error != null) {
AddContactErrorDialog(error, onErrorDialogDismissed)
CompositionLocalProvider(LocalDensity provides density) {
window.minimumSize = DensityDimension(360, 512)
window.preferredSize = DensityDimension(520, 512)
val clipboardManager = LocalClipboardManager.current
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
val aliasFocusRequester = remember { FocusRequester() }
Surface {
Scaffold(
modifier = Modifier.padding(horizontal = 24.dp).padding(top = 24.dp, bottom = 12.dp),
topBar = {
Box(Modifier.fillMaxWidth()) {
Text(
i18n("contact.add.remote.title"),
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(bottom = 12.dp)
)
}
OwnLink(
handshakeLink,
clipboardManager,
coroutineScope,
scaffoldState,
)
ContactLink(
remoteHandshakeLink,
setRemoteHandshakeLink,
clipboardManager,
coroutineScope,
scaffoldState,
aliasFocusRequester,
)
Alias(
alias,
setAddContactAlias,
aliasFocusRequester,
onSubmitAddContactDialog,
)
}
},
bottomBar = {
Box(Modifier.fillMaxWidth()) {
Row(Modifier.align(Alignment.CenterEnd)) {
TextButton(
onClose,
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colors.error)
) {
Text(i18n("cancel"))
},
scaffoldState = scaffoldState,
content = {
Column(Modifier.fillMaxSize()) {
if (error != null) {
AddContactErrorDialog(error, onErrorDialogDismissed)
}
Button(onSubmitAddContactDialog, modifier = Modifier.padding(start = 8.dp)) {
Text(i18n("add"))
OwnLink(
handshakeLink,
clipboardManager,
coroutineScope,
scaffoldState,
)
ContactLink(
remoteHandshakeLink,
setRemoteHandshakeLink,
clipboardManager,
coroutineScope,
scaffoldState,
aliasFocusRequester,
)
Alias(
alias,
setAddContactAlias,
aliasFocusRequester,
onSubmitAddContactDialog,
)
}
},
bottomBar = {
Box(Modifier.fillMaxWidth()) {
Row(Modifier.align(Alignment.CenterEnd)) {
TextButton(
onClose,
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colors.error)
) {
Text(i18n("cancel"))
}
Button(onSubmitAddContactDialog, modifier = Modifier.padding(start = 8.dp)) {
Text(i18n("add"))
}
}
}
}
},
)
},
)
}
}
}
}
......
......@@ -29,18 +29,25 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.OutlinedExposedDropDownMenu
import androidx.compose.material.Slider
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.FormatSize
import androidx.compose.material.icons.filled.Warning
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
......@@ -90,6 +97,33 @@ fun SettingDetails(viewModel: SettingsViewModel) {
)
}
// TODO: add description
DetailItem(
label = i18n("settings.display.ui_scale.title"),
description = ""
) {
val uiScale = remember { mutableStateOf(viewModel.selectedUiScale.value) }
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(200.dp)
) {
Icon(Icons.Default.FormatSize, null, Modifier.scale(0.7f))
Slider(
value = uiScale.value ?: LocalDensity.current.density,
onValueChange = { uiScale.value = it },
onValueChangeFinished = { viewModel.selectUiScale(uiScale.value!!) },
valueRange = 1f..3f,
steps = 3,
// todo: without setting the width explicitly,
// the slider takes up the whole remaining space
modifier = Modifier.width(150.dp)
)
Icon(Icons.Default.FormatSize, null)
}
}
DetailItem(
label = i18n("settings.security.title"),
description = i18n("access.settings.click_to_change_password")
......
......@@ -61,6 +61,9 @@ constructor(
private val _selectedLanguage = mutableStateOf(unencryptedSettings.language)
val selectedLanguage = _selectedLanguage.asState()
private val _selectedUiScale = mutableStateOf(unencryptedSettings.uiScale)
val selectedUiScale = _selectedUiScale.asState()
private val _changePasswordDialogVisible = mutableStateOf(false)
val changePasswordDialogVisible = _changePasswordDialogVisible.asState()
......@@ -92,6 +95,11 @@ constructor(
briarExecutors.onIoThread { unencryptedSettings.language = language }
}
fun selectUiScale(uiScale: Float) {
_selectedUiScale.value = uiScale
briarExecutors.onIoThread { unencryptedSettings.uiScale = uiScale }
}
fun showChangePasswordDialog() {
_changePasswordDialogVisible.value = true
}
......
......@@ -25,6 +25,7 @@ import java.util.Locale
interface UnencryptedSettingsReadOnly {
val theme: UnencryptedSettings.Theme
val language: UnencryptedSettings.Language
val uiScale: Float?
val invalidateScreen: SingleStateEvent<Unit>
}
......@@ -57,6 +58,7 @@ interface UnencryptedSettings : UnencryptedSettingsReadOnly {
override var theme: Theme
override var language: Language
override var uiScale: Float?
override val invalidateScreen: SingleStateEvent<Unit>
}
......@@ -33,6 +33,7 @@ import kotlin.reflect.KProperty
const val PREF_THEME = "theme" // NON-NLS
const val PREF_LANG = "language" // NON-NLS
const val PREF_UI_SCALE = "uiScale" // NON-NLS
class UnencryptedSettingsImpl @Inject internal constructor() : UnencryptedSettings {
......@@ -40,14 +41,26 @@ class UnencryptedSettingsImpl @Inject internal constructor() : UnencryptedSettin
private val LOG = KotlinLogging.logger {}
}
// used for unencrypted settings, namely theme and language
// used for unencrypted settings, namely theme, language and UI scale factor
private val prefs = Preferences.userNodeForPackage(this::class.java)
override val invalidateScreen = SingleStateEvent<Unit>()
override var theme by EnumEntry(PREF_THEME, AUTO, Theme::class.java)
override var theme by EnumEntry(PREF_THEME, AUTO, Theme::class.java, invalidateScreenOnChange = true)
override var language by EnumEntry(PREF_LANG, DEFAULT, Language::class.java, ::updateLocale)
override var language by EnumEntry(
PREF_LANG,
DEFAULT,
Language::class.java,
onChange = ::updateLocale,
invalidateScreenOnChange = true
)
override var uiScale by FloatEntry(
PREF_UI_SCALE,
null,
invalidateScreenOnChange = true
)
init {
updateLocale(language)
......@@ -57,22 +70,20 @@ class UnencryptedSettingsImpl @Inject internal constructor() : UnencryptedSettin
InternationalizationUtils.locale = language.locale
}
private class EnumEntry<T : Enum<*>>(
private open class Entry<T : Any>(
private val key: String,
private val default: T,
private val enumClass: Class<T>,
private val onChange: (value: T) -> Unit = {}
private val deserialize: (string: String) -> T?,
private val serialize: (value: T) -> String = { it.toString() },
private val onChange: (value: T) -> Unit = {},
private val invalidateScreenOnChange: Boolean = false,
) {
private lateinit var current: T
operator fun getValue(thisRef: UnencryptedSettingsImpl, property: KProperty<*>): T {
if (!::current.isInitialized) {
val value = thisRef.prefs.get(key, default.name)
current = enumClass.enumConstants.find { it.name == value }
?: run {
LOG.e { "Unexpected enum value for ${enumClass.simpleName}: $value" }
default
}
current = deserialize(thisRef.prefs.get(key, serialize(default)))
?: throw IllegalArgumentException()
}
return current
}
......@@ -82,10 +93,75 @@ class UnencryptedSettingsImpl @Inject internal constructor() : UnencryptedSettin
if (current == value) return
current = value
thisRef.prefs.put(key, value.name)
thisRef.prefs.put(key, serialize(value))
thisRef.prefs.flush() // write preferences to disk
onChange(value)
thisRef.invalidateScreen.emit(Unit)
if (invalidateScreenOnChange) thisRef.invalidateScreen.emit(Unit)
}
}
private open class NullableEntry<T : Any>(
private val key: String,
private val default: T?,
private val deserialize: (string: String?) -> T?,
private val serialize: (value: T?) -> String? = { it.toString() },
private val onChange: (value: T?) -> Unit = {},
private val invalidateScreenOnChange: Boolean = false,
) {
private var read = false
private var current: T? = null
operator fun getValue(thisRef: UnencryptedSettingsImpl, property: KProperty<*>): T? {
if (!read) {
read = true
current = deserialize(thisRef.prefs.get(key, serialize(default)))
}
return current
}
@IoExecutor
operator fun setValue(thisRef: UnencryptedSettingsImpl, property: KProperty<*>, value: T?) {
if (current == value) return
current = value
if (current == default || serialize(value) == null) {
thisRef.prefs.remove(key)
} else {
thisRef.prefs.put(key, serialize(value))
}
thisRef.prefs.flush() // write preferences to disk
onChange(value)
if (invalidateScreenOnChange) thisRef.invalidateScreen.emit(Unit)
}
}
private class EnumEntry<T : Enum<*>>(
key: String,
default: T,
private val enumClass: Class<T>,
serialize: (value: T) -> String = { it.toString() },
deserialize: (string: String) -> T? = { string ->
enumClass.enumConstants.find {
serialize(it) == string
} ?: run {
LOG.e { "Unexpected enum value for ${enumClass.simpleName}: $string" }
default
}
},
onChange: (value: T) -> Unit = {},
invalidateScreenOnChange: Boolean = false,
) : Entry<T>(
key, default, deserialize, serialize, onChange, invalidateScreenOnChange
)
private class FloatEntry(
key: String,
default: Float?,
deserialize: (string: String?) -> Float? = { it?.toFloatOrNull() },
serialize: (value: Float?) -> String? = { it?.toString() },
onChange: (value: Float?) -> Unit = {},
invalidateScreenOnChange: Boolean = false,
) : NullableEntry<Float>(
key, default, deserialize, serialize, onChange, invalidateScreenOnChange
)
}
......@@ -33,9 +33,11 @@ import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.sp
val Colors.divider: Color get() = if (isLight) Gray300 else Gray800
......@@ -53,12 +55,12 @@ val Colors.textSecondary: Color get() = if (isLight) TextSecondaryMaterialLight
val Colors.privateMessageDate: Color get() = Gray200
val Colors.buttonTextNegative: Color get() = Red500
val Colors.buttonTextPositive: Color get() = Blue400
val Colors.warningBackground get() = Red500
val Colors.warningForeground get() = Color.White
val Colors.sendButton get() = if (isLight) Lime700 else Lime500
val Colors.passwordStrengthWeak get() = Red500
val Colors.passwordStrengthMiddle get() = if (isLight) Orange700 else Orange500
val Colors.passwordStrengthStrong get() = if (isLight) Lime700 else Lime500
val Colors.warningBackground: Color get() = Red500
val Colors.warningForeground: Color get() = Color.White
val Colors.sendButton: Color get() = if (isLight) Lime700 else Lime500
val Colors.passwordStrengthWeak: Color get() = Red500
val Colors.passwordStrengthMiddle: Color get() = if (isLight) Orange700 else Orange500
val Colors.passwordStrengthStrong: Color get() = if (isLight) Lime700 else Lime500
val DarkColors = darkColors(
primary = Blue500,
......@@ -119,6 +121,7 @@ val briarTypography = Typography(
@Composable
fun BriarTheme(
isDarkTheme: Boolean = isSystemInDarkTheme(),
density: Float? = null,
colors: Colors? = null,
content: @Composable () -> Unit,
) = MaterialTheme(
......@@ -138,6 +141,7 @@ fun BriarTheme(
CompositionLocalProvider(
LocalTextSelectionColors provides customTextSelectionColors,
LocalContextMenuRepresentation provides contextMenuRepresentation,
LocalDensity provides if (density != null) Density(density) else LocalDensity.current,
) {
Surface {
content()
......
......@@ -37,6 +37,7 @@ import androidx.compose.ui.graphics.toAwtImage
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.window.FrameWindowScope
import androidx.compose.ui.window.Window
import org.briarproject.bramble.api.event.EventBus
......@@ -62,8 +63,9 @@ import org.briarproject.briar.desktop.ui.Screen.EXPIRED
import org.briarproject.briar.desktop.ui.Screen.MAIN
import org.briarproject.briar.desktop.ui.Screen.STARTUP
import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
import org.briarproject.briar.desktop.utils.UiUtils.DensityDimension
import org.briarproject.briar.desktop.utils.UiUtils.GlobalDensity
import org.briarproject.briar.desktop.viewmodel.ViewModelProvider
import java.awt.Dimension
import java.awt.event.WindowEvent
import java.awt.event.WindowFocusListener
import javax.annotation.concurrent.Immutable
......@@ -205,7 +207,13 @@ constructor(
}
}
window.minimumSize = Dimension(800, 600)
CompositionLocalProvider(
LocalDensity provides Density(configuration.uiScale ?: GlobalDensity),
) {
window.minimumSize = DensityDimension(800, 600)
window.preferredSize = DensityDimension(800, 600)
}
CompositionLocalProvider(
LocalWindowScope provides this,
LocalWindowFocusState provides focusState,
......@@ -221,7 +229,7 @@ constructor(
val isDarkTheme = configuration.theme == DARK ||
(configuration.theme == AUTO && isSystemInDarkTheme())
BriarTheme(isDarkTheme) {
BriarTheme(isDarkTheme, configuration.uiScale) {
Column(Modifier.fillMaxSize()) {
ExpirationBanner { screenState = EXPIRED; stop() }
when (screenState) {
......
......@@ -47,6 +47,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
import org.briarproject.bramble.api.UniqueId
......@@ -55,7 +57,9 @@ import org.briarproject.briar.desktop.theme.BriarTheme
import org.briarproject.briar.desktop.ui.LocalWindowFocusState
import org.briarproject.briar.desktop.ui.LocalWindowScope
import org.briarproject.briar.desktop.ui.WindowFocusState
import org.briarproject.briar.desktop.utils.UiUtils.DensityDimension
import org.briarproject.briar.desktop.viewmodel.SingleStateEvent
import java.util.prefs.Preferences
import kotlin.random.Random
object PreviewUtils {
......@@ -208,6 +212,9 @@ object PreviewUtils {
) {
val scope = PreviewScope()
val prefs = Preferences.userNodeForPackage(PreviewUtils::class.java)
val settingsDensity: Float? = prefs.get("previewsUiScale", null)?.toFloat()
singleWindowApplication(title = "Interactive Preview") {
val focusState = remember { WindowFocusState() }
CompositionLocalProvider(
......@@ -215,22 +222,27 @@ object PreviewUtils {
LocalWindowFocusState provides focusState
) {
Column {
Column(Modifier.padding(10.dp)) {
scope.addBooleanParameter("darkTheme", true)
scope.addDropDownParameter(
"language",
DropDownValues(0, UnencryptedSettings.Language.values().toList().map { it.name })
)
parameters.forEach { (name, initial) ->
when (initial) {
is String -> scope.addStringParameter(name, initial)
is Boolean -> scope.addBooleanParameter(name, initial)
is Int -> scope.addIntParameter(name, initial)
is Long -> scope.addLongParameter(name, initial)
is Float -> scope.addFloatParameter(name, initial)
is FloatSlider -> scope.addFloatSliderParameter(name, initial)
is DropDownValues -> scope.addDropDownParameter(name, initial)
else -> throw IllegalArgumentException("Type ${initial::class.simpleName} is not supported for previewing.")
val density = settingsDensity ?: LocalDensity.current.density
CompositionLocalProvider(LocalDensity provides Density(density)) {
window.preferredSize = DensityDimension(800, 600)
Column(Modifier.padding(10.dp)) {
scope.addBooleanParameter("darkTheme", true)
scope.addDropDownParameter(
"language",
DropDownValues(0, UnencryptedSettings.Language.values().toList().map { it.name })
)
scope.addFloatParameter("density", density)
parameters.forEach { (name, initial) ->
when (initial) {
is String -> scope.addStringParameter(name, initial)
is Boolean -> scope.addBooleanParameter(name, initial)
is Int -> scope.addIntParameter(name, initial)
is Long -> scope.addLongParameter(name, initial)
is Float -> scope.addFloatParameter(name, initial)
is FloatSlider -> scope.addFloatSliderParameter(name, initial)
is DropDownValues -> scope.addDropDownParameter(name, initial)
else -> throw IllegalArgumentException("Type ${initial::class.simpleName} is not supported for previewing.")
}
}
}
}
......@@ -245,7 +257,10 @@ object PreviewUtils {
invalidate.react { return@Column }
BriarTheme(isDarkTheme = scope.getBooleanParameter("darkTheme")) {
BriarTheme(
isDarkTheme = scope.getBooleanParameter("darkTheme"),
density = scope.getFloatParameter("density"),
) {
Box(Modifier.fillMaxSize(1f)) {
Column(Modifier.padding(10.dp)) {
content(scope)
......
......@@ -18,7 +18,43 @@
package org.briarproject.briar.desktop.utils
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import java.awt.Dimension
import java.awt.GraphicsConfiguration
import java.awt.GraphicsEnvironment
object UiUtils {
fun getContactDisplayName(name: String, alias: String?) =
if (alias == null) name else "$alias ($name)"
// See androidx.compose.ui.window.LayoutConfiguration
internal val GlobalDensity
get() = GraphicsEnvironment.getLocalGraphicsEnvironment()
.defaultScreenDevice
.defaultConfiguration
.density
// See androidx.compose.ui.window.LayoutConfiguration
private val GraphicsConfiguration.density: Float
get() = defaultTransform.scaleX.toFloat()
/**
* Compute an AWT Dimension for the given width and height in dp units, taking
* into account the LocalDensity as well as the global density as detected by the
* local graphics environment.
*
* On macOS hidpi devices, the global density is usually something like 2 while on Linux
* it is usually 1 independent of the actual density. The global density is taken into
* account by AWT itself, so we need to remove that factor from the equation, otherwise
* it will be accounted for twice resulting in windows that are bigger than expected.
*/
@Composable
fun DensityDimension(width: Int, height: Int): Dimension {
with(Density(LocalDensity.current.density / GlobalDensity)) {
return Dimension(width.dp.roundToPx(), height.dp.roundToPx())
}
}
}
......@@ -323,6 +323,7 @@ settings.display.theme.dark=Dark
settings.display.theme.light=Light
settings.display.language.title=Language
settings.display.language.auto=System default
settings.display.ui_scale.title=UI Scale
# Settings Connections
settings.connections.title=Connections
......
/*
* Briar Desktop
* Copyright (C) 2021-2022 The Briar Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.briarproject.briar.desktop
import org.briarproject.briar.desktop.utils.PreviewUtils
import java.util.prefs.Preferences
/**
* This executable stores a custom density used for UI previews created using [PreviewUtils] into the user settings.
* On hidpi Linux devices it makes sense to set this to some value once that makes the previews appear big enough from
* then on.
* We're using a different dedicated preference node in order to keep this independent of the settings used in Briar
* itself.
*/
fun main() {
val prefs = Preferences.userNodeForPackage(PreviewUtils::class.java)
prefs.put("previewsUiScale", "2.0")
prefs.flush()
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment