mirror of
https://github.com/amir1376/ab-download-manager.git
synced 2025-02-20 11:43:24 +08:00
add ui scale feature
This commit is contained in:
parent
9cdb1f3844
commit
6b704cc9ea
@ -852,7 +852,7 @@ class AppComponent(
|
|||||||
val showOpenSourceLibraries = MutableStateFlow(false)
|
val showOpenSourceLibraries = MutableStateFlow(false)
|
||||||
val showTranslators = MutableStateFlow(false)
|
val showTranslators = MutableStateFlow(false)
|
||||||
val theme = appRepository.theme
|
val theme = appRepository.theme
|
||||||
// val uiScale = appRepository.uiScale
|
val uiScale = appRepository.uiScale
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DownloadDialogManager {
|
interface DownloadDialogManager {
|
||||||
|
@ -199,17 +199,17 @@ fun proxyConfig(proxyManager: ProxyManager, scope: CoroutineScope): ProxyConfigu
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
fun uiScaleConfig(appSettings: AppSettingsStorage): EnumConfigurable<Float?> {
|
||||||
fun uiScaleConfig(appSettings: AppSettings): EnumConfigurable<Float?> {
|
|
||||||
return EnumConfigurable(
|
return EnumConfigurable(
|
||||||
title = "Ui Scale",
|
title = Res.string.settings_ui_scale.asStringSource(),
|
||||||
description = "Scale Ui Elements",
|
description = Res.string.settings_ui_scale_description.asStringSource(),
|
||||||
backedBy = appSettings.uiScale,
|
backedBy = appSettings.uiScale,
|
||||||
possibleValues = listOf(
|
possibleValues = listOf(
|
||||||
null,
|
null,
|
||||||
0.5f,
|
0.8f,
|
||||||
0.75f,
|
0.9f,
|
||||||
1f,
|
1f,
|
||||||
|
1.1f,
|
||||||
1.25f,
|
1.25f,
|
||||||
1.5f,
|
1.5f,
|
||||||
1.75f,
|
1.75f,
|
||||||
@ -218,14 +218,13 @@ fun uiScaleConfig(appSettings: AppSettings): EnumConfigurable<Float?> {
|
|||||||
renderMode = EnumConfigurable.RenderMode.Spinner,
|
renderMode = EnumConfigurable.RenderMode.Spinner,
|
||||||
describe = {
|
describe = {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
"System"
|
Res.string.system.asStringSource()
|
||||||
} else {
|
} else {
|
||||||
"$it x"
|
"$it x".asStringSource()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
fun themeConfig(
|
fun themeConfig(
|
||||||
themeManager: ThemeManager,
|
themeManager: ThemeManager,
|
||||||
@ -377,7 +376,7 @@ class SettingsComponent(
|
|||||||
Appearance -> listOf(
|
Appearance -> listOf(
|
||||||
themeConfig(themeManager, scope),
|
themeConfig(themeManager, scope),
|
||||||
languageConfig(languageManager, scope),
|
languageConfig(languageManager, scope),
|
||||||
// uiScaleConfig(appSettings),
|
uiScaleConfig(appSettings),
|
||||||
autoStartConfig(appSettings),
|
autoStartConfig(appSettings),
|
||||||
mergeTopBarWithTitleBarConfig(appSettings),
|
mergeTopBarWithTitleBarConfig(appSettings),
|
||||||
playSoundNotification(appSettings),
|
playSoundNotification(appSettings),
|
||||||
|
@ -23,7 +23,7 @@ class AppRepository : KoinComponent {
|
|||||||
private val proxyManager: ProxyManager by inject()
|
private val proxyManager: ProxyManager by inject()
|
||||||
val theme = appSettings.theme
|
val theme = appSettings.theme
|
||||||
|
|
||||||
// val uiScale = appSettings.uiScale
|
val uiScale = appSettings.uiScale
|
||||||
private val downloadSystem : DownloadSystem by inject()
|
private val downloadSystem : DownloadSystem by inject()
|
||||||
private val downloadSettings: DownloadSettings by inject()
|
private val downloadSettings: DownloadSettings by inject()
|
||||||
private val downloadManager: DownloadManager = downloadSystem.downloadManager
|
private val downloadManager: DownloadManager = downloadSystem.downloadManager
|
||||||
|
@ -4,6 +4,7 @@ import com.abdownloadmanager.desktop.utils.*
|
|||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import arrow.optics.Lens
|
import arrow.optics.Lens
|
||||||
import arrow.optics.optics
|
import arrow.optics.optics
|
||||||
|
import com.abdownloadmanager.desktop.App
|
||||||
import ir.amirab.util.compose.localizationmanager.LanguageStorage
|
import ir.amirab.util.compose.localizationmanager.LanguageStorage
|
||||||
import ir.amirab.util.config.*
|
import ir.amirab.util.config.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -15,6 +16,7 @@ import java.io.File
|
|||||||
data class AppSettingsModel(
|
data class AppSettingsModel(
|
||||||
val theme: String = "dark",
|
val theme: String = "dark",
|
||||||
val language: String = "en",
|
val language: String = "en",
|
||||||
|
val uiScale: Float? = null,
|
||||||
val mergeTopBarWithTitleBar: Boolean = false,
|
val mergeTopBarWithTitleBar: Boolean = false,
|
||||||
val threadCount: Int = 8,
|
val threadCount: Int = 8,
|
||||||
val dynamicPartCreation: Boolean = true,
|
val dynamicPartCreation: Boolean = true,
|
||||||
@ -40,6 +42,7 @@ data class AppSettingsModel(
|
|||||||
object Keys {
|
object Keys {
|
||||||
val theme = stringKeyOf("theme")
|
val theme = stringKeyOf("theme")
|
||||||
val language = stringKeyOf("language")
|
val language = stringKeyOf("language")
|
||||||
|
val uiScale = floatKeyOf("uiScale")
|
||||||
val mergeTopBarWithTitleBar = booleanKeyOf("mergeTopBarWithTitleBar")
|
val mergeTopBarWithTitleBar = booleanKeyOf("mergeTopBarWithTitleBar")
|
||||||
val threadCount = intKeyOf("threadCount")
|
val threadCount = intKeyOf("threadCount")
|
||||||
val dynamicPartCreation = booleanKeyOf("dynamicPartCreation")
|
val dynamicPartCreation = booleanKeyOf("dynamicPartCreation")
|
||||||
@ -57,12 +60,12 @@ data class AppSettingsModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun get(source: MapConfig): AppSettingsModel {
|
override fun get(source: MapConfig): AppSettingsModel {
|
||||||
val default by lazy { AppSettingsModel.default }
|
val default by lazy { AppSettingsModel.default }
|
||||||
return AppSettingsModel(
|
return AppSettingsModel(
|
||||||
theme = source.get(Keys.theme) ?: default.theme,
|
theme = source.get(Keys.theme) ?: default.theme,
|
||||||
language = source.get(Keys.language) ?: default.language,
|
language = source.get(Keys.language) ?: default.language,
|
||||||
|
uiScale = source.get(Keys.uiScale) ?: default.uiScale,
|
||||||
mergeTopBarWithTitleBar = source.get(Keys.mergeTopBarWithTitleBar) ?: default.mergeTopBarWithTitleBar,
|
mergeTopBarWithTitleBar = source.get(Keys.mergeTopBarWithTitleBar) ?: default.mergeTopBarWithTitleBar,
|
||||||
threadCount = source.get(Keys.threadCount) ?: default.threadCount,
|
threadCount = source.get(Keys.threadCount) ?: default.threadCount,
|
||||||
dynamicPartCreation = source.get(Keys.dynamicPartCreation) ?: default.dynamicPartCreation,
|
dynamicPartCreation = source.get(Keys.dynamicPartCreation) ?: default.dynamicPartCreation,
|
||||||
@ -88,6 +91,7 @@ data class AppSettingsModel(
|
|||||||
return source.apply {
|
return source.apply {
|
||||||
put(Keys.theme, focus.theme)
|
put(Keys.theme, focus.theme)
|
||||||
put(Keys.language, focus.language)
|
put(Keys.language, focus.language)
|
||||||
|
putNullable(Keys.uiScale, focus.uiScale)
|
||||||
put(Keys.mergeTopBarWithTitleBar, focus.mergeTopBarWithTitleBar)
|
put(Keys.mergeTopBarWithTitleBar, focus.mergeTopBarWithTitleBar)
|
||||||
put(Keys.threadCount, focus.threadCount)
|
put(Keys.threadCount, focus.threadCount)
|
||||||
put(Keys.dynamicPartCreation, focus.dynamicPartCreation)
|
put(Keys.dynamicPartCreation, focus.dynamicPartCreation)
|
||||||
@ -107,6 +111,16 @@ data class AppSettingsModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val uiScaleLens: Lens<AppSettingsModel, Float?>
|
||||||
|
get() = Lens(
|
||||||
|
get = {
|
||||||
|
it.uiScale
|
||||||
|
},
|
||||||
|
set = { s, f ->
|
||||||
|
s.copy(uiScale = f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
class AppSettingsStorage(
|
class AppSettingsStorage(
|
||||||
settings: DataStore<MapConfig>,
|
settings: DataStore<MapConfig>,
|
||||||
) :
|
) :
|
||||||
@ -114,6 +128,7 @@ class AppSettingsStorage(
|
|||||||
LanguageStorage {
|
LanguageStorage {
|
||||||
var theme = from(AppSettingsModel.theme)
|
var theme = from(AppSettingsModel.theme)
|
||||||
override val selectedLanguage = from(AppSettingsModel.language)
|
override val selectedLanguage = from(AppSettingsModel.language)
|
||||||
|
var uiScale = from(uiScaleLens)
|
||||||
var mergeTopBarWithTitleBar = from(AppSettingsModel.mergeTopBarWithTitleBar)
|
var mergeTopBarWithTitleBar = from(AppSettingsModel.mergeTopBarWithTitleBar)
|
||||||
val threadCount = from(AppSettingsModel.threadCount)
|
val threadCount = from(AppSettingsModel.threadCount)
|
||||||
val dynamicPartCreation = from(AppSettingsModel.dynamicPartCreation)
|
val dynamicPartCreation = from(AppSettingsModel.dynamicPartCreation)
|
||||||
|
@ -60,7 +60,7 @@ object Ui : KoinComponent {
|
|||||||
ProvideNotificationManager {
|
ProvideNotificationManager {
|
||||||
ABDownloaderTheme(
|
ABDownloaderTheme(
|
||||||
myColors = theme,
|
myColors = theme,
|
||||||
// uiScale = appComponent.uiScale.collectAsState().value
|
uiScale = appComponent.uiScale.collectAsState().value
|
||||||
) {
|
) {
|
||||||
ProvideGlobalExceptionHandler(globalAppExceptionHandler) {
|
ProvideGlobalExceptionHandler(globalAppExceptionHandler) {
|
||||||
val trayState = rememberTrayState()
|
val trayState = rememberTrayState()
|
||||||
|
@ -5,7 +5,7 @@ import com.abdownloadmanager.utils.compose.WithContentAlpha
|
|||||||
import com.abdownloadmanager.utils.compose.WithContentColor
|
import com.abdownloadmanager.utils.compose.WithContentColor
|
||||||
import ir.amirab.util.compose.IconSource
|
import ir.amirab.util.compose.IconSource
|
||||||
import com.abdownloadmanager.desktop.ui.icon.MyIcons
|
import com.abdownloadmanager.desktop.ui.icon.MyIcons
|
||||||
//import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
|
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
|
||||||
import com.abdownloadmanager.desktop.ui.theme.myColors
|
import com.abdownloadmanager.desktop.ui.theme.myColors
|
||||||
import com.abdownloadmanager.desktop.ui.theme.myTextSizes
|
import com.abdownloadmanager.desktop.ui.theme.myTextSizes
|
||||||
import com.abdownloadmanager.desktop.utils.*
|
import com.abdownloadmanager.desktop.utils.*
|
||||||
@ -25,9 +25,11 @@ import androidx.compose.ui.graphics.RectangleShape
|
|||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.takeOrElse
|
import androidx.compose.ui.graphics.takeOrElse
|
||||||
import androidx.compose.ui.input.key.KeyEvent
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.platform.LocalWindowInfo
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.FrameWindowScope
|
import androidx.compose.ui.window.FrameWindowScope
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
@ -323,52 +325,63 @@ fun CustomWindow(
|
|||||||
else Color.Black
|
else Color.Black
|
||||||
}.toWindowColorType()
|
}.toWindowColorType()
|
||||||
}
|
}
|
||||||
CompositionLocalProvider(
|
UiScaledContent {
|
||||||
LocalWindowController provides windowController,
|
CompositionLocalProvider(
|
||||||
LocalWindowState provides state,
|
LocalWindowController provides windowController,
|
||||||
LocalWindow provides window,
|
LocalWindowState provides state,
|
||||||
) {
|
LocalWindow provides window,
|
||||||
if (preventMinimize) {
|
|
||||||
PreventMinimize()
|
|
||||||
}
|
|
||||||
// a window frame which totally rendered with compose
|
|
||||||
CustomWindowFrame(
|
|
||||||
onRequestMinimize = onRequestMinimize,
|
|
||||||
onRequestClose = onCloseRequest,
|
|
||||||
onRequestToggleMaximize = onRequestToggleMaximize,
|
|
||||||
title = title,
|
|
||||||
titlePosition = titlePosition,
|
|
||||||
windowIcon = icon,
|
|
||||||
background = background,
|
|
||||||
onBackground = myColors.onBackground,
|
|
||||||
start = start,
|
|
||||||
end = end,
|
|
||||||
) {
|
) {
|
||||||
// val defaultDensity = LocalDensity.current
|
if (preventMinimize) {
|
||||||
// val uiScale = LocalUiScale.current
|
PreventMinimize()
|
||||||
// val density = remember(uiScale) {
|
}
|
||||||
// if (uiScale == null) {
|
// a window frame which totally rendered with compose
|
||||||
// defaultDensity
|
CustomWindowFrame(
|
||||||
// } else {
|
onRequestMinimize = onRequestMinimize,
|
||||||
// Density(uiScale)
|
onRequestClose = onCloseRequest,
|
||||||
// }
|
onRequestToggleMaximize = onRequestToggleMaximize,
|
||||||
// }
|
title = title,
|
||||||
// CompositionLocalProvider(
|
titlePosition = titlePosition,
|
||||||
// LocalDensity provides density
|
windowIcon = icon,
|
||||||
// ) {
|
background = background,
|
||||||
ResponsiveBox {
|
onBackground = myColors.onBackground,
|
||||||
Box(Modifier.clearFocusOnTap()) {
|
start = start,
|
||||||
PopUpContainer {
|
end = end,
|
||||||
content()
|
) {
|
||||||
|
ResponsiveBox {
|
||||||
|
Box(Modifier.clearFocusOnTap()) {
|
||||||
|
PopUpContainer {
|
||||||
|
content()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* put this in every window because [Window] composable override [LocalDensity]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun UiScaledContent(
|
||||||
|
defaultDensity: Density = LocalDensity.current,
|
||||||
|
uiScale: Float? = LocalUiScale.current,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val density = remember(uiScale) {
|
||||||
|
if (uiScale == null) {
|
||||||
|
defaultDensity
|
||||||
|
} else {
|
||||||
|
Density(uiScale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalDensity provides density,
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreventMinimize() {
|
private fun PreventMinimize() {
|
||||||
val state = LocalWindowState.current
|
val state = LocalWindowState.current
|
||||||
|
@ -10,7 +10,7 @@ import java.awt.event.WindowFocusListener
|
|||||||
fun BaseOptionDialog(
|
fun BaseOptionDialog(
|
||||||
onCloseRequest: () -> Unit,
|
onCloseRequest: () -> Unit,
|
||||||
state: DialogState = rememberDialogState(),
|
state: DialogState = rememberDialogState(),
|
||||||
resizeable:Boolean=true,
|
resizeable: Boolean = true,
|
||||||
content: @Composable WindowScope.() -> Unit,
|
content: @Composable WindowScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
DialogWindow(
|
DialogWindow(
|
||||||
@ -21,7 +21,7 @@ fun BaseOptionDialog(
|
|||||||
resizable = resizeable,
|
resizable = resizeable,
|
||||||
onCloseRequest = onCloseRequest,
|
onCloseRequest = onCloseRequest,
|
||||||
) {
|
) {
|
||||||
val focusListener=remember {
|
val focusListener = remember {
|
||||||
object : WindowFocusListener {
|
object : WindowFocusListener {
|
||||||
override fun windowGainedFocus(e: WindowEvent?) {
|
override fun windowGainedFocus(e: WindowEvent?) {
|
||||||
//do nothing
|
//do nothing
|
||||||
@ -32,16 +32,18 @@ fun BaseOptionDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisposableEffect(window){
|
DisposableEffect(window) {
|
||||||
window.addWindowFocusListener(focusListener);
|
window.addWindowFocusListener(focusListener);
|
||||||
window.isAlwaysOnTop=true
|
window.isAlwaysOnTop = true
|
||||||
//we need this to allow click outside
|
//we need this to allow click outside
|
||||||
window.modalityType=java.awt.Dialog.ModalityType.MODELESS
|
window.modalityType = java.awt.Dialog.ModalityType.MODELESS
|
||||||
onDispose{
|
onDispose {
|
||||||
window.removeWindowFocusListener(focusListener)
|
window.removeWindowFocusListener(focusListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// window.subtractInset()
|
// window.subtractInset()
|
||||||
content()
|
UiScaledContent {
|
||||||
|
content()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,12 +12,15 @@ import androidx.compose.foundation.*
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
import androidx.compose.ui.unit.TextUnit
|
import androidx.compose.ui.unit.TextUnit
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import androidx.compose.ui.window.rememberPopupPositionProviderAtPosition
|
import androidx.compose.ui.window.rememberPopupPositionProviderAtPosition
|
||||||
|
import com.abdownloadmanager.desktop.ui.customwindow.UiScaledContent
|
||||||
import ir.amirab.util.compose.asStringSource
|
import ir.amirab.util.compose.asStringSource
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -101,12 +104,14 @@ private val textSizes = TextSizes(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ABDownloaderTheme(
|
fun ABDownloaderTheme(
|
||||||
myColors: MyColors,
|
myColors: MyColors,
|
||||||
// uiScale: Float? = null,
|
uiScale: Float? = null,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val systemDensity = LocalDensity.current
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalMyColors provides AnimatedColors(myColors, tween(500)),
|
LocalMyColors provides AnimatedColors(myColors, tween(500)),
|
||||||
// LocalUiScale provides uiScale,
|
LocalUiScale provides uiScale,
|
||||||
|
LocalSystemDensity provides systemDensity,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalContextMenuRepresentation provides myContextMenuRepresentation(),
|
LocalContextMenuRepresentation provides myContextMenuRepresentation(),
|
||||||
@ -120,7 +125,11 @@ fun ABDownloaderTheme(
|
|||||||
fontSize = textSizes.base,
|
fontSize = textSizes.base,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
content()
|
// it is overridden by [Window] Composable,
|
||||||
|
// but I put this here. maybe I need this outside of window scope!
|
||||||
|
UiScaledContent {
|
||||||
|
content()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,7 +181,7 @@ private fun myDefaultScrollBarStyle(): ScrollbarStyle {
|
|||||||
thickness = 12.dp,
|
thickness = 12.dp,
|
||||||
shape = RoundedCornerShape(4.dp),
|
shape = RoundedCornerShape(4.dp),
|
||||||
hoverDurationMillis = 300,
|
hoverDurationMillis = 300,
|
||||||
unhoverColor = myColors.onBackground/10,
|
unhoverColor = myColors.onBackground / 10,
|
||||||
hoverColor = myColors.onBackground/30
|
hoverColor = myColors.onBackground / 30
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
import androidx.compose.ui.unit.TextUnit
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
|
||||||
//val LocalUiScale = staticCompositionLocalOf { null as Float? }
|
val LocalSystemDensity = staticCompositionLocalOf<Density?> { null }
|
||||||
|
val LocalUiScale = staticCompositionLocalOf<Float?> { null }
|
||||||
|
|
||||||
val LocalTextSizes = compositionLocalOf<TextSizes> {
|
val LocalTextSizes = compositionLocalOf<TextSizes> {
|
||||||
error("LocalTextSizes not provided")
|
error("LocalTextSizes not provided")
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
plugins{
|
plugins{
|
||||||
id(MyPlugins.kotlin)
|
id(MyPlugins.kotlin)
|
||||||
id(MyPlugins.composeDesktop)
|
id(MyPlugins.composeDesktop)
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":desktop:shared"))
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
package ir.amirab.util.customwindow
|
package ir.amirab.util.customwindow
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -6,16 +7,17 @@ import androidx.compose.ui.composed
|
|||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.layout.positionInWindow
|
import androidx.compose.ui.layout.positionInWindow
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalWindowInfo
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.compose.ui.window.FrameWindowScope
|
import androidx.compose.ui.window.FrameWindowScope
|
||||||
import ir.amirab.util.customwindow.util.CustomWindowDecorationAccessing
|
import ir.amirab.util.customwindow.util.CustomWindowDecorationAccessing
|
||||||
|
import ir.amirab.util.desktop.GlobalDensity
|
||||||
import java.awt.Rectangle
|
import java.awt.Rectangle
|
||||||
import java.awt.Shape
|
import java.awt.Shape
|
||||||
import java.awt.Window
|
import java.awt.Window
|
||||||
import java.awt.event.ComponentAdapter
|
import java.awt.event.ComponentAdapter
|
||||||
import java.awt.event.ComponentEvent
|
import java.awt.event.ComponentEvent
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
object HitSpots {
|
object HitSpots {
|
||||||
const val NO_HIT_SPOT = 0
|
const val NO_HIT_SPOT = 0
|
||||||
@ -56,7 +58,9 @@ context (FrameWindowScope)
|
|||||||
private fun Modifier.onPositionInRect(
|
private fun Modifier.onPositionInRect(
|
||||||
onChange: (Rectangle) -> Unit,
|
onChange: (Rectangle) -> Unit,
|
||||||
) = composed {
|
) = composed {
|
||||||
val density = LocalDensity.current
|
// we use Global for sake of awt here.
|
||||||
|
// because we want to calculate height and pass it to awt
|
||||||
|
val density = GlobalDensity
|
||||||
onGloballyPositioned {
|
onGloballyPositioned {
|
||||||
onChange(
|
onChange(
|
||||||
it.positionInWindow().toDpRectangle(
|
it.positionInWindow().toDpRectangle(
|
||||||
@ -114,8 +118,10 @@ context (FrameWindowScope)
|
|||||||
fun ProvideWindowSpotContainer(
|
fun ProvideWindowSpotContainer(
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
// we use Global for sake of awt here.
|
||||||
val windowSize =getCurrentWindowSize()
|
// because we want to calculate height and pass it to awt
|
||||||
|
val density = GlobalDensity
|
||||||
|
val windowSize = getCurrentWindowSize()
|
||||||
val containerSize = with(density) {
|
val containerSize = with(density) {
|
||||||
LocalWindowInfo.current.containerSize.let {
|
LocalWindowInfo.current.containerSize.let {
|
||||||
DpSize(it.width.toDp(), it.height.toDp())
|
DpSize(it.width.toDp(), it.height.toDp())
|
||||||
@ -142,10 +148,15 @@ fun ProvideWindowSpotContainer(
|
|||||||
//
|
//
|
||||||
if (CustomWindowDecorationAccessing.isSupported) {
|
if (CustomWindowDecorationAccessing.isSupported) {
|
||||||
val startOffset = (windowSize - containerSize) / 2
|
val startOffset = (windowSize - containerSize) / 2
|
||||||
val startWidthOffsetInDp = startOffset.width.value.toInt()
|
val startWidthOffsetInDp = startOffset.width.value.roundToInt()
|
||||||
// val startHeightInDp=delta.height.value.toInt() //it seems no need here
|
// val startHeightOffsetInDp = startOffset.width.value.roundToInt() //it seems no need here
|
||||||
val spots: Map<Shape, Int> = spotsWithInfo.values.associate { (rect, spot) ->
|
val spots: Map<Shape, Int> = spotsWithInfo.values.associate { (rect, spot) ->
|
||||||
Rectangle(rect.x + startWidthOffsetInDp, rect.y, rect.width, rect.height) to spot
|
Rectangle(
|
||||||
|
rect.x + startWidthOffsetInDp,
|
||||||
|
rect.y /*+ startHeightOffsetInDp*/,
|
||||||
|
rect.width,
|
||||||
|
rect.height
|
||||||
|
) to spot
|
||||||
}
|
}
|
||||||
placeHitSpots(window, spots, toolbarHeight)
|
placeHitSpots(window, spots, toolbarHeight)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
package ir.amirab.util.desktop.screen
|
||||||
|
|
||||||
|
import androidx.compose.ui.unit.*
|
||||||
|
//import androidx.compose.ui.window.WindowPlacement
|
||||||
|
//import androidx.compose.ui.window.WindowPosition
|
||||||
|
//import androidx.compose.ui.window.WindowState
|
||||||
|
import ir.amirab.util.desktop.GlobalDensity
|
||||||
|
import java.awt.GraphicsEnvironment
|
||||||
|
|
||||||
|
fun getGlobalScale(): Float {
|
||||||
|
val graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||||
|
val defaultScreenDevice = graphicsEnvironment.defaultScreenDevice
|
||||||
|
val defaultTransform = defaultScreenDevice.defaultConfiguration.defaultTransform
|
||||||
|
return defaultTransform.scaleX.toFloat() // Assuming uniform scaling
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.applyUiScale(
|
||||||
|
userUiScale: Float?,
|
||||||
|
systemUiScale: Float = GlobalDensity.density,
|
||||||
|
): Int {
|
||||||
|
if (userUiScale == null) return this
|
||||||
|
return (this * userUiScale / systemUiScale).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.unApplyUiScale(
|
||||||
|
userUiScale: Float?,
|
||||||
|
systemUiScale: Float = GlobalDensity.density,
|
||||||
|
): Int {
|
||||||
|
if (userUiScale == null) return this
|
||||||
|
return (this * systemUiScale / userUiScale).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DpSize.applyUiScale(
|
||||||
|
userUiScale: Float?,
|
||||||
|
systemUiScale: Float = GlobalDensity.density,
|
||||||
|
): DpSize {
|
||||||
|
if (userUiScale == null) return this
|
||||||
|
if (this == DpSize.Unspecified) return this
|
||||||
|
return DpSize(
|
||||||
|
width = width.let {
|
||||||
|
if (isSpecified) it.value.toInt().applyUiScale(userUiScale, systemUiScale).dp
|
||||||
|
else it
|
||||||
|
},
|
||||||
|
height = height.let {
|
||||||
|
if (isSpecified) it.value.toInt().applyUiScale(userUiScale, systemUiScale).dp
|
||||||
|
else it
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DpSize.unApplyUiScale(
|
||||||
|
userUiScale: Float?,
|
||||||
|
systemUiScale: Float = GlobalDensity.density,
|
||||||
|
): DpSize {
|
||||||
|
if (userUiScale == null) return this
|
||||||
|
if (this == DpSize.Unspecified) return this
|
||||||
|
return DpSize(
|
||||||
|
width = width.let {
|
||||||
|
if (isSpecified) it.value.toInt().unApplyUiScale(userUiScale, systemUiScale).dp
|
||||||
|
else it
|
||||||
|
},
|
||||||
|
height = height.let {
|
||||||
|
if (isSpecified) it.value.toInt().applyUiScale(userUiScale, systemUiScale).dp
|
||||||
|
else it
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
class WindowStateUiScaleAware(
|
||||||
|
private val delegate: WindowState,
|
||||||
|
private val uiScale: Float?,
|
||||||
|
) : WindowState {
|
||||||
|
override var isMinimized: Boolean
|
||||||
|
get() = delegate.isMinimized
|
||||||
|
set(value) {
|
||||||
|
delegate.isMinimized = value
|
||||||
|
}
|
||||||
|
override var placement: WindowPlacement
|
||||||
|
get() = delegate.placement
|
||||||
|
set(value) {
|
||||||
|
delegate.placement = value
|
||||||
|
}
|
||||||
|
override var position: WindowPosition
|
||||||
|
get() = delegate.position
|
||||||
|
set(value) {
|
||||||
|
delegate.position = value
|
||||||
|
}
|
||||||
|
override var size: DpSize
|
||||||
|
get() = run {
|
||||||
|
val s = delegate.size
|
||||||
|
s.applyUiScale(uiScale)
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
delegate.size = value.unApplyUiScale(uiScale)
|
||||||
|
}
|
||||||
|
}*/
|
@ -5,27 +5,27 @@ import kotlinx.serialization.serializer
|
|||||||
|
|
||||||
|
|
||||||
context(Json)
|
context(Json)
|
||||||
inline fun <reified T:Any> MapConfig.putEncoded(key:String, value:T){
|
inline fun <reified T : Any> MapConfig.putEncoded(key: String, value: T) {
|
||||||
putString(key,encodeToString(serializer<T>(),value))
|
putString(key, encodeToString(serializer<T>(), value))
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Json)
|
context(Json)
|
||||||
inline fun <reified T> MapConfig.putEncodedNullable(key: ConfigKey.OfNotPrimitiveType<T>, value:T?){
|
inline fun <reified T> MapConfig.putEncodedNullable(key: ConfigKey.OfNotPrimitiveType<T>, value: T?) {
|
||||||
if (value != null){
|
if (value != null) {
|
||||||
putString(key.keyName,encodeToString(serializer<T>(),value))
|
putString(key.keyName, encodeToString(serializer<T>(), value))
|
||||||
}else{
|
} else {
|
||||||
removeKey(key)
|
removeKey(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Json)
|
context(Json)
|
||||||
inline fun <reified T:Any>MapConfig.putEncoded(key: ConfigKey.OfNotPrimitiveType<T>, value: T){
|
inline fun <reified T : Any> MapConfig.putEncoded(key: ConfigKey.OfNotPrimitiveType<T>, value: T) {
|
||||||
putEncoded<T>(key.keyName,value)
|
putEncoded<T>(key.keyName, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Json)
|
context(Json)
|
||||||
inline fun <reified T> MapConfig.getDecoded(key:String):T?{
|
inline fun <reified T> MapConfig.getDecoded(key: String): T? {
|
||||||
val str=getString(key)?:return null
|
val str = getString(key) ?: return null
|
||||||
return runCatching<T> {
|
return runCatching<T> {
|
||||||
decodeFromString(str)
|
decodeFromString(str)
|
||||||
}
|
}
|
||||||
@ -34,7 +34,16 @@ inline fun <reified T> MapConfig.getDecoded(key:String):T?{
|
|||||||
}
|
}
|
||||||
.getOrNull()
|
.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
context(Json)
|
context(Json)
|
||||||
inline fun <reified T> MapConfig.getDecoded(key: ConfigKey.OfNotPrimitiveType<T>):T?{
|
inline fun <reified T> MapConfig.getDecoded(key: ConfigKey.OfNotPrimitiveType<T>): T? {
|
||||||
return getDecoded(key.keyName)
|
return getDecoded(key.keyName)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> MapConfig.putNullable(key: ConfigKey.OfPrimitiveType<T>, value: T?) {
|
||||||
|
if (value == null) {
|
||||||
|
removeKey(key)
|
||||||
|
} else {
|
||||||
|
put(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
@ -56,6 +56,7 @@ file=File
|
|||||||
tasks=Tasks
|
tasks=Tasks
|
||||||
tools=Tools
|
tools=Tools
|
||||||
help=Help
|
help=Help
|
||||||
|
system=System
|
||||||
all_finished=All Finished
|
all_finished=All Finished
|
||||||
all_unfinished=All Unfinished
|
all_unfinished=All Unfinished
|
||||||
entire_list=Entire List
|
entire_list=Entire List
|
||||||
@ -194,6 +195,8 @@ settings_use_proxy_describe_system_proxy=System Proxy will be used
|
|||||||
settings_use_proxy_describe_manual_proxy="{{value}}" will be used
|
settings_use_proxy_describe_manual_proxy="{{value}}" will be used
|
||||||
settings_theme=Theme
|
settings_theme=Theme
|
||||||
settings_theme_description=Select a theme for the App
|
settings_theme_description=Select a theme for the App
|
||||||
|
settings_ui_scale=UI Scale
|
||||||
|
settings_ui_scale_description=Adjust the scale of the user interface to make elements larger or smaller
|
||||||
settings_language=Language
|
settings_language=Language
|
||||||
settings_compact_top_bar=Compact Top Bar
|
settings_compact_top_bar=Compact Top Bar
|
||||||
settings_compact_top_bar_description=Merge top bar with title bar when the main window has enough width
|
settings_compact_top_bar_description=Merge top bar with title bar when the main window has enough width
|
||||||
|
Loading…
x
Reference in New Issue
Block a user