소스 검색

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.material.MaterialTheme
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 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.controlcenter.ControlCenter
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
 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.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.rememberDesktopProvider
 import eu.mjdev.desktop.windows.MainWindow
 import eu.mjdev.desktop.windows.MainWindow
 
 
 fun main() = application(
 fun main() = application(
     exitProcessOnExit = true
     exitProcessOnExit = true
 ) {
 ) {
-    val scope = rememberCoroutineScope()
-    val api = DesktopProvider(
-        scope = scope,
-        imageLoader = asyncImageLoader()
-    )
+    val api = rememberDesktopProvider()
     val controlCenterState = rememberVisibilityState()
     val controlCenterState = rememberVisibilityState()
     val panelState = rememberVisibilityState()
     val panelState = rememberVisibilityState()
     val menuState = rememberVisibilityState()
     val menuState = rememberVisibilityState()
@@ -38,6 +32,14 @@ fun main() = application(
     }
     }
 
 
     val handlePanelFocus: (Boolean) -> Unit = { //focus ->
     val handlePanelFocus: (Boolean) -> Unit = { //focus ->
+//        if (focus) {
+//            if (menuState.isVisible) {
+//                menuState.hide()
+//            }
+//            if (controlCenterState.isVisible) {
+//                controlCenterState.hide()
+//            }
+//        }
 //        val somethingVisible = menuState.isVisible || controlCenterState.isVisible
 //        val somethingVisible = menuState.isVisible || controlCenterState.isVisible
 //        if (!focus && !somethingVisible) {
 //        if (!focus && !somethingVisible) {
 //            panelState.hide()
 //            panelState.hide()
@@ -45,7 +47,7 @@ fun main() = application(
     }
     }
 
 
     val handleControlCenterFocus: (Boolean) -> Unit = { focus ->
     val handleControlCenterFocus: (Boolean) -> Unit = { focus ->
-        if (!focus) {
+        if (!menuState.isWindowFocus && !focus && !panelState.isWindowFocus) {
             controlCenterState.hide()
             controlCenterState.hide()
         }
         }
     }
     }
@@ -75,6 +77,7 @@ fun main() = application(
                 )
                 )
                 DesktopPanel(
                 DesktopPanel(
                     panelState = panelState,
                     panelState = panelState,
+                    menuState = menuState,
                     onMenuIconClicked = { menuState.toggle() },
                     onMenuIconClicked = { menuState.toggle() },
                     onFocusChange = { focus -> handlePanelFocus(focus) }
                     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.material.Divider
 import androidx.compose.runtime.*
 import androidx.compose.runtime.*
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 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.layout.onPlaced
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
 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.components.sliding.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.data.Category
 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.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.AppsMenuEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
 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
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindow
+import eu.mjdev.desktop.windows.ChromeWindowState
+import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 
 
+@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 @Composable
 fun AppsMenu(
 fun AppsMenu(
     api: DesktopProvider = LocalDesktop.current,
     api: DesktopProvider = LocalDesktop.current,
@@ -53,20 +65,28 @@ fun AppsMenu(
 ) {
 ) {
     val appMenuMinWidth by remember { api.currentUser.theme.appMenuMinWidthState }
     val appMenuMinWidth by remember { api.currentUser.theme.appMenuMinWidthState }
     val appMenuMinHeight by remember { api.currentUser.theme.appMenuMinHeightState }
     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) }
     var items: List<Any> by remember(api.appsProvider) { mutableStateOf(api.appsProvider.appCategories) }
     val onCategoryClick: (Category) -> Unit = { category ->
     val onCategoryClick: (Category) -> Unit = { category ->
         items = api.appsProvider.categoriesAndApps[category.name]?.sortedBy { it.name } ?: emptyList()
         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(
     ChromeWindow(
         visible = menuState.isVisible,
         visible = menuState.isVisible,
         enterAnimation = enterAnimation,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
         exitAnimation = exitAnimation,
-        position = WindowPosition.Absolute(
-            panelState.bounds.x,
-            api.containerSize.height - (panelState.bounds.height + appMenuMinHeight)
-        ),
+        windowState = windowState,
         onFocusChange = { focused ->
         onFocusChange = { focused ->
             menuState.onFocusChange(focused)
             menuState.onFocusChange(focused)
             onFocusChange(focused)
             onFocusChange(focused)
@@ -79,22 +99,33 @@ fun AppsMenu(
                     min = appMenuMinHeight,
                     min = appMenuMinHeight,
                     max = appMenuMinHeight
                     max = appMenuMinHeight
                 )
                 )
+                .dropShadow(
+                    RoundedCornerShape(24.dp),
+                    borderColor.alpha(0.5f),
+                    0.dp,
+                    0.dp,
+                    4.dp,
+                    0.dp
+                )
                 .onPlaced(menuState::onPlaced)
                 .onPlaced(menuState::onPlaced)
+                .onPointerEvent(PointerEventType.Enter) { windowState.requestFocus() }
         ) {
         ) {
             Box(
             Box(
                 modifier = Modifier
                 modifier = Modifier
                     .fillMaxSize()
                     .fillMaxSize()
                     .padding(menuPadding)
                     .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))
                     .clip(RoundedCornerShape(24.dp))
-                    .border(2.dp, Color.White.copy(0.1f), RoundedCornerShape(24.dp))
             ) {
             ) {
                 Column(
                 Column(
                     modifier = Modifier.fillMaxSize()
                     modifier = Modifier.fillMaxSize()
                 ) {
                 ) {
                     UserAvatar(
                     UserAvatar(
                         avatarSize = 64.dp,
                         avatarSize = 64.dp,
-                        backgroundColor = appMenuBackgroundColor,
+                        backgroundColor = backgroundColor,
+                        iconTintColor = iconsTintColor,
+                        textColor = textColor,
                         orientation = Orientation.Horizontal,
                         orientation = Orientation.Horizontal,
                         onUserAvatarClick = onUserAvatarClick
                         onUserAvatarClick = onUserAvatarClick
                     )
                     )
@@ -106,7 +137,7 @@ fun AppsMenu(
                                 start = 2.dp,
                                 start = 2.dp,
                                 end = 2.dp
                                 end = 2.dp
                             ),
                             ),
-                        color = Color.White.copy(0.1f),
+                        color = borderColor,
                         thickness = 2.dp
                         thickness = 2.dp
                     )
                     )
                     LazyColumn(
                     LazyColumn(
@@ -120,8 +151,9 @@ fun AppsMenu(
                                 items(items) { item ->
                                 items(items) { item ->
                                     AppsMenuCategory(
                                     AppsMenuCategory(
                                         category = item as Category,
                                         category = item as Category,
-                                        backgroundColor = appMenuBackgroundColor,
-                                        iconTint = Color.White,
+                                        backgroundColor = backgroundColor,
+                                        iconTint = textColor,
+                                        textColor = textColor,
                                         onClick = { onCategoryClick(item) },
                                         onClick = { onCategoryClick(item) },
                                         onContextMenuClick = { onCategoryContextMenuClick(item) }
                                         onContextMenuClick = { onCategoryContextMenuClick(item) }
                                     )
                                     )
@@ -132,8 +164,9 @@ fun AppsMenu(
                                 items(items) { item ->
                                 items(items) { item ->
                                     AppsMenuApp(
                                     AppsMenuApp(
                                         app = item as App,
                                         app = item as App,
-                                        backgroundColor = appMenuBackgroundColor,
-                                        iconTint = Color.White,
+                                        backgroundColor = backgroundColor,
+                                        iconTint = textColor,
+                                        textColor = textColor,
                                         onClick = {
                                         onClick = {
                                             onAppClick(item)
                                             onAppClick(item)
                                         },
                                         },
@@ -160,14 +193,16 @@ fun AppsMenu(
                                 start = 2.dp,
                                 start = 2.dp,
                                 end = 2.dp
                                 end = 2.dp
                             ),
                             ),
-                        color = Color.White.copy(0.1f),
+                        color = borderColor,
                         thickness = 2.dp
                         thickness = 2.dp
                     )
                     )
                     AppsBottomBar(
                     AppsBottomBar(
                         modifier = Modifier
                         modifier = Modifier
                             .fillMaxWidth()
                             .fillMaxWidth()
                             .wrapContentHeight()
                             .wrapContentHeight()
-                            .background(appMenuBackgroundColor),
+                            .background(backgroundColor),
+                        iconColor = borderColor,
+                        iconBackgroundColor = iconsTintColor,
                         backButtonVisible = items.first() is App,
                         backButtonVisible = items.first() is App,
                         onBackClick = {
                         onBackClick = {
                             items = api.appsProvider.appCategories
                             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 androidx.compose.ui.layout.ContentScale
 import eu.mjdev.desktop.components.image.ImageAny
 import eu.mjdev.desktop.components.image.ImageAny
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 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.helpers.internal.Queue.Companion.mutableQueue
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 
 
 @Preview
 @Preview
 @Composable
 @Composable
@@ -31,11 +33,7 @@ fun BackgroundImage(
     val switchDelay by remember { api.currentUser.theme.backgroundRotationDelayState }
     val switchDelay by remember { api.currentUser.theme.backgroundRotationDelayState }
     val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
     val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
     val userBackgrounds by remember { api.currentUser.config.desktopBackgroundsState }
     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) }
     val backgroundQueue = remember { mutableQueue(backgrounds) }
     var currentBackground: Any? by remember { mutableStateOf(backgroundQueue.value) }
     var currentBackground: Any? by remember { mutableStateOf(backgroundQueue.value) }
     Box(
     Box(
@@ -48,6 +46,9 @@ fun BackgroundImage(
                         fadeOut(animationSpec = tween(durationMillis = fadeOutDuration.toInt()))
                         fadeOut(animationSpec = tween(durationMillis = fadeOutDuration.toInt()))
             }
             }
         ) { value ->
         ) { value ->
+            api.scope.launch {
+                onChange(currentBackground)
+            }
             ImageAny(
             ImageAny(
                 modifier = modifier,
                 modifier = modifier,
                 contentScale = ContentScale.Crop,
                 contentScale = ContentScale.Crop,
@@ -59,7 +60,6 @@ fun BackgroundImage(
             if (switchDelay > 0) {
             if (switchDelay > 0) {
                 delay(switchDelay)
                 delay(switchDelay)
                 currentBackground = backgroundQueue.value
                 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.AnimationSpec
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.runtime.Composable
 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.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.TextStyle
 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.DonutChart
 import com.aay.compose.donutChart.model.PieChartData
 import com.aay.compose.donutChart.model.PieChartData
 import eu.mjdev.desktop.extensions.Compose.DarkDarkGray
 import eu.mjdev.desktop.extensions.Compose.DarkDarkGray
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 
 
 @Composable
 @Composable
 fun DualDonutChart(
 fun DualDonutChart(
@@ -30,7 +30,7 @@ fun DualDonutChart(
     animation: AnimationSpec<Float> = TweenSpec(durationMillis = 3000),
     animation: AnimationSpec<Float> = TweenSpec(durationMillis = 3000),
     legendPosition: LegendPosition = LegendPosition.BOTTOM,
     legendPosition: LegendPosition = LegendPosition.BOTTOM,
 ) {
 ) {
-    val data = remember(
+    val data by rememberCalculated(
         firstValue,
         firstValue,
         firstColor,
         firstColor,
         firstTitle,
         firstTitle,
@@ -38,16 +38,14 @@ fun DualDonutChart(
         secondColor,
         secondColor,
         secondTitle
         secondTitle
     ) {
     ) {
-        derivedStateOf {
-            listOf(
-                PieChartData(firstValue, firstColor, firstTitle),
-                PieChartData(secondValue, secondColor, secondTitle)
-            )
-        }
+        listOf(
+            PieChartData(firstValue, firstColor, firstTitle),
+            PieChartData(secondValue, secondColor, secondTitle)
+        )
     }
     }
     DonutChart(
     DonutChart(
         modifier = modifier,
         modifier = modifier,
-        pieChartData = data.value,
+        pieChartData = data,
         centerTitle = title,
         centerTitle = title,
         centerTitleStyle = TextStyle(color = textColor),
         centerTitleStyle = TextStyle(color = textColor),
         descriptionStyle = 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.animation.core.TweenSpec
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
 import com.aay.compose.baseComponents.model.LegendPosition
 import com.aay.compose.baseComponents.model.LegendPosition
 import eu.mjdev.desktop.components.draggable.DraggableView
 import eu.mjdev.desktop.components.draggable.DraggableView
 import eu.mjdev.desktop.extensions.Compose.DarkDarkGray
 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
 import eu.mjdev.desktop.helpers.system.MemInfo.Companion.toReadable
 import eu.mjdev.desktop.helpers.system.MemInfo.Companion.toReadable
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.delay
@@ -36,7 +35,7 @@ fun MemoryChart(
     modifier = modifier,
     modifier = modifier,
     dragEnabled = dragEnabled
     dragEnabled = dragEnabled
 ) {
 ) {
-    val memData = remember { mutableStateOf(MemInfo()) }
+    val memData = rememberState(MemInfo())
     DualDonutChart(
     DualDonutChart(
         modifier = modifier,
         modifier = modifier,
         title = mainTitle,
         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.SlidingMenu
 import eu.mjdev.desktop.components.sliding.VisibilityState
 import eu.mjdev.desktop.components.sliding.VisibilityState
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
 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.ControlCenterEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterExitAnimation
 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
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
 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.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 
 
@@ -41,8 +47,7 @@ import kotlinx.coroutines.launch
 @Composable
 @Composable
 fun ControlCenter(
 fun ControlCenter(
     api: DesktopProvider = LocalDesktop.current,
     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(),
     controlCenterState: VisibilityState = rememberVisibilityState(),
     scope: CoroutineScope = rememberCoroutineScope(),
     scope: CoroutineScope = rememberCoroutineScope(),
     enterAnimation: EnterTransition = ControlCenterEnterAnimation,
     enterAnimation: EnterTransition = ControlCenterEnterAnimation,
@@ -50,29 +55,31 @@ fun ControlCenter(
     onFocusChange: (Boolean) -> Unit = {},
     onFocusChange: (Boolean) -> Unit = {},
     onContextMenuClick: () -> 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 backgroundAlpha by remember { api.currentUser.theme.controlCenterBackgroundAlphaState }
     val backgroundInvoker = remember { { backgroundColor } }
     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 controlCenterExpandedWidth by remember { api.currentUser.theme.controlCenterExpandedWidthState }
     val controlCenterDividerWidth by remember { api.currentUser.theme.controlCenterDividerWidthState }
     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 controlCenterIconSize by remember { api.currentUser.theme.controlCenterIconSizeState }
     val pages by remember { api.controlCenterPagesState }
     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(
     ChromeWindow(
         visible = true,
         visible = true,
+        windowState = windowState,
         enterAnimation = enterAnimation,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
         exitAnimation = exitAnimation,
-        position = WindowPosition.Aligned(Alignment.TopEnd),
-        size = if (controlCenterState.isVisible) {
-            DpSize(controlCenterExpandedWidth, api.containerSize.height)
-        } else {
-            DpSize(controlCenterDividerWidth, api.containerSize.height)
-        },
         onFocusChange = { focused ->
         onFocusChange = { focused ->
             controlCenterState.onFocusChange(focused)
             controlCenterState.onFocusChange(focused)
             onFocusChange(focused)
             onFocusChange(focused)
@@ -82,11 +89,19 @@ fun ControlCenter(
             modifier = Modifier.fillMaxHeight(),
             modifier = Modifier.fillMaxHeight(),
             orientation = Horizontal,
             orientation = Horizontal,
             state = controlCenterState,
             state = controlCenterState,
+            onPointerEnter = {
+                controlCenterState.show()
+                windowState.requestFocus()
+            },
+            onPointerLeave = {
+                // controlCenterState.hide()
+            }
         ) { isVisible ->
         ) { isVisible ->
             if (!isVisible) {
             if (!isVisible) {
                 Divider(
                 Divider(
-                    modifier = Modifier.fillMaxHeight().width(controlCenterDividerWidth),
-                    color = Color.Transparent,
+                    modifier = Modifier.fillMaxHeight()
+                        .width(controlCenterDividerWidth),
+                    color = borderColor,
                     thickness = controlCenterDividerWidth
                     thickness = controlCenterDividerWidth
                 )
                 )
             } else {
             } else {
@@ -94,10 +109,12 @@ fun ControlCenter(
                     modifier = Modifier
                     modifier = Modifier
                         .fillMaxHeight()
                         .fillMaxHeight()
                         .wrapContentSize()
                         .wrapContentSize()
-                        .background(backgroundColor.copy(alpha = backgroundAlpha)),
+                        .background(
+                            backgroundColor.copy(alpha = backgroundAlpha)
+                        ),
                 ) {
                 ) {
                     Row(
                     Row(
-                        modifier = Modifier.fillMaxHeight().wrapContentSize(),
+                        modifier = Modifier.fillMaxHeight(),
                     ) {
                     ) {
                         Box(
                         Box(
                             contentAlignment = Alignment.TopEnd
                             contentAlignment = Alignment.TopEnd
@@ -117,7 +134,7 @@ fun ControlCenter(
                                             .padding(vertical = 8.dp)
                                             .padding(vertical = 8.dp)
                                             .size(controlCenterIconSize)
                                             .size(controlCenterIconSize)
                                             .background(
                                             .background(
-                                                color = if (isSelected) Color.White else Color.Transparent,
+                                                color = if (isSelected) iconsTintColor else borderColor,
                                                 shape = RoundedCornerShape(8.dp)
                                                 shape = RoundedCornerShape(8.dp)
                                             )
                                             )
                                             .pointerInput(Unit) {
                                             .pointerInput(Unit) {
@@ -138,7 +155,7 @@ fun ControlCenter(
                                             },
                                             },
                                         imageVector = page.icon,
                                         imageVector = page.icon,
                                         contentDescription = "",
                                         contentDescription = "",
-                                        tint = if (isSelected) backgroundColor else controlCenterIconColor
+                                        tint = if (isSelected) backgroundColor else textColor
                                     )
                                     )
                                 }
                                 }
                             }
                             }
@@ -147,30 +164,30 @@ fun ControlCenter(
                                     .padding(
                                     .padding(
                                         end = controlCenterIconSize.width + 16.dp
                                         end = controlCenterIconSize.width + 16.dp
                                     )
                                     )
-                                    .fillMaxHeight()
-                                    .wrapContentSize(),
+                                    .fillMaxHeight(),
                             ) {
                             ) {
-                                Row {
+                                Row(
+                                    Modifier.fillMaxHeight()
+                                ) {
                                     Divider(
                                     Divider(
-                                        modifier = Modifier
-                                            .fillMaxHeight()
+                                        modifier = Modifier.fillMaxHeight()
                                             .width(2.dp),
                                             .width(2.dp),
-                                        color = Color.White.copy(0.1f),
+                                        color = borderColor,
                                         thickness = 2.dp
                                         thickness = 2.dp
                                     )
                                     )
                                     Box(
                                     Box(
-                                        modifier = Modifier
-                                            .width(controlCenterExpandedWidth)
-                                            .rightShadow(
-                                                color = shadowColor,
-                                                offsetX = 4.dp,
-                                                blur = 10.dp,
-                                            ),
+                                        modifier = Modifier.width(controlCenterExpandedWidth),
                                     ) {
                                     ) {
                                         with(pagesFiltered.value[pagerState.value]) {
                                         with(pagesFiltered.value[pagerState.value]) {
                                             content(ControlCenterPageScope(api, backgroundInvoker, cache))
                                             content(ControlCenterPageScope(api, backgroundInvoker, cache))
                                         }
                                         }
                                     }
                                     }
+                                    Divider(
+                                        modifier = Modifier.fillMaxHeight()
+                                            .width(2.dp),
+                                        color = borderColor,
+                                        thickness = 2.dp
+                                    )
                                 }
                                 }
                             }
                             }
                             Divider(
                             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.Icons
 import androidx.compose.material.icons.filled.Campaign
 import androidx.compose.material.icons.filled.Campaign
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 
 
 @Suppress("FunctionName")
 @Suppress("FunctionName")
@@ -25,7 +25,7 @@ fun AIPage() = ControlCenterPage(
     condition = { ai.isAvailable() }
     condition = { ai.isAvailable() }
 ) {
 ) {
     val questionsList = remember { mutableStateListOf<Pair<String, String>>() }
     val questionsList = remember { mutableStateListOf<Pair<String, String>>() }
-    val request = remember { mutableStateOf("") }
+    val request = rememberState("")
     val onDone: (String) -> Unit = { what ->
     val onDone: (String) -> Unit = { what ->
         if (what.isNotEmpty()) {
         if (what.isNotEmpty()) {
             questionsList.add(Pair(what, ""))
             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.Divider
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Home
 import androidx.compose.material.icons.filled.Home
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.custom.DateTime
 import eu.mjdev.desktop.components.custom.DateTime
 import eu.mjdev.desktop.components.custom.PowerBlock
 import eu.mjdev.desktop.components.custom.PowerBlock
 import eu.mjdev.desktop.components.custom.UserAvatar
 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.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")
 @Suppress("FunctionName")
 fun MainSettingsPage() = ControlCenterPage(
 fun MainSettingsPage() = ControlCenterPage(
     icon = Icons.Filled.Home,
     icon = Icons.Filled.Home,
     name = "Home"
     name = "Home"
 ) {
 ) {
+    val backgroundColor by rememberBackgroundColor(api)
+    val textColor by rememberTextColor(api)
+    val borderColor by rememberBorderColor(api)
+    val iconsTintColor by rememberIconTintColor(api)
     Box(
     Box(
         modifier = Modifier.fillMaxSize()
         modifier = Modifier.fillMaxSize()
     ) {
     ) {
@@ -27,10 +36,12 @@ fun MainSettingsPage() = ControlCenterPage(
             modifier = Modifier
             modifier = Modifier
                 .fillMaxWidth()
                 .fillMaxWidth()
                 .align(Alignment.TopCenter)
                 .align(Alignment.TopCenter)
-                .rectShadow(8.dp, Color.Black.copy(alpha = 0.3f))
+                .rectShadow(8.dp, borderColor.copy(alpha = 0.3f))
         ) {
         ) {
             UserAvatar(
             UserAvatar(
                 backgroundColor = backgroundColor,
                 backgroundColor = backgroundColor,
+                iconTintColor = iconsTintColor,
+                textColor = textColor,
                 avatarSize = 128.dp,
                 avatarSize = 128.dp,
                 orientation = Orientation.Vertical,
                 orientation = Orientation.Vertical,
                 onUserAvatarClick = {
                 onUserAvatarClick = {
@@ -38,11 +49,13 @@ fun MainSettingsPage() = ControlCenterPage(
                 }
                 }
             )
             )
             Divider(
             Divider(
-                modifier = Modifier.fillMaxWidth().height(2.dp),
-                color = Color.Transparent,
+                modifier = Modifier.fillMaxWidth()
+                    .height(2.dp),
+                color = borderColor,
                 thickness = 2.dp
                 thickness = 2.dp
             )
             )
             DateTime(
             DateTime(
+                textColor = textColor,
                 backgroundColor = backgroundColor
                 backgroundColor = backgroundColor
             )
             )
         }
         }
@@ -81,6 +94,9 @@ fun MainSettingsPage() = ControlCenterPage(
             ) {
             ) {
                 PowerBlock(
                 PowerBlock(
                     backgroundColor = backgroundColor,
                     backgroundColor = backgroundColor,
+                    shadowColor=borderColor.alpha(0.3f),
+                    iconTintColor = iconsTintColor,
+                    textColor = textColor,
                     onPowerButtonClick = {
                     onPowerButtonClick = {
                         // todo : dialog
                         // todo : dialog
                         api.shutdown()
                         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
 @Composable
 fun DateTime(
 fun DateTime(
     backgroundColor: Color,
     backgroundColor: Color,
+    textColor: Color = Color.White
 ) = Box(
 ) = Box(
     modifier = Modifier
     modifier = Modifier
         .fillMaxWidth()
         .fillMaxWidth()
@@ -21,6 +22,8 @@ fun DateTime(
         .padding(16.dp)
         .padding(16.dp)
 ) {
 ) {
     Clock(
     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(
 fun PowerBlock(
     backgroundColor: Color,
     backgroundColor: Color,
     shadowColor: Color = Color.Black.copy(alpha = 0.3f),
     shadowColor: Color = Color.Black.copy(alpha = 0.3f),
+    textColor: Color = Color.Black,
+    iconTintColor: Color = Color.White,
     bottomBoxHeight: Dp = 128.dp,
     bottomBoxHeight: Dp = 128.dp,
     iconHeight: Dp = 64.dp,
     iconHeight: Dp = 64.dp,
     onPowerButtonClick: () -> Unit = {}
     onPowerButtonClick: () -> Unit = {}
@@ -62,12 +64,12 @@ fun PowerBlock(
             Image(
             Image(
                 modifier = Modifier
                 modifier = Modifier
                     .size(64.dp)
                     .size(64.dp)
-                    .background(Color.White, CircleShape)
+                    .background(iconTintColor, CircleShape)
                     .clipCircle()
                     .clipCircle()
                     .padding(8.dp),
                     .padding(8.dp),
                 imageVector = Icons.Filled.SettingsPower,
                 imageVector = Icons.Filled.SettingsPower,
                 contentDescription = "",
                 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
 package eu.mjdev.desktop.components.custom
 
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.layout.*
 import androidx.compose.material.Button
 import androidx.compose.material.Button
 import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
 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.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Dp
 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.noElevation
 import eu.mjdev.desktop.extensions.Compose.transparent
 import eu.mjdev.desktop.extensions.Compose.transparent
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
@@ -30,6 +32,8 @@ fun UserAvatar(
     avatarSize: Dp = 128.dp,
     avatarSize: Dp = 128.dp,
     orientation: Orientation = Orientation.Vertical,
     orientation: Orientation = Orientation.Vertical,
     api: DesktopProvider = LocalDesktop.current,
     api: DesktopProvider = LocalDesktop.current,
+    iconTintColor: Color = Color.White,
+    textColor: Color = Color.White,
     onUserAvatarClick: () -> Unit
     onUserAvatarClick: () -> Unit
 ) = Box(
 ) = Box(
     modifier = Modifier
     modifier = Modifier
@@ -53,21 +57,33 @@ fun UserAvatar(
                 colors = ButtonDefaults.transparent(),
                 colors = ButtonDefaults.transparent(),
                 elevation = ButtonDefaults.noElevation()
                 elevation = ButtonDefaults.noElevation()
             ) {
             ) {
-                Image(
+                ImageAny(
                     modifier = Modifier
                     modifier = Modifier
                         .size(avatarSize)
                         .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 = ""
                     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(
         Orientation.Horizontal -> Row(
@@ -85,21 +101,31 @@ fun UserAvatar(
                 colors = ButtonDefaults.transparent(),
                 colors = ButtonDefaults.transparent(),
                 elevation = ButtonDefaults.noElevation()
                 elevation = ButtonDefaults.noElevation()
             ) {
             ) {
-                Image(
+                ImageAny(
                     modifier = Modifier
                     modifier = Modifier
                         .size(avatarSize)
                         .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 = ""
                     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
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.data.App
 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.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.extensions.Modifier.topShadow
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelExitAnimation
 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
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindow
+import kotlinx.coroutines.delay
 
 
 @OptIn(ExperimentalFoundationApi::class)
 @OptIn(ExperimentalFoundationApi::class)
 @Suppress("FunctionName")
 @Suppress("FunctionName")
 @Composable
 @Composable
 fun DesktopPanel(
 fun DesktopPanel(
     api: DesktopProvider = LocalDesktop.current,
     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,
     dividerWidth: Dp = 4.dp,
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
@@ -50,23 +56,17 @@ fun DesktopPanel(
     showMenuIcon: Boolean = true,
     showMenuIcon: Boolean = true,
     panelContentPadding: PaddingValues = PaddingValues(4.dp),
     panelContentPadding: PaddingValues = PaddingValues(4.dp),
     panelState: VisibilityState = rememberVisibilityState(),
     panelState: VisibilityState = rememberVisibilityState(),
+    menuState: VisibilityState = rememberVisibilityState(),
     enterAnimation: EnterTransition = DesktopPanelEnterAnimation,
     enterAnimation: EnterTransition = DesktopPanelEnterAnimation,
     exitAnimation: ExitTransition = DesktopPanelExitAnimation,
     exitAnimation: ExitTransition = DesktopPanelExitAnimation,
     tooltipHeight: Dp = 64.dp,
     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 ->
     tooltipConverter: (Any?) -> TooltipData = { item ->
         when (item) {
         when (item) {
             is App -> TooltipData(title = item.name, description = item.comment)
             is App -> TooltipData(title = item.name, description = item.comment)
             else -> TooltipData(title = item.toString())
             else -> TooltipData(title = item.toString())
         }
         }
     },
     },
+    tooltipDelay: Long = 1000L,
     onMenuIconClicked: () -> Unit = {
     onMenuIconClicked: () -> Unit = {
         // todo
         // todo
     },
     },
@@ -82,7 +82,18 @@ fun DesktopPanel(
         // todo
         // 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(
     ChromeWindow(
         visible = true,
         visible = true,
         enterAnimation = enterAnimation,
         enterAnimation = enterAnimation,
@@ -101,6 +112,15 @@ fun DesktopPanel(
             modifier = Modifier.fillMaxWidth(),
             modifier = Modifier.fillMaxWidth(),
             orientation = Vertical,
             orientation = Vertical,
             state = panelState,
             state = panelState,
+            onPointerEnter = {
+                panelState.show()
+                // todo focus
+            },
+            onPointerLeave = {
+                if(!menuState.isVisible) {
+                    panelState.hide()
+                }
+            }
         ) { isVisible ->
         ) { isVisible ->
             Divider(
             Divider(
                 modifier = Modifier
                 modifier = Modifier
@@ -114,8 +134,11 @@ fun DesktopPanel(
                     modifier = Modifier.fillMaxWidth().wrapContentHeight(),
                     modifier = Modifier.fillMaxWidth().wrapContentHeight(),
                     tooltip = {
                     tooltip = {
                         DesktopPanelTooltip(
                         DesktopPanelTooltip(
-                            tooltipData.value,
-                            converter = tooltipConverter
+                            textColor = textColor,
+                            borderColor = borderColor,
+                            item = tooltipData,
+                            converter = tooltipConverter,
+                            backgroundColor = backgroundColor
                         )
                         )
                     },
                     },
                     tooltipPlacement = TooltipPlacement.CursorPoint(
                     tooltipPlacement = TooltipPlacement.CursorPoint(
@@ -143,9 +166,8 @@ fun DesktopPanel(
                                     )
                                     )
                                 )
                                 )
                                 .topShadow(
                                 .topShadow(
-                                    color = backgroundColor.copy(alpha = 0.3f),
-                                    offsetY = 4.dp,
-                                    blur = 10.dp
+                                    color = borderColor.alpha(0.3f),
+                                    blur = 4.dp
                                 )
                                 )
                                 .onPlaced(panelState::onPlaced),
                                 .onPlaced(panelState::onPlaced),
                         ) {
                         ) {
@@ -153,7 +175,7 @@ fun DesktopPanel(
                                 modifier = Modifier
                                 modifier = Modifier
                                     .fillMaxWidth()
                                     .fillMaxWidth()
                                     .height(2.dp),
                                     .height(2.dp),
-                                color = Color.White.copy(0.1f),
+                                color = borderColor,
                                 thickness = 2.dp
                                 thickness = 2.dp
                             )
                             )
                             Box(
                             Box(
@@ -164,8 +186,8 @@ fun DesktopPanel(
                                 if (showMenuIcon) {
                                 if (showMenuIcon) {
                                     DesktopMenuIcon(
                                     DesktopMenuIcon(
                                         modifier = Modifier.align(Alignment.CenterStart),
                                         modifier = Modifier.align(Alignment.CenterStart),
-                                        iconColor = iconColor,
-                                        iconBackgroundColor = iconBackgroundColor,
+                                        iconColor = borderColor,
+                                        iconBackgroundColor = iconsTintColor,
                                         iconSize = iconSize,
                                         iconSize = iconSize,
                                         iconPadding = iconPadding,
                                         iconPadding = iconPadding,
                                         iconOuterPadding = iconOuterPadding,
                                         iconOuterPadding = iconOuterPadding,
@@ -176,9 +198,9 @@ fun DesktopPanel(
                                 }
                                 }
                                 DesktopPanelFavoriteApps(
                                 DesktopPanelFavoriteApps(
                                     modifier = Modifier.align(Alignment.Center),
                                     modifier = Modifier.align(Alignment.Center),
-                                    iconColor = iconColor,
-                                    iconBackgroundColor = iconBackgroundColor,
-                                    iconColorRunning = iconColorRunning,
+                                    iconColor = borderColor,
+                                    iconBackgroundColor = iconsTintColor,
+                                    iconColorRunning = iconsTintColor.lighter(0.3f),
                                     iconSize = iconSize,
                                     iconSize = iconSize,
                                     iconPadding = iconPadding,
                                     iconPadding = iconPadding,
                                     iconOuterPadding = iconOuterPadding,
                                     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
 package eu.mjdev.desktop.components.desktoppanel
 
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 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.Button
 import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.remember
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 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.data.App
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.noElevation
 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.extensions.Modifier.clipRect
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
@@ -43,7 +40,7 @@ fun DesktopPanelIcon(
     iconSize: DpSize = DpSize(48.dp, 48.dp),
     iconSize: DpSize = DpSize(48.dp, 48.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
-    iconState: MutableState<Boolean> = remember { mutableStateOf(false) },
+    iconState: MutableState<Boolean> = rememberState(false),
     contentDescription: String? = null,
     contentDescription: String? = null,
     onToolTip: (item: Any?) -> Unit,
     onToolTip: (item: Any?) -> Unit,
     onClick: () -> Unit,
     onClick: () -> Unit,
@@ -90,8 +87,8 @@ fun DesktopPanelIcon(
         }
         }
         if (isStarting) {
         if (isStarting) {
             CircularProgressIndicator(
             CircularProgressIndicator(
-                modifier = Modifier.size(iconSize),
-                color = Color.White,
+                modifier = Modifier.padding(4.dp).size(iconSize),
+                color = iconColor,
                 strokeWidth = 4.dp,
                 strokeWidth = 4.dp,
                 backgroundColor = Color.Transparent
                 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.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
@@ -21,6 +19,7 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.color
 import eu.mjdev.desktop.extensions.Compose.noElevation
 import eu.mjdev.desktop.extensions.Compose.noElevation
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 
 @OptIn(ExperimentalComposeUiApi::class)
 @OptIn(ExperimentalComposeUiApi::class)
 @Preview
 @Preview
@@ -31,7 +30,7 @@ fun DesktopPanelText(
     textColor: Color = Color.White,
     textColor: Color = Color.White,
     backgroundHover: Color = Color.Red,
     backgroundHover: Color = Color.Red,
     textPadding: PaddingValues = PaddingValues(4.dp),
     textPadding: PaddingValues = PaddingValues(4.dp),
-    buttonState: MutableState<Boolean> = remember { mutableStateOf(false) },
+    buttonState: MutableState<Boolean> = rememberState(false),
     onToolTip: (item: Any?) -> Unit = {},
     onToolTip: (item: Any?) -> Unit = {},
     onClick: () -> Unit,
     onClick: () -> Unit,
 ) = Box(
 ) = 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.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.unit.sp
 import eu.mjdev.desktop.components.text.TextAny
 import eu.mjdev.desktop.components.text.TextAny
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 
 @Composable
 @Composable
 fun DesktopPanelTooltip(
 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()) }
     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)) {
         with(converter(item)) {
             Column {
             Column {
                 if (title.isNotEmpty()) {
                 if (title.isNotEmpty()) {
                     TextAny(
                     TextAny(
                         text = title,
                         text = title,
-                        color = Color.White,
+                        color = textColor,
                         fontSize = 14.sp
                         fontSize = 14.sp
                     )
                     )
                 }
                 }
                 if (description.isNotEmpty()) {
                 if (description.isNotEmpty()) {
                     TextAny(
                     TextAny(
                         text = description,
                         text = description,
-                        color = Color.White,
+                        color = textColor,
                         fontSize = 12.sp
                         fontSize = 12.sp
                     )
                     )
                 }
                 }
             }
             }
         }
         }
     }
     }
-}
+} else Unit
 
 
 data class TooltipData(
 data class TooltipData(
     val title: String = "",
     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.foundation.lazy.items
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
@@ -17,6 +15,7 @@ import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanelIcon
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanelIcon
 import eu.mjdev.desktop.data.App
 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
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 
@@ -34,7 +33,7 @@ fun DesktopPanelFavoriteApps(
     onAppClick: (app: App) -> Unit,
     onAppClick: (app: App) -> Unit,
     onContextMenuClick: (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(
     Box(
         modifier = modifier
         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.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 
 @Composable
 @Composable
 fun DraggableView(
 fun DraggableView(
@@ -23,8 +24,8 @@ fun DraggableView(
     contentPadding: PaddingValues = PaddingValues(0.dp),
     contentPadding: PaddingValues = PaddingValues(0.dp),
     content: @Composable BoxScope.() -> Unit = {}
     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(
     Box(
         modifier = modifier
         modifier = modifier
             .graphicsLayer(
             .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.text.style.TextOverflow
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.unit.sp
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 
 @Composable
 @Composable
 fun AutoResizeText(
 fun AutoResizeText(
@@ -35,7 +36,7 @@ fun AutoResizeText(
     style: TextStyle = LocalTextStyle.current,
     style: TextStyle = LocalTextStyle.current,
 ) {
 ) {
     var fontSizeValue by remember(text, fontSizeRange) { mutableStateOf(fontSizeRange.max.value) }
     var fontSizeValue by remember(text, fontSizeRange) { mutableStateOf(fontSizeRange.max.value) }
-    var readyToDraw by remember { mutableStateOf(false) }
+    var readyToDraw by rememberState(false)
     Text(
     Text(
         text = text,
         text = text,
         color = color,
         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")
 @Suppress("MemberVisibilityCanBePrivate")
 class User(
 class User(
     name: String?,
     name: String?,
-    picture: ImageVector,
+    picture: Any?,
     config: DesktopConfig,
     config: DesktopConfig,
     theme: Theme
     theme: Theme
 ) {
 ) {

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

@@ -1,19 +1,46 @@
 package eu.mjdev.desktop.extensions
 package eu.mjdev.desktop.extensions
 
 
 import androidx.compose.ui.graphics.*
 import androidx.compose.ui.graphics.*
+import eu.mjdev.desktop.extensions.ColorUtils.nonAlphaValue
 import eu.mjdev.desktop.helpers.bitmap.Bitmap
 import eu.mjdev.desktop.helpers.bitmap.Bitmap
 import org.jetbrains.skiko.toBufferedImage
 import org.jetbrains.skiko.toBufferedImage
 
 
-typealias  SkiaBitmap = org.jetbrains.skia.Bitmap
+typealias SkiaBitmap = org.jetbrains.skia.Bitmap
 
 
 @Suppress("MemberVisibilityCanBePrivate", "unused")
 @Suppress("MemberVisibilityCanBePrivate", "unused")
 object BitmapUtils {
 object BitmapUtils {
 
 
     fun SkiaBitmap.toBitmap() = Bitmap(toBufferedImage())
     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
 package eu.mjdev.desktop.extensions
 
 
+import androidx.compose.material.MaterialTheme
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.graphics.toArgb
 import java.util.*
 import java.util.*
-import kotlin.math.cos
 
 
 @Suppress("unused", "UnusedReceiverParameter")
 @Suppress("unused", "UnusedReceiverParameter")
 object ColorUtils {
 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 {
     fun Color.r(r: Int): Color {
         return 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 ->
     fun Color.randomColor(alpha: Float): Color = Random().let { rnd ->
         Color(
         Color(
             rnd.nextInt(256),
             rnd.nextInt(256),
             rnd.nextInt(256),
             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.foundation.layout.PaddingValues
 import androidx.compose.material.ButtonDefaults
 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.graphics.Color
 import androidx.compose.ui.unit.*
 import androidx.compose.ui.unit.*
 import coil3.ImageLoader
 import coil3.ImageLoader
@@ -116,7 +114,19 @@ object Compose {
         .memoryCache { imageLoaderMemoryCache }
         .memoryCache { imageLoaderMemoryCache }
         .build()
         .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
 //        }, AWTEvent.MOUSE_EVENT_MASK or AWTEvent.FOCUS_EVENT_MASK
 //    )
 //    )
 
 
@@ -126,4 +136,6 @@ object Compose {
 //        }
 //        }
 //    }, AWTEvent.WINDOW_EVENT_MASK)
 //    }, 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(
     fun Modifier.rightShadow(
         color: Color,
         color: Color,
-        offsetX: Dp,
-        blur: Dp
+        offsetX: Dp = 0.dp,
+        blur: Dp = 8.dp,
     ) = dropShadow(
     ) = dropShadow(
         shape = RectangleShape,
         shape = RectangleShape,
         color = color,
         color = color,
@@ -141,8 +141,8 @@ object Modifier {
 
 
     fun Modifier.leftShadow(
     fun Modifier.leftShadow(
         color: Color,
         color: Color,
-        offsetX: Dp,
-        blur: Dp
+        offsetX: Dp = 0.dp,
+        blur: Dp = 8.dp
     ) = dropShadow(
     ) = dropShadow(
         shape = RectangleShape,
         shape = RectangleShape,
         color = color,
         color = color,
@@ -154,8 +154,8 @@ object Modifier {
 
 
     fun Modifier.topShadow(
     fun Modifier.topShadow(
         color: Color,
         color: Color,
-        offsetY: Dp,
-        blur: Dp
+        offsetY: Dp = 0.dp,
+        blur: Dp = 8.dp
     ) = dropShadow(
     ) = dropShadow(
         shape = RectangleShape,
         shape = RectangleShape,
         color = color,
         color = color,
@@ -167,8 +167,8 @@ object Modifier {
 
 
     fun Modifier.bottomShadow(
     fun Modifier.bottomShadow(
         color: Color,
         color: Color,
-        offsetY: Dp,
-        blur: Dp
+        offsetY: Dp = 0.dp,
+        blur: Dp = 8.dp
     ) = dropShadow(
     ) = dropShadow(
         shape = RectangleShape,
         shape = RectangleShape,
         color = color,
         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.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 
 
 @Suppress("MemberVisibilityCanBePrivate")
 @Suppress("MemberVisibilityCanBePrivate")
@@ -15,15 +16,19 @@ class AdbDiscover(
 
 
     init {
     init {
         coroutineScope?.launch {
         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)
                 delay(discoverDelay)
             }
             }

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

@@ -78,8 +78,7 @@ object AdbServer {
                 } else {
                 } else {
                     parts[0]
                     parts[0]
                 }
                 }
-            }
-            .map { createDadb(adbServerHost, adbServerPort, "host:transport:${it}") }
+            }.map { createDadb(adbServerHost, adbServerPort, "host:transport:${it}") }
     }
     }
 
 
     internal fun readString(inputStream: DataInputStream): String {
     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 javax.imageio.stream.ImageOutputStream
 import kotlin.math.abs
 import kotlin.math.abs
 
 
-
 @Suppress("unused", "UNUSED_PARAMETER")
 @Suppress("unused", "UNUSED_PARAMETER")
 class Bitmap(
 class Bitmap(
     val image: BufferedImage
     val image: BufferedImage
@@ -41,6 +40,9 @@ class Bitmap(
         RGBA_1010102,
         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 {
     fun compress(format: CompressFormat, quality: Int, sink: Sink): Boolean {
         require(quality in 0..100) { "quality must be 0..100" }
         require(quality in 0..100) { "quality must be 0..100" }
         val qualityFloat = quality.toFloat() / 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 {
     companion object {
         fun Bitmap.applyCanvas(block: Canvas.() -> Unit): Bitmap {
         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
 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 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.Compose.SuperDarkGray
 import eu.mjdev.desktop.extensions.Image.loadPicture
 import eu.mjdev.desktop.extensions.Image.loadPicture
+import eu.mjdev.desktop.provider.DesktopProvider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 
 
-@Suppress("unused", "MemberVisibilityCanBePrivate")
+@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
 class Palette(
 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)
     val backgroundColorState = mutableStateOf(baseColor)
     var backgroundColor
     var backgroundColor
@@ -23,24 +28,68 @@ class Palette(
             backgroundColorState.value = value
             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 {
     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 {
     companion object {
         @Composable
         @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,
         val desktop: Long,
         override val window: Window = Window(id)
         override val window: Window = Window(id)
     ) : IWindow {
     ) : IWindow {
-        override fun toBack() {
-            // todo
-        }
-
         override fun toFront() {
         override fun toFront() {
             windowTracker.activateWindow(window)
             windowTracker.activateWindow(window)
         }
         }
@@ -369,7 +365,6 @@ class WindowsManager {
     interface IWindow {
     interface IWindow {
         val window: Window
         val window: Window
 
 
-        fun toBack() {}
         fun toFront() {}
         fun toFront() {}
         fun minimize() {}
         fun minimize() {}
         fun maximize() {}
         fun maximize() {}

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

@@ -1,19 +1,20 @@
 package eu.mjdev.desktop.provider
 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.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import coil3.ImageLoader
 import coil3.ImageLoader
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.pages.*
 import eu.mjdev.desktop.components.controlcenter.pages.*
 import eu.mjdev.desktop.data.User
 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.adb.AdbDiscover.Companion.adbDevicesHandler
+import eu.mjdev.desktop.helpers.internal.Palette
 import eu.mjdev.desktop.helpers.managers.ConnectivityManager
 import eu.mjdev.desktop.helpers.managers.ConnectivityManager
 import eu.mjdev.desktop.helpers.managers.FileSystemWatcher
 import eu.mjdev.desktop.helpers.managers.FileSystemWatcher
 import eu.mjdev.desktop.helpers.managers.KCEFHelper
 import eu.mjdev.desktop.helpers.managers.KCEFHelper
 import eu.mjdev.desktop.helpers.managers.WindowsManager
 import eu.mjdev.desktop.helpers.managers.WindowsManager
+import eu.mjdev.desktop.helpers.system.Command
 import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
 import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Dispatchers
@@ -31,15 +32,20 @@ class DesktopProvider(
 //    val scriptManager: ScriptEngineManager = ScriptEngineManager(),
 //    val scriptManager: ScriptEngineManager = ScriptEngineManager(),
     val kcefHelper: KCEFHelper = KCEFHelper(scope),
     val kcefHelper: KCEFHelper = KCEFHelper(scope),
     val ai: AIProvider = AIProvider(scope, AiPluginGemini(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: User by lazy { User.load() }
     private val _currentUser: MutableState<User> = mutableStateOf(__currentUser)
     private val _currentUser: MutableState<User> = mutableStateOf(__currentUser)
     val currentUser
     val currentUser
         get() = _currentUser.value
         get() = _currentUser.value
 
 
+    val palette: Palette = Palette(scope, currentUser.theme.backgroundColor)
+
     val controlCenterPagesState: MutableState<List<ControlCenterPage>> = mutableStateOf(CONTROL_CENTER_PAGES)
     val controlCenterPagesState: MutableState<List<ControlCenterPage>> = mutableStateOf(CONTROL_CENTER_PAGES)
 
 
+    val machineName
+        get() = Command("hostname").execute()
+
 //    private val engine: ScriptEngine by lazy {
 //    private val engine: ScriptEngine by lazy {
 //        scriptManager.getEngineByName("JavaScript").apply {
 //        scriptManager.getEngineByName("JavaScript").apply {
 //            // todo
 //            // todo
@@ -143,5 +149,13 @@ class DesktopProvider(
         val LocalDesktop = compositionLocalOf {
         val LocalDesktop = compositionLocalOf {
             DesktopProvider()
             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,
     enabled: Boolean = true,
     focusable: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = true,
     alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+    onCreate: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: () -> Unit = {},
-    onClosed: () -> Unit = {},
+    onOpened: ChromeWindowState.() -> Unit = {},
+    onClosed: ChromeWindowState.() -> Unit = {},
     content: @Composable ChromeWindowScope.() -> Unit = {}
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) = ChromeWindow(
 ) = 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
 @Composable
 fun ChromeWindow(
 fun ChromeWindow(
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
@@ -65,34 +68,36 @@ fun ChromeWindow(
     enabled: Boolean = true,
     enabled: Boolean = true,
     focusable: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = true,
     alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+    onCreate: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: () -> Unit = {},
-    onClosed: () -> Unit = {},
+    onOpened: ChromeWindowState.() -> Unit = {},
+    onClosed: ChromeWindowState.() -> Unit = {},
     content: @Composable ChromeWindowScope.() -> Unit = {}
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) = ChromeWindow(
 ) = 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
 @Composable
@@ -104,34 +109,46 @@ fun ChromeWindow(
     enabled: Boolean = true,
     enabled: Boolean = true,
     focusable: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = true,
     alwaysOnTop: Boolean = true,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     enterAnimation: EnterTransition = fadeIn() + slideInVertically(initialOffsetY = { it }),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
     exitAnimation: ExitTransition = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
     onCloseRequest: () -> Unit = {},
     onCloseRequest: () -> Unit = {},
+    onCreate: ChromeWindowState.() -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
-    onOpened: () -> Unit = {},
-    onClosed: () -> Unit = {},
+    onOpened: ChromeWindowState.() -> Unit = {},
+    onClosed: ChromeWindowState.() -> Unit = {},
     content: @Composable ChromeWindowScope.() -> Unit = {}
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) {
 ) {
     val animState = rememberAnimState(visible)
     val animState = rememberAnimState(visible)
     val wnState = rememberWnState()
     val wnState = rememberWnState()
     windowState.onOpened.add {
     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()
         windowState.updatePositionAndSize()
         if (visible) {
         if (visible) {
             animState.value.targetState = true
             animState.value.targetState = true
         }
         }
-        onOpened()
+        onOpened(windowState)
     }
     }
     windowState.onClosed.add(onClosed)
     windowState.onClosed.add(onClosed)
     windowState.onFocusChange.add(onFocusChange)
     windowState.onFocusChange.add(onFocusChange)
     WindowEx(
     WindowEx(
+        onCreate = { window ->
+            windowState.window = window
+            onCreate(windowState)
+        },
+//        onOpen = {
+//            onOpened(windowState)
+//        },
         onCloseRequest,
         onCloseRequest,
         windowState,
         windowState,
         wnState.value,
         wnState.value,
@@ -143,7 +160,7 @@ fun ChromeWindow(
         enabled,
         enabled,
         focusable,
         focusable,
         alwaysOnTop,
         alwaysOnTop,
-        alwaysOnBottom,
+//        alwaysOnBottom,
         onPreviewKeyEvent,
         onPreviewKeyEvent,
         onKeyEvent,
         onKeyEvent,
         stateHelper = windowState.stateHelper,
         stateHelper = windowState.stateHelper,

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

@@ -44,8 +44,8 @@ class ChromeWindowScope(
         block()
         block()
     }
     }
 
 
-    fun requestFocus() =
-        stateHelper.requestFocus()
+//    fun requestFocus() =
+//        stateHelper.requestFocus()
 
 
     fun setBounds(
     fun setBounds(
         x: Dp = this.x,
         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.WindowPosition
 import androidx.compose.ui.window.WindowState
 import androidx.compose.ui.window.WindowState
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
+import eu.mjdev.desktop.extensions.Compose.rememberCalculated
+import eu.mjdev.desktop.extensions.Compose.rememberState
 
 
 class ChromeWindowState(
 class ChromeWindowState(
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
@@ -18,8 +20,8 @@ class ChromeWindowState(
 ) : WindowState {
 ) : WindowState {
 
 
     var onFocusChange: MutableList<ChromeWindowState.(focus: Boolean) -> Unit> = mutableStateListOf()
     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 ->
     val focusHelper: WindowFocusHelper = WindowFocusHelper { _, focus ->
         this@ChromeWindowState.onFocusChange.forEach {
         this@ChromeWindowState.onFocusChange.forEach {
@@ -30,16 +32,19 @@ class ChromeWindowState(
     val stateHelper: WindowStateHelper = WindowStateHelper({
     val stateHelper: WindowStateHelper = WindowStateHelper({
         setSize(size)
         setSize(size)
         this@ChromeWindowState.onOpened.forEach {
         this@ChromeWindowState.onOpened.forEach {
-            it.invoke()
+            it.invoke(this@ChromeWindowState)
         }
         }
     }, {
     }, {
         this@ChromeWindowState.onClosed.forEach {
         this@ChromeWindowState.onClosed.forEach {
-            it.invoke()
+            it.invoke(this@ChromeWindowState)
         }
         }
     })
     })
 
 
-    val window
+    var window
         get() = stateHelper.window
         get() = stateHelper.window
+        set(value) {
+            stateHelper.window = value
+        }
 
 
     @Suppress("CanBePrimaryConstructorProperty")
     @Suppress("CanBePrimaryConstructorProperty")
     val closeAction: WindowCloseAction = closeAction
     val closeAction: WindowCloseAction = closeAction
@@ -82,17 +87,15 @@ class ChromeWindowState(
         }
         }
 
 
         @Composable
         @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
         @Composable
-        fun rememberWnState() = remember { mutableStateOf(false) }
+        fun rememberWnState() = rememberState(false)
 
 
         @Composable
         @Composable
         fun updateWindowState(
         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,
     api: DesktopProvider = LocalDesktop.current,
     enabled: Boolean = true,
     enabled: Boolean = true,
     focusable: Boolean = true,
     focusable: Boolean = true,
+    onOpened: ChromeWindowState.() -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onCloseRequest: () -> Unit = {},
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     windowState: ChromeWindowState = rememberChromeWindowState(
     windowState: ChromeWindowState = rememberChromeWindowState(
-        placement = WindowPlacement.Fullscreen,
+        placement = WindowPlacement.Maximized,
         size = api.containerSize,
         size = api.containerSize,
         position = WindowPosition.Aligned(Alignment.Center)
         position = WindowPosition.Aligned(Alignment.Center)
     ),
     ),
-    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
+    onCreate: ChromeWindowState.() -> Unit = {},
+    onFocusChange: ChromeWindowState.(Boolean) -> Unit = {
+    },
     content: @Composable ChromeWindowScope.() -> Unit = {}
     content: @Composable ChromeWindowScope.() -> Unit = {}
 ) = ChromeWindow(
 ) = ChromeWindow(
     windowState = windowState,
     windowState = windowState,
@@ -35,6 +38,8 @@ fun FullScreenWindow(
     onPreviewKeyEvent = onPreviewKeyEvent,
     onPreviewKeyEvent = onPreviewKeyEvent,
     onKeyEvent = onKeyEvent,
     onKeyEvent = onKeyEvent,
     onCloseRequest = onCloseRequest,
     onCloseRequest = onCloseRequest,
+    onCreate = onCreate,
+    onOpened = onOpened,
     onFocusChange = onFocusChange,
     onFocusChange = onFocusChange,
     content = content
     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.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxSize
@@ -11,13 +10,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.isSecondaryPressed
 import androidx.compose.ui.input.pointer.isSecondaryPressed
 import eu.mjdev.desktop.components.background.BackgroundImage
 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.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
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 
@@ -30,27 +23,45 @@ fun MainWindow(
     panelState: VisibilityState,
     panelState: VisibilityState,
     menuState: VisibilityState,
     menuState: VisibilityState,
     api: DesktopProvider = LocalDesktop.current,
     api: DesktopProvider = LocalDesktop.current,
-    palette: Palette = rememberPalette(api.currentUser.theme.backgroundColor),
 //    onDesktopMenuShow: () -> Unit = {},
 //    onDesktopMenuShow: () -> Unit = {},
-    contextMenuState: ContextMenuState = rememberContextMenuState("item1", "item2"),
+//    contextMenuState: ContextMenuState = rememberContextMenuState("item1", "item2"),
     content: @Composable BoxScope.() -> Unit = {}
     content: @Composable BoxScope.() -> Unit = {}
 ) = FullScreenWindow(
 ) = FullScreenWindow(
-    onFocusChange = {
+//    onOpened = {
+//        println("Window opened $window")
+//        window?.toBack()
+//    },
+    onFocusChange = { focus ->
+//        RuntimeException("Focus change: $it").printStackTrace()
         // todo better solution
         // todo better solution
-        window?.toBack()
+//        val focusOwner = window?.mostRecentFocusOwner
+        if (focus) {
+            window?.toBack()
+        }
+//        focusOwner?.requestFocus()
     }
     }
 ) {
 ) {
+//    val backgroundColor by rememberBackgroundColor(api)
     Box(
     Box(
         modifier = Modifier
         modifier = Modifier
             .fillMaxSize()
             .fillMaxSize()
-            .background(api.currentUser.theme.backgroundColor)
+//            .background(backgroundColor)
+//            .pointerInput(Unit) {
+//                awaitPointerEventScope {
+//                    while (true) {
+//                        awaitPointerEvent(pass = PointerEventPass.Initial)
+//                            .changes
+//                            .forEach(PointerInputChange::consume)
+//                    }
+//                }
+//            }
     ) {
     ) {
         BackgroundImage(
         BackgroundImage(
             modifier = Modifier
             modifier = Modifier
                 .fillMaxSize()
                 .fillMaxSize()
                 .mouseClickable {
                 .mouseClickable {
                     if (buttons.isSecondaryPressed) {
                     if (buttons.isSecondaryPressed) {
-                        contextMenuState.show()
+//                        contextMenuState.show()
                     } else {
                     } else {
                         panelState.hide()
                         panelState.hide()
                         menuState.hide()
                         menuState.hide()
@@ -58,7 +69,7 @@ fun MainWindow(
                     }
                     }
                 },
                 },
             onChange = { src ->
             onChange = { src ->
-                palette.apply {
+                api.palette.apply {
                     update(src)
                     update(src)
                 }.also { p ->
                 }.also { p ->
                     api.currentUser.theme.backgroundColor = p.backgroundColor
                     api.currentUser.theme.backgroundColor = p.backgroundColor
@@ -71,12 +82,12 @@ fun MainWindow(
 //        )
 //        )
         content()
         content()
     }
     }
-    ContextMenu(
-        contextMenuState = contextMenuState,
-        onMenuItemClick = { item -> log { "Context menu : $item" } },
+//    ContextMenu(
+//        contextMenuState = contextMenuState,
+//        onMenuItemClick = { item -> log { "Context menu : $item" } },
 //        onShow = { onDesktopMenuShow() },
 //        onShow = { onDesktopMenuShow() },
 //        onHide = {}
 //        onHide = {}
-    )
+//    )
 //    launchedEffect {
 //    launchedEffect {
 //        runCatching {
 //        runCatching {
 //            Notification("Hello!", "I just wanted to say hello :)").apply {
 //            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
 package eu.mjdev.desktop.windows
 
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.awt.ComposeWindow
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.window.FrameWindowScope
 import androidx.compose.ui.window.FrameWindowScope
-import androidx.compose.ui.window.Window
 import androidx.compose.ui.window.WindowState
 import androidx.compose.ui.window.WindowState
 import androidx.compose.ui.window.rememberWindowState
 import androidx.compose.ui.window.rememberWindowState
 
 
 @Composable
 @Composable
 fun WindowEx(
 fun WindowEx(
+    onCreate: (window: ComposeWindow) -> Unit = {},
+//    onOpen: (window: ComposeWindow) -> Unit = {},
     onCloseRequest: () -> Unit,
     onCloseRequest: () -> Unit,
     state: WindowState = rememberWindowState(),
     state: WindowState = rememberWindowState(),
     visible: Boolean = true,
     visible: Boolean = true,
@@ -21,13 +23,15 @@ fun WindowEx(
     enabled: Boolean = true,
     enabled: Boolean = true,
     focusable: Boolean = true,
     focusable: Boolean = true,
     alwaysOnTop: Boolean = false,
     alwaysOnTop: Boolean = false,
-    alwaysOnBottom: Boolean = false,
+//    alwaysOnBottom: Boolean = false,
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onPreviewKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     onKeyEvent: (KeyEvent) -> Boolean = { false },
     focusHelper: WindowFocusHelper,
     focusHelper: WindowFocusHelper,
     stateHelper: WindowStateHelper,
     stateHelper: WindowStateHelper,
     content: @Composable FrameWindowScope.() -> Unit = {}
     content: @Composable FrameWindowScope.() -> Unit = {}
 ) = Window(
 ) = Window(
+    onCreate = onCreate,
+//    onOpen = onOpen,
     onCloseRequest = onCloseRequest,
     onCloseRequest = onCloseRequest,
     state = state,
     state = state,
     visible = visible,
     visible = visible,
@@ -39,16 +43,12 @@ fun WindowEx(
     enabled = enabled,
     enabled = enabled,
     focusable = focusable,
     focusable = focusable,
     alwaysOnTop = alwaysOnTop,
     alwaysOnTop = alwaysOnTop,
+//    alwaysOnBottom = alwaysOnBottom,
     onPreviewKeyEvent = onPreviewKeyEvent,
     onPreviewKeyEvent = onPreviewKeyEvent,
     onKeyEvent = onKeyEvent,
     onKeyEvent = onKeyEvent,
     content = {
     content = {
         content()
         content()
         with(window) {
         with(window) {
-            if (alwaysOnBottom) {
-                toBack()
-            } else if (alwaysOnTop) {
-                toFront()
-            }
             stateHelper.register(this)
             stateHelper.register(this)
             focusHelper.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() {
     fun requestFocus() {
         window?.toFront()
         window?.toFront()
+//        window?.requestFocus()
     }
     }
 }
 }