Bläddra i källkod

code improvements, previews

code improvements, previews
milan jurkulák 2 år sedan
förälder
incheckning
36e375a1eb
83 ändrade filer med 1432 tillägg och 993 borttagningar
  1. 4 0
      build.gradle.kts
  2. 3 62
      src/main/kotlin/eu/mjdev/desktop/Main.kt
  3. 48 45
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt
  4. 27 18
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  5. 38 34
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt
  6. 4 1
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt
  7. 6 6
      src/main/kotlin/eu/mjdev/desktop/components/background/BackgroundImage.kt
  8. 17 7
      src/main/kotlin/eu/mjdev/desktop/components/charts/DonutChart.kt
  9. 8 9
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt
  10. 0 63
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenterPage.kt
  11. 23 0
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPage.kt
  12. 30 0
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPageScope.kt
  13. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/IControlCenterPageDataSaver.kt
  14. 30 0
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/PageCache.kt
  15. 8 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/AIPage.kt
  16. 7 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/BluetoothSettingsPage.kt
  17. 7 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/Devices.kt
  18. 7 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/DisplaySettingsPage.kt
  19. 7 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/EthSettingsPage.kt
  20. 9 12
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/MainSettingsPage.kt
  21. 7 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/SoundSettingsPage.kt
  22. 7 1
      src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/WifiSettingsPage.kt
  23. 41 38
      src/main/kotlin/eu/mjdev/desktop/components/custom/Clock.kt
  24. 7 4
      src/main/kotlin/eu/mjdev/desktop/components/custom/DateTime.kt
  25. 0 2
      src/main/kotlin/eu/mjdev/desktop/components/custom/MeasurableComponent.kt
  26. 1 2
      src/main/kotlin/eu/mjdev/desktop/components/custom/MeasureUnconstrainedView.kt
  27. 41 35
      src/main/kotlin/eu/mjdev/desktop/components/custom/PowerBlock.kt
  28. 7 4
      src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt
  29. 6 4
      src/main/kotlin/eu/mjdev/desktop/components/desktop/Desktop.kt
  30. 31 28
      src/main/kotlin/eu/mjdev/desktop/components/desktop/widgets/MemoryChart.kt
  31. 14 11
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  32. 12 7
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt
  33. 6 2
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelText.kt
  34. 8 5
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopMenuIcon.kt
  35. 11 10
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt
  36. 7 5
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelLanguage.kt
  37. 0 2
      src/main/kotlin/eu/mjdev/desktop/components/draggable/DraggableView.kt
  38. 6 5
      src/main/kotlin/eu/mjdev/desktop/components/file/FolderIcon.kt
  39. 6 10
      src/main/kotlin/eu/mjdev/desktop/components/file/FolderView.kt
  40. 8 4
      src/main/kotlin/eu/mjdev/desktop/components/fonticon/FontIcon.kt
  41. 7 6
      src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt
  42. 6 3
      src/main/kotlin/eu/mjdev/desktop/components/icon/ShapedIcon.kt
  43. 6 3
      src/main/kotlin/eu/mjdev/desktop/components/image/GifView.kt
  44. 8 3
      src/main/kotlin/eu/mjdev/desktop/components/image/ImageAny.kt
  45. 15 12
      src/main/kotlin/eu/mjdev/desktop/components/info/InfoWindow.kt
  46. 5 0
      src/main/kotlin/eu/mjdev/desktop/components/input/PasswordTextView.kt
  47. 75 0
      src/main/kotlin/eu/mjdev/desktop/components/main/MainWindow.kt
  48. 6 24
      src/main/kotlin/eu/mjdev/desktop/components/menu/ContextMenu.kt
  49. 29 0
      src/main/kotlin/eu/mjdev/desktop/components/menu/base/ContextMenuState.kt
  50. 5 2
      src/main/kotlin/eu/mjdev/desktop/components/qrcode/QrCodeView.kt
  51. 7 3
      src/main/kotlin/eu/mjdev/desktop/components/sliding/SlidingMenu.kt
  52. 4 9
      src/main/kotlin/eu/mjdev/desktop/components/sliding/base/VisibilityState.kt
  53. 5 2
      src/main/kotlin/eu/mjdev/desktop/components/text/AutoResizeText.kt
  54. 4 1
      src/main/kotlin/eu/mjdev/desktop/components/text/CircleText.kt
  55. 5 2
      src/main/kotlin/eu/mjdev/desktop/components/text/TextAny.kt
  56. 6 3
      src/main/kotlin/eu/mjdev/desktop/components/text/TextWithShadow.kt
  57. 4 5
      src/main/kotlin/eu/mjdev/desktop/components/tooltip/Tooltip.kt
  58. 6 1
      src/main/kotlin/eu/mjdev/desktop/components/video/VideoView.kt
  59. 5 3
      src/main/kotlin/eu/mjdev/desktop/components/video/base/KFFMpeg.kt
  60. 5 2
      src/main/kotlin/eu/mjdev/desktop/components/video/base/VideoPlayerFFMpeg.kt
  61. 58 55
      src/main/kotlin/eu/mjdev/desktop/components/web/ComposeWebView.kt
  62. 52 84
      src/main/kotlin/eu/mjdev/desktop/data/App.kt
  63. 6 0
      src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt
  64. 11 0
      src/main/kotlin/eu/mjdev/desktop/data/DesktopFolderItem.kt
  65. 2 2
      src/main/kotlin/eu/mjdev/desktop/data/Theme.kt
  66. 6 0
      src/main/kotlin/eu/mjdev/desktop/data/TooltipData.kt
  67. 14 12
      src/main/kotlin/eu/mjdev/desktop/data/User.kt
  68. 9 0
      src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt
  69. 1 1
      src/main/kotlin/eu/mjdev/desktop/fonts/CodePointsFile.kt
  70. 1 1
      src/main/kotlin/eu/mjdev/desktop/fonts/FontFile.kt
  71. 1 1
      src/main/kotlin/eu/mjdev/desktop/fonts/MaterialIconFont.kt
  72. 17 10
      src/main/kotlin/eu/mjdev/desktop/helpers/managers/GnomeManager.kt
  73. 69 67
      src/main/kotlin/eu/mjdev/desktop/helpers/managers/WindowsManager.kt
  74. 0 56
      src/main/kotlin/eu/mjdev/desktop/helpers/system/Command.kt
  75. 10 0
      src/main/kotlin/eu/mjdev/desktop/helpers/system/DBus.kt
  76. 2 3
      src/main/kotlin/eu/mjdev/desktop/helpers/system/Environment.kt
  77. 177 0
      src/main/kotlin/eu/mjdev/desktop/helpers/system/Shell.kt
  78. 14 19
      src/main/kotlin/eu/mjdev/desktop/provider/AppsProvider.kt
  79. 157 102
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  80. 44 0
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopScope.kt
  81. 55 50
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindow.kt
  82. 2 2
      src/main/kotlin/eu/mjdev/desktop/windows/DesktopWindow.kt
  83. 1 1
      src/main/kotlin/eu/mjdev/desktop/windows/FullScreenWindow.kt

+ 4 - 0
build.gradle.kts

@@ -41,6 +41,7 @@ allprojects {
         // foundation
         implementation(compose.foundation)
         // preview
+        implementation(compose.uiTooling)
         implementation(compose.preview)
         // resources
         implementation(compose.components.resources)
@@ -98,6 +99,9 @@ allprojects {
         implementation("com.github.skydoves:flexible-bottomsheet-material3:0.1.5")
         // fs watcher
         implementation("io.github.irgaly.kfswatch:kfswatch:1.3.0")
+        // java gnome
+        implementation("com.github.bailuk:java-gtk:0.5.0")
+
         // compose debugger
 //        implementation("io.github.theapache64:rebugger:1.0.0-rc03")
         // music

+ 3 - 62
src/main/kotlin/eu/mjdev/desktop/Main.kt

@@ -1,81 +1,22 @@
 package eu.mjdev.desktop
 
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.ui.Modifier
 import androidx.compose.ui.window.application
-import eu.mjdev.desktop.components.appsmenu.AppsMenu
-import eu.mjdev.desktop.components.controlcenter.ControlCenter
-import eu.mjdev.desktop.components.desktop.Desktop
-import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
-import eu.mjdev.desktop.components.greeter.Greeter
-import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.components.main.MainWindow
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.rememberDesktopProvider
-import eu.mjdev.desktop.windows.MainWindow
 
 fun main(
-    @Suppress("unused")
     args: Array<String>
 ) = application(
     exitProcessOnExit = true
 ) {
     val api = rememberDesktopProvider()
-    val controlCenterState = rememberVisibilityState()
-    val panelState = rememberVisibilityState()
-    val menuState = rememberVisibilityState()
-    val handleMenuFocus: (Boolean) -> Unit = { focus ->
-        if (!menuState.isWindowFocus && !focus && !controlCenterState.isWindowFocus) {
-            menuState.hide()
-        }
-    }
-    val handlePanelFocus: (Boolean) -> Unit = { focus ->
-        if (!focus) {
-            if (!menuState.isVisible) {
-                panelState.hide()
-            }
-        }
-    }
-    val handleControlCenterFocus: (Boolean) -> Unit = { focus ->
-        if (!focus) {
-            controlCenterState.hide()
-        }
-    }
+    println("Started with args: ${args.toList()}")
     MaterialTheme {
         CompositionLocalProvider(LocalDesktop provides api) {
-            MainWindow(
-                panelState = panelState,
-                controlCenterState = controlCenterState,
-                menuState = menuState
-            ) {
-                Desktop(
-                    modifier = Modifier.fillMaxSize()
-                )
-                DesktopPanel(
-                    panelState = panelState,
-                    menuState = menuState,
-                    onMenuIconClicked = { if (!menuState.isVisible) menuState.show() },
-                    onFocusChange = { focus -> handlePanelFocus(focus) }
-                )
-                AppsMenu(
-                    menuState = menuState,
-                    panelState = panelState,
-                    onFocusChange = { focus -> handleMenuFocus(focus) }
-                )
-                ControlCenter(
-                    controlCenterState = controlCenterState,
-                    onFocusChange = { focus -> handleControlCenterFocus(focus) }
-                )
-                Greeter()
-//                InfoWindow()
-            }
-            DisposableEffect(Unit) {
-                onDispose {
-                    api.dispose()
-                }
-            }
+            MainWindow()
         }
     }
 }

+ 48 - 45
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsBottomBar.kt

@@ -15,64 +15,67 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.icon.ShapedIcon
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun AppsBottomBar(
-    api: DesktopProvider = LocalDesktop.current,
     modifier: Modifier = Modifier,
     iconBackgroundColor: Color = Color.White,
     iconColor: Color = Color.Black,
     backButtonVisible: Boolean = false,
-    onContextMenuClick: () -> Unit,
-    onBackClick: () -> Unit
-) = Box(
-    modifier = modifier.padding(8.dp)
-) {
-    Row(
-        modifier = Modifier.align(Alignment.BottomStart)
+    onContextMenuClick: () -> Unit = {},
+    onBackClick: () -> Unit = {}
+) = withDesktopScope {
+    Box(
+        modifier = modifier.padding(8.dp)
     ) {
-        if (backButtonVisible) {
+        Row(
+            modifier = Modifier.align(Alignment.BottomStart)
+        ) {
+            if (backButtonVisible) {
+                ShapedIcon(
+                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+                    iconColor = iconColor,
+                    iconBackgroundColor = iconBackgroundColor,
+                    onRightClick = onContextMenuClick,
+                    onClick = onBackClick
+                )
+            }
+        }
+        Row(
+            modifier = Modifier.align(Alignment.BottomCenter)
+        ) {
+            // todo : center content if needed
+        }
+        Row(
+            modifier = Modifier.align(Alignment.BottomEnd)
+        ) {
+            ShapedIcon(
+                imageVector = Icons.Filled.RestartAlt,
+                iconColor = iconColor,
+                iconBackgroundColor = iconBackgroundColor,
+                onRightClick = onContextMenuClick,
+                onClick = { api.restart() }
+            )
+            ShapedIcon(
+                imageVector = Icons.AutoMirrored.Filled.Logout,
+                iconColor = iconColor,
+                iconBackgroundColor = iconBackgroundColor,
+                onRightClick = onContextMenuClick,
+                onClick = { api.logOut() }
+            )
             ShapedIcon(
-                imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+                imageVector = Icons.Filled.PowerOff,
                 iconColor = iconColor,
                 iconBackgroundColor = iconBackgroundColor,
                 onRightClick = onContextMenuClick,
-                onClick = onBackClick
+                onClick = { api.shutdown() }
             )
         }
     }
-    Row(
-        modifier = Modifier.align(Alignment.BottomCenter)
-    ) {
-        // todo : center content if needed
-    }
-    Row(
-        modifier = Modifier.align(Alignment.BottomEnd)
-    ) {
-        ShapedIcon(
-            imageVector = Icons.Filled.RestartAlt,
-            iconColor = iconColor,
-            iconBackgroundColor = iconBackgroundColor,
-            onRightClick = onContextMenuClick,
-            onClick = { api.restart() }
-        )
-        ShapedIcon(
-            imageVector = Icons.AutoMirrored.Filled.Logout,
-            iconColor = iconColor,
-            iconBackgroundColor = iconBackgroundColor,
-            onRightClick = onContextMenuClick,
-            onClick = { api.logOut() }
-        )
-        ShapedIcon(
-            imageVector = Icons.Filled.PowerOff,
-            iconColor = iconColor,
-            iconBackgroundColor = iconBackgroundColor,
-            onRightClick = onContextMenuClick,
-            onClick = { api.shutdown() }
-        )
-    }
-}
+}
+
+@Preview
+@Composable
+fun AppsBottomBarPreview() = AppsBottomBar()

+ 27 - 18
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -22,8 +22,8 @@ import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
 import eu.mjdev.desktop.components.custom.UserAvatar
-import eu.mjdev.desktop.components.sliding.VisibilityState
-import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.data.Category
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
@@ -33,24 +33,21 @@ import eu.mjdev.desktop.extensions.Modifier.dropShadow
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
 import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindowState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 
-@Suppress("FunctionName")
 @OptIn(ExperimentalComposeUiApi::class)
-@Preview
+@Suppress("FunctionName", "LocalVariableName")
 @Composable
 fun AppsMenu(
-    api: DesktopProvider = LocalDesktop.current,
     panelState: VisibilityState = rememberVisibilityState(),
     menuState: VisibilityState = rememberVisibilityState(),
     enterAnimation: EnterTransition = AppsMenuEnterAnimation,
     exitAnimation: ExitTransition = AppsMenuExitAnimation,
     onFocusChange: (Boolean) -> Unit = {},
-    onAppClick: (App) -> Unit = { app ->
+    onAppClick: (App, DesktopProvider) -> Unit = { app, api ->
         api.appsProvider.startApp(app)
     },
     onAppContextMenuClick: (App) -> Unit = {
@@ -78,16 +75,24 @@ fun AppsMenu(
         )
     }
     val windowState: ChromeWindowState = rememberChromeWindowState(position = position)
-    ChromeWindow(
-        visible = menuState.isVisible,
-        enterAnimation = enterAnimation,
-        exitAnimation = exitAnimation,
-        windowState = windowState,
-        onFocusChange = { focused ->
-            menuState.onFocusChange(focused)
-            onFocusChange(focused)
+    val Container: @Composable (content: @Composable () -> Unit) -> Unit = { content ->
+        if (isDebug) {
+            content()
+        } else {
+            ChromeWindow(
+                visible = menuState.isVisible,
+                enterAnimation = enterAnimation,
+                exitAnimation = exitAnimation,
+                windowState = windowState,
+                onFocusChange = { focused ->
+                    menuState.onFocusChange(focused)
+                    onFocusChange(focused)
+                },
+                content = { content() }
+            )
         }
-    ) {
+    }
+    Container {
         Box(
             modifier = Modifier
                 .width(appMenuMinWidth)
@@ -164,7 +169,7 @@ fun AppsMenu(
                                         iconTint = textColor,
                                         textColor = textColor,
                                         onClick = {
-                                            onAppClick(item)
+                                            onAppClick(item, api)
                                         },
                                         onContextMenuClick = {
                                             onAppContextMenuClick(item)
@@ -217,3 +222,7 @@ fun AppsMenu(
         }
     }
 }
+
+@Preview
+@Composable
+fun AppsMenuPreview() = AppsMenu()

+ 38 - 34
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt

@@ -19,12 +19,10 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.fonticon.FontIcon
 import eu.mjdev.desktop.components.text.TextAny
 import eu.mjdev.desktop.data.App
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("DEPRECATION", "FunctionName")
 @OptIn(ExperimentalFoundationApi::class)
-@Preview
 @Composable
 fun AppsMenuApp(
     modifier: Modifier = Modifier,
@@ -34,35 +32,41 @@ fun AppsMenuApp(
     iconTint: Color = Color.Black,
     textColor: Color = Color.White,
     backgroundColor: Color = Color.White,
-    api: DesktopProvider = LocalDesktop.current,
-    onClick: () -> Unit,
-    onContextMenuClick: () -> Unit
-) = Row(
-    modifier = modifier.mouseClickable {
-        if (buttons.isSecondaryPressed) {
-            onContextMenuClick()
-        } else {
-            onClick()
-        }
-    },
-    verticalAlignment = Alignment.CenterVertically,
-) {
-    val appIconName = remember(app, icon) { app?.name ?: icon }
-    val materialIcon = remember(appIconName) { api.currentUser.theme.iconSet.iconForName(appIconName) ?: "?".toInt() }
-    FontIcon(
-        iconId = materialIcon,
-        iconSize = iconSize,
-        iconColor = iconTint,
-        iconBackgroundColor = backgroundColor,
-        outerPadding = PaddingValues(2.dp),
-        innerPadding = PaddingValues(0.dp),
-        onClick = onClick,
-        onRightClick = onContextMenuClick
-    )
-    TextAny(
-        modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),
-        text = app?.name ?: "",
-        color = textColor,
-        fontWeight = FontWeight.Bold
-    )
+    onClick: () -> Unit = {},
+    onContextMenuClick: () -> Unit = {}
+) = withDesktopScope {
+    Row(
+        modifier = modifier.mouseClickable {
+            if (buttons.isSecondaryPressed) {
+                onContextMenuClick()
+            } else {
+                onClick()
+            }
+        },
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        val appIconName = remember(app, icon) { app?.name ?: icon }
+        val materialIcon =
+            remember(appIconName) { api.currentUser.theme.iconSet.iconForName(appIconName) ?: "?".toInt() }
+        FontIcon(
+            iconId = materialIcon,
+            iconSize = iconSize,
+            iconColor = iconTint,
+            iconBackgroundColor = backgroundColor,
+            outerPadding = PaddingValues(2.dp),
+            innerPadding = PaddingValues(0.dp),
+            onClick = onClick,
+            onRightClick = onContextMenuClick
+        )
+        TextAny(
+            modifier = Modifier.padding(start = 4.dp).fillMaxWidth(),
+            text = app?.name ?: "",
+            color = textColor,
+            fontWeight = FontWeight.Bold
+        )
+    }
 }
+
+@Preview
+@Composable
+fun AppsMenuAppPreview() = AppsMenuApp()

+ 4 - 1
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuCategory.kt

@@ -24,7 +24,6 @@ import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @Suppress("DEPRECATION")
 @OptIn(ExperimentalFoundationApi::class)
-@Preview
 @Composable
 fun AppsMenuCategory(
     modifier: Modifier = Modifier,
@@ -65,3 +64,7 @@ fun AppsMenuCategory(
         fontWeight = FontWeight.Bold
     )
 }
+
+@Preview
+@Composable
+fun AppsMenuCategoryPreview() = AppsMenuCategory()

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

@@ -15,24 +15,20 @@ import eu.mjdev.desktop.components.image.ImageAny
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.helpers.internal.Queue.Companion.mutableQueue
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun BackgroundImage(
     modifier: Modifier = Modifier,
-    api: DesktopProvider = LocalDesktop.current,
     fadeInDuration: Long = 3000L,
     fadeOutDuration: Long = 5000L,
     onError: (Throwable) -> Unit = { e -> e.printStackTrace() },
     onChange: (bck: Any?) -> Unit = {}
-) {
+) = withDesktopScope {
     val switchDelay by remember { api.currentUser.theme.backgroundRotationDelayState }
-    val backgroundColor by remember { api.currentUser.theme.backgroundColorState }
     val userBackgrounds by remember { api.currentUser.config.desktopBackgroundsState }
     val backgrounds by rememberCalculated { api.appsProvider.backgrounds + userBackgrounds }
     val backgroundQueue = remember { mutableQueue(backgrounds) }
@@ -65,3 +61,7 @@ fun BackgroundImage(
         }
     }
 }
+
+@Preview
+@Composable
+fun BackgroundImagePreview() = BackgroundImage()

+ 17 - 7
src/main/kotlin/eu/mjdev/desktop/components/charts/DonutChart.kt

@@ -18,20 +18,19 @@ import kotlinx.coroutines.delay
 import kotlinx.coroutines.isActive
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun DonutChart(
     modifier: Modifier = Modifier,
     title: String = "",
-    textColor: Color = Color.White,
-    outerCircularColor: Color = Color.White,
-    innerCircularColor: Color = Color.White,
-    ratioLineColor: Color = Color.White,
+    textColor: Color = Color.Black,
+    outerCircularColor: Color = Color.Black,
+    innerCircularColor: Color = Color.Black,
+    ratioLineColor: Color = Color.Black,
     animationDuration: Int = 3000,
     refreshTimeout: Long = 5000L,
     animation: AnimationSpec<Float> = TweenSpec(durationMillis = animationDuration),
     legendPosition: LegendPosition = LegendPosition.BOTTOM,
-    dataHandler: () -> List<PieChartData>
+    dataHandler: () -> List<PieChartData> = { emptyList() }
 ) {
     var data by rememberState(dataHandler())
     val textStyle by rememberState(TextStyle(color = textColor))
@@ -58,4 +57,15 @@ fun DonutChart(
             delay(refreshTimeout)
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun DonutChartPreview() = DonutChart(
+    dataHandler = {
+        listOf(
+            PieChartData(0.4, Color.Blue, "test1"),
+            PieChartData(0.4, Color.Gray, "test1")
+        )
+    }
+)

+ 8 - 9
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenter.kt

@@ -23,17 +23,16 @@ import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage.ControlCenterPageScope
 import eu.mjdev.desktop.components.sliding.SlidingMenu
-import eu.mjdev.desktop.components.sliding.VisibilityState
-import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.ControlCenterExitAnimation
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindowState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
@@ -42,7 +41,6 @@ import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalFoundationApi::class)
 @Suppress("FunctionName", "DEPRECATION")
-@Preview
 @Composable
 fun ControlCenter(
     api: DesktopProvider = LocalDesktop.current,
@@ -55,7 +53,6 @@ fun ControlCenter(
     onContextMenuClick: () -> Unit = {}
 ) = withDesktopScope {
     val backgroundAlpha by remember { api.currentUser.theme.controlCenterBackgroundAlphaState }
-    val backgroundInvoker = remember { { backgroundColor } }
     val controlCenterExpandedWidth by remember { api.currentUser.theme.controlCenterExpandedWidthState }
     val controlCenterDividerWidth by remember { api.currentUser.theme.controlCenterDividerWidthState }
     val controlCenterIconSize by remember { api.currentUser.theme.controlCenterIconSizeState }
@@ -173,9 +170,7 @@ fun ControlCenter(
                                     Box(
                                         modifier = Modifier.width(controlCenterExpandedWidth),
                                     ) {
-                                        with(pagesFiltered.value[pagerState.value]) {
-                                            content(ControlCenterPageScope(api, backgroundInvoker, cache))
-                                        }
+                                        pagesFiltered.value[pagerState.value].render()
                                     }
                                     Divider(
                                         modifier = Modifier.fillMaxHeight()
@@ -197,3 +192,7 @@ fun ControlCenter(
         }
     }
 }
+
+@Preview
+@Composable
+fun ControlCenterPreview() = ControlCenter()

+ 0 - 63
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/ControlCenterPage.kt

@@ -1,63 +0,0 @@
-package eu.mjdev.desktop.components.controlcenter
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Settings
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisallowComposableCalls
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage.ControlCenterPageScope.PageCache
-import eu.mjdev.desktop.provider.DesktopProvider
-
-@Suppress("UNCHECKED_CAST")
-class ControlCenterPage(
-    val icon: ImageVector = Icons.Filled.Settings,
-    val name: String = "",
-    val condition: DesktopProvider.() -> Boolean = { true },
-    private val saver: IControlCenterPageDataSaver? = null,
-    val cache: PageCache = PageCache(saver),
-    val content: @Composable ControlCenterPageScope.() -> Unit = {},
-) {
-    class ControlCenterPageScope(
-        val api: DesktopProvider,
-        private val backgroundColorInvoker: () -> Color,
-        private val cache: PageCache
-    ) {
-        val scope
-            get() = api.scope
-
-        val backgroundColor
-            get() = backgroundColorInvoker()
-
-        @Composable
-        fun <T> remember(
-            calculation: @DisallowComposableCalls () -> T
-        ): T = cache.cache(false, calculation)
-
-        @Suppress("unused")
-        class PageCache(
-            private val saver: IControlCenterPageDataSaver? = null
-        ) : HashMap<Int, Any>() {
-            init {
-                saver?.load()?.onEach { (t, u) -> this[t] = u }
-            }
-
-            fun save() {
-                saver?.save(this)
-            }
-
-            fun <T> cache(
-                invalid: Boolean,
-                block: @DisallowComposableCalls () -> T
-            ): T {
-                val hash = block.hashCode()
-                var data: T = this[hash] as T
-                if (invalid || data == null) {
-                    data = block()
-                }
-                if (data != null) this[hash] = data
-                return data
-            }
-        }
-    }
-}

+ 23 - 0
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPage.kt

@@ -0,0 +1,23 @@
+package eu.mjdev.desktop.components.controlcenter.base
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPageScope.Companion.withControlCenterPageScope
+import eu.mjdev.desktop.provider.DesktopProvider
+
+@Suppress("MemberVisibilityCanBePrivate")
+class ControlCenterPage(
+    val icon: ImageVector = Icons.Filled.Settings,
+    val name: String = "",
+    val condition: DesktopProvider.() -> Boolean = { true },
+    val saver: IControlCenterPageDataSaver? = null,
+    val cache: PageCache = PageCache(saver),
+    val content: @Composable ControlCenterPageScope.() -> Unit = {},
+) {
+    @Composable
+    fun render() = withControlCenterPageScope(cache = cache) {
+        content()
+    }
+}

+ 30 - 0
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/ControlCenterPageScope.kt

@@ -0,0 +1,30 @@
+package eu.mjdev.desktop.components.controlcenter.base
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisallowComposableCalls
+import eu.mjdev.desktop.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope
+
+class ControlCenterPageScope(
+    api: DesktopProvider,
+    val cache: PageCache
+) : DesktopScope(api) {
+
+    companion object {
+        @Suppress("UnusedReceiverParameter")
+        @Composable
+        fun ControlCenterPage.withControlCenterPageScope(
+            api: DesktopProvider = LocalDesktop.current,
+            cache: PageCache,
+            block: @Composable ControlCenterPageScope.() -> Unit
+        ) = ControlCenterPageScope(api, cache).apply {
+            block()
+        }
+
+        @Composable
+        fun <T> ControlCenterPageScope.remember(
+            calculation: @DisallowComposableCalls () -> T
+        ): T = cache.cache(false, calculation)
+    }
+}

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/IControlCenterPageDataSaver.kt → src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/IControlCenterPageDataSaver.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components.controlcenter
+package eu.mjdev.desktop.components.controlcenter.base
 
 interface IControlCenterPageDataSaver {
     fun save(data: Map<Int, Any>)

+ 30 - 0
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/base/PageCache.kt

@@ -0,0 +1,30 @@
+package eu.mjdev.desktop.components.controlcenter.base
+
+import androidx.compose.runtime.DisallowComposableCalls
+
+@Suppress("unused")
+class PageCache(
+    private val saver: IControlCenterPageDataSaver? = null
+) : HashMap<Int, Any>() {
+    init {
+        saver?.load()?.onEach { (t, u) -> this[t] = u }
+    }
+
+    fun save() {
+        saver?.save(this)
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    fun <T> cache(
+        invalid: Boolean,
+        block: @DisallowComposableCalls () -> T
+    ): T {
+        val hash = block.hashCode()
+        var data: T = this[hash] as T
+        if (invalid || data == null) {
+            data = block()
+        }
+        if (data != null) this[hash] = data
+        return data
+    }
+}

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

@@ -1,5 +1,6 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
@@ -10,11 +11,13 @@ import androidx.compose.material.Text
 import androidx.compose.material.TextFieldDefaults
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Campaign
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPageScope.Companion.remember
 import eu.mjdev.desktop.extensions.Compose.rememberState
 import kotlinx.coroutines.launch
 
@@ -105,3 +108,7 @@ fun AIPage() = ControlCenterPage(
         }
     }
 }
+
+@Preview
+@Composable
+fun AIPagePreview() =  AIPage().render()

+ 7 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/BluetoothSettingsPage.kt

@@ -1,11 +1,13 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Bluetooth
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 
 @Suppress("FunctionName")
 fun BluetoothSettingsPage() = ControlCenterPage(
@@ -17,3 +19,7 @@ fun BluetoothSettingsPage() = ControlCenterPage(
         modifier = Modifier.fillMaxSize()
     )
 }
+
+@Preview
+@Composable
+fun BluetoothSettingsPagePreview() =  BluetoothSettingsPage().render()

+ 7 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/Devices.kt

@@ -1,11 +1,13 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.MobileFriendly
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 
 @Suppress("FunctionName")
 fun DevicesPage() = ControlCenterPage(
@@ -18,3 +20,7 @@ fun DevicesPage() = ControlCenterPage(
 
     }
 }
+
+@Preview
+@Composable
+fun DevicesPagePreview() =  DevicesPage().render()

+ 7 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/DisplaySettingsPage.kt

@@ -1,11 +1,13 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ScreenshotMonitor
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 
 @Suppress("FunctionName")
 fun DisplaySettingsPage() = ControlCenterPage(
@@ -16,3 +18,7 @@ fun DisplaySettingsPage() = ControlCenterPage(
         modifier = Modifier.fillMaxSize()
     )
 }
+
+@Preview
+@Composable
+fun DisplaySettingsPagePreview() =  DisplaySettingsPage().render()

+ 7 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/EthSettingsPage.kt

@@ -1,11 +1,13 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.SettingsEthernet
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 
 @Suppress("FunctionName")
 fun EthSettingsPage() = ControlCenterPage(
@@ -17,3 +19,7 @@ fun EthSettingsPage() = ControlCenterPage(
         modifier = Modifier.fillMaxSize()
     )
 }
+
+@Preview
+@Composable
+fun EthSettingsPagePreview() =  EthSettingsPage().render()

+ 9 - 12
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/MainSettingsPage.kt

@@ -1,35 +1,28 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.*
 import androidx.compose.material.Divider
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Home
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import eu.mjdev.desktop.components.custom.DateTime
 import eu.mjdev.desktop.components.custom.PowerBlock
 import eu.mjdev.desktop.components.custom.UserAvatar
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Modifier.rectShadow
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 
 @Suppress("FunctionName")
 fun MainSettingsPage() = ControlCenterPage(
     icon = Icons.Filled.Home,
     name = "Home"
-) {
-    val backgroundColor by rememberBackgroundColor(api)
-    val textColor by rememberTextColor(api)
-    val borderColor by rememberBorderColor(api)
-    val iconsTintColor by rememberIconTintColor(api)
+)  {
     Box(
         modifier = Modifier.fillMaxSize()
     ) {
@@ -93,7 +86,7 @@ fun MainSettingsPage() = ControlCenterPage(
             ) {
                 PowerBlock(
                     backgroundColor = backgroundColor,
-                    shadowColor=borderColor.alpha(0.3f),
+                    shadowColor = borderColor.alpha(0.3f),
                     iconTintColor = iconsTintColor,
 //                    textColor = textColor,
                     onPowerButtonClick = {
@@ -105,3 +98,7 @@ fun MainSettingsPage() = ControlCenterPage(
         }
     }
 }
+
+@Preview
+@Composable
+fun MainSettingsPagePreview() = MainSettingsPage().render()

+ 7 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/SoundSettingsPage.kt

@@ -1,11 +1,13 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Headphones
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 
 @Suppress("FunctionName")
 fun SoundSettingsPage() = ControlCenterPage(
@@ -16,3 +18,7 @@ fun SoundSettingsPage() = ControlCenterPage(
         modifier = Modifier.fillMaxSize()
     )
 }
+
+@Preview
+@Composable
+fun SoundSettingsPagePreview() = SoundSettingsPage().render()

+ 7 - 1
src/main/kotlin/eu/mjdev/desktop/components/controlcenter/pages/WifiSettingsPage.kt

@@ -1,11 +1,13 @@
 package eu.mjdev.desktop.components.controlcenter.pages
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Wifi
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 
 @Suppress("FunctionName")
 fun WifiSettingsPage() = ControlCenterPage(
@@ -17,3 +19,7 @@ fun WifiSettingsPage() = ControlCenterPage(
         modifier = Modifier.fillMaxSize()
     )
 }
+
+@Preview
+@Composable
+fun WifiSettingsPagePreview() = WifiSettingsPage().render()

+ 41 - 38
src/main/kotlin/eu/mjdev/desktop/components/custom/Clock.kt

@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -16,53 +15,57 @@ import androidx.compose.ui.unit.sp
 import eu.mjdev.desktop.components.text.TextWithShadow
 import eu.mjdev.desktop.extensions.Custom.dateFlow
 import eu.mjdev.desktop.extensions.Custom.timeFlow
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
-@Preview
+// todo remove params
 @Composable
 fun Clock(
     modifier: Modifier = Modifier,
     timeTextSize: TextUnit = 32.sp,
     timeTextWeight: FontWeight = FontWeight.Bold,
-    timeTextColor: Color = Color.White,
+    timeTextColor: Color = Color.Black,
     dateTextSize: TextUnit = 16.sp,
     dateTextWeight: FontWeight = FontWeight.Bold,
-    dateTextColor: Color = Color.White,
+    dateTextColor: Color = Color.Black,
     showTime: Boolean = true,
     showDate: Boolean = true,
-    talkEveryHour: Boolean = true,
-    api: DesktopProvider = LocalDesktop.current,
-) = Box(
-    modifier = modifier,
-    contentAlignment = Alignment.Center
-) {
-    val time = timeFlow.value
-    Column {
-        if (showTime) TextWithShadow(
-            modifier = Modifier.fillMaxWidth(),
-            text = time,
-            textAlign = TextAlign.Center,
-            fontWeight = timeTextWeight,
-            fontSize = timeTextSize,
-            color = timeTextColor
-        )
-        if (showDate) TextWithShadow(
-            modifier = Modifier.fillMaxWidth(),
-            text = dateFlow.value,
-            textAlign = TextAlign.Center,
-            fontWeight = dateTextWeight,
-            fontSize = dateTextSize,
-            color = dateTextColor
-        )
-    }
-    LaunchedEffect(time) {
-        if (time.isNotEmpty()) {
-            val parsed = time.split(":")
-            val isZeroMinutes = (parsed.getOrNull(1) ?: "-") == "00"
-            if (talkEveryHour && isZeroMinutes) {
-                api.ai.talk("It is ${parsed.firstOrNull()} hour")
-            }
+//    talkEveryHour: Boolean = true,
+) = withDesktopScope {
+    Box(
+        modifier = modifier,
+        contentAlignment = Alignment.Center
+    ) {
+        val time = timeFlow.value
+        Column {
+            if (showTime) TextWithShadow(
+                modifier = Modifier.fillMaxWidth(),
+                text = time,
+                textAlign = TextAlign.Center,
+                fontWeight = timeTextWeight,
+                fontSize = timeTextSize,
+                color = timeTextColor
+            )
+            if (showDate) TextWithShadow(
+                modifier = Modifier.fillMaxWidth(),
+                text = dateFlow.value,
+                textAlign = TextAlign.Center,
+                fontWeight = dateTextWeight,
+                fontSize = dateTextSize,
+                color = dateTextColor
+            )
         }
+//        LaunchedEffect(time) {
+//            if (time.isNotEmpty()) {
+//                val parsed = time.split(":")
+//                val isZeroMinutes = (parsed.getOrNull(1) ?: "-") == "00"
+//                if (talkEveryHour && isZeroMinutes) {
+//                    api.ai.talk("It is ${parsed.firstOrNull()} hour")
+//                }
+//            }
+//        }
     }
 }
+
+@Preview
+@Composable
+fun ClockPreview() = Clock()

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

@@ -10,11 +10,10 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 
-@Preview
 @Composable
 fun DateTime(
-    backgroundColor: Color,
-    textColor: Color = Color.White
+    backgroundColor: Color=Color.Transparent,
+    textColor: Color = Color.Black
 ) = Box(
     modifier = Modifier
         .fillMaxWidth()
@@ -26,4 +25,8 @@ fun DateTime(
         timeTextColor = textColor,
         dateTextColor = textColor
     )
-}
+}
+
+@Preview
+@Composable
+fun DateTimePreview() = DateTime()

+ 0 - 2
src/main/kotlin/eu/mjdev/desktop/components/custom/MeasurableComponent.kt

@@ -1,13 +1,11 @@
 package eu.mjdev.desktop.components.custom
 
-import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 
 @Suppress("FunctionName", "unused")
-@Preview
 @Composable
 fun MeasurableComponent(
     key: Any,

+ 1 - 2
src/main/kotlin/eu/mjdev/desktop/components/custom/MeasureUnconstrainedView.kt

@@ -1,6 +1,5 @@
 package eu.mjdev.desktop.components.custom
 
-import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.layout.SubcomposeLayout
@@ -8,7 +7,6 @@ import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 
 @Suppress("FunctionName", "unused")
-@Preview
 @Composable
 fun MeasureUnconstrainedView(
     viewToMeasure: @Composable () -> Unit,
@@ -36,6 +34,7 @@ fun TestMeasurable() {
             Text("your sample text")
         }
     ) { mw, mh ->
+        println("size: $mw,$mh")
         // measure view by another view
     }
 }

+ 41 - 35
src/main/kotlin/eu/mjdev/desktop/components/custom/PowerBlock.kt

@@ -22,55 +22,61 @@ import eu.mjdev.desktop.extensions.Modifier.circleBorder
 import eu.mjdev.desktop.extensions.Modifier.circleShadow
 import eu.mjdev.desktop.extensions.Modifier.clipCircle
 import eu.mjdev.desktop.extensions.Modifier.rectShadow
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
-@Preview
+// todo shadow? background?
 @Composable
 fun PowerBlock(
-    backgroundColor: Color,
+    backgroundColor: Color=Color.Transparent,
     shadowColor: Color = Color.Black.copy(alpha = 0.3f),
-//    textColor: Color = Color.Black,
-    iconTintColor: Color = Color.White,
+    iconTintColor: Color = Color.Black,
     bottomBoxHeight: Dp = 128.dp,
     iconHeight: Dp = 64.dp,
     onPowerButtonClick: () -> Unit = {}
-) = Column(
-    modifier = Modifier.fillMaxWidth()
-) {
-    Box(
-        modifier = Modifier
-            .fillMaxWidth()
-            .height(bottomBoxHeight),
-        contentAlignment = Alignment.BottomCenter
+) = withDesktopScope {
+    Column(
+        modifier = Modifier.fillMaxWidth()
     ) {
         Box(
             modifier = Modifier
                 .fillMaxWidth()
-                .height(iconHeight)
-                .rectShadow(color = shadowColor)
-                .background(backgroundColor)
-        )
-        Button(
-            modifier = Modifier
-                .padding(bottom = 36.dp)
-                .size(64.dp)
-                .clipCircle()
-                .circleBorder(4.dp, backgroundColor)
-                .circleShadow(color = shadowColor),
-            contentPadding = PaddingValues(1.dp),
-            onClick = onPowerButtonClick,
-            colors = ButtonDefaults.transparent(),
-            elevation = ButtonDefaults.noElevation()
+                .height(bottomBoxHeight),
+            contentAlignment = Alignment.BottomCenter
         ) {
-            Image(
+            Box(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .height(iconHeight)
+                    .rectShadow(color = shadowColor)
+                    .background(backgroundColor)
+            )
+            Button(
                 modifier = Modifier
+                    .padding(bottom = 36.dp)
                     .size(64.dp)
-                    .background(iconTintColor, CircleShape)
                     .clipCircle()
-                    .padding(8.dp),
-                imageVector = Icons.Filled.SettingsPower,
-                contentDescription = "",
-                colorFilter = ColorFilter.tint(backgroundColor)
-            )
+                    .circleBorder(4.dp, backgroundColor)
+                    .circleShadow(4.dp, shadowColor),
+                contentPadding = PaddingValues(1.dp),
+                onClick = onPowerButtonClick,
+                colors = ButtonDefaults.transparent(),
+                elevation = ButtonDefaults.noElevation()
+            ) {
+                Image(
+                    modifier = Modifier
+                        .size(64.dp)
+                        .background(iconTintColor, CircleShape)
+                        .clipCircle()
+                        .padding(8.dp),
+                    imageVector = Icons.Filled.SettingsPower,
+                    contentDescription = "",
+                    colorFilter = ColorFilter.tint(backgroundColor)
+                )
+            }
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun PowerBlockPreview() = PowerBlock()

+ 7 - 4
src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt

@@ -20,16 +20,15 @@ import eu.mjdev.desktop.extensions.Compose.noElevation
 import eu.mjdev.desktop.extensions.Compose.transparent
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
 import eu.mjdev.desktop.extensions.Modifier.clipCircle
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun UserAvatar(
     modifier: Modifier = Modifier,
     avatarSize: Dp = 128.dp,
     orientation: Orientation = Orientation.Vertical,
-    onUserAvatarClick: () -> Unit
+    onUserAvatarClick: () -> Unit = {}
 ) = withDesktopScope {
     val content: @Composable (orientation: Orientation) -> Unit = {
         Button(
@@ -94,4 +93,8 @@ fun UserAvatar(
             )
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun UserAvatarPreview() = UserAvatar()

+ 6 - 4
src/main/kotlin/eu/mjdev/desktop/components/desktop/Desktop.kt

@@ -3,7 +3,6 @@ package eu.mjdev.desktop.components.desktop
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
@@ -12,9 +11,8 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.desktop.widgets.MemoryChart
 import eu.mjdev.desktop.components.file.FolderView
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
-@OptIn(ExperimentalLayoutApi::class)
 @Suppress("FunctionName")
 @Preview
 @Composable
@@ -39,4 +37,8 @@ fun Desktop(
 //                .align(Alignment.Center)
 //        )
     }
-}
+}
+
+@Preview
+@Composable
+fun DesktopPreview() = Desktop()

+ 31 - 28
src/main/kotlin/eu/mjdev/desktop/components/desktop/widgets/MemoryChart.kt

@@ -15,11 +15,9 @@ import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundCol
 import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
 import eu.mjdev.desktop.helpers.system.MemInfo
 import eu.mjdev.desktop.helpers.system.MemInfo.Companion.toReadable
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun MemoryChart(
     modifier: Modifier = Modifier,
@@ -31,33 +29,38 @@ fun MemoryChart(
     legendPosition: LegendPosition = LegendPosition.BOTTOM,
     updateTimeOut: Long = 10000,
     dragEnabled: Boolean = true,
-    api: DesktopProvider = LocalDesktop.current,
-) = DraggableView(
-    modifier = modifier,
-    dragEnabled = dragEnabled
-) {
-    val backgroundColor by rememberBackgroundColor(api)
-    val iconsTintColor by rememberIconTintColor(api)
-    DonutChart(
+) = withDesktopScope {
+    DraggableView(
         modifier = modifier,
-        title = mainTitle,
-        textColor = iconsTintColor,
-        outerCircularColor = iconsTintColor,
-        innerCircularColor = iconsTintColor,
-        ratioLineColor = ratioLineColor,
-        refreshTimeout = updateTimeOut,
-        animation = animation,
-        legendPosition = legendPosition
+        dragEnabled = dragEnabled
     ) {
-        MemInfo().let { mem ->
-            val secondValue = mem.free
-            val firstValue = mem.used
-            val secondTitle = freeTitle.plus(": ").plus(mem.free.toReadable())
-            val firstTitle = usedTitle.plus(": ").plus(mem.used.toReadable())
-            listOf(
-                PieChartData(firstValue, iconsTintColor, firstTitle),
-                PieChartData(secondValue, backgroundColor, secondTitle)
-            )
+        val backgroundColor by rememberBackgroundColor(api)
+        val iconsTintColor by rememberIconTintColor(api)
+        DonutChart(
+            modifier = modifier,
+            title = mainTitle,
+            textColor = iconsTintColor,
+            outerCircularColor = iconsTintColor,
+            innerCircularColor = iconsTintColor,
+            ratioLineColor = ratioLineColor,
+            refreshTimeout = updateTimeOut,
+            animation = animation,
+            legendPosition = legendPosition
+        ) {
+            MemInfo().let { mem ->
+                val secondValue = mem.free
+                val firstValue = mem.used
+                val secondTitle = freeTitle.plus(": ").plus(mem.free.toReadable())
+                val firstTitle = usedTitle.plus(": ").plus(mem.used.toReadable())
+                listOf(
+                    PieChartData(firstValue, iconsTintColor, firstTitle),
+                    PieChartData(secondValue, backgroundColor, secondTitle)
+                )
+            }
         }
     }
 }
+
+@Preview
+@Composable
+fun MemoryChartPreview() = MemoryChart()

+ 14 - 11
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -25,11 +25,11 @@ import eu.mjdev.desktop.components.desktoppanel.applets.DesktopMenuIcon
 import eu.mjdev.desktop.components.desktoppanel.applets.DesktopPanelFavoriteApps
 import eu.mjdev.desktop.components.desktoppanel.applets.DesktopPanelLanguage
 import eu.mjdev.desktop.components.sliding.SlidingMenu
-import eu.mjdev.desktop.components.sliding.VisibilityState
-import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 import eu.mjdev.desktop.components.tooltip.Tooltip
-import eu.mjdev.desktop.components.tooltip.TooltipData
 import eu.mjdev.desktop.data.App
+import eu.mjdev.desktop.data.TooltipData
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.ColorUtils.lighter
 import eu.mjdev.desktop.extensions.Compose.height
@@ -37,18 +37,15 @@ import eu.mjdev.desktop.extensions.Modifier.topShadow
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelExitAnimation
 import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.windows.ChromeWindowState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 
 @OptIn(ExperimentalFoundationApi::class)
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun DesktopPanel(
-    api: DesktopProvider = LocalDesktop.current,
     dividerWidth: Dp = 4.dp,
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
@@ -74,7 +71,9 @@ fun DesktopPanel(
         // todo
     },
     onFocusChange: (Boolean) -> Unit = {},
-    onAppClick: (App) -> Unit = { app -> api.appsProvider.startApp(app) },
+    onAppClick: (App, DesktopProvider) -> Unit = { app, api ->
+        api.appsProvider.startApp(app)
+    },
     onAppContextMenuClick: (App) -> Unit = {
         // todo
     },
@@ -82,7 +81,7 @@ fun DesktopPanel(
         // todo
     },
 ) = withDesktopScope {
-    var tooltipState = remember { mutableStateOf<Any?>(null) }
+    val tooltipState = remember { mutableStateOf<Any?>(null) }
     val panelHeight: (visible: Boolean) -> Dp = { visible ->
         if (visible)
             iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height
@@ -205,7 +204,7 @@ fun DesktopPanel(
                                     iconPadding = iconPadding,
                                     iconOuterPadding = iconOuterPadding,
                                     onTooltip = { item -> tooltipState.value = item },
-                                    onAppClick = onAppClick,
+                                    onAppClick = { app -> onAppClick(app, api) },
                                     onContextMenuClick = onAppContextMenuClick
                                 )
                                 DesktopPanelLanguage(
@@ -220,4 +219,8 @@ fun DesktopPanel(
             }
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun DesktopPanelPreview() = DesktopPanel()

+ 12 - 7
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanelIcon.kt

@@ -25,7 +25,6 @@ import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @OptIn(ExperimentalComposeUiApi::class)
-@Preview
 @Composable
 fun DesktopPanelIcon(
     api: DesktopProvider = LocalDesktop.current,
@@ -42,12 +41,14 @@ fun DesktopPanelIcon(
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     iconState: MutableState<Boolean> = rememberState(false),
     contentDescription: String? = null,
-    onToolTip: (item: Any?) -> Unit,
-    onClick: () -> Unit,
-    onContextMenuClick: () -> Unit
+    onToolTip: (item: Any?) -> Unit = {},
+    onClick: () -> Unit = {},
+    onContextMenuClick: () -> Unit = {}
 ) {
-    val iconName = remember(app, icon) { app?.name ?: icon }
-    val materialIcon = remember(iconName) { api.currentUser.theme.iconSet.iconForName(iconName) ?: "?".toInt() }
+    val materialIcon = remember(app, icon) {
+        val iconName = app?.name ?: icon // todo better guess
+        api.currentUser.theme.iconSet.iconForName(iconName) ?: "?".toInt()
+    }
     val background = when {
         iconState.value -> iconBackgroundHover
         else -> Color.Transparent
@@ -85,7 +86,7 @@ fun DesktopPanelIcon(
                 onRightClick = onContextMenuClick
             )
         }
-        if (isStarting) {
+        if (isStarting || isRunning) {
             CircularProgressIndicator(
                 modifier = Modifier.padding(4.dp).size(iconSize),
                 color = iconColor,
@@ -95,3 +96,7 @@ fun DesktopPanelIcon(
         }
     }
 }
+
+@Preview
+@Composable
+fun DesktopPanelIconPreview() = DesktopPanelIcon()

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

@@ -26,13 +26,13 @@ import eu.mjdev.desktop.extensions.Compose.rememberState
 @Composable
 fun DesktopPanelText(
     modifier: Modifier = Modifier,
-    text: String = "button",
+    text: String = "text",
     textColor: Color = Color.White,
     backgroundHover: Color = Color.Red,
     textPadding: PaddingValues = PaddingValues(4.dp),
     buttonState: MutableState<Boolean> = rememberState(false),
     onToolTip: (item: Any?) -> Unit = {},
-    onClick: () -> Unit,
+    onClick: () -> Unit = {},
 ) = Box(
     modifier = modifier
         .wrapContentSize()
@@ -59,3 +59,7 @@ fun DesktopPanelText(
     }
 }
 
+@Preview
+@Composable
+fun DesktopPanelTextPreview() = DesktopPanelText()
+

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

@@ -12,7 +12,6 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanelIcon
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun DesktopMenuIcon(
     modifier: Modifier = Modifier,
@@ -21,9 +20,9 @@ fun DesktopMenuIcon(
     iconSize: DpSize = DpSize(56.dp, 56.dp),
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
-    onTooltip: (item: Any?) -> Unit,
-    onClick: () -> Unit,
-    onContextMenuClick: () -> Unit
+    onTooltip: (item: Any?) -> Unit = {},
+    onClick: () -> Unit = {},
+    onContextMenuClick: () -> Unit = {}
 ) = Row(
     modifier = modifier
 ) {
@@ -47,4 +46,8 @@ fun DesktopMenuIcon(
             .width(2.dp)
             .background(Color.White.copy(alpha = 0.4f))
     )
-}
+}
+
+@Preview
+@Composable
+fun DesktopMenuIconPreview() = DesktopMenuIcon()

+ 11 - 10
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelFavoriteApps.kt

@@ -17,15 +17,12 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanelIcon
 import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.extensions.Compose.rememberCalculated
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun DesktopPanelFavoriteApps(
     modifier: Modifier = Modifier,
-    api: DesktopProvider = LocalDesktop.current,
     iconColor: Color = Color.Black,
     iconBackgroundColor: Color = Color.White,
     iconColorRunning: Color = Color.White,
@@ -33,9 +30,9 @@ fun DesktopPanelFavoriteApps(
     iconPadding: PaddingValues = PaddingValues(4.dp),
     iconOuterPadding: PaddingValues = PaddingValues(2.dp),
     onTooltip: (item: Any?) -> Unit = {},
-    onAppClick: (app: App) -> Unit,
-    onContextMenuClick: (app: App) -> Unit
-) {
+    onAppClick: (app: App) -> Unit = {},
+    onContextMenuClick: (app: App) -> Unit = {}
+) = withDesktopScope {
     val apps by rememberCalculated(api.appsProvider.favoriteApps.size) { api.appsProvider.favoriteApps }
     Box(
         modifier = modifier
@@ -48,8 +45,8 @@ fun DesktopPanelFavoriteApps(
             items(apps) { app ->
                 DesktopPanelIcon(
                     app = app,
-                    isRunning = app.isRunning || app.hasWindow(api),
-                    isStarting = app.isStarted,
+                    isStarting = app.isStarting,
+                    isRunning = app.isRunning,
                     iconColor = iconColor,
                     iconBackgroundColor = iconBackgroundColor,
                     iconColorRunning = iconColorRunning,
@@ -64,4 +61,8 @@ fun DesktopPanelFavoriteApps(
             }
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun DesktopPanelFavoriteAppsPreview() = DesktopPanelFavoriteApps()

+ 7 - 5
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/applets/DesktopPanelLanguage.kt

@@ -5,18 +5,16 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import eu.mjdev.desktop.components.desktoppanel.DesktopPanelText
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 @Suppress("FunctionName")
 @Preview
 @Composable
 fun DesktopPanelLanguage(
     modifier: Modifier = Modifier,
-    api: DesktopProvider = LocalDesktop.current,
     onTooltip: (item: Any?) -> Unit = {},
     onClick: () -> Unit = {},
-) {
+) = withDesktopScope {
     DesktopPanelText(
         modifier = modifier,
         text = api.appsProvider.currentLocale.country,
@@ -24,4 +22,8 @@ fun DesktopPanelLanguage(
         onToolTip = onTooltip,
         onClick = onClick
     )
-}
+}
+
+@Preview
+@Composable
+fun DesktopPanelLanguagePreview() = DesktopPanelLanguage()

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

@@ -1,6 +1,5 @@
 package eu.mjdev.desktop.components.draggable
 
-import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.gestures.detectTapGestures
@@ -21,7 +20,6 @@ import eu.mjdev.desktop.extensions.Compose.rememberState
 import eu.mjdev.desktop.extensions.Modifier.conditional
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun DraggableView(
     modifier: Modifier = Modifier,

+ 6 - 5
src/main/kotlin/eu/mjdev/desktop/components/file/FolderIcon.kt

@@ -1,7 +1,6 @@
 package eu.mjdev.desktop.components.file
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
@@ -20,18 +19,16 @@ import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.draggable.DraggableView
 import eu.mjdev.desktop.components.fonticon.FontIcon
 import eu.mjdev.desktop.components.text.TextWithShadow
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import java.io.File
 import eu.mjdev.desktop.extensions.Custom.plus
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Modifier.circleBorder
 
-@OptIn(ExperimentalFoundationApi::class)
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun FolderIcon(
-    path: File,
+    path: File = File("/"),
     customName: String? = null,
     dragEnabled: Boolean = true,
     iconSize: DpSize = DpSize(128.dp, 128.dp),
@@ -94,3 +91,7 @@ fun FolderIcon(
         }
     }
 }
+
+@Preview
+@Composable
+fun FolderIconPreview() = FolderIcon()

+ 6 - 10
src/main/kotlin/eu/mjdev/desktop/components/file/FolderView.kt

@@ -17,7 +17,7 @@ import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.DpOffset
+import eu.mjdev.desktop.data.DesktopFolderItem
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import java.io.File
@@ -32,16 +32,15 @@ import java.io.File
 // XWrapperBase
 // ShellFolderManager
 // X11GraphicsEnvironment
-@Suppress("FunctionName")
+@Suppress("FunctionName", "SimplifyBooleanWithConstants")
 @OptIn(ExperimentalLayoutApi::class)
-@Preview
 @Composable
 fun FolderView(
     modifier: Modifier = Modifier,
     showHidden: Boolean = false,
     directoryFirst: Boolean = true,
     showHomeFolder: Boolean = false,
-    path: File?,
+    path: File? = File("/"),
     api: DesktopProvider = LocalDesktop.current,
     files: List<DesktopFolderItem> = remember(path) {
         path?.list()
@@ -104,9 +103,6 @@ fun FolderView(
     }
 }
 
-data class DesktopFolderItem(
-    val path: File,
-    val customName: String? = null,
-    var priority: Int = Int.MIN_VALUE,
-    var position: DpOffset = DpOffset.Unspecified
-)
+@Preview
+@Composable
+fun FolderViewPreview() = FolderView()

+ 8 - 4
src/main/kotlin/eu/mjdev/desktop/components/fonticon/FontIcon.kt

@@ -14,13 +14,13 @@ import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import eu.mjdev.desktop.components.icon.ShapedIcon
 import eu.mjdev.desktop.components.text.AutoResizeText
+import eu.mjdev.desktop.fonts.MaterialIconFont
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun FontIcon(
-    iconId: Int,
+    iconId: Int = 0,
     iconicFont: MaterialIconFont = LocalDesktop.current.currentUser.theme.iconSet,
     iconSize: DpSize = DpSize(32.dp, 32.dp),
     iconColor: Color = Color.Black,
@@ -29,8 +29,8 @@ fun FontIcon(
     contentDescription: String = "",
     outerPadding: PaddingValues = PaddingValues(2.dp),
     iconShape: Shape = CircleShape,
-    onRightClick: () -> Unit,
-    onClick: () -> Unit
+    onRightClick: () -> Unit = {},
+    onClick: () -> Unit = {}
 ) = ShapedIcon(
     iconSize = iconSize,
     iconColor = iconColor,
@@ -50,3 +50,7 @@ fun FontIcon(
     onClick = onClick,
     onRightClick = onRightClick
 )
+
+@Preview
+@Composable
+fun FontIconPreview() = FontIcon()

+ 7 - 6
src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt

@@ -1,7 +1,6 @@
 package eu.mjdev.desktop.components.greeter
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
@@ -19,22 +18,20 @@ import androidx.compose.ui.window.WindowPosition
 import eu.mjdev.desktop.components.custom.UserAvatar
 import eu.mjdev.desktop.components.input.PasswordTextView
 import eu.mjdev.desktop.data.User
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 import eu.mjdev.desktop.extensions.ColorUtils.alpha
 import eu.mjdev.desktop.extensions.Compose.rememberState
 
 // todo : all users
-@OptIn(ExperimentalFoundationApi::class)
 @Suppress("FunctionName", "unused")
-@Preview
 @Composable
 fun Greeter() = withDesktopScope {
     val user: User by rememberState(api.currentUser)
     var isUserLoggedIn by rememberState(user.isLoggedIn)
     var passwordVisible by rememberState(false)
     val onDone: (password: String) -> Unit = { password ->
-        isUserLoggedIn = user.login(password)
+        isUserLoggedIn = user.login(api, password)
     }
     ChromeWindow(
         position = WindowPosition.Aligned(Alignment.Center),
@@ -77,4 +74,8 @@ fun Greeter() = withDesktopScope {
             }
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun GreeterPreview() = Greeter()

+ 6 - 3
src/main/kotlin/eu/mjdev/desktop/components/icon/ShapedIcon.kt

@@ -22,7 +22,6 @@ import eu.mjdev.desktop.extensions.Compose.size
 
 @Suppress("DEPRECATION")
 @OptIn(ExperimentalFoundationApi::class)
-@Preview
 @Composable
 fun ShapedIcon(
     modifier: Modifier = Modifier,
@@ -35,8 +34,8 @@ fun ShapedIcon(
     iconShape: Shape = CircleShape,
     innerPadding: PaddingValues = PaddingValues(4.dp),
     outerPadding: PaddingValues = PaddingValues(2.dp),
-    onRightClick: () -> Unit,
-    onClick: () -> Unit
+    onRightClick: () -> Unit = {},
+    onClick: () -> Unit = {}
 ) = Box(
     modifier = modifier
         .padding(outerPadding)
@@ -75,3 +74,7 @@ fun ShapedIcon(
         }
     }
 }
+
+@Preview
+@Composable
+fun ShapedIconPreview() = ShapedIcon()

+ 6 - 3
src/main/kotlin/eu/mjdev/desktop/components/image/GifView.kt

@@ -14,11 +14,10 @@ import java.io.File
 import java.io.FileInputStream
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun GifView(
     modifier: Modifier = Modifier,
-    src: String
+    src: String = ""
 ) = BoxWithConstraints {
     val file by remember(src) { mutableStateOf(File(src)) }
     val streamData by remember(file) { mutableStateOf(FileInputStream(file)) }
@@ -47,4 +46,8 @@ fun GifView(
         }
         currentFrame = nextFrame
     }
-}
+}
+
+@Preview
+@Composable
+fun GifViewPreview() = GifView()

+ 8 - 3
src/main/kotlin/eu/mjdev/desktop/components/image/ImageAny.kt

@@ -2,6 +2,8 @@ package eu.mjdev.desktop.components.image
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.layout.Box
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.SupervisedUserCircle
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -17,16 +19,15 @@ import androidx.compose.ui.layout.ContentScale
 import coil3.compose.AsyncImage
 import eu.mjdev.desktop.components.video.VideoView
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import org.jetbrains.skia.Bitmap
 import java.io.File
 import androidx.compose.foundation.Image as ComposeImage
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun ImageAny(
-    src: Any?,
+    src: Any? = Icons.Filled.SupervisedUserCircle,
     contentDescription: String? = "",
     modifier: Modifier = Modifier,
     alignment: Alignment = Alignment.Center,
@@ -122,3 +123,7 @@ fun ImageAny(
         )
     }
 }
+
+@Preview
+@Composable
+fun ImageAnyPreview() = ImageAny()

+ 15 - 12
src/main/kotlin/eu/mjdev/desktop/components/info/InfoWindow.kt

@@ -2,38 +2,37 @@ package eu.mjdev.desktop.components.info
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.shadow
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.WindowPosition
 import eu.mjdev.desktop.components.background.BackgroundImage
-import eu.mjdev.desktop.extensions.Modifier.dropShadow
-import eu.mjdev.desktop.extensions.Modifier.rectShadow
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.withDesktopScope
+import eu.mjdev.desktop.extensions.Compose.rememberState
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindow
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun InfoWindow() = withDesktopScope {
+    var visible by rememberState(true)
     ChromeWindow(
-        visible = true,
+        visible = visible,
         size = DpSize(808.dp, 608.dp),
         position = WindowPosition.Aligned(Alignment.Center)
     ) {
         Box(
-            modifier = Modifier.fillMaxSize()
+            modifier = Modifier.fillMaxSize().clickable {
+                visible = false
+            }
         ) {
             Box(
                 modifier = Modifier.fillMaxSize()
@@ -54,4 +53,8 @@ fun InfoWindow() = withDesktopScope {
             }
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun InfoWindowPreview() = InfoWindow()

+ 5 - 0
src/main/kotlin/eu/mjdev/desktop/components/input/PasswordTextView.kt

@@ -1,5 +1,6 @@
 package eu.mjdev.desktop.components.input
 
+import androidx.compose.desktop.ui.tooling.preview.Preview
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.padding
@@ -99,3 +100,7 @@ fun PasswordTextView(
         }
     )
 }
+
+@Preview
+@Composable
+fun PasswordTextViewPreview() = PasswordTextView()

+ 75 - 0
src/main/kotlin/eu/mjdev/desktop/components/main/MainWindow.kt

@@ -0,0 +1,75 @@
+package eu.mjdev.desktop.components.main
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.Modifier
+import eu.mjdev.desktop.components.appsmenu.AppsMenu
+import eu.mjdev.desktop.components.controlcenter.ControlCenter
+import eu.mjdev.desktop.components.desktop.Desktop
+import eu.mjdev.desktop.components.desktoppanel.DesktopPanel
+import eu.mjdev.desktop.components.greeter.Greeter
+import eu.mjdev.desktop.components.info.InfoWindow
+import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
+import eu.mjdev.desktop.windows.DesktopWindow
+
+@Composable
+fun MainWindow() = withDesktopScope {
+    val controlCenterState = rememberVisibilityState()
+    val panelState = rememberVisibilityState()
+    val menuState = rememberVisibilityState()
+    val handleMenuFocus: (Boolean) -> Unit = { focus ->
+        if (!menuState.isWindowFocus && !focus && !controlCenterState.isWindowFocus) {
+            menuState.hide()
+        }
+    }
+    val handlePanelFocus: (Boolean) -> Unit = { focus ->
+        if (!focus) {
+            if (!menuState.isVisible) {
+                panelState.hide()
+            }
+        }
+    }
+    val handleControlCenterFocus: (Boolean) -> Unit = { focus ->
+        if (!focus) {
+            controlCenterState.hide()
+        }
+    }
+    DesktopWindow(
+        panelState = panelState,
+        controlCenterState = controlCenterState,
+        menuState = menuState
+    ) {
+        Desktop(
+            modifier = Modifier.fillMaxSize()
+        )
+        DesktopPanel(
+            panelState = panelState,
+            menuState = menuState,
+            onMenuIconClicked = { if (!menuState.isVisible) menuState.show() },
+            onFocusChange = { focus -> handlePanelFocus(focus) }
+        )
+        AppsMenu(
+            menuState = menuState,
+            panelState = panelState,
+            onFocusChange = { focus -> handleMenuFocus(focus) }
+        )
+        ControlCenter(
+            controlCenterState = controlCenterState,
+            onFocusChange = { focus -> handleControlCenterFocus(focus) }
+        )
+        Greeter()
+        InfoWindow()
+    }
+    DisposableEffect(Unit) {
+        onDispose {
+            api.close()
+        }
+    }
+}
+
+@Preview
+@Composable
+fun MainWindowPreview() = MainWindow()

+ 6 - 24
src/main/kotlin/eu/mjdev/desktop/components/menu/ContextMenu.kt

@@ -10,16 +10,17 @@ import androidx.compose.runtime.*
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import eu.mjdev.desktop.components.menu.base.ContextMenuState
+import eu.mjdev.desktop.components.menu.base.ContextMenuState.Companion.rememberContextMenuState
 import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 
 // todo
 @Suppress("unused", "FunctionName")
-@Preview
 @Composable
 fun ContextMenu(
     backgroundColor: Color = Color.SuperDarkGray,
     textColor: Color = Color.White,
-    contextMenuState: ContextMenuState,
+    contextMenuState: ContextMenuState = rememberContextMenuState(),
 //    onShow: () -> Unit = {},
 //    onHide: () -> Unit = {},
     onMenuItemClick: (item: String) -> Unit = {}
@@ -57,25 +58,6 @@ fun ContextMenu(
     }
 }
 
-class ContextMenuState(
-    val menuExpanded: MutableState<Boolean> = mutableStateOf(false),
-    val menuRenderCount: MutableState<Int> = mutableStateOf(0),
-    val items: List<String> = listOf()
-) {
-    fun show() {
-        menuExpanded.value = true
-    }
-
-    fun hide() {
-        menuExpanded.value = false
-    }
-
-    companion object {
-        @Composable
-        fun rememberContextMenuState(vararg items: String) = remember {
-            ContextMenuState(
-                items = items.asList()
-            )
-        }
-    }
-}
+@Preview
+@Composable
+fun ContextMenuPreview() = ContextMenu()

+ 29 - 0
src/main/kotlin/eu/mjdev/desktop/components/menu/base/ContextMenuState.kt

@@ -0,0 +1,29 @@
+package eu.mjdev.desktop.components.menu.base
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+
+class ContextMenuState(
+    val menuExpanded: MutableState<Boolean> = mutableStateOf(false),
+    val menuRenderCount: MutableState<Int> = mutableStateOf(0),
+    val items: List<String> = listOf()
+) {
+    fun show() {
+        menuExpanded.value = true
+    }
+
+    fun hide() {
+        menuExpanded.value = false
+    }
+
+    companion object {
+        @Composable
+        fun rememberContextMenuState(vararg items: String) = remember {
+            ContextMenuState(
+                items = items.asList()
+            )
+        }
+    }
+}

+ 5 - 2
src/main/kotlin/eu/mjdev/desktop/components/qrcode/QrCodeView.kt

@@ -16,7 +16,6 @@ import io.github.alexzhirkevich.qrose.rememberQrCodePainter
 
 // todo : params
 @Suppress("unused", "FunctionName")
-@Preview
 @Composable
 fun QrCodeView(
     modifier: Modifier = Modifier,
@@ -55,4 +54,8 @@ fun QrCodeView(
             contentDescription = contentDescription
         )
     }
-}
+}
+
+@Preview
+@Composable
+fun QrCodeViewPreview() = QrCodeView()

+ 7 - 3
src/main/kotlin/eu/mjdev/desktop/components/sliding/SlidingMenu.kt

@@ -13,11 +13,11 @@ import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.onPointerEvent
-import eu.mjdev.desktop.components.sliding.VisibilityState.Companion.rememberVisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState.Companion.rememberVisibilityState
 
 @OptIn(ExperimentalComposeUiApi::class)
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun SlidingMenu(
     modifier: Modifier = Modifier,
@@ -29,7 +29,7 @@ fun SlidingMenu(
     onPointerLeave: () -> Unit = {
 //        state.hide()
     },
-    content: @Composable BoxScope.(isVisible: Boolean) -> Unit
+    content: @Composable BoxScope.(isVisible: Boolean) -> Unit = {}
 ) {
     when (orientation) {
         Horizontal -> Box(
@@ -51,3 +51,7 @@ fun SlidingMenu(
         }
     }
 }
+
+@Preview
+@Composable
+fun SlidingMenuPreview() = SlidingMenu()

+ 4 - 9
src/main/kotlin/eu/mjdev/desktop/components/sliding/VisibilityState.kt → src/main/kotlin/eu/mjdev/desktop/components/sliding/base/VisibilityState.kt

@@ -1,21 +1,17 @@
-package eu.mjdev.desktop.components.sliding
+package eu.mjdev.desktop.components.sliding.base
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.unit.DpSize
 import eu.mjdev.desktop.helpers.internal.DpBounds
 import eu.mjdev.desktop.helpers.internal.DpBounds.Companion.toDpBounds
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
 @Suppress("CanBeParameter", "unused", "MemberVisibilityCanBePrivate")
 class VisibilityState(
     private val startState: Boolean = false,
-    var enabled: Boolean = true,
-    val containerSize: DpSize = DpSize.Zero
+    var enabled: Boolean = true
 ) {
     private val visibleState: MutableState<Boolean> = mutableStateOf(startState)
     private val boundsState: MutableState<DpBounds> = mutableStateOf(DpBounds.Zero)
@@ -73,10 +69,9 @@ class VisibilityState(
         fun rememberVisibilityState(
             startState: Boolean = false,
             enabled: Boolean = true,
-            api: DesktopProvider = LocalDesktop.current,
         ): VisibilityState {
-            return remember(startState, enabled, api.containerSize) {
-                VisibilityState(startState, enabled, api.containerSize)
+            return remember(startState, enabled) {
+                VisibilityState(startState, enabled)
             }
         }
     }

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

@@ -17,10 +17,9 @@ import kotlin.math.min
 
 // todo may be need refactor
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun AutoResizeText(
-    text: String,
+    text: String = "A",
     modifier: Modifier = Modifier,
     color: Color = Color.Unspecified,
     fontStyle: FontStyle? = null,
@@ -39,3 +38,7 @@ fun AutoResizeText(
         overflow = TextOverflow.Clip,
     )
 }
+
+@Preview
+@Composable
+fun AutoResizeTextPreview() = AutoResizeText()

+ 4 - 1
src/main/kotlin/eu/mjdev/desktop/components/text/CircleText.kt

@@ -22,7 +22,6 @@ import androidx.compose.ui.unit.sp
 import eu.mjdev.desktop.extensions.Modifier.clipCircle
 
 @Suppress("unused", "FunctionName")
-@Preview
 @Composable
 fun CircleText(
     modifier: Modifier = Modifier,
@@ -57,3 +56,7 @@ fun CircleText(
         fontSize = textSize
     )
 }
+
+@Preview
+@Composable
+fun CircleTextPreview() = CircleText()

+ 5 - 2
src/main/kotlin/eu/mjdev/desktop/components/text/TextAny.kt

@@ -18,7 +18,6 @@ import androidx.compose.ui.unit.TextUnit
 import eu.mjdev.desktop.extensions.Compose.textFrom
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun TextAny(
     text: Any? = "test",
@@ -56,4 +55,8 @@ fun TextAny(
     maxLines = maxLines,
     onTextLayout = onTextLayout,
     style = style
-)
+)
+
+@Preview
+@Composable
+fun TextAnyPreview() = TextAny()

+ 6 - 3
src/main/kotlin/eu/mjdev/desktop/components/text/TextWithShadow.kt

@@ -17,18 +17,17 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun TextWithShadow(
     modifier: Modifier = Modifier,
     shadowSize: Dp = 4.dp,
-    shadowColor: Color = Color.Black.copy(alpha = 0.3f),
+    shadowColor: Color = Color.Black.copy(alpha = 0.5f),
     offsetX: Dp = 0.dp,
     offsetY: Dp = 0.dp,
     fontWeight: FontWeight = FontWeight.Normal,
     textAlign: TextAlign? = null,
     fontSize: TextUnit = 16.sp,
-    color: Color = Color.White,
+    color: Color = Color.Red,
     overflow: TextOverflow = Ellipsis,
     minLines: Int = 2,
     maxLines: Int = 2,
@@ -51,3 +50,7 @@ fun TextWithShadow(
     minLines = minLines,
     maxLines = maxLines
 )
+
+@Preview
+@Composable
+fun TextWithShadowPreview() = TextWithShadow()

+ 4 - 5
src/main/kotlin/eu/mjdev/desktop/components/tooltip/Tooltip.kt

@@ -17,11 +17,11 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import eu.mjdev.desktop.components.text.TextAny
+import eu.mjdev.desktop.data.TooltipData
 import eu.mjdev.desktop.extensions.Compose.launchedEffect
 import kotlinx.coroutines.delay
 
 @Suppress("FunctionName")
-@Preview
 @Composable
 fun Tooltip(
     textColor: Color = Color.White,
@@ -63,7 +63,6 @@ fun Tooltip(
     }
 } else Unit
 
-data class TooltipData(
-    val title: String = "",
-    val description: String = ""
-)
+@Preview
+@Composable
+fun TooltipPreview() = Tooltip()

+ 6 - 1
src/main/kotlin/eu/mjdev/desktop/components/video/VideoView.kt

@@ -8,6 +8,7 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.layout.ContentScale
+import eu.mjdev.desktop.components.video.base.VideoPlayerFFMpeg
 import java.io.File
 
 @Suppress("UNUSED_PARAMETER", "FunctionName")
@@ -15,7 +16,7 @@ import java.io.File
 @Composable
 fun VideoView(
     modifier: Modifier = Modifier,
-    src: Any?,
+    src: Any? = null,
     alignment: Alignment = Alignment.Center,
     alpha: Float = 1f,
     contentDescription: String? = "",
@@ -35,3 +36,7 @@ fun VideoView(
         }
     )
 }
+
+@Preview
+@Composable
+fun VideoViewPreview() = VideoView()

+ 5 - 3
src/main/kotlin/eu/mjdev/desktop/components/video/KFFMpeg.kt → src/main/kotlin/eu/mjdev/desktop/components/video/base/KFFMpeg.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components.video;
+package eu.mjdev.desktop.components.video.base
 
 import androidx.compose.ui.graphics.asComposeImageBitmap
 import androidx.compose.ui.unit.IntSize
@@ -66,6 +66,7 @@ data class KVideoStream(
     val codec: String
 ) : KAVStream
 
+@Suppress("unused")
 data class KAudioStream(
     override val id: Int
 ) : KAVStream
@@ -205,8 +206,8 @@ class KFrameGrabber(
     private val stream = fmtCtx.streams(kStream.id)
     private val kCodecContext = KAVCodecContext(stream.codecpar(), hwDecoder)
     private val codecCtx = kCodecContext.codecCtx
-    val targetWidth = targetSize?.width ?: codecCtx.width()
-    val targetHeight = targetSize?.height ?: codecCtx.height()
+    private val targetWidth = targetSize?.width ?: codecCtx.width()
+    private val targetHeight = targetSize?.height ?: codecCtx.height()
     private val dstFormat = avutil.AV_PIX_FMT_BGRA // Format matches the internal skia image format
     private val skiaFormat = ImageInfo(
         targetWidth,
@@ -328,6 +329,7 @@ fun errCheck(returnCode: Int, errorMessage: String? = null) {
     }
 }
 
+@Suppress("FunctionName")
 fun av_err2str(errNum: Int): String {
     val charArray = ByteArray(avutil.AV_ERROR_MAX_STRING_SIZE)
     avutil.av_strerror(errNum, charArray, avutil.AV_ERROR_MAX_STRING_SIZE.toLong())

+ 5 - 2
src/main/kotlin/eu/mjdev/desktop/components/video/VideoPlayerFFMpeg.kt → src/main/kotlin/eu/mjdev/desktop/components/video/base/VideoPlayerFFMpeg.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components.video
+package eu.mjdev.desktop.components.video.base
 
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.runtime.*
@@ -12,6 +12,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.isActive
 import kotlin.math.min
 
+@Suppress("unused")
 class FFmpegVideoPlayerState {
     private val kContext = KAVFormatContext()
 
@@ -19,7 +20,8 @@ class FFmpegVideoPlayerState {
         internal set
     var progress: Float by mutableStateOf(0f)
         internal set
-    var aspectRatio: Float by mutableStateOf(1f)
+    @Suppress("RedundantSetter")
+    private var aspectRatio: Float by mutableStateOf(1f)
         private set
     var displayFPS: Float by mutableStateOf(0f)
         internal set
@@ -29,6 +31,7 @@ class FFmpegVideoPlayerState {
     var frameGrabber: KFrameGrabber? by mutableStateOf(null)
         private set
 
+    @Suppress("MemberVisibilityCanBePrivate")
     var metadata: Map<String, String> by mutableStateOf(emptyMap())
         private set
 

+ 58 - 55
src/main/kotlin/eu/mjdev/desktop/components/web/ComposeWebView.kt

@@ -21,12 +21,10 @@ import com.multiplatform.webview.web.WebView
 import com.multiplatform.webview.web.WebViewState
 import eu.mjdev.desktop.components.draggable.DraggableView
 import eu.mjdev.desktop.extensions.Compose.DarkBlue
-import eu.mjdev.desktop.provider.DesktopProvider
-import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 
 // todo improve
 @Suppress("LocalVariableName", "FunctionName", "unused")
-@Preview
 @Composable
 fun ComposeWebView(
     modifier: Modifier = Modifier,
@@ -38,63 +36,68 @@ fun ComposeWebView(
     loadingLineColor: Color = Color.DarkBlue,
     jsBridge: IJsMessageHandler? = null,
     disablePopupWindows: Boolean = true,
-    api: DesktopProvider = LocalDesktop.current
-) = DraggableView(
-    modifier = modifier,
-    contentPadding = contentPadding,
-    dragEnabled = dragEnabled,
-    backgroundColor = backgroundColor,
-    dragBackgroundColor = dragBackgroundColor
-) {
-    val webViewState = remember(url) {
-        if (url.startsWith("http")) WebViewState(WebContent.Url(url))
-        else WebViewState(WebContent.Url("file://$url"))
-    }
-    val loadingState = remember(webViewState) { webViewState.loadingState }
-    val _jsBridge: WebViewJsBridge = rememberWebViewJsBridge()
-    if (api.kcefHelper.restartRequired.value) {
-        Text(text = "Restart required.")
-    } else {
-        if (api.kcefHelper.initialized.value) {
-            WebView(
-                modifier = modifier,
-                state = webViewState
-            )
-            if (loadingState is LoadingState.Loading) {
-                LinearProgressIndicator(
-                    progress = loadingState.progress,
-                    color = loadingLineColor,
-                    modifier = Modifier.fillMaxWidth().align(Alignment.BottomStart)
-                )
-            }
+) = withDesktopScope {
+    DraggableView(
+        modifier = modifier,
+        contentPadding = contentPadding,
+        dragEnabled = dragEnabled,
+        backgroundColor = backgroundColor,
+        dragBackgroundColor = dragBackgroundColor
+    ) {
+        val webViewState = remember(url) {
+            if (url.startsWith("http")) WebViewState(WebContent.Url(url))
+            else WebViewState(WebContent.Url("file://$url"))
+        }
+        val loadingState = remember(webViewState) { webViewState.loadingState }
+        val _jsBridge: WebViewJsBridge = rememberWebViewJsBridge()
+        if (api.kcefHelper.restartRequired.value) {
+            Text(text = "Restart required.")
         } else {
-            Box(
-                modifier = Modifier.matchParentSize()
-                    .padding(8.dp)
-            ) {
-                CircularProgressIndicator(
-                    modifier = Modifier.fillMaxSize(),
-                    color = Color.White,
-                    strokeWidth = 10.dp,
-                    backgroundColor = Color.Transparent
-                )
-                Text(
-                    modifier = Modifier.align(Alignment.Center),
-                    text = "${api.kcefHelper.downloading.value}%"
+            if (api.kcefHelper.initialized.value) {
+                WebView(
+                    modifier = modifier,
+                    state = webViewState
                 )
+                if (loadingState is LoadingState.Loading) {
+                    LinearProgressIndicator(
+                        progress = loadingState.progress,
+                        color = loadingLineColor,
+                        modifier = Modifier.fillMaxWidth().align(Alignment.BottomStart)
+                    )
+                }
+            } else {
+                Box(
+                    modifier = Modifier.matchParentSize()
+                        .padding(8.dp)
+                ) {
+                    CircularProgressIndicator(
+                        modifier = Modifier.fillMaxSize(),
+                        color = Color.White,
+                        strokeWidth = 10.dp,
+                        backgroundColor = Color.Transparent
+                    )
+                    Text(
+                        modifier = Modifier.align(Alignment.Center),
+                        text = "${api.kcefHelper.downloading.value}%"
+                    )
+                }
             }
         }
-    }
-    LaunchedEffect(Unit) {
-        api.kcefHelper.init()
-        if (jsBridge != null) _jsBridge.register(jsBridge)
-        webViewState.webSettings.apply {
-            isJavaScriptEnabled = true
-            with(desktopWebSettings) {
+        LaunchedEffect(Unit) {
+            api.kcefHelper.init()
+            if (jsBridge != null) _jsBridge.register(jsBridge)
+            webViewState.webSettings.apply {
+                isJavaScriptEnabled = true
+                with(desktopWebSettings) {
 //                offScreenRendering = true
-                transparent = true
-                this.disablePopupWindows = disablePopupWindows
+                    transparent = true
+                    this.disablePopupWindows = disablePopupWindows
+                }
             }
         }
     }
-}
+}
+
+@Preview
+@Composable
+fun ComposeWebViewPreview() = ComposeWebView()

+ 52 - 84
src/main/kotlin/eu/mjdev/desktop/data/App.kt

@@ -3,10 +3,8 @@ package eu.mjdev.desktop.data
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.graphics.Color
-import eu.mjdev.desktop.extensions.Custom.command
 import eu.mjdev.desktop.helpers.exception.EmptyException.Companion.EmptyException
-import eu.mjdev.desktop.helpers.system.Command
-import eu.mjdev.desktop.helpers.system.Environment
+import eu.mjdev.desktop.helpers.system.Shell
 import eu.mjdev.desktop.provider.DesktopProvider
 import java.io.File
 
@@ -16,6 +14,8 @@ class App(
     val desktopFile: DesktopFile = DesktopFile(file),
     val isFavorite: Boolean = false
 ) {
+    val fileName
+        get() = desktopFile.fileName
     val type
         get() = desktopFile.type
     val version
@@ -44,11 +44,21 @@ class App(
     val onStartingHandler: MutableState<() -> Unit> = mutableStateOf({})
     val onStartedHandler: MutableState<() -> Unit> = mutableStateOf({})
 
-    var process: Process? = null
-    val isRunning: Boolean
-        get() = (process?.isAlive == true) || process?.children()?.anyMatch { it.isAlive } == true
+    var process: Shell? = null
 
-    var isStarted: Boolean = false
+    var isStartingState = mutableStateOf(false)
+    var isStarting: Boolean
+        get() = isStartingState.value
+        set(value) {
+            isStartingState.value = value
+        }
+
+    val isRunningState = mutableStateOf(false)
+    var isRunning: Boolean
+        get() = isStartingState.value
+        set(value) {
+            isStartingState.value = value
+        }
 
     // todo :  replace all params with what needed
     val cmd
@@ -58,56 +68,47 @@ class App(
         get() = process?.command
 
     val pids: List<Long>
-        get() = getProcessPids(process?.toHandle())
-
-    val runningProcesses
-        get() = ProcessHandle.allProcesses()
+        get() = process?.pids ?: emptyList()
 
-    fun getProcessPids(p: ProcessHandle?): List<Long> {
-        val pids = mutableListOf<Long>()
-        if (p != null) {
-            pids.add(p.pid())
-            p.children().forEach { pch ->
-                pids.addAll(getProcessPids(pch))
-            }
-        }
-        return pids
-    }
-
-    fun start() = runCatching {
+    fun start(
+        api: DesktopProvider
+    ) = runCatching {
+        println("Starting app: $name [$fileName]")
         triggerStart()
-        println("Starting app: $name [$cmd]")
-        val env = Environment()
-        Command(
-            "/bin/sh",
-            "-c",
-            "\"DBUS_SESSION_BUS_ADDRESS='unix:path=\$XDG_RUNTIME_DIR/bus' &&  $cmd \""
-        ).executeWithEnv(env).also {
-            process = it
-        }.apply {
-            onExit().thenRun {
+        process = Shell(api)
+            .writeCommand("export DBUS_SESSION_BUS_ADDRESS=\"unix:path=\$XDG_RUNTIME_DIR/bus\"")
+            .writeCommand("gtk-launch", fileName)
+            .apply {
+                triggerStarted()
+                println("app started")
+                println("system windows : ${api.windows.allSystemWindows}")
+            }
+            .onExit {
                 triggerStop()
+                println("app stopped")
+                println("system windows : ${api.windows.allSystemWindows}")
             }
-        }
-        triggerStarted()
     }.onFailure { error ->
         triggerStop(error)
     }
 
     fun triggerStart() {
-        isStarted = true
+        isRunning = false
+        isStarting = true
         onStartingHandler.value.invoke()
     }
 
     fun triggerStarted() {
-        isStarted = true
+        isStarting = false
+        isRunning = true
         onStartedHandler.value.invoke()
     }
 
     fun triggerStop() = triggerStop(null)
 
     fun triggerStop(result: Throwable?) {
-        isStarted = false
+        isStarting = false
+        isRunning = false
         onStopHandler.value.invoke(result ?: EmptyException)
     }
 
@@ -126,53 +127,8 @@ class App(
         return this
     }
 
-    fun isWindowFocus(api: DesktopProvider): Boolean =
-        api.windows.isWindowActive(pids)
-
-    fun hasWindow(api: DesktopProvider) =
-        api.windows.getWindowsByPids(pids).isNotEmpty()
-
-    fun requestWindowFocus(api: DesktopProvider) {
-        val windows = api.windows.getWindowsByPids(pids)
-        if (windows.isNotEmpty()) {
-            println("Got windows:${windows.size}")
-            println("$windows")
-            windows.forEach { w -> w.toFront() }
-        } else {
-            println("No window for process PID: [${pids.joinToString { p -> "$p," }}]")
-            closeWindow(api)
-        }
-    }
-
-    fun minimizeWindow(api: DesktopProvider) {
-        val windows = api.windows.getWindowsByPids(pids)
-        if (windows.isNotEmpty()) {
-            windows.forEach { w -> w.minimize() }
-        } else {
-            closeWindow(api)
-        }
-    }
-
-    fun maximizeWindow(api: DesktopProvider) {
-        val windows = api.windows.getWindowsByPids(pids)
-        if (windows.isNotEmpty()) {
-            windows.forEach { w -> w.maximize() }
-        } else {
-            closeWindow(api)
-        }
-    }
-
-    fun closeWindow(api: DesktopProvider) {
-        val windows = api.windows.getWindowsByPids(pids)
-        if (windows.isNotEmpty()) {
-            windows.forEach { w -> w.close() }
-        } else {
-            exit()
-        }
-    }
-
     fun exit() {
-        process?.destroy()
+        process?.close()
     }
 
     override fun equals(other: Any?): Boolean {
@@ -193,6 +149,18 @@ class App(
         return result
     }
 
+//    fun hasWindow(api: DesktopProvider): Boolean =
+//        process?.hasWindow(api) ?: false
+
+//    fun isWindowFocus(api: DesktopProvider): Boolean =
+//        process?.isWindowFocus(api) ?: false
+
+//    fun minimizeWindow(api: DesktopProvider) =
+//        process?.minimizeWindow(api) ?: false
+
+//    fun requestWindowFocus(api: DesktopProvider) =
+//        process?.requestWindowFocus(api) ?: false
+
     companion object {
         val Empty: App = App()
 

+ 6 - 0
src/main/kotlin/eu/mjdev/desktop/data/DesktopFile.kt

@@ -13,6 +13,9 @@ class DesktopFile(
     private val props
         get() = content?.get("Desktop Entry")
 
+    val fileName: String
+        get() = file.name
+
     val type
         get() = props?.get(Prop_Type) ?: ""
 
@@ -43,6 +46,9 @@ class DesktopFile(
     val runInTerminal
         get() = (props?.get(Prop_Terminal) ?: "false").asBoolean()
 
+    val absolutePath: String
+        get() = file.absolutePath
+
     companion object {
         const val Prop_Type = "Type" // string "Application"
         const val Prop_Version = "Version" // string

+ 11 - 0
src/main/kotlin/eu/mjdev/desktop/data/DesktopFolderItem.kt

@@ -0,0 +1,11 @@
+package eu.mjdev.desktop.data
+
+import androidx.compose.ui.unit.DpOffset
+import java.io.File
+
+data class DesktopFolderItem(
+    val path: File,
+    val customName: String? = null,
+    var priority: Int = Int.MIN_VALUE,
+    var position: DpOffset = DpOffset.Unspecified
+)

+ 2 - 2
src/main/kotlin/eu/mjdev/desktop/data/Theme.kt

@@ -5,8 +5,8 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
-import eu.mjdev.desktop.components.fonticon.MaterialIconFont
-import eu.mjdev.desktop.components.fonticon.MaterialSymbolsSharp
+import eu.mjdev.desktop.fonts.MaterialIconFont
+import eu.mjdev.desktop.fonts.MaterialSymbolsSharp
 import eu.mjdev.desktop.extensions.Compose.SuperDarkGray
 
 // todo all customizable

+ 6 - 0
src/main/kotlin/eu/mjdev/desktop/data/TooltipData.kt

@@ -0,0 +1,6 @@
+package eu.mjdev.desktop.data
+
+data class TooltipData(
+    val title: String = "",
+    val description: String = ""
+)

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

@@ -1,10 +1,12 @@
 package eu.mjdev.desktop.data
 
-import eu.mjdev.desktop.helpers.system.Command
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.SupervisedUserCircle
+import eu.mjdev.desktop.helpers.system.Shell
 import eu.mjdev.desktop.helpers.system.UserDirs
+import eu.mjdev.desktop.provider.DesktopProvider
 import java.io.File
 
-// todo
 @Suppress("MemberVisibilityCanBePrivate", "unused")
 class User(
     val data: String,
@@ -26,8 +28,8 @@ class User(
         get() = dataItems[5]
     val shell: String
         get() = dataItems[6]
-    val picture: Any?
-        get() = loadPicture(dataItems[5], ".face")
+    val picture: Any
+        get() = loadPicture(dataItems[5], ".face") ?: Icons.Filled.SupervisedUserCircle
 
     val homeDir
         get() = File(home)
@@ -43,21 +45,21 @@ class User(
     }
 
     fun login(
-        string: String
-    ): Boolean {
-        // todo
-        return true
-    }
+        api:DesktopProvider,
+        password: String
+    ): Boolean = api.login(userName, password)
 
     companion object {
         val Nobody = User(":x:-1:-1:::")
 
         fun isLoggedIn(un: String): Boolean =
-            Command("whoami").execute()?.trim()?.contentEquals(un) == true
+            Shell.executeAndRead("whoami").trim().contentEquals(un)
 
-        fun loadPicture(home: String, pic: String): File = loadPicture(File(home), pic)
+        fun loadPicture(home: String, pic: String): File? = loadPicture(File(home), pic)
 
-        fun loadPicture(homeDir: File, picName: String): File = File(homeDir, picName)
+        fun loadPicture(homeDir: File, picName: String): File? = File(homeDir, picName).let {
+            if(it.exists()) it else null
+        }
 
         val allUsers: List<User>
             get() = File("/etc/passwd")

+ 9 - 0
src/main/kotlin/eu/mjdev/desktop/extensions/Custom.kt

@@ -6,6 +6,7 @@ import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.graphics.NativePaint
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
+import com.google.gson.Gson
 import eu.mjdev.desktop.helpers.streams.ResourceStream
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.channelFlow
@@ -67,6 +68,14 @@ object Custom {
         ResourceStream("keys/$key.key").string
     }.getOrNull().orEmpty()
 
+    inline fun <reified T> String.jsonToList(): List<T> = runCatching {
+        Gson().fromJson(this, List::class.java).mapNotNull { item -> item as? T }
+    }.getOrNull() ?: emptyList()
+
+    inline fun <reified T> String.to(): T? = runCatching {
+        Gson().fromJson(this, T::class.java)
+    }.getOrNull()
+
     val Process.command: String?
         get() = info().command().getOrNull()
 

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/fonticon/CodePointsFile.kt → src/main/kotlin/eu/mjdev/desktop/fonts/CodePointsFile.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components.fonticon
+package eu.mjdev.desktop.fonts
 
 import eu.mjdev.desktop.helpers.streams.ResourceStream
 

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/fonticon/FontFile.kt → src/main/kotlin/eu/mjdev/desktop/fonts/FontFile.kt

@@ -1,4 +1,4 @@
-package eu.mjdev.desktop.components.fonticon
+package eu.mjdev.desktop.fonts
 
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily

+ 1 - 1
src/main/kotlin/eu/mjdev/desktop/components/fonticon/MaterialIconFont.kt → src/main/kotlin/eu/mjdev/desktop/fonts/MaterialIconFont.kt

@@ -1,6 +1,6 @@
 @file:Suppress("unused")
 
-package eu.mjdev.desktop.components.fonticon
+package eu.mjdev.desktop.fonts
 
 import androidx.compose.ui.text.font.FontFamily
 import me.xdrop.fuzzywuzzy.FuzzySearch

+ 17 - 10
src/main/kotlin/eu/mjdev/desktop/helpers/managers/GnomeManager.kt

@@ -1,6 +1,7 @@
 package eu.mjdev.desktop.helpers.managers
 
-import eu.mjdev.desktop.helpers.system.Command
+import eu.mjdev.desktop.helpers.system.Shell
+
 //import org.freedesktop.dbus.annotations.DBusInterfaceName
 //import org.freedesktop.dbus.connections.impl.DBusConnection
 //import org.freedesktop.dbus.interfaces.DBusInterface
@@ -24,43 +25,50 @@ class GnomeManager {
 
     fun setColorScheme(
         schemeName: String
-    ) = Command(
+    ) = Shell.executeAndRead(
         "gsettings",
         "set",
         "org.gnome.desktop.interface",
         "color-scheme",
         schemeName
-    ).execute()
+    )
 
     fun setGTKTheme(
         themeName: String = THEME_YARU
-    ) = Command(
+    ) = Shell.executeAndRead(
         "gsettings",
         "set",
         "org.gnome.desktop.interface",
         "gtk-theme",
         themeName
-    ).execute()
+    )
+
+    fun getGTKTheme(): String = Shell.executeAndRead(
+        "gsettings",
+        "get",
+        "org.gnome.desktop.interface",
+        "gtk-theme"
+    ).replace("'", "").trim()
 
     fun setIconTheme(
         themeName: String = THEME_YARU
-    ) = Command(
+    ) = Shell.executeAndRead(
         "gsettings",
         "set",
         "org.gnome.desktop.interface",
         "icon-theme",
         themeName
-    ).execute()
+    )
 
     fun setSoundTheme(
         themeName: String = THEME_YARU
-    ) = Command(
+    ) = Shell.executeAndRead(
         "gsettings",
         "set",
         "org.gnome.desktop.sound",
         "gtk-theme",
         themeName
-    ).execute()
+    )
 
     fun setDarkColorScheme() =
         setColorScheme(COLOR_SCHEME_PREFER_DARK)
@@ -84,7 +92,6 @@ class GnomeManager {
         const val THEME_YARU = "Yaru"
         const val THEME_ADWAITA_DARK = "Adwaita-dark"
         const val THEME_MJDEV = "mjdev"
-
     }
 
 }

+ 69 - 67
src/main/kotlin/eu/mjdev/desktop/helpers/managers/WindowsManager.kt

@@ -9,25 +9,18 @@ import com.sun.jna.platform.unix.X11.*
 import com.sun.jna.ptr.IntByReference
 import com.sun.jna.ptr.NativeLongByReference
 import com.sun.jna.ptr.PointerByReference
+import kotlin.jvm.optionals.getOrNull
 
-@Suppress(
-    "unused", "MemberVisibilityCanBePrivate", "FunctionName", "LocalVariableName", "SameParameterValue",
-    "DEPRECATION", "UNUSED_PARAMETER"
-)
+@Suppress("LocalVariableName", "FunctionName", "SameParameterValue", "unused")
 class WindowsManager {
-    private val x11: X11 by lazy {
-        INSTANCE
-    }
-    private val x11Ext by lazy {
-        Native.loadLibrary("X11", X11Ext::class.java)
-    }
-    private val xmu by lazy {
-        Native.loadLibrary("Xmu", Xmu::class.java)
-    }
+    private val x11: X11 by lazy { Native.load("X11", X11::class.java) }
+    private val x11Ext by lazy { Native.load("X11", X11Ext::class.java) }
+
+    @Suppress("unused")
+    private val xmu by lazy { Native.load("Xmu", Xmu::class.java) }
     private val display: Display by lazy { x11.XOpenDisplay(null) }
     private val rootWindow: Window
         get() = x11.XDefaultRootWindow(display)
-
     val allSystemWindows: List<SystemWindow>
         get() = getAllWindows(rootWindow, 0)
 
@@ -53,27 +46,24 @@ class WindowsManager {
         for (id in ids) {
             if (id == 0L) continue
             Window(id).also { window ->
-                windowList.add(
-                    SystemWindow(
-                        this,
-                        id,
-                        getWindowName(window) ?: "",
-                        getWindowPid(window) ?: 0L,
-                        getWindowClass(window) ?: "",
-                        getWindowDesktop(window) ?: 0L
-                    )
-                )
+                val pid = getWindowPid(window) ?: 0L
+                val name = getWindowName(window) ?: ""
+                val cls = getWindowClass(window) ?: ""
+                val desk = getWindowDesktop(window) ?: 0L
+                val cmd = getWindowCommand(pid) ?: ""
+                val icon = getWindowIconName(window) ?: ""
+                windowList.add(SystemWindow(id, name, icon, pid, cls, desk, cmd))
                 getAllWindows(window, depth + 1)
             }
         }
         return windowList
     }
 
-    fun getWindowsByPids(
+    private fun getWindowsByPids(
         pids: List<Long>
-    ): List<IWindow> = allSystemWindows.filter { pids.contains(it.pid) }
+    ): List<SystemWindow> = allSystemWindows.filter { pids.contains(it.pid) }
 
-    fun getWindowPid(
+    private fun getWindowPid(
         window: Window
     ) = get_property_as_long(
         window,
@@ -81,7 +71,7 @@ class WindowsManager {
         "_NET_WM_PID"
     )
 
-    fun getWindowName(
+    private fun getWindowName(
         window: Window
     ) = get_property_as_utf8_string(
         window,
@@ -89,7 +79,7 @@ class WindowsManager {
         "_NET_WM_NAME"
     )
 
-    fun getWindowClass(
+    private fun getWindowClass(
         window: Window
     ) = get_property_as_utf8_string(
         window,
@@ -97,7 +87,13 @@ class WindowsManager {
         "WM_CLASS"
     )
 
-    fun getWindowDesktop(
+    private fun getWindowCommand(
+        pid: Long
+    ): String? = ProcessHandle.allProcesses().toList().firstOrNull {
+        it.pid() == pid
+    }?.info()?.command()?.getOrNull()
+
+    private fun getWindowDesktop(
         window: Window
     ) = get_property_as_long(
         window,
@@ -105,10 +101,26 @@ class WindowsManager {
         "_NET_SHOWING_DESKTOP"
     )
 
+    // todo
+    private fun getWindowIconName(
+        @Suppress("UNUSED_PARAMETER") window: Window
+    ): String? {
+//        val icon_name_return = PointerByReference()
+//        if (x11Ext.XGetIconName(display, window, icon_name_return) == 0) {
+//            g_free(icon_name_return.pointer)
+//            return null
+//        }
+//        return g_strdup(icon_name_return.pointer)
+        return null
+    }
+
     fun minimizeWindow(window: Window): Boolean =
         x11Ext.XIconifyWindow(display, window, x11.XDefaultScreen(display)) == TRUE
 
-    fun maximizeWindow(window: Window) {
+    @Suppress("UNUSED_PARAMETER")
+    fun maximizeWindow(
+        window: Window
+    ) {
         // todo
     }
 
@@ -125,7 +137,7 @@ class WindowsManager {
         pids: List<Long>
     ): Boolean = get_active_window()?.let { aw ->
         getWindowsByPids(pids).any {
-            it.window.toLong() == aw.toLong()
+            it.id == aw.toLong()
         }
     } ?: false
 
@@ -157,7 +169,6 @@ class WindowsManager {
     }
 
     private fun get_property_as_int(
-        display: Display,
         win: Window,
         xa_prop_type: Atom,
         prop_name: String
@@ -292,11 +303,11 @@ class WindowsManager {
 
     private fun activate_window(
         window: Window,
-        switch_desktop: Boolean
+        switch_desktop: Boolean = true
     ): Boolean {
-        var desktop: Int? = get_property_as_int(display, window, XA_CARDINAL, "_NET_WM_DESKTOP")
+        var desktop: Int? = get_property_as_int(window, XA_CARDINAL, "_NET_WM_DESKTOP")
         if (desktop == null) {
-            desktop = get_property_as_int(display, window, XA_CARDINAL, "_WIN_WORKSPACE")
+            desktop = get_property_as_int(window, XA_CARDINAL, "_WIN_WORKSPACE")
         }
         if (switch_desktop && (desktop != null)) {
             client_msg(
@@ -332,47 +343,38 @@ class WindowsManager {
         const val EXIT_FAILURE: Boolean = false
     }
 
-    class SystemWindow(
-        val windowTracker: WindowsManager,
+    data class SystemWindow(
         val id: Long,
         val name: String,
+        val iconName: String,
         val pid: Long,
         val windowClass: String,
         val desktop: Long,
-        override val window: Window = Window(id)
-    ) : IWindow {
-        override fun toFront() {
-            windowTracker.activateWindow(window)
-        }
+        val command: String,
+    ) {
+        companion object {
+            val SystemWindow.window: Window get() = Window(id)
 
-        override fun minimize() {
-            windowTracker.minimizeWindow(window)
-        }
+            fun SystemWindow.toFront(wmanager: WindowsManager) {
+                wmanager.activateWindow(window)
+            }
 
-        override fun maximize() {
-            windowTracker.maximizeWindow(window)
-        }
+            fun SystemWindow.minimize(wmanager: WindowsManager) {
+                wmanager.minimizeWindow(window)
+            }
 
-        override fun close() {
-            windowTracker.closeWindow(window)
-        }
+            fun SystemWindow.maximize(wmanager: WindowsManager) {
+                wmanager.maximizeWindow(window)
+            }
 
-        override fun toString(): String {
-            return "WID : $id  PID: $pid NAME: $name"
+            fun SystemWindow.close(wmanager: WindowsManager) {
+                wmanager.closeWindow(window)
+            }
         }
     }
 
-    interface IWindow {
-        val window: Window
-
-        fun toFront() {}
-        fun minimize() {}
-        fun maximize() {}
-        fun close() {}
-    }
-
     private interface Xmu : Library {
-        fun XmuClientWindow(display: Display?, win: Window?): Window?
+        fun XmuClientWindow(display: Display?, window: Window?): Window?
     }
 
     private interface X11Ext : Library {
@@ -444,8 +446,8 @@ class WindowsManager {
             screen: Int
         ): Int
 
-        companion object {
-            const val XC_CROSS_HAIR: Int = 34
-        }
+//        companion object {
+//            const val XC_CROSS_HAIR: Int = 34
+//        }
     }
 }

+ 0 - 56
src/main/kotlin/eu/mjdev/desktop/helpers/system/Command.kt

@@ -1,56 +0,0 @@
-package eu.mjdev.desktop.helpers.system
-
-import com.google.gson.Gson
-import java.io.BufferedReader
-import java.io.InputStreamReader
-import java.util.*
-
-@Suppress("MemberVisibilityCanBePrivate", "unused")
-class Command(
-    val cmd: String,
-    vararg val args: String
-) {
-    private val _cmd
-        get() = mutableListOf<String>().apply {
-            add(cmd)
-            addAll(args)
-        }
-
-    var error: Throwable? = null
-    var result: String = ""
-
-    fun execute(): String? = runCatching {
-        StringJoiner(System.lineSeparator()).let { sj ->
-            val p = Runtime.getRuntime().exec(_cmd.toTypedArray())
-            BufferedReader(InputStreamReader(p.inputStream))
-                .lines()
-                .iterator()
-                .forEachRemaining { newElement: String? ->
-                    sj.add(newElement)
-                }
-            result = sj.toString()
-            p.waitFor()
-            p.destroy()
-            result
-        }
-    }.onFailure { e ->
-        error = e
-    }.getOrNull()
-
-    fun executeWithEnv(
-        env: Environment
-    ): Process = Runtime.getRuntime().exec(
-        _cmd.toTypedArray(),
-        env.toTypedArray()
-    )
-
-    companion object {
-        inline fun <reified T> String.to(): T? = runCatching {
-            Gson().fromJson(this, T::class.java)
-        }.getOrNull()
-
-        inline fun <reified T> String.toList(): List<T>? = runCatching {
-            Gson().fromJson(this, List::class.java).mapNotNull { item -> item as? T }
-        }.getOrNull()
-    }
-}

+ 10 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/system/DBus.kt

@@ -0,0 +1,10 @@
+package eu.mjdev.desktop.helpers.system
+
+class DBus {
+    fun updateEnvironment(): String =
+        Shell.executeAndRead("dbus-update-activation-environment")
+
+    // todo
+    fun send(vararg args: String): String =
+        Shell.executeAndRead("dbus-send", *args)
+}

+ 2 - 3
src/main/kotlin/eu/mjdev/desktop/helpers/system/Environment.kt

@@ -1,11 +1,10 @@
 package eu.mjdev.desktop.helpers.system
 
-// todo from bash
 class Environment(
-    val data: String = Command("env").execute() ?: ""
+    val data : List<String> = Shell.executeAndReadLines("env")
 ) : ArrayList<String>() {
     init {
-        addAll(data.split("\n"))
+        addAll(data)
         add("export DBUS_SESSION_BUS_ADDRESS=\"unix:path=\$XDG_RUNTIME_DIR/bus\"")
     }
 }

+ 177 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/system/Shell.kt

@@ -0,0 +1,177 @@
+package eu.mjdev.desktop.helpers.system
+
+import eu.mjdev.desktop.extensions.Custom.command
+import eu.mjdev.desktop.provider.DesktopProvider
+import java.io.BufferedReader
+import java.io.BufferedWriter
+
+@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter", "unused")
+class Shell(
+    val api: DesktopProvider,
+    val shellType: ShellType = ShellType.SH,
+) : AutoCloseable {
+    val environment: Environment = Environment()
+    val process: Process = Runtime.getRuntime().exec(
+        arrayOf(shellType.cmd),
+        environment.toTypedArray()
+    )
+    val input: BufferedReader = process.inputReader()
+    val output: BufferedWriter = process.outputWriter()
+
+    val isAlive: Boolean
+        get() = process.isAlive
+
+    val pid
+        get() = process.pid()
+
+    val pids
+        get() = getProcessPids()
+
+    val command: String
+        get() = process.command ?: ""
+
+    val allProcesses: List<ProcessHandle>
+        get() = ProcessHandle.allProcesses().toList()
+
+    val allProcessesPids
+        get() = allProcesses.map { it.pid() }
+
+    val childProcesses: List<ProcessHandle>
+        get() = process.descendants().toList()
+
+//    val allWindows
+//        get() = api.windows.allSystemWindows
+
+//    val allWindowPids
+//        get() = allWindows.map { it.pid }.toList()
+
+//    val processWindows
+//        get() = allWindows.filter { pids.contains(it.pid) }
+
+//    val processWindowsPids
+//        get() = processWindows.map { it.pid }
+
+//    val hasWindow: Boolean
+//        get() = processWindows.isNotEmpty()
+
+//    val isRunning: Boolean = hasWindow
+
+//    fun isWindowFocus(api: DesktopProvider): Boolean =
+//        api.windows.isWindowActive(pids)
+
+//    fun hasWindow(api: DesktopProvider) =
+//        api.windows.getWindowsByPids(pids).isNotEmpty()
+
+//    fun requestWindowFocus(api: DesktopProvider) {
+//        val windows = api.windows.getWindowsByPids(pids)
+//        if (windows.isNotEmpty()) {
+//            windows.forEach { w -> w.toFront() }
+//        }
+//    }
+
+//    fun minimizeWindow(api: DesktopProvider) {
+//        val windows = api.windows.getWindowsByPids(pids)
+//        if (windows.isNotEmpty()) {
+//            windows.forEach { w -> w.minimize() }
+//        }
+//    }
+
+//    fun maximizeWindow(api: DesktopProvider) {
+//        val windows = api.windows.getWindowsByPids(pids)
+//        if (windows.isNotEmpty()) {
+//            windows.forEach { w -> w.maximize() }
+//        }
+//    }
+
+//    fun closeWindow(api: DesktopProvider) {
+//        val windows = api.windows.getWindowsByPids(pids)
+//        if (windows.isNotEmpty()) {
+//            windows.forEach { w -> w.close() }
+//        } else {
+//            close()
+//        }
+//    }
+
+    fun onExit(
+        block: (Shell) -> Unit
+    ): Shell {
+        waitFor(block)
+        return this
+    }
+
+    fun getProcessPids(): List<Long> = mutableListOf<Long>().apply {
+        add(process.pid())
+        addAll(process.descendants().map { it.pid() }.toList())
+    }.distinct()
+
+    fun toHandle(): ProcessHandle =
+        process.toHandle()
+
+    fun waitFor(
+        block: (Shell) -> Unit
+    ): Shell {
+//        while (hasWindow) {
+//            process.waitFor()
+//        }
+        return apply {
+            block(this)
+            close()
+        }
+    }
+
+    fun writeCommand(cmd: String): Shell {
+        output.write(cmd + "\n")
+        output.flush()
+        return this
+    }
+
+    fun writeCommand(cmd: String, vararg args: String): Shell {
+        output.write(cmd + args.joinToString { " $it " } + "\n")
+        output.flush()
+        return this
+    }
+
+    fun readOutput(): String =
+        input.readText()
+
+    fun readOutputLines(): List<String> = input.readLines().filter {
+        it.isNotEmpty()
+    }
+
+    fun readWhileDone(): Shell {
+        process.waitFor()
+        return this
+    }
+
+    override fun close() =
+        process.destroy()
+
+    companion object {
+        fun executeAndRead(cmd: String, vararg args: String): String =
+            Runtime.getRuntime().exec(
+                mutableListOf<String>().apply {
+                    add(cmd)
+                    addAll(args)
+                }.toTypedArray()
+            ).apply {
+                waitFor()
+            }.inputReader().readText()
+
+        fun executeAndReadLines(cmd: String, vararg args: String): List<String> =
+            Runtime.getRuntime().exec(
+                mutableListOf<String>().apply {
+                    add(cmd)
+                    addAll(args)
+                }.toTypedArray()
+            ).apply {
+                waitFor()
+            }.inputReader().readText().lines()
+    }
+
+    enum class ShellType(
+        val cmd: String
+    ) {
+        SH("/bin/sh"),
+        BASH("/bin/bash")
+    }
+}

+ 14 - 19
src/main/kotlin/eu/mjdev/desktop/provider/AppsProvider.kt

@@ -5,11 +5,10 @@ import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.data.Category
 import eu.mjdev.desktop.data.DesktopFile
 import eu.mjdev.desktop.extensions.Custom.invalidate
+import eu.mjdev.desktop.extensions.Custom.jsonToList
 import eu.mjdev.desktop.extensions.Locale.toLocale
 import eu.mjdev.desktop.helpers.exception.EmptyException.Companion.EmptyException
-import eu.mjdev.desktop.helpers.managers.GnomeManager
-import eu.mjdev.desktop.helpers.system.Command
-import eu.mjdev.desktop.helpers.system.Command.Companion.toList
+import eu.mjdev.desktop.helpers.system.Shell
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import java.io.File
@@ -142,16 +141,14 @@ class AppsProvider(
 
     fun startApp(app: App) {
         scope.launch(Dispatchers.IO) {
-            api.gnome.setDarkColorScheme()
-            api.gnome.setGTKTheme(GnomeManager.THEME_ADWAITA_DARK)
-            if (app.isRunning) {
-                // todo menu to switch
-                if (app.isWindowFocus(api)) {
-                    app.minimizeWindow(api)
-                } else {
-                    app.requestWindowFocus(api)
-                }
-            } else {
+//            if (app.isRunning) {
+//                // todo menu to switch
+//                if (app.isWindowFocus(api)) {
+//                    app.minimizeWindow(api)
+//                } else {
+//                    app.requestWindowFocus(api)
+//                }
+//            } else {
                 app.onStarting {
                     if (favoriteApps.contains(app)) {
                         favoriteApps.invalidate()
@@ -169,21 +166,19 @@ class AppsProvider(
                     if (result != EmptyException) {
                         result.printStackTrace()
                     }
-                }.start()
+                }.start(api)
                 favoriteApps.invalidate()
-            }
+//            }
         }
     }
 
     init {
-        Command(
+        Shell.executeAndRead(
             "gsettings",
             "get",
             "org.gnome.shell",
             "favorite-apps"
-        ).execute()
-            ?.toList<String>()
-            ?.mapNotNull { deskFileName ->
+        ).jsonToList<String>().mapNotNull { deskFileName ->
                 allAppsDesktopFiles.filter { deskFile ->
                     deskFile.file.name?.contentEquals(deskFileName) == true
                 }.map { deskFile ->

+ 157 - 102
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -1,59 +1,76 @@
 package eu.mjdev.desktop.provider
 
 import androidx.compose.runtime.*
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.toAwtImage
+import androidx.compose.ui.platform.*
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import coil3.ImageLoader
-import eu.mjdev.desktop.components.controlcenter.ControlCenterPage
+import eu.mjdev.desktop.components.controlcenter.base.ControlCenterPage
 import eu.mjdev.desktop.components.controlcenter.pages.*
 import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
 import eu.mjdev.desktop.helpers.adb.AdbDiscover.Companion.adbDevicesHandler
+import eu.mjdev.desktop.helpers.bitmap.Bitmap
 import eu.mjdev.desktop.helpers.internal.Palette
 import eu.mjdev.desktop.helpers.managers.*
-import eu.mjdev.desktop.helpers.system.Command
+import eu.mjdev.desktop.helpers.system.DBus
 import eu.mjdev.desktop.helpers.system.OsRelease
+import eu.mjdev.desktop.helpers.system.Shell
 import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import java.awt.GraphicsDevice
-import java.awt.GraphicsEnvironment
-import java.awt.Toolkit
+import java.awt.*
 import java.io.File
+import java.io.FileReader
+import java.net.URI
+import javax.script.ScriptEngine
+import javax.script.ScriptEngineManager
 import kotlin.system.exitProcess
 
 @Suppress("unused", "MemberVisibilityCanBePrivate", "SameParameterValue")
 class DesktopProvider(
     val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
     val imageLoader: ImageLoader? = null,
-    val connection: ConnectivityManager = ConnectivityManager(),
-//    val scriptManager: ScriptEngineManager = ScriptEngineManager(),
-    val kcefHelper: KCEFHelper = KCEFHelper(scope),
-    val ai: AIProvider = AIProvider(scope, AiPluginGemini(scope)),
-    val windows: WindowsManager = WindowsManager(),
-    val gnome: GnomeManager = GnomeManager(),
-    val allUsers: List<User> = User.allUsers,
-) {
-    val osDetails = OsRelease()
-    val palette: Palette = Palette(scope, currentUser.theme.backgroundColor)
-    val controlCenterPagesState: MutableState<List<ControlCenterPage>> = mutableStateOf(CONTROL_CENTER_PAGES)
+    val isDebug: Boolean = true
+) : AutoCloseable {
+    val scriptManager: ScriptEngineManager by lazy { ScriptEngineManager() }
+    val scriptEngine: ScriptEngine by lazy {
+        scriptManager.getEngineByName("JavaScript").apply {
+            // todo
+        }
+    }
+    val osDetails by lazy { OsRelease() }
+    val toolkit: Toolkit by lazy { Toolkit.getDefaultToolkit() }
+    val kcefHelper: KCEFHelper by lazy { KCEFHelper(scope) }
+    val gnome: GnomeManager by lazy { GnomeManager() }
+    val connection: ConnectivityManager by lazy { ConnectivityManager() }
+    val windows: WindowsManager by lazy { WindowsManager() }
+    val dbus: DBus by lazy { DBus() }
+    val ai: AIProvider by lazy {
+        // todo user can configure
+        AIProvider(scope, AiPluginGemini(scope))
+    }
+    val appsProvider by lazy { AppsProvider(this) }
+    val mounts by lazy {
+        FileSystemWatcher(scope) {
+            println("Mounted: ${this.targetDirectory}")
+        }
+    }
+    val palette: Palette by lazy { Palette(scope, currentUser.theme.backgroundColor) }
+    val homeDir: File
+        get() = currentUser.homeDir
+    val allUsers: List<User>
+        get() = User.allUsers
+    val machineName: String
+        get() = Shell.executeAndRead("hostname").trim()
 
     // todo state
     val currentUser: User
-        get() = allUsers.filter { u -> u.isLoggedIn == true }.firstOrNull() ?: User.Nobody
-
-    val homeDir : File
-        get() = currentUser.homeDir
-
-    val machineName : String
-        get() = Command("hostname").execute()?.trim().orEmpty()
-
-//    private val engine: ScriptEngine by lazy {
-//        scriptManager.getEngineByName("JavaScript").apply {
-//            // todo
-//        }
-//    }
-
+        get() = allUsers.firstOrNull { u -> u.isLoggedIn } ?: User.Nobody
+    val controlCenterPagesState: MutableState<List<ControlCenterPage>> = mutableStateOf(CONTROL_CENTER_PAGES)
+    var lastTheme: String = ""
     val adbHandler = adbDevicesHandler(
         coroutineScope = scope,
         onAdded = { device ->
@@ -65,18 +82,25 @@ class DesktopProvider(
             // when device removed
         }
     )
-
-    val containerSize: DpSize by lazy {
-        Toolkit.getDefaultToolkit().screenSize.let {
-            DpSize(
-                it.width.dp,
-                it.height.dp
-            )
-        }
+    val containerSize: DpSize
+        get() = runCatching {
+            toolkit.screenSize.let { screen -> DpSize(screen.width.dp, screen.height.dp) }
+        }.getOrNull() ?: DpSize.Zero
+    val isAlwaysOnTopSupported
+        get() = runCatching {
+            toolkit.isAlwaysOnTopSupported
+        }.getOrNull() ?: false
+//    val cursorSize
+//        get() = toolkit.getBestCursorSize(32, 32).let {
+//            DpSize(it.width.dp, it.height.dp)
+//        }
+    val desktopUtils: Desktop? by lazy {
+        if (Desktop.isDesktopSupported()) {
+            Desktop.getDesktop()
+        } else null
     }
-    val controlCenterPages get() = controlCenterPagesState.value
-    val appsProvider = AppsProvider(this)
-
+    val controlCenterPages
+        get() = controlCenterPagesState.value
     val graphicsEnvironment: GraphicsEnvironment
         get() = GraphicsEnvironment.getLocalGraphicsEnvironment()
     val graphicsDevice: GraphicsDevice
@@ -84,48 +108,106 @@ class DesktopProvider(
     val isTransparencySupported
         get() = graphicsDevice.isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency.TRANSLUCENT)
 
-    val mounts = FileSystemWatcher(scope) {
-        println("Mounted: ${this.targetDirectory}")
+    init {
+        runCatching {
+            currentUser.theme.controlCenterExpandedWidth = containerSize.width.div(4)
+        }
+        runCatching {
+            mounts.init()
+        }
+        runCatching {
+            dbus.updateEnvironment()
+        }
+        runCatching {
+            lastTheme = gnome.getGTKTheme()
+        }
+        runCatching {
+            gnome.setGTKTheme(GnomeManager.THEME_MJDEV)
+        }
     }
 
-    init {
-        currentUser.theme.controlCenterExpandedWidth = containerSize.width.div(4)
-        mounts.init()
+    override fun close() {
+        runCatching {
+            gnome.setGTKTheme(lastTheme)
+        }
+        runCatching {
+            mounts.dispose()
+        }
+        runCatching {
+            kcefHelper.dispose()
+        }
+        runCatching {
+            windows.dispose()
+        }
+    }
+
+    fun openMail(emailAddress: String) = runCatching {
+        desktopUtils?.mail(URI.create("mailto:$emailAddress"))
+    }
+
+    fun openBrowser(url: String) = runCatching {
+        desktopUtils?.browse(URI.create(url))
+    }
+
+    fun openDirectoryForFile(path: String) = runCatching {
+        desktopUtils?.browseFileDirectory(File(path))
+    }
+
+    fun moveToTrash(file: File) = runCatching {
+        desktopUtils?.moveToTrash(file)
+    }
+
+    fun moveToTrash(filePath: String) = runCatching {
+        moveToTrash(File(filePath))
+    }
 
-        println(allUsers)
+    fun openFileInAssociated(file: File) = runCatching {
+        desktopUtils?.open(file)
     }
 
-    fun dispose() {
-        kcefHelper.dispose()
-        mounts.dispose()
-        windows.dispose()
+    fun openFileInAssociated(filePath: String) = runCatching {
+        openFileInAssociated(File(filePath))
     }
 
-//    fun runScript(script: String): Any =
-//        engine.eval(script)
+    fun runScript(script: String): Any = runCatching {
+        scriptEngine.eval(script)
+    }
 
-//    fun runScript(file: File): Any =
-//        engine.eval(FileReader(file))
+    fun runScript(file: File): Any = runCatching {
+        scriptEngine.eval(FileReader(file))
+    }
 
-    fun executeCmd(cmd: String) {
-        ProcessBuilder().apply {
-            command(cmd)
-        }.start()
+    fun beep() = runCatching {
+        toolkit.beep()
     }
 
-    fun executeCmd(cmd: String, directory: File) {
-        ProcessBuilder().apply {
-            directory(directory)
-            command(cmd)
-        }.start()
+    fun sync() = runCatching {
+        toolkit.sync()
     }
 
+    fun createCustomCursor(cursor: Image, hotSpot: Point, name: String): Cursor? = runCatching {
+        toolkit.createCustomCursor(cursor, hotSpot, name)
+    }.getOrNull()
+
+    fun createCustomCursor(cursor: Bitmap, hotSpot: Point, name: String): Cursor? = runCatching {
+        toolkit.createCustomCursor(cursor.image, hotSpot, name)
+    }.getOrNull()
+
+    fun createCustomCursor(cursor: ImageBitmap, hotSpot: Point, name: String): Cursor? = runCatching {
+        toolkit.createCustomCursor(cursor.toAwtImage(), hotSpot, name)
+    }.getOrNull()
+
+    // todo
+//    fun createCustomCursor(cursor: ImageVector, hotSpot: Point, name: String) =
+//        toolkit.createCustomCursor(cursor, hotSpot, name)
+
     @Suppress("UNUSED_PARAMETER")
     fun login(
         user: String,
         password: String
-    ) {
+    ): Boolean {
         // todo
+        return true
     }
 
     fun logOut() {
@@ -143,32 +225,6 @@ class DesktopProvider(
         exitProcess(0)
     }
 
-    // theme support
-    class DesktopScope(
-        val api: DesktopProvider
-    ) {
-        val scope
-            get() = api.scope
-
-        val currentUser
-            get() = api.currentUser
-
-        val backgroundColorState
-            get() = api.palette.backgroundColorState
-        val backgroundColor
-            get() = api.palette.backgroundColor
-        val iconsTintColorState
-            get() = api.palette.iconsTintColor
-        val iconsTintColor
-            get() = api.palette.iconsTintColor
-        val textColorState
-            get() = api.palette.textColorState
-        val textColor
-            get() = textColorState.value
-        val borderColor
-            get() = api.palette.borderColor
-    }
-
     companion object {
         private val CONTROL_CENTER_PAGES = listOf(
             MainSettingsPage(),
@@ -181,24 +237,23 @@ class DesktopProvider(
             DevicesPage()
         )
 
-        val LocalDesktop = compositionLocalOf {
+        val LocalDesktop = staticCompositionLocalOf {
+            println("default empty desktop provider created")
             DesktopProvider()
         }
 
         @Composable
         fun rememberDesktopProvider(
             scope: CoroutineScope = rememberCoroutineScope(),
-            imageLoader: ImageLoader = asyncImageLoader()
+            imageLoader: ImageLoader = asyncImageLoader(),
+            isDebug: Boolean = LocalInspectionMode.current,
         ) = remember {
-            DesktopProvider(scope, imageLoader)
-        }
-
-        @Composable
-        fun withDesktopScope(
-            api: DesktopProvider = LocalDesktop.current,
-            block: @Composable DesktopScope.() -> Unit
-        ) = DesktopScope(api).apply {
-            block()
+            println("default initialized desktop provider created")
+            DesktopProvider(
+                scope,
+                imageLoader,
+                isDebug
+            )
         }
     }
 }

+ 44 - 0
src/main/kotlin/eu/mjdev/desktop/provider/DesktopScope.kt

@@ -0,0 +1,44 @@
+package eu.mjdev.desktop.provider
+
+import androidx.compose.runtime.Composable
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+
+// todo theme user support
+@Suppress("unused", "MemberVisibilityCanBePrivate")
+open class DesktopScope(
+    val api: DesktopProvider
+) {
+    val isDebug: Boolean
+        get() = api.isDebug
+
+    val scope
+        get() = api.scope
+
+    val currentUser
+        get() = api.currentUser
+
+    val backgroundColorState
+        get() = api.palette.backgroundColorState
+    val backgroundColor
+        get() = api.palette.backgroundColor
+    val iconsTintColorState
+        get() = api.palette.iconsTintColor
+    val iconsTintColor
+        get() = api.palette.iconsTintColor
+    val textColorState
+        get() = api.palette.textColorState
+    val textColor
+        get() = textColorState.value
+    val borderColor
+        get() = api.palette.borderColor
+
+    companion object {
+        @Composable
+        fun withDesktopScope(
+            api: DesktopProvider = LocalDesktop.current,
+            block: @Composable DesktopScope.() -> Unit
+        ) = DesktopScope(api).apply {
+            block()
+        }
+    }
+}

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

@@ -8,6 +8,7 @@ import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.window.WindowPosition
+import eu.mjdev.desktop.provider.DesktopScope.Companion.withDesktopScope
 import eu.mjdev.desktop.windows.ChromeWindowScope.Companion.withChromeWindowScope
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberAnimState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
@@ -38,7 +39,7 @@ fun ChromeWindow(
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
     onOpened: ChromeWindowState.() -> Unit = {},
     onClosed: ChromeWindowState.() -> Unit = {},
-    content: @Composable ChromeWindowScope.() -> Unit = {}
+    content: @Composable () -> Unit = {}
 ) = ChromeWindow(
     windowState = windowState,
     visible = visible,
@@ -82,7 +83,7 @@ fun ChromeWindow(
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
     onOpened: ChromeWindowState.() -> Unit = {},
     onClosed: ChromeWindowState.() -> Unit = {},
-    content: @Composable ChromeWindowScope.() -> Unit = {}
+    content: @Composable () -> Unit = {}
 ) = ChromeWindow(
     windowState = windowState,
     visible = visible,
@@ -125,11 +126,14 @@ fun ChromeWindow(
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {},
     onOpened: ChromeWindowState.() -> Unit = {},
     onClosed: ChromeWindowState.() -> Unit = {},
-    content: @Composable ChromeWindowScope.() -> Unit = {}
-) {
-    val animState = rememberAnimState(visible)
-    val wnState = rememberWnState()
-    windowState.onOpened.add {
+    content: @Composable () -> Unit = {}
+) = withDesktopScope {
+    if (isDebug) {
+        content()
+    } else {
+        val animState = rememberAnimState(visible)
+        val wnState = rememberWnState()
+        windowState.onOpened.add {
 //        if (alwaysOnBottom) {
             // todo
 //            val focusOwner = window?.mostRecentFocusOwner
@@ -139,54 +143,55 @@ fun ChromeWindow(
 //        else if (alwaysOnTop) {
 //            windowState.requestFocus()
 //        }
-        windowState.updatePositionAndSize()
-        if (visible) {
-            animState.value.targetState = true
+            windowState.updatePositionAndSize()
+            if (visible) {
+                animState.value.targetState = true
+            }
+            onOpened(windowState)
         }
-        onOpened(windowState)
-    }
-    windowState.onClosed.add(onClosed)
-    windowState.onFocusChange.add(onFocusChange)
-    WindowEx(
-        onCreate = { window ->
-            windowState.window = window
-            onCreate(windowState)
-        },
+        windowState.onClosed.add(onClosed)
+        windowState.onFocusChange.add(onFocusChange)
+        WindowEx(
+            onCreate = { window ->
+                windowState.window = window
+                onCreate(windowState)
+            },
 //        onOpen = {
 //            onOpened(windowState)
 //        },
-        onCloseRequest,
-        windowState,
-        wnState.value,
-        "",
-        null,
-        true,
-        transparent,
-        resizable,
-        enabled,
-        focusable,
-        alwaysOnTop,
+            onCloseRequest,
+            windowState,
+            wnState.value,
+            "",
+            null,
+            true,
+            transparent,
+            resizable,
+            enabled,
+            focusable,
+            alwaysOnTop,
 //        alwaysOnBottom,
-        onPreviewKeyEvent,
-        onKeyEvent,
-        stateHelper = windowState.stateHelper,
-        focusHelper = windowState.focusHelper,
-        content = {
-            withChromeWindowScope(
-                windowState,
-                animState,
-                wnState
-            ) {
-                AnimatedVisibility(
-                    animState.value,
-                    enter = enterAnimation,
-                    exit = exitAnimation
+            onPreviewKeyEvent,
+            onKeyEvent,
+            stateHelper = windowState.stateHelper,
+            focusHelper = windowState.focusHelper,
+            content = {
+                withChromeWindowScope(
+                    windowState,
+                    animState,
+                    wnState
                 ) {
-                    content()
+                    AnimatedVisibility(
+                        animState.value,
+                        enter = enterAnimation,
+                        exit = exitAnimation
+                    ) {
+                        content()
+                    }
                 }
             }
-        }
-    )
-    updateAnimState(visible, animState, wnState)
-    updateWindowState(visible, animState, wnState, windowState)
-}
+        )
+        updateAnimState(visible, animState, wnState)
+        updateWindowState(visible, animState, wnState, windowState)
+    }
+}

+ 2 - 2
src/main/kotlin/eu/mjdev/desktop/windows/MainWindow.kt → src/main/kotlin/eu/mjdev/desktop/windows/DesktopWindow.kt

@@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.isSecondaryPressed
 import eu.mjdev.desktop.components.background.BackgroundImage
-import eu.mjdev.desktop.components.sliding.VisibilityState
+import eu.mjdev.desktop.components.sliding.base.VisibilityState
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
@@ -18,7 +18,7 @@ import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 @Suppress("FunctionName", "DEPRECATION")
 @Preview
 @Composable
-fun MainWindow(
+fun DesktopWindow(
     controlCenterState: VisibilityState,
     panelState: VisibilityState,
     menuState: VisibilityState,

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

@@ -29,7 +29,7 @@ fun FullScreenWindow(
     onCreate: ChromeWindowState.() -> Unit = {},
     onFocusChange: ChromeWindowState.(Boolean) -> Unit = {
     },
-    content: @Composable ChromeWindowScope.() -> Unit = {}
+    content: @Composable () -> Unit = {}
 ) = ChromeWindow(
     windowState = windowState,
     enabled = enabled,