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 showTranslators = MutableStateFlow(false)
val theme = appRepository.theme
// val uiScale = appRepository.uiScale
val uiScale = appRepository.uiScale
}
interface DownloadDialogManager {

View File

@ -9,9 +9,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.resources.Res
import com.abdownloadmanager.resources.*
import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.desktop.screen.applyUiScale
@Composable
fun ShowAboutDialog(appComponent: AppComponent) {
@ -41,6 +43,7 @@ fun AboutDialog(
onRequestToggleMaximize = null,
state = rememberWindowState(
size = DpSize(400.dp, 330.dp)
.applyUiScale(LocalUiScale.current)
),
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.single.AddDownloadPage
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.WindowIcon
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.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.resources.Res
import com.abdownloadmanager.resources.*
import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension
@Composable
@ -32,10 +32,11 @@ fun ShowAddDownloadDialogs(component: AddDownloadDialogManager) {
val onRequestClose = {
component.closeAddDownloadDialog(addDownloadComponent.id)
}
val uiScale = LocalUiScale.current
when (addDownloadComponent) {
is AddSingleDownloadComponent -> {
val h = 265
val w = 500
val h = 265.applyUiScale(uiScale)
val w = 500.applyUiScale(uiScale)
val size = remember {
DpSize(
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.window.rememberDialogState
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.MouseInfo
@ -36,7 +38,7 @@ fun ExtraConfig(
size = DpSize(
height = h.dp,
width = w.dp,
),
).applyUiScale(LocalUiScale.current),
)
BaseOptionDialog(onDismiss, state) {
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.rememberWindowState
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.desktop.utils.mvi.HandleEffects
import ir.amirab.util.desktop.screen.applyUiScale
@Composable
fun BatchDownloadWindow(batchDownloadComponent: BatchDownloadComponent) {
CustomWindow(
state = rememberWindowState(
size = DpSize(500.dp, 420.dp),
size = DpSize(500.dp, 420.dp)
.applyUiScale(LocalUiScale.current),
position = WindowPosition(Alignment.Center)
),
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.rememberWindowState
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.util.desktop.screen.applyUiScale
@Composable
fun ShowCategoryDialogs(dialogManager: CategoryDialogManager) {
@ -28,7 +30,7 @@ private fun CategoryDialog(
},
alwaysOnTop = true,
state = rememberWindowState(
size = DpSize(350.dp, 400.dp),
size = DpSize(350.dp, 400.dp).applyUiScale(LocalUiScale.current),
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.ui.customwindow.CustomWindow
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.utils.mvi.HandleEffects
import com.abdownloadmanager.resources.Res
@ -41,6 +42,7 @@ import com.abdownloadmanager.utils.compose.WithContentColor
import ir.amirab.util.UrlUtils
import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.compose.asStringSource
import ir.amirab.util.desktop.screen.applyUiScale
@Composable
fun EditDownloadWindow(
@ -48,7 +50,8 @@ fun EditDownloadWindow(
) {
CustomWindow(
state = rememberWindowState(
size = DpSize(450.dp, 230.dp),
size = DpSize(450.dp, 230.dp)
.applyUiScale(LocalUiScale.current),
position = WindowPosition.Aligned(Alignment.Center)
),
alwaysOnTop = true,

View File

@ -9,6 +9,8 @@ import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.util.desktop.screen.applyUiScale
@Composable
fun NewQueueDialog(
@ -17,7 +19,8 @@ fun NewQueueDialog(
if (appComponent.showCreateQueueDialog.collectAsState().value){
CustomWindow(
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),
),
resizable = false,

View File

@ -1,16 +1,9 @@
package com.abdownloadmanager.desktop.pages.settings
import com.abdownloadmanager.desktop.ui.customwindow.CustomWindow
import com.abdownloadmanager.desktop.ui.theme.myColors
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPosition

View File

@ -199,17 +199,17 @@ fun proxyConfig(proxyManager: ProxyManager, scope: CoroutineScope): ProxyConfigu
)
}
/*
fun uiScaleConfig(appSettings: AppSettings): EnumConfigurable<Float?> {
fun uiScaleConfig(appSettings: AppSettingsStorage): EnumConfigurable<Float?> {
return EnumConfigurable(
title = "Ui Scale",
description = "Scale Ui Elements",
title = Res.string.settings_ui_scale.asStringSource(),
description = Res.string.settings_ui_scale_description.asStringSource(),
backedBy = appSettings.uiScale,
possibleValues = listOf(
null,
0.5f,
0.75f,
0.8f,
0.9f,
1f,
1.1f,
1.25f,
1.5f,
1.75f,
@ -218,14 +218,13 @@ fun uiScaleConfig(appSettings: AppSettings): EnumConfigurable<Float?> {
renderMode = EnumConfigurable.RenderMode.Spinner,
describe = {
if (it == null) {
"System"
Res.string.system.asStringSource()
} else {
"$it x"
"$it x".asStringSource()
}
}
)
}
*/
fun themeConfig(
themeManager: ThemeManager,
@ -377,7 +376,7 @@ class SettingsComponent(
Appearance -> listOf(
themeConfig(themeManager, scope),
languageConfig(languageManager, scope),
// uiScaleConfig(appSettings),
uiScaleConfig(appSettings),
autoStartConfig(appSettings),
mergeTopBarWithTitleBarConfig(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.WindowState
import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.downloader.downloaditem.DownloadJobStatus
import ir.amirab.downloader.monitor.CompletedDownloadItemState
import ir.amirab.downloader.monitor.IDownloadItemState
import ir.amirab.downloader.monitor.ProcessingDownloadItemState
import ir.amirab.downloader.monitor.statusOrFinished
import ir.amirab.downloader.utils.ExceptionUtils
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension
import java.awt.Taskbar
import java.awt.Window
@ -110,9 +112,12 @@ private fun CompletedWindow(
}
val defaultHeight = 160f
val defaultWidth = 450f
val uiScale = LocalUiScale.current
val state = rememberWindowState(
height = defaultHeight.dp,
width = defaultWidth.dp,
size = DpSize(
height = defaultHeight.dp,
width = defaultWidth.dp
).applyUiScale(uiScale),
position = WindowPosition(Alignment.Center)
)
CustomWindow(
@ -136,7 +141,7 @@ private fun CompletedWindow(
state.size = DpSize(
width = w.dp,
height = h.dp
)
).applyUiScale(uiScale)
}
CompletedDownloadPage(
singleDownloadComponent,
@ -153,8 +158,9 @@ private fun ProgressWindow(
val onRequestClose = {
singleDownloadComponent.close()
}
val defaultHeight = 290f
val defaultWidth = 450f
val uiScale = LocalUiScale.current
val defaultHeight = 290f.applyUiScale(uiScale)
val defaultWidth = 450f.applyUiScale(uiScale)
val showPartInfo by singleDownloadComponent.showPartInfo.collectAsState()
val state = rememberWindowState(
@ -181,6 +187,7 @@ private fun ProgressWindow(
var w = defaultWidth
if (showPartInfo) {
h += singleDownloadPageSizing.partInfoHeight.value
.applyUiScale(uiScale)
}
LaunchedEffect(w, h) {
state.size = DpSize(

View File

@ -23,7 +23,7 @@ class AppRepository : KoinComponent {
private val proxyManager: ProxyManager by inject()
val theme = appSettings.theme
// val uiScale = appSettings.uiScale
val uiScale = appSettings.uiScale
private val downloadSystem : DownloadSystem by inject()
private val downloadSettings: DownloadSettings by inject()
private val downloadManager: DownloadManager = downloadSystem.downloadManager

View File

@ -4,6 +4,7 @@ import com.abdownloadmanager.desktop.utils.*
import androidx.datastore.core.DataStore
import arrow.optics.Lens
import arrow.optics.optics
import com.abdownloadmanager.desktop.App
import ir.amirab.util.compose.localizationmanager.LanguageStorage
import ir.amirab.util.config.*
import kotlinx.serialization.Serializable
@ -15,6 +16,7 @@ import java.io.File
data class AppSettingsModel(
val theme: String = "dark",
val language: String = "en",
val uiScale: Float? = null,
val mergeTopBarWithTitleBar: Boolean = false,
val threadCount: Int = 8,
val dynamicPartCreation: Boolean = true,
@ -40,6 +42,7 @@ data class AppSettingsModel(
object Keys {
val theme = stringKeyOf("theme")
val language = stringKeyOf("language")
val uiScale = floatKeyOf("uiScale")
val mergeTopBarWithTitleBar = booleanKeyOf("mergeTopBarWithTitleBar")
val threadCount = intKeyOf("threadCount")
val dynamicPartCreation = booleanKeyOf("dynamicPartCreation")
@ -57,12 +60,12 @@ data class AppSettingsModel(
}
override fun get(source: MapConfig): AppSettingsModel {
val default by lazy { AppSettingsModel.default }
return AppSettingsModel(
theme = source.get(Keys.theme) ?: default.theme,
language = source.get(Keys.language) ?: default.language,
uiScale = source.get(Keys.uiScale) ?: default.uiScale,
mergeTopBarWithTitleBar = source.get(Keys.mergeTopBarWithTitleBar) ?: default.mergeTopBarWithTitleBar,
threadCount = source.get(Keys.threadCount) ?: default.threadCount,
dynamicPartCreation = source.get(Keys.dynamicPartCreation) ?: default.dynamicPartCreation,
@ -88,6 +91,7 @@ data class AppSettingsModel(
return source.apply {
put(Keys.theme, focus.theme)
put(Keys.language, focus.language)
putNullable(Keys.uiScale, focus.uiScale)
put(Keys.mergeTopBarWithTitleBar, focus.mergeTopBarWithTitleBar)
put(Keys.threadCount, focus.threadCount)
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(
settings: DataStore<MapConfig>,
) :
@ -114,6 +128,7 @@ class AppSettingsStorage(
LanguageStorage {
var theme = from(AppSettingsModel.theme)
override val selectedLanguage = from(AppSettingsModel.language)
var uiScale = from(uiScaleLens)
var mergeTopBarWithTitleBar = from(AppSettingsModel.mergeTopBarWithTitleBar)
val threadCount = from(AppSettingsModel.threadCount)
val dynamicPartCreation = from(AppSettingsModel.dynamicPartCreation)

View File

@ -60,7 +60,7 @@ object Ui : KoinComponent {
ProvideNotificationManager {
ABDownloaderTheme(
myColors = theme,
// uiScale = appComponent.uiScale.collectAsState().value
uiScale = appComponent.uiScale.collectAsState().value
) {
ProvideGlobalExceptionHandler(globalAppExceptionHandler) {
val trayState = rememberTrayState()

View File

@ -5,7 +5,7 @@ import com.abdownloadmanager.utils.compose.WithContentAlpha
import com.abdownloadmanager.utils.compose.WithContentColor
import ir.amirab.util.compose.IconSource
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.myTextSizes
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.takeOrElse
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.FrameWindowScope
import androidx.compose.ui.window.Window
@ -323,52 +325,63 @@ fun CustomWindow(
else Color.Black
}.toWindowColorType()
}
CompositionLocalProvider(
LocalWindowController provides windowController,
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,
UiScaledContent {
CompositionLocalProvider(
LocalWindowController provides windowController,
LocalWindowState provides state,
LocalWindow provides window,
) {
// val defaultDensity = LocalDensity.current
// val uiScale = LocalUiScale.current
// val density = remember(uiScale) {
// if (uiScale == null) {
// defaultDensity
// } else {
// Density(uiScale)
// }
// }
// CompositionLocalProvider(
// LocalDensity provides density
// ) {
ResponsiveBox {
Box(Modifier.clearFocusOnTap()) {
PopUpContainer {
content()
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,
) {
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
private fun PreventMinimize() {
val state = LocalWindowState.current

View File

@ -10,7 +10,7 @@ import java.awt.event.WindowFocusListener
fun BaseOptionDialog(
onCloseRequest: () -> Unit,
state: DialogState = rememberDialogState(),
resizeable:Boolean=true,
resizeable: Boolean = true,
content: @Composable WindowScope.() -> Unit,
) {
DialogWindow(
@ -21,7 +21,7 @@ fun BaseOptionDialog(
resizable = resizeable,
onCloseRequest = onCloseRequest,
) {
val focusListener=remember {
val focusListener = remember {
object : WindowFocusListener {
override fun windowGainedFocus(e: WindowEvent?) {
//do nothing
@ -32,16 +32,18 @@ fun BaseOptionDialog(
}
}
}
DisposableEffect(window){
DisposableEffect(window) {
window.addWindowFocusListener(focusListener);
window.isAlwaysOnTop=true
window.isAlwaysOnTop = true
//we need this to allow click outside
window.modalityType=java.awt.Dialog.ModalityType.MODELESS
onDispose{
window.modalityType = java.awt.Dialog.ModalityType.MODELESS
onDispose {
window.removeWindowFocusListener(focusListener)
}
}
// window.subtractInset()
content()
UiScaledContent {
content()
}
}
}

View File

@ -12,12 +12,15 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.*
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.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.compose.ui.window.rememberPopupPositionProviderAtPosition
import com.abdownloadmanager.desktop.ui.customwindow.UiScaledContent
import ir.amirab.util.compose.asStringSource
/*
@ -101,12 +104,14 @@ private val textSizes = TextSizes(
@Composable
fun ABDownloaderTheme(
myColors: MyColors,
// uiScale: Float? = null,
uiScale: Float? = null,
content: @Composable () -> Unit,
) {
val systemDensity = LocalDensity.current
CompositionLocalProvider(
LocalMyColors provides AnimatedColors(myColors, tween(500)),
// LocalUiScale provides uiScale,
LocalUiScale provides uiScale,
LocalSystemDensity provides systemDensity,
) {
CompositionLocalProvider(
LocalContextMenuRepresentation provides myContextMenuRepresentation(),
@ -120,7 +125,11 @@ fun ABDownloaderTheme(
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,
shape = RoundedCornerShape(4.dp),
hoverDurationMillis = 300,
unhoverColor = myColors.onBackground/10,
hoverColor = myColors.onBackground/30
unhoverColor = myColors.onBackground / 10,
hoverColor = myColors.onBackground / 30
)
}

View File

@ -4,9 +4,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Density
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> {
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.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import com.abdownloadmanager.resources.Res
import ir.amirab.util.compose.StringSource
import ir.amirab.util.compose.resources.myStringResource
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension
@Suppress("unused")
@ -39,8 +41,9 @@ fun ConfirmDialog(
onConfirm: () -> Unit,
onCancel: () -> Unit,
) {
val h = 180
val w = 400
val uiScale = LocalUiScale.current
val h = 180.applyUiScale(uiScale)
val w = 400.applyUiScale(uiScale)
val state = rememberWindowState(
size = DpSize(w.dp, h.dp),
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.dp
import androidx.compose.ui.window.rememberWindowState
import com.abdownloadmanager.desktop.ui.theme.LocalUiScale
import ir.amirab.util.compose.StringSource
import ir.amirab.util.desktop.screen.applyUiScale
import java.awt.Dimension
import java.util.UUID
@ -59,8 +61,9 @@ fun MessageDialog(
msgContent: MessageDialogModel,
onConfirm: () -> Unit,
) {
val h = 200
val w = 400
val uiScale = LocalUiScale.current
val h = 200.applyUiScale(uiScale)
val w = 400.applyUiScale(uiScale)
val state = rememberWindowState(
size = DpSize(w.dp, h.dp)
)

View File

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

View File

@ -1,4 +1,5 @@
package ir.amirab.util.customwindow
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@ -6,16 +7,17 @@ import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.FrameWindowScope
import ir.amirab.util.customwindow.util.CustomWindowDecorationAccessing
import ir.amirab.util.desktop.GlobalDensity
import java.awt.Rectangle
import java.awt.Shape
import java.awt.Window
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import kotlin.math.roundToInt
object HitSpots {
const val NO_HIT_SPOT = 0
@ -56,7 +58,9 @@ context (FrameWindowScope)
private fun Modifier.onPositionInRect(
onChange: (Rectangle) -> Unit,
) = 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 {
onChange(
it.positionInWindow().toDpRectangle(
@ -114,8 +118,10 @@ context (FrameWindowScope)
fun ProvideWindowSpotContainer(
content: @Composable () -> Unit,
) {
val density = LocalDensity.current
val windowSize =getCurrentWindowSize()
// we use Global for sake of awt here.
// because we want to calculate height and pass it to awt
val density = GlobalDensity
val windowSize = getCurrentWindowSize()
val containerSize = with(density) {
LocalWindowInfo.current.containerSize.let {
DpSize(it.width.toDp(), it.height.toDp())
@ -142,10 +148,15 @@ fun ProvideWindowSpotContainer(
//
if (CustomWindowDecorationAccessing.isSupported) {
val startOffset = (windowSize - containerSize) / 2
val startWidthOffsetInDp = startOffset.width.value.toInt()
// val startHeightInDp=delta.height.value.toInt() //it seems no need here
val startWidthOffsetInDp = startOffset.width.value.roundToInt()
// val startHeightOffsetInDp = startOffset.width.value.roundToInt() //it seems no need here
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)
}

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)
inline fun <reified T:Any> MapConfig.putEncoded(key:String, value:T){
putString(key,encodeToString(serializer<T>(),value))
inline fun <reified T : Any> MapConfig.putEncoded(key: String, value: T) {
putString(key, encodeToString(serializer<T>(), value))
}
context(Json)
inline fun <reified T> MapConfig.putEncodedNullable(key: ConfigKey.OfNotPrimitiveType<T>, value:T?){
if (value != null){
putString(key.keyName,encodeToString(serializer<T>(),value))
}else{
inline fun <reified T> MapConfig.putEncodedNullable(key: ConfigKey.OfNotPrimitiveType<T>, value: T?) {
if (value != null) {
putString(key.keyName, encodeToString(serializer<T>(), value))
} else {
removeKey(key)
}
}
context(Json)
inline fun <reified T:Any>MapConfig.putEncoded(key: ConfigKey.OfNotPrimitiveType<T>, value: T){
putEncoded<T>(key.keyName,value)
inline fun <reified T : Any> MapConfig.putEncoded(key: ConfigKey.OfNotPrimitiveType<T>, value: T) {
putEncoded<T>(key.keyName, value)
}
context(Json)
inline fun <reified T> MapConfig.getDecoded(key:String):T?{
val str=getString(key)?:return null
inline fun <reified T> MapConfig.getDecoded(key: String): T? {
val str = getString(key) ?: return null
return runCatching<T> {
decodeFromString(str)
}
@ -34,7 +34,16 @@ inline fun <reified T> MapConfig.getDecoded(key:String):T?{
}
.getOrNull()
}
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)
}
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
tools=Tools
help=Help
system=System
all_finished=All Finished
all_unfinished=All Unfinished
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_theme=Theme
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_compact_top_bar=Compact Top Bar
settings_compact_top_bar_description=Merge top bar with title bar when the main window has enough width