Merge pull request #255 from amir1376/feature/ui-scale

feature/ui scale
This commit is contained in:
AmirHossein Abdolmotallebi 2024-12-05 01:49:28 +03:30 committed by GitHub
commit 4de61fdb26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 308 additions and 108 deletions

View File

@ -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 {

View File

@ -9,9 +9,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.resources.Res import com.abdownloadmanager.resources.Res
import com.abdownloadmanager.resources.* import com.abdownloadmanager.resources.*
import ir.amirab.util.compose.resources.myStringResource import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.desktop.screen.applyUiScale
@Composable @Composable
fun ShowAboutDialog(appComponent: AppComponent) { fun ShowAboutDialog(appComponent: AppComponent) {
@ -41,6 +43,7 @@ fun AboutDialog(
onRequestToggleMaximize = null, onRequestToggleMaximize = null,
state = rememberWindowState( state = rememberWindowState(
size = DpSize(400.dp, 330.dp) size = DpSize(400.dp, 330.dp)
.applyUiScale(LocalUiScale.current)
), ),
onCloseRequest = onClose onCloseRequest = onClose
) { ) {

View File

@ -5,7 +5,6 @@ import com.abdownloadmanager.desktop.pages.addDownload.multiple.AddMultiDownload
import com.abdownloadmanager.desktop.pages.addDownload.multiple.AddMultiItemPage import com.abdownloadmanager.desktop.pages.addDownload.multiple.AddMultiItemPage
import com.abdownloadmanager.desktop.pages.addDownload.single.AddDownloadPage import com.abdownloadmanager.desktop.pages.addDownload.single.AddDownloadPage
import com.abdownloadmanager.desktop.pages.addDownload.single.AddSingleDownloadComponent import com.abdownloadmanager.desktop.pages.addDownload.single.AddSingleDownloadComponent
import com.abdownloadmanager.desktop.pages.singleDownloadPage.SingleDownloadEffects
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.customwindow.WindowIcon import com.abdownloadmanager.desktop.ui.customwindow.WindowIcon
import com.abdownloadmanager.desktop.ui.customwindow.WindowTitle import com.abdownloadmanager.desktop.ui.customwindow.WindowTitle
@ -19,9 +18,10 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.resources.Res import com.abdownloadmanager.resources.Res
import com.abdownloadmanager.resources.*
import ir.amirab.util.compose.resources.myStringResource import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension import java.awt.Dimension
@Composable @Composable
@ -32,10 +32,11 @@ fun ShowAddDownloadDialogs(component: AddDownloadDialogManager) {
val onRequestClose = { val onRequestClose = {
component.closeAddDownloadDialog(addDownloadComponent.id) component.closeAddDownloadDialog(addDownloadComponent.id)
} }
val uiScale = LocalUiScale.current
when (addDownloadComponent) { when (addDownloadComponent) {
is AddSingleDownloadComponent -> { is AddSingleDownloadComponent -> {
val h = 265 val h = 265.applyUiScale(uiScale)
val w = 500 val w = 500.applyUiScale(uiScale)
val size = remember { val size = remember {
DpSize( DpSize(
height = h.dp, height = h.dp,

View File

@ -22,6 +22,8 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberDialogState import androidx.compose.ui.window.rememberDialogState
import com.abdownloadmanager.desktop.pages.settings.configurable.Configurable import com.abdownloadmanager.desktop.pages.settings.configurable.Configurable
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension import java.awt.Dimension
import java.awt.MouseInfo import java.awt.MouseInfo
@ -36,7 +38,7 @@ fun ExtraConfig(
size = DpSize( size = DpSize(
height = h.dp, height = h.dp,
width = w.dp, width = w.dp,
), ).applyUiScale(LocalUiScale.current),
) )
BaseOptionDialog(onDismiss, state) { BaseOptionDialog(onDismiss, state) {
LaunchedEffect(window){ LaunchedEffect(window){

View File

@ -7,13 +7,16 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.desktop.utils.mvi.HandleEffects import com.abdownloadmanager.desktop.utils.mvi.HandleEffects
import ir.amirab.util.desktop.screen.applyUiScale
@Composable @Composable
fun BatchDownloadWindow(batchDownloadComponent: BatchDownloadComponent) { fun BatchDownloadWindow(batchDownloadComponent: BatchDownloadComponent) {
CustomWindow( CustomWindow(
state = rememberWindowState( state = rememberWindowState(
size = DpSize(500.dp, 420.dp), size = DpSize(500.dp, 420.dp)
.applyUiScale(LocalUiScale.current),
position = WindowPosition(Alignment.Center) position = WindowPosition(Alignment.Center)
), ),
onCloseRequest = batchDownloadComponent.onClose onCloseRequest = batchDownloadComponent.onClose

View File

@ -9,6 +9,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.util.desktop.screen.applyUiScale
@Composable @Composable
fun ShowCategoryDialogs(dialogManager: CategoryDialogManager) { fun ShowCategoryDialogs(dialogManager: CategoryDialogManager) {
@ -28,7 +30,7 @@ private fun CategoryDialog(
}, },
alwaysOnTop = true, alwaysOnTop = true,
state = rememberWindowState( state = rememberWindowState(
size = DpSize(350.dp, 400.dp), size = DpSize(350.dp, 400.dp).applyUiScale(LocalUiScale.current),
position = WindowPosition.Aligned(Alignment.Center), position = WindowPosition.Aligned(Alignment.Center),
) )
) { ) {

View File

@ -33,6 +33,7 @@ import androidx.compose.ui.window.*
import com.abdownloadmanager.desktop.pages.addDownload.shared.ExtraConfig import com.abdownloadmanager.desktop.pages.addDownload.shared.ExtraConfig
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.customwindow.WindowTitle import com.abdownloadmanager.desktop.ui.customwindow.WindowTitle
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.desktop.ui.util.ifThen import com.abdownloadmanager.desktop.ui.util.ifThen
import com.abdownloadmanager.desktop.utils.mvi.HandleEffects import com.abdownloadmanager.desktop.utils.mvi.HandleEffects
import com.abdownloadmanager.resources.Res import com.abdownloadmanager.resources.Res
@ -41,6 +42,7 @@ import com.abdownloadmanager.utils.compose.WithContentColor
import ir.amirab.util.UrlUtils import ir.amirab.util.UrlUtils
import ir.amirab.util.compose.resources.myStringResource import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.compose.asStringSource import ir.amirab.util.compose.asStringSource
import ir.amirab.util.desktop.screen.applyUiScale
@Composable @Composable
fun EditDownloadWindow( fun EditDownloadWindow(
@ -48,7 +50,8 @@ fun EditDownloadWindow(
) { ) {
CustomWindow( CustomWindow(
state = rememberWindowState( state = rememberWindowState(
size = DpSize(450.dp, 230.dp), size = DpSize(450.dp, 230.dp)
.applyUiScale(LocalUiScale.current),
position = WindowPosition.Aligned(Alignment.Center) position = WindowPosition.Aligned(Alignment.Center)
), ),
alwaysOnTop = true, alwaysOnTop = true,

View File

@ -9,6 +9,8 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.util.desktop.screen.applyUiScale
@Composable @Composable
fun NewQueueDialog( fun NewQueueDialog(
@ -17,7 +19,8 @@ fun NewQueueDialog(
if (appComponent.showCreateQueueDialog.collectAsState().value){ if (appComponent.showCreateQueueDialog.collectAsState().value){
CustomWindow( CustomWindow(
state = rememberWindowState( state = rememberWindowState(
size = DpSize(width = 300.dp, height = 130.dp), size = DpSize(width = 300.dp, height = 130.dp)
.applyUiScale(LocalUiScale.current),
position = WindowPosition.Aligned(Alignment.Center), position = WindowPosition.Aligned(Alignment.Center),
), ),
resizable = false, resizable = false,

View File

@ -1,16 +1,9 @@
package com.abdownloadmanager.desktop.pages.settings package com.abdownloadmanager.desktop.pages.settings
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.theme.myColors
import com.abdownloadmanager.desktop.utils.mvi.HandleEffects import com.abdownloadmanager.desktop.utils.mvi.HandleEffects
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition

View File

@ -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),

View File

@ -14,12 +14,14 @@ import androidx.compose.ui.window.FrameWindowScope
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.WindowState import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.downloader.downloaditem.DownloadJobStatus import ir.amirab.downloader.downloaditem.DownloadJobStatus
import ir.amirab.downloader.monitor.CompletedDownloadItemState import ir.amirab.downloader.monitor.CompletedDownloadItemState
import ir.amirab.downloader.monitor.IDownloadItemState import ir.amirab.downloader.monitor.IDownloadItemState
import ir.amirab.downloader.monitor.ProcessingDownloadItemState import ir.amirab.downloader.monitor.ProcessingDownloadItemState
import ir.amirab.downloader.monitor.statusOrFinished import ir.amirab.downloader.monitor.statusOrFinished
import ir.amirab.downloader.utils.ExceptionUtils import ir.amirab.downloader.utils.ExceptionUtils
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension import java.awt.Dimension
import java.awt.Taskbar import java.awt.Taskbar
import java.awt.Window import java.awt.Window
@ -110,9 +112,12 @@ private fun CompletedWindow(
} }
val defaultHeight = 160f val defaultHeight = 160f
val defaultWidth = 450f val defaultWidth = 450f
val uiScale = LocalUiScale.current
val state = rememberWindowState( val state = rememberWindowState(
height = defaultHeight.dp, size = DpSize(
width = defaultWidth.dp, height = defaultHeight.dp,
width = defaultWidth.dp
).applyUiScale(uiScale),
position = WindowPosition(Alignment.Center) position = WindowPosition(Alignment.Center)
) )
CustomWindow( CustomWindow(
@ -136,7 +141,7 @@ private fun CompletedWindow(
state.size = DpSize( state.size = DpSize(
width = w.dp, width = w.dp,
height = h.dp height = h.dp
) ).applyUiScale(uiScale)
} }
CompletedDownloadPage( CompletedDownloadPage(
singleDownloadComponent, singleDownloadComponent,
@ -153,8 +158,9 @@ private fun ProgressWindow(
val onRequestClose = { val onRequestClose = {
singleDownloadComponent.close() singleDownloadComponent.close()
} }
val defaultHeight = 290f val uiScale = LocalUiScale.current
val defaultWidth = 450f val defaultHeight = 290f.applyUiScale(uiScale)
val defaultWidth = 450f.applyUiScale(uiScale)
val showPartInfo by singleDownloadComponent.showPartInfo.collectAsState() val showPartInfo by singleDownloadComponent.showPartInfo.collectAsState()
val state = rememberWindowState( val state = rememberWindowState(
@ -181,6 +187,7 @@ private fun ProgressWindow(
var w = defaultWidth var w = defaultWidth
if (showPartInfo) { if (showPartInfo) {
h += singleDownloadPageSizing.partInfoHeight.value h += singleDownloadPageSizing.partInfoHeight.value
.applyUiScale(uiScale)
} }
LaunchedEffect(w, h) { LaunchedEffect(w, h) {
state.size = DpSize( state.size = DpSize(

View File

@ -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

View File

@ -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)

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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")

View File

@ -18,9 +18,11 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.resources.Res import com.abdownloadmanager.resources.Res
import ir.amirab.util.compose.StringSource import ir.amirab.util.compose.StringSource
import ir.amirab.util.compose.resources.myStringResource import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension import java.awt.Dimension
@Suppress("unused") @Suppress("unused")
@ -39,8 +41,9 @@ fun ConfirmDialog(
onConfirm: () -> Unit, onConfirm: () -> Unit,
onCancel: () -> Unit, onCancel: () -> Unit,
) { ) {
val h = 180 val uiScale = LocalUiScale.current
val w = 400 val h = 180.applyUiScale(uiScale)
val w = 400.applyUiScale(uiScale)
val state = rememberWindowState( val state = rememberWindowState(
size = DpSize(w.dp, h.dp), size = DpSize(w.dp, h.dp),
position = WindowPosition.Aligned(Alignment.Center) position = WindowPosition.Aligned(Alignment.Center)

View File

@ -19,7 +19,9 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.util.compose.StringSource import ir.amirab.util.compose.StringSource
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension import java.awt.Dimension
import java.util.UUID import java.util.UUID
@ -59,8 +61,9 @@ fun MessageDialog(
msgContent: MessageDialogModel, msgContent: MessageDialogModel,
onConfirm: () -> Unit, onConfirm: () -> Unit,
) { ) {
val h = 200 val uiScale = LocalUiScale.current
val w = 400 val h = 200.applyUiScale(uiScale)
val w = 400.applyUiScale(uiScale)
val state = rememberWindowState( val state = rememberWindowState(
size = DpSize(w.dp, h.dp) size = DpSize(w.dp, h.dp)
) )

View File

@ -1,4 +1,7 @@
plugins{ plugins{
id(MyPlugins.kotlin) id(MyPlugins.kotlin)
id(MyPlugins.composeDesktop) id(MyPlugins.composeDesktop)
}
dependencies {
implementation(project(":desktop:shared"))
} }

View File

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

View File

@ -0,0 +1,111 @@
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 Float.applyUiScale(
userUiScale: Float?,
systemUiScale: Float = GlobalDensity.density,
): Float {
if (userUiScale == null) return this
return (this * userUiScale / systemUiScale)
}
fun Int.unApplyUiScale(
userUiScale: Float?,
systemUiScale: Float = GlobalDensity.density,
): Int {
if (userUiScale == null) return this
return (this * systemUiScale / userUiScale).toInt()
}
fun Float.unApplyUiScale(
userUiScale: Float?,
systemUiScale: Float = GlobalDensity.density,
): Float {
if (userUiScale == null) return this
return (this * systemUiScale / userUiScale)
}
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)
}
}*/

View File

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

View File

@ -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