瀏覽代碼

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement

colors improvement
Milan Jurkulak 2 年之前
父節點
當前提交
03727453ce
共有 39 個文件被更改,包括 1071 次插入 和 388 次删除
  1. 12 9
      src/main/kotlin/eu/mjdev/desktop/Main.kt
  2. 52 17
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  3. 6 6
      src/main/kotlin/eu/mjdev/desktop/components/background/BackgroundImage.kt
  4. 8 10
      src/main/kotlin/eu/mjdev/desktop/components/charts/DualDonutChart.kt
  5. 2 3
      src/main/kotlin/eu/mjdev/desktop/components/charts/MemoryChart.kt
  6. 51 34
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt
  7. 2 2
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/AIPage.kt
  8. 20 4
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/MainSettingsPage.kt
  9. 4 1
      src/main/kotlin/eu/mjdev/desktop/components/custom/DateTime.kt
  10. 4 2
      src/main/kotlin/eu/mjdev/desktop/components/custom/PowerBlock.kt
  11. 48 22
      src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt
  12. 49 23
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  13. 5 8
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt
  14. 2 3
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelText.kt
  15. 16 15
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelTooltip.kt
  16. 2 3
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt
  17. 3 2
      src/main/kotlin/eu/mjdev/desktop/components/draggable/DraggableView.kt
  18. 2 1
      src/main/kotlin/eu/mjdev/desktop/components/text/AutoResizeText.kt
  19. 1 1
      src/main/kotlin/eu/mjdev/desktop/data/User.kt
  20. 31 4
      src/main/kotlin/eu/mjdev/desktop/extensions/BitmapUtils.kt
  21. 26 61
      src/main/kotlin/eu/mjdev/desktop/extensions/ColorUtils.kt
  22. 16 4
      src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt
  23. 8 8
      src/main/kotlin/eu/mjdev/desktop/extensions/Modifier.kt
  24. 12 7
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/AdbDiscover.kt
  25. 1 2
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/adbserver/AdbServer.kt
  26. 5 14
      src/main/kotlin/eu/mjdev/desktop/helpers/bitmap/Bitmap.kt
  27. 71 22
      src/main/kotlin/eu/mjdev/desktop/helpers/internal/Palette.kt
  28. 0 5
      src/main/kotlin/eu/mjdev/desktop/helpers/managers/WindowsManager.kt
  29. 18 4
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  30. 67 50
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindow.kt
  31. 2 2
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowScope.kt
  32. 15 12
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowState.kt
  33. 27 0
      src/main/kotlin/eu/mjdev/desktop/windows/ComponentUpdater.kt
  34. 7 2
      src/main/kotlin/eu/mjdev/desktop/windows/FullScreenWindow.kt
  35. 29 18
      src/main/kotlin/eu/mjdev/desktop/windows/MainWindow.kt
  36. 378 0
      src/main/kotlin/eu/mjdev/desktop/windows/Window.kt
  37. 7 7
      src/main/kotlin/eu/mjdev/desktop/windows/WindowEx.kt
  38. 61 0
      src/main/kotlin/eu/mjdev/desktop/windows/WindowLocationTracker.kt
  39. 1 0
      src/main/kotlin/eu/mjdev/desktop/windows/WindowStateHelper.kt

+ 12 - 9
src/main/kotlin/eu/mjdev/desktop/Main.kt

@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.size
 import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
@@ -14,19 +13,14 @@ import eu.mjdev.desktop.components.charts.MemoryChart
 import eu.mjdev.desktop.components.controlcenter.ControlCenter
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
-import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
-import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.rememberDesktopProvider
 import eu.mjdev.desktop.windows.MainWindow
 
 fun main() = application(
     exitProcessOnExit = true
 ) {
-    val scope = rememberCoroutineScope()
-    val api = DesktopProvider(
-        scope = scope,
-        imageLoader = asyncImageLoader()
-    )
+    val api = rememberDesktopProvider()
     val controlCenterState = rememberVisibilityState()
     val panelState = rememberVisibilityState()
     val menuState = rememberVisibilityState()
@@ -38,6 +32,14 @@ fun main() = application(
     }
 
     val handlePanelFocus: (Boolean) -> Unit = { //focus ->
+//        if (focus) {
+//            if (menuState.isVisible) {
+//                menuState.hide()
+//            }
+//            if (controlCenterState.isVisible) {
+//                controlCenterState.hide()
+//            }
+//        }
 //        val somethingVisible = menuState.isVisible || controlCenterState.isVisible
 //        if (!focus && !somethingVisible) {
 //            panelState.hide()
@@ -45,7 +47,7 @@ fun main() = application(
     }
 
     val handleControlCenterFocus: (Boolean) -> Unit = { focus ->
-        if (!focus) {
+        if (!menuState.isWindowFocus && !focus && !panelState.isWindowFocus) {
             controlCenterState.hide()
         }
     }
@@ -75,6 +77,7 @@ fun main() = application(
                 )
                 DesktopPanel(
                     panelState = panelState,
+                    menuState = menuState,
                     onMenuIconClicked = { menuState.toggle() },
                     onFocusChange = { focus -> handlePanelFocus(focus) }
                 )

+ 52 - 17
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -12,9 +12,11 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.Divider
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.onPointerEvent
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
@@ -23,13 +25,23 @@ import eu.mjdev.desktop.components.sliding.VisibilityState
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.data.Category
+import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
+import eu.mjdev.desktop.extensions.Modifier.dropShadow
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
+import eu.mjdev.desktop.windows.ChromeWindowState
+import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 
+@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 fun AppsMenu(
     api: DesktopProvider = LocalDesktop.current,
@@ -53,20 +65,28 @@ fun AppsMenu(
 ) {
     val appMenuMinWidth by remember { api.currentUser.theme.appMenuMinWidthState }
     val appMenuMinHeight by remember { api.currentUser.theme.appMenuMinHeightState }
-    val appMenuBackgroundColor by remember { api.currentUser.theme.backgroundColorState }
-    val menuPadding by remember { api.currentUser.theme.appMenuOuterPaddingState } // todo
+    val menuPadding by remember { api.currentUser.theme.appMenuOuterPaddingState }
+    // todo
     var items: List<Any> by remember(api.appsProvider) { mutableStateOf(api.appsProvider.appCategories) }
     val onCategoryClick: (Category) -> Unit = { category ->
         items = api.appsProvider.categoriesAndApps[category.name]?.sortedBy { it.name } ?: emptyList()
     }
+    val backgroundColor by rememberBackgroundColor(api)
+    val textColor by rememberTextColor(api)
+    val borderColor by rememberBorderColor(api)
+    val iconsTintColor by rememberIconTintColor(api)
+    val position by rememberCalculated {
+        WindowPosition.Absolute(
+            panelState.bounds.x,
+            api.containerSize.height - (panelState.bounds.height + appMenuMinHeight)
+        )
+    }
+    val windowState: ChromeWindowState = rememberChromeWindowState(position = position)
     ChromeWindow(
         visible = menuState.isVisible,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
-        position = WindowPosition.Absolute(
-            panelState.bounds.x,
-            api.containerSize.height - (panelState.bounds.height + appMenuMinHeight)
-        ),
+        windowState = windowState,
         onFocusChange = { focused ->
             menuState.onFocusChange(focused)
             onFocusChange(focused)
@@ -79,22 +99,33 @@ fun AppsMenu(
                     min = appMenuMinHeight,
                     max = appMenuMinHeight
                 )
+                .dropShadow(
+                    RoundedCornerShape(24.dp),
+                    borderColor.alpha(0.5f),
+                    0.dp,
+                    0.dp,
+                    4.dp,
+                    0.dp
+                )
                 .onPlaced(menuState::onPlaced)
+                .onPointerEvent(PointerEventType.Enter) { windowState.requestFocus() }
         ) {
             Box(
                 modifier = Modifier
                     .fillMaxSize()
                     .padding(menuPadding)
-                    .background(appMenuBackgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
+                    .background(backgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
+                    .border(2.dp, borderColor, RoundedCornerShape(24.dp))
                     .clip(RoundedCornerShape(24.dp))
-                    .border(2.dp, Color.White.copy(0.1f), RoundedCornerShape(24.dp))
             ) {
                 Column(
                     modifier = Modifier.fillMaxSize()
                 ) {
                     UserAvatar(
                         avatarSize = 64.dp,
-                        backgroundColor = appMenuBackgroundColor,
+                        backgroundColor = backgroundColor,
+                        iconTintColor = iconsTintColor,
+                        textColor = textColor,
                         orientation = Orientation.Horizontal,
                         onUserAvatarClick = onUserAvatarClick
                     )
@@ -106,7 +137,7 @@ fun AppsMenu(
                                 start = 2.dp,
                                 end = 2.dp
                             ),
-                        color = Color.White.copy(0.1f),
+                        color = borderColor,
                         thickness = 2.dp
                     )
                     LazyColumn(
@@ -120,8 +151,9 @@ fun AppsMenu(
                                 items(items) { item ->
                                     AppsMenuCategory(
                                         category = item as Category,
-                                        backgroundColor = appMenuBackgroundColor,
-                                        iconTint = Color.White,
+                                        backgroundColor = backgroundColor,
+                                        iconTint = textColor,
+                                        textColor = textColor,
                                         onClick = { onCategoryClick(item) },
                                         onContextMenuClick = { onCategoryContextMenuClick(item) }
                                     )
@@ -132,8 +164,9 @@ fun AppsMenu(
                                 items(items) { item ->
                                     AppsMenuApp(
                                         app = item as App,
-                                        backgroundColor = appMenuBackgroundColor,
-                                        iconTint = Color.White,
+                                        backgroundColor = backgroundColor,
+                                        iconTint = textColor,
+                                        textColor = textColor,
                                         onClick = {
                                             onAppClick(item)
                                         },
@@ -160,14 +193,16 @@ fun AppsMenu(
                                 start = 2.dp,
                                 end = 2.dp
                             ),
-                        color = Color.White.copy(0.1f),
+                        color = borderColor,
                         thickness = 2.dp
                     )
                     AppsBottomBar(
                         modifier = Modifier
                             .fillMaxWidth()
                             .wrapContentHeight()
-                            .background(appMenuBackgroundColor),
+                            .background(backgroundColor),
+                        iconColor = borderColor,
+                        iconBackgroundColor = iconsTintColor,
                         backButtonVisible = items.first() is App,
                         onBackClick = {
                             items = api.appsProvider.appCategories

+ 6 - 6
src/main/kotlin/eu/mjdev/desktop/components/background/BackgroundImage.kt

@@ -13,10 +13,12 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
 import eu.mjdev.desktop.components.image.ImageAny
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.helpers.internal.Queue.Companion.mutableQueue
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 
 @Preview
 @Composable
@@ -31,11 +33,7 @@ fun BackgroundImage(
     val switchDelay by remember { api.currentUser.theme.backgroundRotationDelayState }
     val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
     val userBackgrounds by remember { api.currentUser.config.desktopBackgroundsState }
-    val backgrounds by remember {
-        derivedStateOf {
-            api.appsProvider.backgrounds + userBackgrounds
-        }
-    }
+    val backgrounds by rememberCalculated { api.appsProvider.backgrounds + userBackgrounds }
     val backgroundQueue = remember { mutableQueue(backgrounds) }
     var currentBackground: Any? by remember { mutableStateOf(backgroundQueue.value) }
     Box(
@@ -48,6 +46,9 @@ fun BackgroundImage(
                         fadeOut(animationSpec = tween(durationMillis = fadeOutDuration.toInt()))
             }
         ) { value ->
+            api.scope.launch {
+                onChange(currentBackground)
+            }
             ImageAny(
                 modifier = modifier,
                 contentScale = ContentScale.Crop,
@@ -59,7 +60,6 @@ fun BackgroundImage(
             if (switchDelay > 0) {
                 delay(switchDelay)
                 currentBackground = backgroundQueue.value
-                onChange(currentBackground)
             }
         }
     }

+ 8 - 10
src/main/kotlin/eu/mjdev/desktop/components/charts/DualDonutChart.kt

@@ -3,8 +3,7 @@ package eu.mjdev.desktop.components.charts
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.TextStyle
@@ -12,6 +11,7 @@ import com.aay.compose.baseComponents.model.LegendPosition
 import com.aay.compose.donutChart.DonutChart
 import com.aay.compose.donutChart.model.PieChartData
 import eu.mjdev.desktop.extensions.Compose.DarkDarkGray
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 
 @Composable
 fun DualDonutChart(
@@ -30,7 +30,7 @@ fun DualDonutChart(
     animation: AnimationSpec<Float> = TweenSpec(durationMillis = 3000),
     legendPosition: LegendPosition = LegendPosition.BOTTOM,
 ) {
-    val data = remember(
+    val data by rememberCalculated(
         firstValue,
         firstColor,
         firstTitle,
@@ -38,16 +38,14 @@ fun DualDonutChart(
         secondColor,
         secondTitle
     ) {
-        derivedStateOf {
-            listOf(
-                PieChartData(firstValue, firstColor, firstTitle),
-                PieChartData(secondValue, secondColor, secondTitle)
-            )
-        }
+        listOf(
+            PieChartData(firstValue, firstColor, firstTitle),
+            PieChartData(secondValue, secondColor, secondTitle)
+        )
     }
     DonutChart(
         modifier = modifier,
-        pieChartData = data.value,
+        pieChartData = data,
         centerTitle = title,
         centerTitleStyle = TextStyle(color = textColor),
         descriptionStyle = TextStyle(color = textColor),

+ 2 - 3
src/main/kotlin/eu/mjdev/desktop/components/charts/MemoryChart.kt

@@ -4,13 +4,12 @@ import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import com.aay.compose.baseComponents.model.LegendPosition
 import eu.mjdev.desktop.components.draggable.DraggableView
 import eu.mjdev.desktop.extensions.Compose.DarkDarkGray
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.helpers.system.MemInfo
 import eu.mjdev.desktop.helpers.system.MemInfo.Companion.toReadable
 import kotlinx.coroutines.delay
@@ -36,7 +35,7 @@ fun MemoryChart(
     modifier = modifier,
     dragEnabled = dragEnabled
 ) {
-    val memData = remember { mutableStateOf(MemInfo()) }
+    val memData = rememberState(MemInfo())
     DualDonutChart(
         modifier = modifier,
         title = mainTitle,

+ 51 - 34
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -26,13 +26,19 @@ import eu.mjdev.desktop.components.controlcenter.ControlCenterPage.ControlCenter
 import eu.mjdev.desktop.components.sliding.SlidingMenu
 import eu.mjdev.desktop.components.sliding.VisibilityState
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
-import eu.mjdev.desktop.extensions.ColorUtils.alpha
-import eu.mjdev.desktop.extensions.Modifier.rightShadow
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterExitAnimation
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
+import eu.mjdev.desktop.windows.ChromeWindowState
+import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
@@ -41,8 +47,7 @@ import kotlinx.coroutines.launch
 @Composable
 fun ControlCenter(
     api: DesktopProvider = LocalDesktop.current,
-    shadowColor: Color = Color.Black.alpha(0.3f), // todo theme
-    pagerState: MutableState<Int> = remember { mutableStateOf(0) },
+    pagerState: MutableState<Int> = rememberState(0),
     controlCenterState: VisibilityState = rememberVisibilityState(),
     scope: CoroutineScope = rememberCoroutineScope(),
     enterAnimation: EnterTransition = ControlCenterEnterAnimation,
@@ -50,29 +55,31 @@ fun ControlCenter(
     onFocusChange: (Boolean) -> Unit = {},
     onContextMenuClick: () -> Unit = {}
 ) {
-    val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
+    val backgroundColor by rememberBackgroundColor(api)
     val backgroundAlpha by remember { api.currentUser.theme.controlCenterBackgroundAlphaState }
     val backgroundInvoker = remember { { backgroundColor } }
+    val textColor by rememberTextColor(api)
+    val borderColor by rememberBorderColor(api)
+    val iconsTintColor by rememberIconTintColor(api)
     val controlCenterExpandedWidth by remember { api.currentUser.theme.controlCenterExpandedWidthState }
     val controlCenterDividerWidth by remember { api.currentUser.theme.controlCenterDividerWidthState }
-    val controlCenterIconColor by remember { api.currentUser.theme.controlCenterIconColorState }
     val controlCenterIconSize by remember { api.currentUser.theme.controlCenterIconSizeState }
     val pages by remember { api.controlCenterPagesState }
-    val pagesFiltered = remember(pages) {
-        derivedStateOf {
-            pages.filter { p -> p.condition.invoke(api) }
+    val pagesFiltered = rememberCalculated(pages) { pages.filter { p -> p.condition.invoke(api) } }
+    val position by rememberCalculated { WindowPosition.Aligned(Alignment.TopEnd) }
+    val size by rememberCalculated {
+        if (controlCenterState.isVisible) {
+            DpSize(controlCenterExpandedWidth, api.containerSize.height)
+        } else {
+            DpSize(controlCenterDividerWidth, api.containerSize.height)
         }
     }
+    val windowState: ChromeWindowState = rememberChromeWindowState(position = position, size = size)
     ChromeWindow(
         visible = true,
+        windowState = windowState,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
-        position = WindowPosition.Aligned(Alignment.TopEnd),
-        size = if (controlCenterState.isVisible) {
-            DpSize(controlCenterExpandedWidth, api.containerSize.height)
-        } else {
-            DpSize(controlCenterDividerWidth, api.containerSize.height)
-        },
         onFocusChange = { focused ->
             controlCenterState.onFocusChange(focused)
             onFocusChange(focused)
@@ -82,11 +89,19 @@ fun ControlCenter(
             modifier = Modifier.fillMaxHeight(),
             orientation = Horizontal,
             state = controlCenterState,
+            onPointerEnter = {
+                controlCenterState.show()
+                windowState.requestFocus()
+            },
+            onPointerLeave = {
+                // controlCenterState.hide()
+            }
         ) { isVisible ->
             if (!isVisible) {
                 Divider(
-                    modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                    color = Color.Transparent,
+                    modifier = Modifier.fillMaxHeight()
+                        .width(controlCenterDividerWidth),
+                    color = borderColor,
                     thickness = controlCenterDividerWidth
                 )
             } else {
@@ -94,10 +109,12 @@ fun ControlCenter(
                     modifier = Modifier
                         .fillMaxHeight()
                         .wrapContentSize()
-                        .background(backgroundColor.copy(alpha = backgroundAlpha)),
+                        .background(
+                            backgroundColor.copy(alpha = backgroundAlpha)
+                        ),
                 ) {
                     Row(
-                        modifier = Modifier.fillMaxHeight().wrapContentSize(),
+                        modifier = Modifier.fillMaxHeight(),
                     ) {
                         Box(
                             contentAlignment = Alignment.TopEnd
@@ -117,7 +134,7 @@ fun ControlCenter(
                                             .padding(vertical = 8.dp)
                                             .size(controlCenterIconSize)
                                             .background(
-                                                color = if (isSelected) Color.White else Color.Transparent,
+                                                color = if (isSelected) iconsTintColor else borderColor,
                                                 shape = RoundedCornerShape(8.dp)
                                             )
                                             .pointerInput(Unit) {
@@ -138,7 +155,7 @@ fun ControlCenter(
                                             },
                                         imageVector = page.icon,
                                         contentDescription = "",
-                                        tint = if (isSelected) backgroundColor else controlCenterIconColor
+                                        tint = if (isSelected) backgroundColor else textColor
                                     )
                                 }
                             }
@@ -147,30 +164,30 @@ fun ControlCenter(
                                     .padding(
                                         end = controlCenterIconSize.width + 16.dp
                                     )
-                                    .fillMaxHeight()
-                                    .wrapContentSize(),
+                                    .fillMaxHeight(),
                             ) {
-                                Row {
+                                Row(
+                                    Modifier.fillMaxHeight()
+                                ) {
                                     Divider(
-                                        modifier = Modifier
-                                            .fillMaxHeight()
+                                        modifier = Modifier.fillMaxHeight()
                                             .width(2.dp),
-                                        color = Color.White.copy(0.1f),
+                                        color = borderColor,
                                         thickness = 2.dp
                                     )
                                     Box(
-                                        modifier = Modifier
-                                            .width(controlCenterExpandedWidth)
-                                            .rightShadow(
-                                                color = shadowColor,
-                                                offsetX = 4.dp,
-                                                blur = 10.dp,
-                                            ),
+                                        modifier = Modifier.width(controlCenterExpandedWidth),
                                     ) {
                                         with(pagesFiltered.value[pagerState.value]) {
                                             content(ControlCenterPageScope(api, backgroundInvoker, cache))
                                         }
                                     }
+                                    Divider(
+                                        modifier = Modifier.fillMaxHeight()
+                                            .width(2.dp),
+                                        color = borderColor,
+                                        thickness = 2.dp
+                                    )
                                 }
                             }
                             Divider(

+ 2 - 2
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/AIPage.kt

@@ -11,11 +11,11 @@ import androidx.compose.material.TextFieldDefaults
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Campaign
 import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import kotlinx.coroutines.launch
 
 @Suppress("FunctionName")
@@ -25,7 +25,7 @@ fun AIPage() = ControlCenterPage(
     condition = { ai.isAvailable() }
 ) {
     val questionsList = remember { mutableStateListOf<Pair<String, String>>() }
-    val request = remember { mutableStateOf("") }
+    val request = rememberState("")
     val onDone: (String) -> Unit = { what ->
         if (what.isNotEmpty()) {
             questionsList.add(Pair(what, ""))

+ 20 - 4
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/MainSettingsPage.kt

@@ -5,21 +5,30 @@ import androidx.compose.foundation.layout.*
 import androidx.compose.material.Divider
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Home
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.custom.DateTime
 import eu.mjdev.desktop.components.custom.PowerBlock
 import eu.mjdev.desktop.components.custom.UserAvatar
+import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Modifier.rectShadow
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 
 @Suppress("FunctionName")
 fun MainSettingsPage() = ControlCenterPage(
     icon = Icons.Filled.Home,
     name = "Home"
 ) {
+    val backgroundColor by rememberBackgroundColor(api)
+    val textColor by rememberTextColor(api)
+    val borderColor by rememberBorderColor(api)
+    val iconsTintColor by rememberIconTintColor(api)
     Box(
         modifier = Modifier.fillMaxSize()
     ) {
@@ -27,10 +36,12 @@ fun MainSettingsPage() = ControlCenterPage(
             modifier = Modifier
                 .fillMaxWidth()
                 .align(Alignment.TopCenter)
-                .rectShadow(8.dp, Color.Black.copy(alpha = 0.3f))
+                .rectShadow(8.dp, borderColor.copy(alpha = 0.3f))
         ) {
             UserAvatar(
                 backgroundColor = backgroundColor,
+                iconTintColor = iconsTintColor,
+                textColor = textColor,
                 avatarSize = 128.dp,
                 orientation = Orientation.Vertical,
                 onUserAvatarClick = {
@@ -38,11 +49,13 @@ fun MainSettingsPage() = ControlCenterPage(
                 }
             )
             Divider(
-                modifier = Modifier.fillMaxWidth().height(2.dp),
-                color = Color.Transparent,
+                modifier = Modifier.fillMaxWidth()
+                    .height(2.dp),
+                color = borderColor,
                 thickness = 2.dp
             )
             DateTime(
+                textColor = textColor,
                 backgroundColor = backgroundColor
             )
         }
@@ -81,6 +94,9 @@ fun MainSettingsPage() = ControlCenterPage(
             ) {
                 PowerBlock(
                     backgroundColor = backgroundColor,
+                    shadowColor=borderColor.alpha(0.3f),
+                    iconTintColor = iconsTintColor,
+                    textColor = textColor,
                     onPowerButtonClick = {
                         // todo : dialog
                         api.shutdown()

+ 4 - 1
src/main/kotlin/eu/mjdev/desktop/components/custom/DateTime.kt

@@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp
 @Composable
 fun DateTime(
     backgroundColor: Color,
+    textColor: Color = Color.White
 ) = Box(
     modifier = Modifier
         .fillMaxWidth()
@@ -21,6 +22,8 @@ fun DateTime(
         .padding(16.dp)
 ) {
     Clock(
-        modifier = Modifier.fillMaxWidth()
+        modifier = Modifier.fillMaxWidth(),
+        timeTextColor = textColor,
+        dateTextColor = textColor
     )
 }

+ 4 - 2
src/main/kotlin/eu/mjdev/desktop/components/custom/PowerBlock.kt

@@ -28,6 +28,8 @@ import eu.mjdev.desktop.extensions.Modifier.rectShadow
 fun PowerBlock(
     backgroundColor: Color,
     shadowColor: Color = Color.Black.copy(alpha = 0.3f),
+    textColor: Color = Color.Black,
+    iconTintColor: Color = Color.White,
     bottomBoxHeight: Dp = 128.dp,
     iconHeight: Dp = 64.dp,
     onPowerButtonClick: () -> Unit = {}
@@ -62,12 +64,12 @@ fun PowerBlock(
             Image(
                 modifier = Modifier
                     .size(64.dp)
-                    .background(Color.White, CircleShape)
+                    .background(iconTintColor, CircleShape)
                     .clipCircle()
                     .padding(8.dp),
                 imageVector = Icons.Filled.SettingsPower,
                 contentDescription = "",
-                colorFilter = ColorFilter.tint(Color.Black)
+                colorFilter = ColorFilter.tint(backgroundColor)
             )
         }
     }

+ 48 - 22
src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt

@@ -1,21 +1,23 @@
 package eu.mjdev.desktop.components.custom
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.*
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.components.image.ImageAny
+import eu.mjdev.desktop.components.text.TextAny
 import eu.mjdev.desktop.extensions.Compose.noElevation
 import eu.mjdev.desktop.extensions.Compose.transparent
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
@@ -30,6 +32,8 @@ fun UserAvatar(
     avatarSize: Dp = 128.dp,
     orientation: Orientation = Orientation.Vertical,
     api: DesktopProvider = LocalDesktop.current,
+    iconTintColor: Color = Color.White,
+    textColor: Color = Color.White,
     onUserAvatarClick: () -> Unit
 ) = Box(
     modifier = Modifier
@@ -53,21 +57,33 @@ fun UserAvatar(
                 colors = ButtonDefaults.transparent(),
                 elevation = ButtonDefaults.noElevation()
             ) {
-                Image(
+                ImageAny(
                     modifier = Modifier
                         .size(avatarSize)
-                        .circleBorder(2.dp, Color.White.copy(alpha = 0.6f)),
-                    imageVector = api.currentUser.picture,
-                    colorFilter = ColorFilter.tint(Color.White),
+                        .circleBorder(2.dp, iconTintColor),
+                    src = api.currentUser.picture,
+                    colorFilter = if (api.currentUser.picture is ImageVector) ColorFilter.tint(iconTintColor) else null,
                     contentDescription = ""
                 )
             }
-            Text(
-                modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
-                text = api.currentUser.name ?: api.appsProvider.homeDir?.name ?: "-",
-                color = Color.White,
-                textAlign = TextAlign.Center
-            )
+            Column(
+                modifier = Modifier.fillMaxWidth()
+                    .padding(top = 8.dp),
+                horizontalAlignment = Alignment.CenterHorizontally
+            ) {
+                TextAny(
+                    text = api.currentUser.name ?: api.appsProvider.homeDir?.name ?: "-",
+                    color = textColor,
+                    textAlign = TextAlign.Center,
+                    fontWeight = FontWeight.Bold
+                )
+                TextAny(
+                    text = api.machineName,
+                    color = textColor,
+                    textAlign = TextAlign.Center,
+                    fontWeight = FontWeight.Medium
+                )
+            }
         }
 
         Orientation.Horizontal -> Row(
@@ -85,21 +101,31 @@ fun UserAvatar(
                 colors = ButtonDefaults.transparent(),
                 elevation = ButtonDefaults.noElevation()
             ) {
-                Image(
+                ImageAny(
                     modifier = Modifier
                         .size(avatarSize)
-                        .circleBorder(2.dp, Color.White.copy(alpha = 0.6f)),
-                    imageVector = api.currentUser.picture,
-                    colorFilter = ColorFilter.tint(Color.White),
+                        .circleBorder(2.dp, iconTintColor),
+                    src = api.currentUser.picture,
+                    colorFilter = if (api.currentUser.picture is ImageVector) ColorFilter.tint(iconTintColor) else null,
                     contentDescription = ""
                 )
             }
-            Text(
-                modifier = Modifier.padding(start = 16.dp).fillMaxWidth().padding(top = 16.dp),
-                text = api.currentUser.name ?: api.appsProvider.homeDir?.name ?: "-",
-                color = Color.White,
-                textAlign = TextAlign.Start
-            )
+            Column(
+                modifier = Modifier.padding(start = 8.dp).fillMaxWidth().padding(top = 8.dp),
+            ) {
+                TextAny(
+                    text = api.currentUser.name ?: api.appsProvider.homeDir?.name ?: "-",
+                    color = textColor,
+                    textAlign = TextAlign.Start,
+                    fontWeight = FontWeight.Bold
+                )
+                TextAny(
+                    text = api.machineName,
+                    color = textColor,
+                    textAlign = TextAlign.Start,
+                    fontWeight = FontWeight.Medium
+                )
+            }
         }
     }
 }

+ 49 - 23
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -27,22 +27,28 @@ import eu.mjdev.desktop.components.sliding.SlidingMenu
 import eu.mjdev.desktop.components.sliding.VisibilityState
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.data.App
+import eu.mjdev.desktop.extensions.ColorUtils.alpha
+import eu.mjdev.desktop.extensions.ColorUtils.lighter
 import eu.mjdev.desktop.extensions.Compose.height
+import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.extensions.Modifier.topShadow
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelExitAnimation
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
+import kotlinx.coroutines.delay
 
 @OptIn(ExperimentalFoundationApi::class)
 @Suppress("FunctionName")
 @Composable
 fun DesktopPanel(
     api: DesktopProvider = LocalDesktop.current,
-    iconColor: Color = Color.Black,
-    iconColorRunning: Color = Color.White.copy(0.8f),
-    iconBackgroundColor: Color = Color.White.copy(0.6f),
     dividerWidth: Dp = 4.dp,
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
@@ -50,23 +56,17 @@ fun DesktopPanel(
     showMenuIcon: Boolean = true,
     panelContentPadding: PaddingValues = PaddingValues(4.dp),
     panelState: VisibilityState = rememberVisibilityState(),
+    menuState: VisibilityState = rememberVisibilityState(),
     enterAnimation: EnterTransition = DesktopPanelEnterAnimation,
     exitAnimation: ExitTransition = DesktopPanelExitAnimation,
     tooltipHeight: Dp = 64.dp,
-    tooltipData: MutableState<Any?> = remember { mutableStateOf(null) },
-    onTooltip: (item: Any?) -> Unit = { item -> tooltipData.value = item },
-    panelHeight: (visible: Boolean) -> Dp = { visible ->
-        if (visible)
-            iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height
-        else
-            dividerWidth
-    },
     tooltipConverter: (Any?) -> TooltipData = { item ->
         when (item) {
             is App -> TooltipData(title = item.name, description = item.comment)
             else -> TooltipData(title = item.toString())
         }
     },
+    tooltipDelay: Long = 1000L,
     onMenuIconClicked: () -> Unit = {
         // todo
     },
@@ -82,7 +82,18 @@ fun DesktopPanel(
         // todo
     },
 ) {
-    val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
+    val backgroundColor by rememberBackgroundColor(api)
+    val textColor by rememberTextColor(api)
+    val borderColor by rememberBorderColor(api)
+    val iconsTintColor by rememberIconTintColor(api)
+    var tooltipData: Any? by rememberState(null)
+    val onTooltip: (item: Any?) -> Unit = { item -> tooltipData = item }
+    val panelHeight: (visible: Boolean) -> Dp = { visible ->
+        if (visible)
+            iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height
+        else
+            dividerWidth
+    }
     ChromeWindow(
         visible = true,
         enterAnimation = enterAnimation,
@@ -101,6 +112,15 @@ fun DesktopPanel(
             modifier = Modifier.fillMaxWidth(),
             orientation = Vertical,
             state = panelState,
+            onPointerEnter = {
+                panelState.show()
+                // todo focus
+            },
+            onPointerLeave = {
+                if(!menuState.isVisible) {
+                    panelState.hide()
+                }
+            }
         ) { isVisible ->
             Divider(
                 modifier = Modifier
@@ -114,8 +134,11 @@ fun DesktopPanel(
                     modifier = Modifier.fillMaxWidth().wrapContentHeight(),
                     tooltip = {
                         DesktopPanelTooltip(
-                            tooltipData.value,
-                            converter = tooltipConverter
+                            textColor = textColor,
+                            borderColor = borderColor,
+                            item = tooltipData,
+                            converter = tooltipConverter,
+                            backgroundColor = backgroundColor
                         )
                     },
                     tooltipPlacement = TooltipPlacement.CursorPoint(
@@ -143,9 +166,8 @@ fun DesktopPanel(
                                     )
                                 )
                                 .topShadow(
-                                    color = backgroundColor.copy(alpha = 0.3f),
-                                    offsetY = 4.dp,
-                                    blur = 10.dp
+                                    color = borderColor.alpha(0.3f),
+                                    blur = 4.dp
                                 )
                                 .onPlaced(panelState::onPlaced),
                         ) {
@@ -153,7 +175,7 @@ fun DesktopPanel(
                                 modifier = Modifier
                                     .fillMaxWidth()
                                     .height(2.dp),
-                                color = Color.White.copy(0.1f),
+                                color = borderColor,
                                 thickness = 2.dp
                             )
                             Box(
@@ -164,8 +186,8 @@ fun DesktopPanel(
                                 if (showMenuIcon) {
                                     DesktopMenuIcon(
                                         modifier = Modifier.align(Alignment.CenterStart),
-                                        iconColor = iconColor,
-                                        iconBackgroundColor = iconBackgroundColor,
+                                        iconColor = borderColor,
+                                        iconBackgroundColor = iconsTintColor,
                                         iconSize = iconSize,
                                         iconPadding = iconPadding,
                                         iconOuterPadding = iconOuterPadding,
@@ -176,9 +198,9 @@ fun DesktopPanel(
                                 }
                                 DesktopPanelFavoriteApps(
                                     modifier = Modifier.align(Alignment.Center),
-                                    iconColor = iconColor,
-                                    iconBackgroundColor = iconBackgroundColor,
-                                    iconColorRunning = iconColorRunning,
+                                    iconColor = borderColor,
+                                    iconBackgroundColor = iconsTintColor,
+                                    iconColorRunning = iconsTintColor.lighter(0.3f),
                                     iconSize = iconSize,
                                     iconPadding = iconPadding,
                                     iconOuterPadding = iconOuterPadding,
@@ -198,4 +220,8 @@ fun DesktopPanel(
             }
         }
     }
+    launchedEffect(tooltipData) {
+        delay(tooltipDelay)
+        tooltipData = null
+    }
 }

+ 5 - 8
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt

@@ -1,16 +1,12 @@
 package eu.mjdev.desktop.components.desktoppanel
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.*
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -23,6 +19,7 @@ import eu.mjdev.desktop.components.fonticon.FontIcon
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.noElevation
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.extensions.Modifier.clipRect
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
@@ -43,7 +40,7 @@ fun DesktopPanelIcon(
     iconSize: DpSize = DpSize(48.dp, 48.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
-    iconState: MutableState<Boolean> = remember { mutableStateOf(false) },
+    iconState: MutableState<Boolean> = rememberState(false),
     contentDescription: String? = null,
     onToolTip: (item: Any?) -> Unit,
     onClick: () -> Unit,
@@ -90,8 +87,8 @@ fun DesktopPanelIcon(
         }
         if (isStarting) {
             CircularProgressIndicator(
-                modifier = Modifier.size(iconSize),
-                color = Color.White,
+                modifier = Modifier.padding(4.dp).size(iconSize),
+                color = iconColor,
                 strokeWidth = 4.dp,
                 backgroundColor = Color.Transparent
             )

+ 2 - 3
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelText.kt

@@ -10,8 +10,6 @@ import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -21,6 +19,7 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.noElevation
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Preview
@@ -31,7 +30,7 @@ fun DesktopPanelText(
     textColor: Color = Color.White,
     backgroundHover: Color = Color.Red,
     textPadding: PaddingValues = PaddingValues(4.dp),
-    buttonState: MutableState<Boolean> = remember { mutableStateOf(false) },
+    buttonState: MutableState<Boolean> = rememberState(false),
     onToolTip: (item: Any?) -> Unit = {},
     onClick: () -> Unit,
 ) = Box(

+ 16 - 15
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelTooltip.kt

@@ -13,42 +13,43 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import eu.mjdev.desktop.components.text.TextAny
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @Composable
 fun DesktopPanelTooltip(
-    item: Any?,
-    api: DesktopProvider = LocalDesktop.current,
+    item: Any? = null,
+    textColor: Color = Color.White,
+    borderColor: Color = Color.White,
+    backgroundColor: Color = Color.Transparent,
     converter: (Any?) -> TooltipData = { TooltipData(description = it.toString()) }
-) = Box(
-    modifier = Modifier
-        .wrapContentSize()
-        .background(api.currentUser.theme.backgroundColor, RoundedCornerShape(8.dp))
-        .border(2.dp, Color.White.copy(0.2f), RoundedCornerShape(8.dp))
-        .padding(8.dp)
-) {
-    if (item != null) {
+) = if (item != null) {
+    Box(
+        modifier = Modifier
+            .wrapContentSize()
+            .background(backgroundColor, RoundedCornerShape(8.dp))
+            .border(2.dp, borderColor, RoundedCornerShape(8.dp))
+            .padding(8.dp)
+    ) {
+
         with(converter(item)) {
             Column {
                 if (title.isNotEmpty()) {
                     TextAny(
                         text = title,
-                        color = Color.White,
+                        color = textColor,
                         fontSize = 14.sp
                     )
                 }
                 if (description.isNotEmpty()) {
                     TextAny(
                         text = description,
-                        color = Color.White,
+                        color = textColor,
                         fontSize = 12.sp
                     )
                 }
             }
         }
     }
-}
+} else Unit
 
 data class TooltipData(
     val title: String = "",

+ 2 - 3
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt

@@ -8,8 +8,6 @@ import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.items
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -17,6 +15,7 @@ import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanelIcon
 import eu.mjdev.desktop.data.App
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
@@ -34,7 +33,7 @@ fun DesktopPanelFavoriteApps(
     onAppClick: (app: App) -> Unit,
     onContextMenuClick: (app: App) -> Unit
 ) {
-    val apps by remember(api.appsProvider.favoriteApps.size) { mutableStateOf( api.appsProvider.favoriteApps) }
+    val apps by rememberCalculated(api.appsProvider.favoriteApps.size) { api.appsProvider.favoriteApps }
     Box(
         modifier = modifier
     ) {

+ 3 - 2
src/main/kotlin/eu/mjdev/desktop/components/draggable/DraggableView.kt

@@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 @Composable
 fun DraggableView(
@@ -23,8 +24,8 @@ fun DraggableView(
     contentPadding: PaddingValues = PaddingValues(0.dp),
     content: @Composable BoxScope.() -> Unit = {}
 ) {
-    var position by remember { mutableStateOf(Offset.Zero) }
-    var isDragging by remember { mutableStateOf(false) }
+    var position by rememberState(Offset.Zero)
+    var isDragging by rememberState(false)
     Box(
         modifier = modifier
             .graphicsLayer(

+ 2 - 1
src/main/kotlin/eu/mjdev/desktop/components/text/AutoResizeText.kt

@@ -15,6 +15,7 @@ import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.sp
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 @Composable
 fun AutoResizeText(
@@ -35,7 +36,7 @@ fun AutoResizeText(
     style: TextStyle = LocalTextStyle.current,
 ) {
     var fontSizeValue by remember(text, fontSizeRange) { mutableStateOf(fontSizeRange.max.value) }
-    var readyToDraw by remember { mutableStateOf(false) }
+    var readyToDraw by rememberState(false)
     Text(
         text = text,
         color = color,

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/data/User.kt

@@ -8,7 +8,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
 @Suppress("MemberVisibilityCanBePrivate")
 class User(
     name: String?,
-    picture: ImageVector,
+    picture: Any?,
     config: DesktopConfig,
     theme: Theme
 ) {

+ 31 - 4
src/main/kotlin/eu/mjdev/desktop/extensions/BitmapUtils.kt

@@ -1,19 +1,46 @@
 package eu.mjdev.desktop.extensions
 
 import androidx.compose.ui.graphics.*
+import eu.mjdev.desktop.extensions.ColorUtils.nonAlphaValue
 import eu.mjdev.desktop.helpers.bitmap.Bitmap
 import org.jetbrains.skiko.toBufferedImage
 
-typealias  SkiaBitmap = org.jetbrains.skia.Bitmap
+typealias SkiaBitmap = org.jetbrains.skia.Bitmap
 
 @Suppress("MemberVisibilityCanBePrivate", "unused")
 object BitmapUtils {
 
     fun SkiaBitmap.toBitmap() = Bitmap(toBufferedImage())
 
-    fun Bitmap.getTopMostColor(): Color = createScaledBitmap(this, 1, 1).getPixel(0, 0)
+    fun ImageBitmap.toBitmap() = Bitmap(asSkiaBitmap().toBufferedImage())
 
-    fun SkiaBitmap.getTopMostColor(): Color = toBitmap().getTopMostColor()
+    val Bitmap.topMostColor
+        get() = createScaledBitmap(this, 1, 1).getPixel(0, 0)
 
-    fun ImageBitmap.getTopMostColor(): Color = asSkiaBitmap().getTopMostColor()
+    val SkiaBitmap.topMostColor
+        get() = toBitmap().topMostColor
+
+    val ImageBitmap.topMostColor
+        get() = asSkiaBitmap().topMostColor
+
+    val Bitmap.maxLightColor
+        get() = allPixels.maxBy { it.nonAlphaValue }
+
+    val SkiaBitmap.maxLightColor
+        get() = toBitmap().topMostColor
+
+    val ImageBitmap.maxLightColor: Color
+        get() = asSkiaBitmap().topMostColor
+
+    val Bitmap.maxDarkColor
+        get() = allPixels.minBy { it.nonAlphaValue }
+
+    val SkiaBitmap.maxDarkColor
+        get() = toBitmap().topMostColor
+
+    val ImageBitmap.maxDarkColor: Color
+        get() = asSkiaBitmap().topMostColor
+
+    fun ImageBitmap.cut(x: Int, y: Int, w: Int, h: Int): ImageBitmap =
+        toBitmap().cut(x, y, w, h).image.toComposeImageBitmap()
 }

+ 26 - 61
src/main/kotlin/eu/mjdev/desktop/extensions/ColorUtils.kt

@@ -1,68 +1,30 @@
 package eu.mjdev.desktop.extensions
 
+import androidx.compose.material.MaterialTheme
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.graphics.toArgb
 import java.util.*
-import kotlin.math.cos
 
 @Suppress("unused", "UnusedReceiverParameter")
 object ColorUtils {
-    val Color.randomAlpha: Float
-        get() {
-            val rnd = Random()
-            return (1f / 255f) * (128f + (rnd.nextInt(128)))
-        }
-
-    fun Color.darker(factor: Float): Color {
-        val a: Int = this.alpha.toInt()
-        val r = Math.round(this.red - factor)
-        val g = Math.round(this.green - factor)
-        val b = Math.round(this.blue - factor)
-        return Color(r, g, b, a)
-    }
 
-    fun Color.lighter(factor: Float): Color {
-        val a: Int = this.alpha.toInt()
-        val r = Math.round(this.red + factor)
-        val g = Math.round(this.green + factor)
-        val b = Math.round(this.blue + factor)
-        return Color(
-            if (r < 256) r else 255,
-            if (g < 256) g else 255,
-            if (b < 256) b else 255,
-            a
-        )
-    }
+    val Color.isLightColor
+        get(): Boolean = luminance() > 0.5
 
-    fun linearInterpolation(
-        start: Int,
-        end: Int,
-        normalizedValue: Float
-    ): Int {
-        return (start + ((end - start) * normalizedValue)).toInt()
-    }
+    val Color.isDarkColor
+        get() = !isLightColor
 
-    fun sinInterpolation(
-        start: Int,
-        end: Int,
-        normalizedValue: Float
-    ): Int {
-        return ((start + (end - start) * (1 - cos(normalizedValue * Math.PI)) / 2)).toInt()
-    }
+    val Color.nonAlphaValue
+        get() = alpha(1f).value
 
-    fun getAlpha(color: Int): Int {
-        return (color shr 24) and 0xff
-    }
+    fun Color.alpha(a: Float) = copy(alpha = a)
 
-    fun Color.alpha(a: Float) = alpha((255 * a).toInt())
+    fun Color.red(red: Float) = copy(red = red)
 
-    fun Color.alpha(alpha: Int): Color {
-        return Color(
-            this.red.toInt(),
-            this.green.toInt(),
-            this.blue.toInt(),
-            (this.alpha.toInt() and 0x00ffffff) or ((alpha and 0xff) shl 24)
-        )
-    }
+    fun Color.green(red: Float) = copy(green = red)
+
+    fun Color.blue(red: Float) = copy(blue = red)
 
     fun Color.r(r: Int): Color {
         return Color(
@@ -91,21 +53,24 @@ object ColorUtils {
         )
     }
 
-    fun Color.isLightColor(): Boolean {
-        val r = this.red
-        val g = this.green
-        val b = this.blue
-        return ((r > 127) && (g > 127) && (b > 127))
-    }
+    fun Color.lighter(factor: Float) = copy(
+        red = red + factor,
+        green = green + factor,
+        blue = blue + factor
+    )
+
+    fun Color.darker(factor: Float) = copy(
+        red = red - factor,
+        green = green - factor,
+        blue = blue - factor
+    )
 
     fun Color.randomColor(alpha: Float): Color = Random().let { rnd ->
         Color(
             rnd.nextInt(256),
             rnd.nextInt(256),
             rnd.nextInt(256),
-            alpha.toInt(),
-        )
+        ).alpha(alpha)
     }
 
-
 }

+ 16 - 4
src/main/kotlin/eu/mjdev/desktop/extensions/Compose.kt

@@ -4,9 +4,7 @@ package eu.mjdev.desktop.extensions
 
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.material.ButtonDefaults
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.*
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.*
 import coil3.ImageLoader
@@ -116,7 +114,19 @@ object Compose {
         .memoryCache { imageLoaderMemoryCache }
         .build()
 
-    //    Toolkit.getDefaultToolkit().addAWTEventListener({ event ->
+    @Composable
+    fun <T> rememberState(
+        value: T,
+        policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
+    ) = remember { mutableStateOf(value, policy) }
+
+    @Composable
+    fun <T> rememberCalculated(
+        vararg key: Any?,
+        calculation: () -> T
+    ) = remember(*key) { derivedStateOf { calculation() } }
+
+//    Toolkit.getDefaultToolkit().addAWTEventListener({ event ->
 //        }, AWTEvent.MOUSE_EVENT_MASK or AWTEvent.FOCUS_EVENT_MASK
 //    )
 
@@ -126,4 +136,6 @@ object Compose {
 //        }
 //    }, AWTEvent.WINDOW_EVENT_MASK)
 
+    operator fun DpSize.plus(dp: Dp) = copy(width = width + dp, height = height + dp)
+
 }

+ 8 - 8
src/main/kotlin/eu/mjdev/desktop/extensions/Modifier.kt

@@ -128,8 +128,8 @@ object Modifier {
 
     fun Modifier.rightShadow(
         color: Color,
-        offsetX: Dp,
-        blur: Dp
+        offsetX: Dp = 0.dp,
+        blur: Dp = 8.dp,
     ) = dropShadow(
         shape = RectangleShape,
         color = color,
@@ -141,8 +141,8 @@ object Modifier {
 
     fun Modifier.leftShadow(
         color: Color,
-        offsetX: Dp,
-        blur: Dp
+        offsetX: Dp = 0.dp,
+        blur: Dp = 8.dp
     ) = dropShadow(
         shape = RectangleShape,
         color = color,
@@ -154,8 +154,8 @@ object Modifier {
 
     fun Modifier.topShadow(
         color: Color,
-        offsetY: Dp,
-        blur: Dp
+        offsetY: Dp = 0.dp,
+        blur: Dp = 8.dp
     ) = dropShadow(
         shape = RectangleShape,
         color = color,
@@ -167,8 +167,8 @@ object Modifier {
 
     fun Modifier.bottomShadow(
         color: Color,
-        offsetY: Dp,
-        blur: Dp
+        offsetY: Dp = 0.dp,
+        blur: Dp = 8.dp
     ) = dropShadow(
         shape = RectangleShape,
         color = color,

+ 12 - 7
src/main/kotlin/eu/mjdev/desktop/helpers/adb/AdbDiscover.kt

@@ -2,6 +2,7 @@ package eu.mjdev.desktop.helpers.adb
 
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 
 @Suppress("MemberVisibilityCanBePrivate")
@@ -15,15 +16,19 @@ class AdbDiscover(
 
     init {
         coroutineScope?.launch {
-            while (true) {
-                val newDevices = Adb.discover()
-                if (newDevices.isNotEmpty()) {
-                    newDevices.forEach { entry ->
-                        if (!devices.containsKey(entry.toString())) {
-                            devices[entry.toString()] = entry
-                            onAdded(entry)
+            while (isActive) {
+                runCatching {
+                    val newDevices = Adb.discover()
+                    if (newDevices.isNotEmpty()) {
+                        newDevices.forEach { entry ->
+                            if (!devices.containsKey(entry.toString())) {
+                                devices[entry.toString()] = entry
+                                onAdded(entry)
+                            }
                         }
                     }
+                }.onFailure { e ->
+                    println("Error : ${e.message}")
                 }
                 delay(discoverDelay)
             }

+ 1 - 2
src/main/kotlin/eu/mjdev/desktop/helpers/adb/adbserver/AdbServer.kt

@@ -78,8 +78,7 @@ object AdbServer {
                 } else {
                     parts[0]
                 }
-            }
-            .map { createDadb(adbServerHost, adbServerPort, "host:transport:${it}") }
+            }.map { createDadb(adbServerHost, adbServerPort, "host:transport:${it}") }
     }
 
     internal fun readString(inputStream: DataInputStream): String {

+ 5 - 14
src/main/kotlin/eu/mjdev/desktop/helpers/bitmap/Bitmap.kt

@@ -14,7 +14,6 @@ import javax.imageio.ImageWriteParam
 import javax.imageio.stream.ImageOutputStream
 import kotlin.math.abs
 
-
 @Suppress("unused", "UNUSED_PARAMETER")
 class Bitmap(
     val image: BufferedImage
@@ -41,6 +40,9 @@ class Bitmap(
         RGBA_1010102,
     }
 
+    val allPixels: List<Color>
+        get() = image.data.getPixels(0, 0, width, height, null as IntArray?).map { Color(it) }
+
     fun compress(format: CompressFormat, quality: Int, sink: Sink): Boolean {
         require(quality in 0..100) { "quality must be 0..100" }
         val qualityFloat = quality.toFloat() / 100
@@ -133,19 +135,8 @@ class Bitmap(
         }
     }
 
-    fun setPixels(
-        destination: IntArray,
-        topLeft: Int,
-        downsampledWidth: Int,
-        downsampledX: Int,
-        downsampledY: Int,
-        downsampledW: Int,
-        downsampledH: Int
-    ) : Bitmap {
-
-        // todo
-        return this
-    }
+    fun cut(x: Int, y: Int, w: Int, h: Int): Bitmap =
+        Bitmap(ImegeScaler.crop(image, w, h))
 
     companion object {
         fun Bitmap.applyCanvas(block: Canvas.() -> Unit): Bitmap {

+ 71 - 22
src/main/kotlin/eu/mjdev/desktop/helpers/internal/Palette.kt

@@ -1,20 +1,25 @@
 package eu.mjdev.desktop.helpers.internal
 
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.*
 import androidx.compose.ui.graphics.Color
-import eu.mjdev.desktop.extensions.BitmapUtils.getTopMostColor
+import eu.mjdev.desktop.extensions.BitmapUtils.cut
+import eu.mjdev.desktop.extensions.BitmapUtils.topMostColor
+import eu.mjdev.desktop.extensions.ColorUtils.darker
+import eu.mjdev.desktop.extensions.ColorUtils.isLightColor
+import eu.mjdev.desktop.extensions.ColorUtils.lighter
+import eu.mjdev.desktop.extensions.ColorUtils.nonAlphaValue
 import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 import eu.mjdev.desktop.extensions.Image.loadPicture
+import eu.mjdev.desktop.provider.DesktopProvider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
-@Suppress("unused", "MemberVisibilityCanBePrivate")
+@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
 class Palette(
-    private val scope: CoroutineScope,
-    private val baseColor: Color = Color.SuperDarkGray
+    val scope: CoroutineScope,
+    val baseColor: Color = Color.SuperDarkGray,
+    val borderFactor: Float = 0.1f,
+    val textFactor: Float = 0.6f,
 ) {
     val backgroundColorState = mutableStateOf(baseColor)
     var backgroundColor
@@ -23,24 +28,68 @@ class Palette(
             backgroundColorState.value = value
         }
 
+    val textColorState = mutableStateOf(if (isLight) Color.Black else Color.White)
+    var textColor
+        get() = textColorState.value
+        set(value) {
+            textColorState.value = value
+        }
+
+    private val isLight
+        get() = backgroundColor.isLightColor
+
+    val borderColor
+        get() = if (isLight) backgroundColor.darker(borderFactor) else backgroundColor.lighter(borderFactor)
+
+    val iconsTintColor
+        get() = if (textColor.isLightColor) textColor else backgroundColor.lighter(textFactor)
+
     fun update(src: Any?) = scope.launch {
-        loadPicture(src)
-            .getOrNull()
-            ?.getTopMostColor()
-            ?.also { color ->
-                backgroundColor = color
-            }
-//            ?.themeColor(fallback = baseColor)?.also { color ->
-//                backgroundColor = color
-//            }
+        loadPicture(src).getOrNull()?.let { image ->
+            val width = image.width
+            val height = image.height
+            val imagePart1 = image.cut(0, 0, 64, 64)
+            val imagePart2 = image.cut(width - 64, 0, 64, 64)
+            val imagePart3 = image.cut(0, height - 64, 64, 64)
+            val imagePart4 = image.cut(width - 64, height - 64, 64, 64)
+            val colors = listOf(
+                imagePart1.topMostColor,
+                imagePart2.topMostColor,
+                imagePart3.topMostColor,
+                imagePart4.topMostColor,
+            )
+            val background = colors.toList().minBy { it.nonAlphaValue }
+            val text = if (isLight) backgroundColor.darker(textFactor) else backgroundColor.lighter(textFactor)
+            backgroundColorState.value = background
+            textColorState.value = text
+        }
     }
 
     companion object {
         @Composable
-        fun rememberPalette(
-            baseColor: Color = Color.SuperDarkGray,
-            scope: CoroutineScope = rememberCoroutineScope()
-        ) = remember { Palette(scope, baseColor) }
+        inline fun <T> rememberPalette(
+            api: DesktopProvider,
+            crossinline calculation: @DisallowComposableCalls () -> T
+        ) = remember(api.palette.backgroundColor) { calculation() }
+
+        @Composable
+        fun rememberBackgroundColor(
+            api: DesktopProvider,
+        ) = rememberPalette(api) { api.currentUser.theme.backgroundColorState }
+
+        @Composable
+        fun rememberTextColor(
+            api: DesktopProvider,
+        ) = rememberPalette(api) { derivedStateOf { api.palette.textColor } }
+
+        @Composable
+        fun rememberBorderColor(
+            api: DesktopProvider,
+        ) = rememberPalette(api) { derivedStateOf { api.palette.borderColor } }
+
+        @Composable
+        fun rememberIconTintColor(
+            api: DesktopProvider,
+        ) = rememberPalette(api) { derivedStateOf { api.palette.iconsTintColor } }
     }
 }
-

+ 0 - 5
src/main/kotlin/eu/mjdev/desktop/helpers/managers/WindowsManager.kt

@@ -341,10 +341,6 @@ class WindowsManager {
         val desktop: Long,
         override val window: Window = Window(id)
     ) : IWindow {
-        override fun toBack() {
-            // todo
-        }
-
         override fun toFront() {
             windowTracker.activateWindow(window)
         }
@@ -369,7 +365,6 @@ class WindowsManager {
     interface IWindow {
         val window: Window
 
-        fun toBack() {}
         fun toFront() {}
         fun minimize() {}
         fun maximize() {}

+ 18 - 4
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -1,19 +1,20 @@
 package eu.mjdev.desktop.provider
 
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.*
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import coil3.ImageLoader
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.pages.*
 import eu.mjdev.desktop.data.User
+import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
 import eu.mjdev.desktop.helpers.adb.AdbDiscover.Companion.adbDevicesHandler
+import eu.mjdev.desktop.helpers.internal.Palette
 import eu.mjdev.desktop.helpers.managers.ConnectivityManager
 import eu.mjdev.desktop.helpers.managers.FileSystemWatcher
 import eu.mjdev.desktop.helpers.managers.KCEFHelper
 import eu.mjdev.desktop.helpers.managers.WindowsManager
+import eu.mjdev.desktop.helpers.system.Command
 import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -31,15 +32,20 @@ class DesktopProvider(
 //    val scriptManager: ScriptEngineManager = ScriptEngineManager(),
     val kcefHelper: KCEFHelper = KCEFHelper(scope),
     val ai: AIProvider = AIProvider(scope, AiPluginGemini(scope)),
-    val windows: WindowsManager = WindowsManager()
+    val windows: WindowsManager = WindowsManager(),
 ) {
     private val __currentUser: User by lazy { User.load() }
     private val _currentUser: MutableState<User> = mutableStateOf(__currentUser)
     val currentUser
         get() = _currentUser.value
 
+    val palette: Palette = Palette(scope, currentUser.theme.backgroundColor)
+
     val controlCenterPagesState: MutableState<List<ControlCenterPage>> = mutableStateOf(CONTROL_CENTER_PAGES)
 
+    val machineName
+        get() = Command("hostname").execute()
+
 //    private val engine: ScriptEngine by lazy {
 //        scriptManager.getEngineByName("JavaScript").apply {
 //            // todo
@@ -143,5 +149,13 @@ class DesktopProvider(
         val LocalDesktop = compositionLocalOf {
             DesktopProvider()
         }
+
+        @Composable
+        fun rememberDesktopProvider(
+            scope: CoroutineScope = rememberCoroutineScope(),
+            imageLoader: ImageLoader = asyncImageLoader()
+        ) = remember {
+            DesktopProvider(scope, imageLoader)
+        }
     }
 }

+ 67 - 50
src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindow.kt

@@ -25,36 +25,39 @@ fun ChromeWindow(
     enabled: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+    onCreate: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: () -> Unit = {},
-    onClosed: () -> Unit = {},
+    onOpened: ChromeWindowState.() -> Unit = {},
+    onClosed: ChromeWindowState.() -> Unit = {},
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) = ChromeWindow(
-    windowState,
-    visible,
-    transparent,
-    resizable,
-    enabled,
-    focusable,
-    alwaysOnTop,
-    alwaysOnBottom,
-    enterAnimation,
-    exitAnimation,
-    onCloseRequest,
-    onPreviewKeyEvent,
-    onKeyEvent,
-    onFocusChange,
-    onOpened,
-    onClosed,
-    content
+    windowState = windowState,
+    visible = visible,
+    transparent = transparent,
+    resizable = resizable,
+    enabled = enabled,
+    focusable = focusable,
+    alwaysOnTop = alwaysOnTop,
+//    alwaysOnBottom = alwaysOnBottom,
+    enterAnimation = enterAnimation,
+    exitAnimation = exitAnimation,
+    onCreate = onCreate,
+    onCloseRequest = onCloseRequest,
+    onPreviewKeyEvent = onPreviewKeyEvent,
+    onKeyEvent = onKeyEvent,
+    onFocusChange = onFocusChange,
+    onOpened = onOpened,
+    onClosed = onClosed,
+    content = content
 )
 
+@Suppress("unused")
 @Composable
 fun ChromeWindow(
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
@@ -65,34 +68,36 @@ fun ChromeWindow(
     enabled: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+    onCreate: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: () -> Unit = {},
-    onClosed: () -> Unit = {},
+    onOpened: ChromeWindowState.() -> Unit = {},
+    onClosed: ChromeWindowState.() -> Unit = {},
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) = ChromeWindow(
-    windowState,
-    visible,
-    transparent,
-    resizable,
-    enabled,
-    focusable,
-    alwaysOnTop,
-    alwaysOnBottom,
-    enterAnimation,
-    exitAnimation,
-    onCloseRequest,
-    onPreviewKeyEvent,
-    onKeyEvent,
-    onFocusChange,
-    onOpened,
-    onClosed,
-    content
+    windowState = windowState,
+    visible = visible,
+    transparent = transparent,
+    resizable = resizable,
+    enabled = enabled,
+    focusable = focusable,
+    alwaysOnTop = alwaysOnTop,
+//    alwaysOnBottom = alwaysOnBottom,
+    enterAnimation = enterAnimation,
+    exitAnimation = exitAnimation,
+    onCreate = onCreate,
+    onCloseRequest = onCloseRequest,
+    onPreviewKeyEvent = onPreviewKeyEvent,
+    onKeyEvent = onKeyEvent,
+    onFocusChange = onFocusChange,
+    onOpened = onOpened,
+    onClosed = onClosed,
+    content = content
 )
 
 @Composable
@@ -104,34 +109,46 @@ fun ChromeWindow(
     enabled: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
     onCloseRequest: () -> Unit = {},
+    onCreate: ChromeWindowState.() -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: () -> Unit = {},
-    onClosed: () -> Unit = {},
+    onOpened: ChromeWindowState.() -> Unit = {},
+    onClosed: ChromeWindowState.() -> Unit = {},
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) {
     val animState = rememberAnimState(visible)
     val wnState = rememberWnState()
     windowState.onOpened.add {
-        if (alwaysOnBottom) {
-            windowState.window?.toBack()
-        } else {
-            windowState.requestFocus()
-        }
+//        if (alwaysOnBottom) {
+            // todo
+//            val focusOwner = window?.mostRecentFocusOwner
+//            window?.toBack()
+//            focusOwner?.requestFocus()
+//        }
+//        else if (alwaysOnTop) {
+//            windowState.requestFocus()
+//        }
         windowState.updatePositionAndSize()
         if (visible) {
             animState.value.targetState = true
         }
-        onOpened()
+        onOpened(windowState)
     }
     windowState.onClosed.add(onClosed)
     windowState.onFocusChange.add(onFocusChange)
     WindowEx(
+        onCreate = { window ->
+            windowState.window = window
+            onCreate(windowState)
+        },
+//        onOpen = {
+//            onOpened(windowState)
+//        },
         onCloseRequest,
         windowState,
         wnState.value,
@@ -143,7 +160,7 @@ fun ChromeWindow(
         enabled,
         focusable,
         alwaysOnTop,
-        alwaysOnBottom,
+//        alwaysOnBottom,
         onPreviewKeyEvent,
         onKeyEvent,
         stateHelper = windowState.stateHelper,

+ 2 - 2
src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowScope.kt

@@ -44,8 +44,8 @@ class ChromeWindowScope(
         block()
     }
 
-    fun requestFocus() =
-        stateHelper.requestFocus()
+//    fun requestFocus() =
+//        stateHelper.requestFocus()
 
     fun setBounds(
         x: Dp = this.x,

+ 15 - 12
src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowState.kt

@@ -9,6 +9,8 @@ import androidx.compose.ui.window.WindowPlacement
 import androidx.compose.ui.window.WindowPosition
 import androidx.compose.ui.window.WindowState
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 class ChromeWindowState(
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
@@ -18,8 +20,8 @@ class ChromeWindowState(
 ) : WindowState {
 
     var onFocusChange: MutableList<ChromeWindowState.(focus: Boolean) -> Unit> = mutableStateListOf()
-    var onOpened: MutableList<() -> Unit> = mutableStateListOf()
-    var onClosed: MutableList<() -> Unit> = mutableStateListOf()
+    var onOpened: MutableList<ChromeWindowState.() -> Unit> = mutableStateListOf()
+    var onClosed: MutableList<ChromeWindowState.() -> Unit> = mutableStateListOf()
 
     val focusHelper: WindowFocusHelper = WindowFocusHelper { _, focus ->
         this@ChromeWindowState.onFocusChange.forEach {
@@ -30,16 +32,19 @@ class ChromeWindowState(
     val stateHelper: WindowStateHelper = WindowStateHelper({
         setSize(size)
         this@ChromeWindowState.onOpened.forEach {
-            it.invoke()
+            it.invoke(this@ChromeWindowState)
         }
     }, {
         this@ChromeWindowState.onClosed.forEach {
-            it.invoke()
+            it.invoke(this@ChromeWindowState)
         }
     })
 
-    val window
+    var window
         get() = stateHelper.window
+        set(value) {
+            stateHelper.window = value
+        }
 
     @Suppress("CanBePrimaryConstructorProperty")
     val closeAction: WindowCloseAction = closeAction
@@ -82,17 +87,15 @@ class ChromeWindowState(
         }
 
         @Composable
-        fun rememberAnimState(visible: Boolean) = remember(visible) {
-            derivedStateOf {
-                when (visible) {
-                    true -> MutableTransitionState(false)
-                    false -> MutableTransitionState(true)
-                }
+        fun rememberAnimState(visible: Boolean) = rememberCalculated(visible) {
+            when (visible) {
+                true -> MutableTransitionState(false)
+                false -> MutableTransitionState(true)
             }
         }
 
         @Composable
-        fun rememberWnState() = remember { mutableStateOf(false) }
+        fun rememberWnState() = rememberState(false)
 
         @Composable
         fun updateWindowState(

+ 27 - 0
src/main/kotlin/eu/mjdev/desktop/windows/ComponentUpdater.kt

@@ -0,0 +1,27 @@
+package eu.mjdev.desktop.windows
+
+internal class ComponentUpdater {
+    private var updatedValues = mutableListOf<Any?>()
+
+    fun update(body: UpdateScope.() -> Unit) {
+        UpdateScope().body()
+    }
+
+    inner class UpdateScope {
+        private var index = 0
+
+        fun <T : Any?> set(value: T, update: (T) -> Unit) {
+            if (index < updatedValues.size) {
+                if (updatedValues[index] != value) {
+                    update(value)
+                    updatedValues[index] = value
+                }
+            } else {
+                check(index == updatedValues.size)
+                update(value)
+                updatedValues.add(value)
+            }
+            index++
+        }
+    }
+}

+ 7 - 2
src/main/kotlin/eu/mjdev/desktop/windows/FullScreenWindow.kt

@@ -14,15 +14,18 @@ fun FullScreenWindow(
     api: DesktopProvider = LocalDesktop.current,
     enabled: Boolean = true,
     focusable: Boolean = true,
+    onOpened: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     windowState: ChromeWindowState = rememberChromeWindowState(
-        placement = WindowPlacement.Fullscreen,
+        placement = WindowPlacement.Maximized,
         size = api.containerSize,
         position = WindowPosition.Aligned(Alignment.Center)
     ),
-    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
+    onCreate: ChromeWindowState.() -> Unit = {},
+    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {
+    },
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) = ChromeWindow(
     windowState = windowState,
@@ -35,6 +38,8 @@ fun FullScreenWindow(
     onPreviewKeyEvent = onPreviewKeyEvent,
     onKeyEvent = onKeyEvent,
     onCloseRequest = onCloseRequest,
+    onCreate = onCreate,
+    onOpened = onOpened,
     onFocusChange = onFocusChange,
     content = content
 )

+ 29 - 18
src/main/kotlin/eu/mjdev/desktop/windows/MainWindow.kt

@@ -2,7 +2,6 @@ package eu.mjdev.desktop.windows
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.fillMaxSize
@@ -11,13 +10,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.isSecondaryPressed
 import eu.mjdev.desktop.components.background.BackgroundImage
-import eu.mjdev.desktop.components.menu.ContextMenu
-import eu.mjdev.desktop.components.menu.ContextMenuState
-import eu.mjdev.desktop.components.menu.ContextMenuState.Companion.rememberContextMenuState
 import eu.mjdev.desktop.components.sliding.VisibilityState
-import eu.mjdev.desktop.helpers.adb.helpers.log
-import eu.mjdev.desktop.helpers.internal.Palette
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberPalette
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
@@ -30,27 +23,45 @@ fun MainWindow(
     panelState: VisibilityState,
     menuState: VisibilityState,
     api: DesktopProvider = LocalDesktop.current,
-    palette: Palette = rememberPalette(api.currentUser.theme.backgroundColor),
 //    onDesktopMenuShow: () -> Unit = {},
-    contextMenuState: ContextMenuState = rememberContextMenuState("item1", "item2"),
+//    contextMenuState: ContextMenuState = rememberContextMenuState("item1", "item2"),
     content: @Composable BoxScope.() -> Unit = {}
 ) = FullScreenWindow(
-    onFocusChange = {
+//    onOpened = {
+//        println("Window opened $window")
+//        window?.toBack()
+//    },
+    onFocusChange = { focus ->
+//        RuntimeException("Focus change: $it").printStackTrace()
         // todo better solution
-        window?.toBack()
+//        val focusOwner = window?.mostRecentFocusOwner
+        if (focus) {
+            window?.toBack()
+        }
+//        focusOwner?.requestFocus()
     }
 ) {
+//    val backgroundColor by rememberBackgroundColor(api)
     Box(
         modifier = Modifier
             .fillMaxSize()
-            .background(api.currentUser.theme.backgroundColor)
+//            .background(backgroundColor)
+//            .pointerInput(Unit) {
+//                awaitPointerEventScope {
+//                    while (true) {
+//                        awaitPointerEvent(pass = PointerEventPass.Initial)
+//                            .changes
+//                            .forEach(PointerInputChange::consume)
+//                    }
+//                }
+//            }
     ) {
         BackgroundImage(
             modifier = Modifier
                 .fillMaxSize()
                 .mouseClickable {
                     if (buttons.isSecondaryPressed) {
-                        contextMenuState.show()
+//                        contextMenuState.show()
                     } else {
                         panelState.hide()
                         menuState.hide()
@@ -58,7 +69,7 @@ fun MainWindow(
                     }
                 },
             onChange = { src ->
-                palette.apply {
+                api.palette.apply {
                     update(src)
                 }.also { p ->
                     api.currentUser.theme.backgroundColor = p.backgroundColor
@@ -71,12 +82,12 @@ fun MainWindow(
 //        )
         content()
     }
-    ContextMenu(
-        contextMenuState = contextMenuState,
-        onMenuItemClick = { item -> log { "Context menu : $item" } },
+//    ContextMenu(
+//        contextMenuState = contextMenuState,
+//        onMenuItemClick = { item -> log { "Context menu : $item" } },
 //        onShow = { onDesktopMenuShow() },
 //        onHide = {}
-    )
+//    )
 //    launchedEffect {
 //        runCatching {
 //            Notification("Hello!", "I just wanted to say hello :)").apply {

+ 378 - 0
src/main/kotlin/eu/mjdev/desktop/windows/Window.kt

@@ -0,0 +1,378 @@
+package eu.mjdev.desktop.windows
+
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.awt.ComposeWindow
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.toAwtImage
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.*
+import androidx.compose.ui.window.*
+import java.awt.*
+import java.awt.event.*
+import java.util.*
+import javax.swing.JFrame
+import kotlin.math.roundToInt
+
+@Composable
+fun Window(
+//    onOpen: (window: ComposeWindow) -> Unit = {},
+    onCreate: (window: ComposeWindow) -> Unit = {},
+    onCloseRequest: () -> Unit,
+    state: WindowState = rememberWindowState(),
+    visible: Boolean = true,
+    title: String = "",
+    icon: Painter? = null,
+    undecorated: Boolean = false,
+    transparent: Boolean = false,
+    resizable: Boolean = true,
+    enabled: Boolean = true,
+    focusable: Boolean = true,
+    alwaysOnTop: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
+    closeAction: Int = JFrame.DO_NOTHING_ON_CLOSE,
+    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
+    onKeyEvent: (KeyEvent) -> Boolean = { false },
+    content: @Composable FrameWindowScope.() -> Unit
+) {
+    val currentState by rememberUpdatedState(state)
+    val currentTitle by rememberUpdatedState(title)
+    val currentIcon by rememberUpdatedState(icon)
+    val currentUndecorated by rememberUpdatedState(undecorated)
+    val currentTransparent by rememberUpdatedState(transparent)
+    val currentResizable by rememberUpdatedState(resizable)
+    val currentEnabled by rememberUpdatedState(enabled)
+    val currentFocusable by rememberUpdatedState(focusable)
+    val currentAlwaysOnTop by rememberUpdatedState(alwaysOnTop)
+//    val currentAlwaysOnBottom by rememberUpdatedState(alwaysOnBottom)
+    val currentOnCloseRequest by rememberUpdatedState(onCloseRequest)
+    val updater = remember(::ComponentUpdater)
+    val appliedState = remember {
+        object {
+            var size: DpSize? = null
+            var position: WindowPosition? = null
+            var placement: WindowPlacement? = null
+            var isMinimized: Boolean? = null
+        }
+    }
+    val listeners = remember {
+        object {
+            var windowListenerRef = windowListenerRef()
+            var windowStateListenerRef = windowStateListenerRef()
+            var componentListenerRef = componentListenerRef()
+
+            fun removeFromAndClear(window: ComposeWindow) {
+                windowListenerRef.unregisterFromAndClear(window)
+                windowStateListenerRef.unregisterFromAndClear(window)
+                componentListenerRef.unregisterFromAndClear(window)
+            }
+        }
+    }
+    Window(
+        visible = visible,
+        onPreviewKeyEvent = onPreviewKeyEvent,
+        onKeyEvent = onKeyEvent,
+        create = {
+            val graphicsConfiguration = WindowLocationTracker.lastActiveGraphicsConfiguration
+            ComposeWindow(graphicsConfiguration = graphicsConfiguration).apply {
+                defaultCloseOperation = closeAction
+                listeners.windowListenerRef.registerWithAndSet(
+                    this,
+                    object : WindowAdapter() {
+                        override fun windowOpened(e: WindowEvent?) {
+//                            onOpen(this@apply)
+                        }
+
+                        override fun windowGainedFocus(e: WindowEvent?) {
+                            super.windowGainedFocus(e)
+//                            onOpen(this@apply)
+                        }
+
+                        override fun windowClosing(e: WindowEvent) {
+                            currentOnCloseRequest()
+                        }
+                    }
+                )
+                listeners.windowStateListenerRef.registerWithAndSet(this) {
+                    currentState.placement = placement
+                    currentState.isMinimized = isMinimized
+                    appliedState.placement = currentState.placement
+                    appliedState.isMinimized = currentState.isMinimized
+                }
+                listeners.componentListenerRef.registerWithAndSet(
+                    this,
+                    object : ComponentAdapter() {
+                        override fun componentResized(e: ComponentEvent) {
+                            currentState.placement = placement
+                            currentState.size = DpSize(width.dp, height.dp)
+                            appliedState.placement = currentState.placement
+                            appliedState.size = currentState.size
+                        }
+
+                        override fun componentMoved(e: ComponentEvent) {
+                            currentState.position = WindowPosition(x.dp, y.dp)
+                            appliedState.position = currentState.position
+                        }
+                    }
+                )
+                onCreate(this)
+                WindowLocationTracker.onWindowCreated(this)
+            }
+        },
+        dispose = {
+            WindowLocationTracker.onWindowDisposed(it)
+            listeners.removeFromAndClear(it)
+            it.dispose()
+        },
+        update = { window ->
+            updater.update {
+                set(currentTitle, window::setTitle)
+                set(currentIcon, window::setIcon)
+                set(currentUndecorated, window::setUndecoratedSafely)
+                set(currentTransparent, window::isTransparent::set)
+                set(currentResizable, window::setResizable)
+                set(currentEnabled, window::setEnabled)
+//                if (alwaysOnBottom) {
+//                    set(false, window::setFocusableWindowState)
+//                    set(false, window::setAlwaysOnTop)
+//                    set(false, window::setAutoRequestFocus)
+//                } else {
+                    set(currentFocusable, window::setFocusableWindowState)
+                    set(currentAlwaysOnTop, window::setAlwaysOnTop)
+//                }
+            }
+            if (state.size != appliedState.size) {
+                window.setSizeSafely(state.size, state.placement)
+                appliedState.size = state.size
+            }
+            if (state.position != appliedState.position) {
+                window.setPositionSafely(
+                    state.position,
+                    state.placement,
+                    platformDefaultPosition = { WindowLocationTracker.getCascadeLocationFor(window) }
+                )
+                appliedState.position = state.position
+            }
+            if (state.placement != appliedState.placement) {
+                window.placement = state.placement
+                appliedState.placement = state.placement
+            }
+            if (state.isMinimized != appliedState.isMinimized) {
+                window.isMinimized = state.isMinimized
+                appliedState.isMinimized = state.isMinimized
+            }
+        },
+        content = content
+    )
+}
+
+internal fun Window.align(alignment: Alignment) {
+    val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(graphicsConfiguration)
+    val screenBounds = graphicsConfiguration.bounds
+    val size = IntSize(size.width, size.height)
+    val screenSize = IntSize(
+        screenBounds.width - screenInsets.left - screenInsets.right,
+        screenBounds.height - screenInsets.top - screenInsets.bottom
+    )
+    val location = alignment.align(size, screenSize, LayoutDirection.Ltr)
+
+    setLocation(
+        screenBounds.x + screenInsets.left + location.x,
+        screenBounds.y + screenInsets.top + location.y
+    )
+}
+
+internal fun Window.setPositionImpl(
+    position: WindowPosition,
+    platformDefaultPosition: () -> Point
+) = when (position) {
+    WindowPosition.PlatformDefault -> location = platformDefaultPosition()
+    is WindowPosition.Aligned -> align(position.alignment)
+    is WindowPosition.Absolute -> setLocation(
+        position.x.value.roundToInt(),
+        position.y.value.roundToInt()
+    )
+}
+
+internal fun Window.setPositionSafely(
+    position: WindowPosition,
+    placement: WindowPlacement,
+    platformDefaultPosition: () -> Point
+) {
+    if (!isVisible || (placement == WindowPlacement.Floating)) {
+        setPositionImpl(position, platformDefaultPosition)
+    }
+}
+
+private fun Window.setSizeImpl(size: DpSize) {
+    val availableSize by lazy {
+        val screenBounds = graphicsConfiguration.bounds
+        val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(graphicsConfiguration)
+        IntSize(
+            width = screenBounds.width - screenInsets.left - screenInsets.right,
+            height = screenBounds.height - screenInsets.top - screenInsets.bottom
+        )
+    }
+    val isWidthSpecified = size.isSpecified && size.width.isSpecified
+    val isHeightSpecified = size.isSpecified && size.height.isSpecified
+    val width = if (isWidthSpecified) {
+        size.width.value.roundToInt().coerceAtLeast(0)
+    } else {
+        availableSize.width
+    }
+    val height = if (isHeightSpecified) {
+        size.height.value.roundToInt().coerceAtLeast(0)
+    } else {
+        availableSize.height
+    }
+    var computedPreferredSize: Dimension? = null
+    if (!isWidthSpecified || !isHeightSpecified) {
+        preferredSize = Dimension(width, height)
+        pack()  // Makes it displayable
+        preferredSize = null
+        computedPreferredSize = preferredSize
+    }
+    if (!isDisplayable) {
+        preferredSize = Dimension(width, height)
+        pack()
+    }
+    setSize(
+        if (isWidthSpecified) width else computedPreferredSize!!.width,
+        if (isHeightSpecified) height else computedPreferredSize!!.height,
+    )
+    revalidate()
+}
+
+internal fun Window.setSizeSafely(size: DpSize, placement: WindowPlacement) {
+    if (!isVisible || (placement == WindowPlacement.Floating)) {
+        setSizeImpl(size)
+    }
+}
+
+internal class ListenerOnWindowRef<T>(
+    private val register: Window.(T) -> Unit,
+    private val unregister: Window.(T) -> Unit
+) {
+    private var value: T? = null
+
+    fun registerWithAndSet(window: Window, listener: T) {
+        window.register(listener)
+        value = listener
+    }
+
+    fun unregisterFromAndClear(window: Window) {
+        value?.let {
+            window.unregister(it)
+            value = null
+        }
+    }
+}
+
+internal fun windowStateListenerRef() = ListenerOnWindowRef<WindowStateListener>(
+    register = Window::addWindowStateListener,
+    unregister = Window::removeWindowStateListener
+)
+
+internal fun windowListenerRef() = ListenerOnWindowRef<WindowListener>(
+    register = Window::addWindowListener,
+    unregister = Window::removeWindowListener
+)
+
+internal fun componentListenerRef() = ListenerOnWindowRef<ComponentListener>(
+    register = Component::addComponentListener,
+    unregister = Component::removeComponentListener
+)
+
+internal val GraphicsConfiguration.density: Density
+    get() = Density(
+        defaultTransform.scaleX.toFloat(),
+        fontScale = 1f
+    )
+
+internal val Locale.layoutDirection: LayoutDirection
+    get() = ComponentOrientation.getOrientation(this).layoutDirection
+
+internal val ComponentOrientation.layoutDirection: LayoutDirection
+    get() = when {
+        isLeftToRight -> LayoutDirection.Ltr
+        isHorizontal -> LayoutDirection.Rtl
+        else -> LayoutDirection.Ltr
+    }
+
+internal fun layoutDirectionFor(component: Component): LayoutDirection {
+    val orientation = component.componentOrientation
+    return if (orientation != ComponentOrientation.UNKNOWN) {
+        orientation.layoutDirection
+    } else {
+        return component.locale.layoutDirection
+    }
+}
+
+internal val Component.density: Density get() = graphicsConfiguration.density
+
+internal fun Window.setIcon(painter: Painter?) {
+    setIconImage(
+        painter?.toAwtImage(
+            density,
+            layoutDirectionFor(this),
+            Size(192f, 192f)
+        )
+    )
+}
+
+internal fun Frame.setUndecoratedSafely(value: Boolean) {
+    if (this.isUndecorated != value) {
+        this.isUndecorated = value
+    }
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Suppress("unused")
+@Composable
+fun Window(
+    visible: Boolean = true,
+    onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
+    onKeyEvent: (KeyEvent) -> Boolean = { false },
+    create: () -> ComposeWindow,
+    dispose: (ComposeWindow) -> Unit,
+    update: (ComposeWindow) -> Unit = {},
+    content: @Composable FrameWindowScope.() -> Unit
+) {
+    val compositionLocalContext by rememberUpdatedState(currentCompositionLocalContext)
+    val windowExceptionHandlerFactory by rememberUpdatedState(
+        LocalWindowExceptionHandlerFactory.current
+    )
+    val layoutDirection = LocalLayoutDirection.current
+    AwtWindow(
+        visible = visible,
+        create = {
+            create().apply {
+                this.compositionLocalContext = compositionLocalContext
+                this.exceptionHandler = windowExceptionHandlerFactory.exceptionHandler(this)
+                setContent(onPreviewKeyEvent, onKeyEvent, content)
+            }
+        },
+        dispose = {
+            dispose(it)
+        },
+        update = { component ->
+            component.compositionLocalContext = compositionLocalContext
+            component.exceptionHandler = windowExceptionHandlerFactory.exceptionHandler(component)
+            component.componentOrientation = layoutDirection.componentOrientation
+            val wasDisplayable = component.isDisplayable
+            update(component)
+            if (!wasDisplayable && component.isDisplayable) {
+                component.contentPane.paint(component.contentPane.graphics)
+            }
+        },
+    )
+}
+
+internal val LayoutDirection.componentOrientation: ComponentOrientation
+    get() = when (this) {
+        LayoutDirection.Ltr -> ComponentOrientation.LEFT_TO_RIGHT
+        LayoutDirection.Rtl -> ComponentOrientation.RIGHT_TO_LEFT
+    }

+ 7 - 7
src/main/kotlin/eu/mjdev/desktop/windows/WindowEx.kt

@@ -1,15 +1,17 @@
 package eu.mjdev.desktop.windows
 
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.awt.ComposeWindow
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.window.FrameWindowScope
-import androidx.compose.ui.window.Window
 import androidx.compose.ui.window.WindowState
 import androidx.compose.ui.window.rememberWindowState
 
 @Composable
 fun WindowEx(
+    onCreate: (window: ComposeWindow) -> Unit = {},
+//    onOpen: (window: ComposeWindow) -> Unit = {},
     onCloseRequest: () -> Unit,
     state: WindowState = rememberWindowState(),
     visible: Boolean = true,
@@ -21,13 +23,15 @@ fun WindowEx(
     enabled: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = false,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     focusHelper: WindowFocusHelper,
     stateHelper: WindowStateHelper,
     content: @Composable FrameWindowScope.() -> Unit = {}
 ) = Window(
+    onCreate = onCreate,
+//    onOpen = onOpen,
     onCloseRequest = onCloseRequest,
     state = state,
     visible = visible,
@@ -39,16 +43,12 @@ fun WindowEx(
     enabled = enabled,
     focusable = focusable,
     alwaysOnTop = alwaysOnTop,
+//    alwaysOnBottom = alwaysOnBottom,
     onPreviewKeyEvent = onPreviewKeyEvent,
     onKeyEvent = onKeyEvent,
     content = {
         content()
         with(window) {
-            if (alwaysOnBottom) {
-                toBack()
-            } else if (alwaysOnTop) {
-                toFront()
-            }
             stateHelper.register(this)
             focusHelper.register(this)
         }

+ 61 - 0
src/main/kotlin/eu/mjdev/desktop/windows/WindowLocationTracker.kt

@@ -0,0 +1,61 @@
+package eu.mjdev.desktop.windows
+
+import java.awt.*
+import java.awt.event.WindowEvent
+import java.awt.event.WindowFocusListener
+
+object WindowLocationTracker {
+    private val cascadeOffset = Point(48, 48)
+
+    private var lastFocusedWindows = mutableSetOf<Window>()
+
+    private val focusListener = object : WindowFocusListener {
+        override fun windowGainedFocus(e: WindowEvent) {
+            lastFocusedWindows.remove(e.window)
+            lastFocusedWindows.add(e.window)
+        }
+
+        override fun windowLostFocus(e: WindowEvent) = Unit
+    }
+
+    fun onWindowCreated(window: Window) {
+        window.addWindowFocusListener(focusListener)
+    }
+
+    fun onWindowDisposed(window: Window) {
+        window.removeWindowFocusListener(focusListener)
+        lastFocusedWindows.remove(window)
+    }
+
+    val lastActiveGraphicsConfiguration: GraphicsConfiguration? get() =
+        lastFocusedWindows.lastOrNull()?.graphicsConfiguration
+
+    fun getCascadeLocationFor(window: Window): Point {
+        val lastWindow = lastFocusedWindows.lastOrNull()
+        val graphicsConfiguration = lastWindow?.graphicsConfiguration ?:
+            GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice?.defaultConfiguration
+        return if (graphicsConfiguration != null) {
+            val screenBounds = graphicsConfiguration.bounds
+            val screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(graphicsConfiguration)
+            val screenLeftTop = screenBounds.leftTop + Point(screenInsets.left, screenInsets.top)
+            val screenRightBottom = screenBounds.rightBottom - Point(screenInsets.right, screenInsets.bottom)
+            val lastLocation = lastWindow?.location ?: screenLeftTop
+            var location = lastLocation + cascadeOffset
+            val rightBottom = location + window.size.rightBottom
+            if (rightBottom.x > screenRightBottom.x || rightBottom.y > screenRightBottom.y) {
+                location = screenLeftTop + cascadeOffset
+            }
+            location
+        } else {
+            cascadeOffset
+        }
+    }
+}
+
+internal val Rectangle.leftTop get() = Point(x, y)
+internal val Rectangle.rightBottom get() = Point(x + width, y + height)
+internal val Dimension.rightBottom get() = Point(width, height)
+
+internal operator fun Point.plus(other: Point) = Point(x + other.x, y + other.y)
+internal operator fun Point.minus(other: Point) = Point(x - other.x, y - other.y)
+

+ 1 - 0
src/main/kotlin/eu/mjdev/desktop/windows/WindowStateHelper.kt

@@ -84,5 +84,6 @@ class WindowStateHelper(
 
     fun requestFocus() {
         window?.toFront()
+//        window?.requestFocus()
     }
 }