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 showTranslators = MutableStateFlow(false)
|
||||
val theme = appRepository.theme
|
||||
// val uiScale = appRepository.uiScale
|
||||
val uiScale = appRepository.uiScale
|
||||
}
|
||||
|
||||
interface DownloadDialogManager {
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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,6 +325,7 @@ fun CustomWindow(
|
||||
else Color.Black
|
||||
}.toWindowColorType()
|
||||
}
|
||||
UiScaledContent {
|
||||
CompositionLocalProvider(
|
||||
LocalWindowController provides windowController,
|
||||
LocalWindowState provides state,
|
||||
@ -344,18 +347,6 @@ fun CustomWindow(
|
||||
start = start,
|
||||
end = end,
|
||||
) {
|
||||
// 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 {
|
||||
@ -363,11 +354,33 @@ fun CustomWindow(
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
@ -42,6 +42,8 @@ fun BaseOptionDialog(
|
||||
}
|
||||
}
|
||||
// window.subtractInset()
|
||||
UiScaledContent {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
@ -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,10 +125,14 @@ fun ABDownloaderTheme(
|
||||
fontSize = textSizes.base,
|
||||
),
|
||||
) {
|
||||
// it is overridden by [Window] Composable,
|
||||
// but I put this here. maybe I need this outside of window scope!
|
||||
UiScaledContent {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MyContextMenuRepresentation : ContextMenuRepresentation {
|
||||
@Composable
|
||||
|
@ -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")
|
||||
|
@ -2,3 +2,6 @@ plugins{
|
||||
id(MyPlugins.kotlin)
|
||||
id(MyPlugins.composeDesktop)
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(":desktop:shared"))
|
||||
}
|
@ -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,7 +118,9 @@ context (FrameWindowScope)
|
||||
fun ProvideWindowSpotContainer(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
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
|
||||
val windowSize = getCurrentWindowSize()
|
||||
val containerSize = with(density) {
|
||||
LocalWindowInfo.current.containerSize.let {
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}*/
|
@ -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? {
|
||||
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
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user