Forráskód Böngészése

desktop & icons

desktop & icons

desktop & icons
Milan Jurkulak 2 éve
szülő
commit
cfa09659f8
27 módosított fájl, 654 hozzáadás és 157 törlés
  1. 7 0
      build.gradle.kts
  2. 2 2
      src/main/kotlin/eu/mjdev/desktop/Main.kt
  3. 24 28
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt
  4. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenuApp.kt
  5. 8 8
      src/main/kotlin/eu/mjdev/desktop/components/charts/DonutChart.kt
  6. 2 2
      src/main/kotlin/eu/mjdev/desktop/components/custom/UserAvatar.kt
  7. 20 10
      src/main/kotlin/eu/mjdev/desktop/components/desktop/Desktop.kt
  8. 30 29
      src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt
  9. 26 1
      src/main/kotlin/eu/mjdev/desktop/components/draggable/DraggableView.kt
  10. 94 0
      src/main/kotlin/eu/mjdev/desktop/components/file/FolderIcon.kt
  11. 110 0
      src/main/kotlin/eu/mjdev/desktop/components/file/FolderView.kt
  12. 1 1
      src/main/kotlin/eu/mjdev/desktop/components/fonticon/FontIcon.kt
  13. 17 0
      src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt
  14. 23 0
      src/main/kotlin/eu/mjdev/desktop/components/image/ImageAny.kt
  15. 3 0
      src/main/kotlin/eu/mjdev/desktop/components/text/TextAny.kt
  16. 10 1
      src/main/kotlin/eu/mjdev/desktop/components/text/TextWithShadow.kt
  17. 2 2
      src/main/kotlin/eu/mjdev/desktop/data/User.kt
  18. 0 21
      src/main/kotlin/eu/mjdev/desktop/helpers/adb/Adb.kt
  19. 54 0
      src/main/kotlin/eu/mjdev/desktop/helpers/system/UserDirs.kt
  20. 4 4
      src/main/kotlin/eu/mjdev/desktop/provider/AppsProvider.kt
  21. 32 6
      src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt
  22. 77 2
      src/main/kotlin/eu/mjdev/desktop/theme/gtk/GtkTheme.kt
  23. 4 2
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindow.kt
  24. 10 9
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowScope.kt
  25. 0 1
      src/main/kotlin/eu/mjdev/desktop/windows/ChromeWindowState.kt
  26. 90 22
      src/main/kotlin/eu/mjdev/desktop/windows/Window.kt
  27. 3 5
      src/main/kotlin/eu/mjdev/desktop/windows/WindowEx.kt

+ 7 - 0
build.gradle.kts

@@ -75,6 +75,12 @@ allprojects {
         implementation(libs.qrose)
         // tts
         implementation(libs.tts)
+        // paths
+//        implementation("me.sujanpoudel.multiplatform.utils:multiplatform-paths:0.2.2")
+        // files
+//        implementation("io.github.vinceglb:filekit-core:0.8.2")
+        // files with composable utilities
+//        implementation("io.github.vinceglb:filekit-compose:0.8.2")
         // dbus
 //        implementation(libs.dbus.java.core)
 //        implementation(libs.dbus.java.transport.native.unixsocket)
@@ -202,6 +208,7 @@ compose {
                     debMaintainer = libs.versions.appMaintainer.get()
                     menuGroup = libs.versions.appMenuGroup.get()
                     vendor = libs.versions.appVendor.get()
+                    modules("jdk.security.auth")
                 }
                 targetFormats(
 //                    TargetFormat.Dmg,

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

@@ -15,8 +15,8 @@ import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.rememberDesktopProvider
 import eu.mjdev.desktop.windows.MainWindow
 
-@Suppress("unused")
 fun main(
+    @Suppress("unused")
     args: Array<String>
 ) = application(
     exitProcessOnExit = true
@@ -57,7 +57,7 @@ fun main(
                 DesktopPanel(
                     panelState = panelState,
                     menuState = menuState,
-                    onMenuIconClicked = { menuState.toggle() },
+                    onMenuIconClicked = { if (!menuState.isVisible) menuState.show() },
                     onFocusChange = { focus -> handlePanelFocus(focus) })
                 AppsMenu(
                     menuState = menuState,

+ 24 - 28
src/main/kotlin/eu/mjdev/desktop/components/appsmenu/AppsMenu.kt

@@ -31,16 +31,14 @@ import eu.mjdev.desktop.extensions.Compose.rememberCalculated
 import eu.mjdev.desktop.extensions.Modifier.dropShadow
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.AppsMenuExitAnimation
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.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)
 @Composable
 fun AppsMenu(
@@ -62,7 +60,7 @@ fun AppsMenu(
     onUserAvatarClick: () -> Unit = {
         // todo
     }
-) {
+) = withDesktopScope {
     val appMenuMinWidth by remember { api.currentUser.theme.appMenuMinWidthState }
     val appMenuMinHeight by remember { api.currentUser.theme.appMenuMinHeightState }
     val menuPadding by remember { api.currentUser.theme.appMenuOuterPaddingState }
@@ -71,10 +69,6 @@ fun AppsMenu(
     val onCategoryClick: (Category) -> Unit = { category ->
         items = api.appsProvider.categoriesAndApps[category.name]?.sortedBy { it.name } ?: emptyList()
     }
-    val backgroundColor by rememberBackgroundColor(api)
-    val textColor by rememberTextColor(api)
-    val borderColor by rememberBorderColor(api)
-    val iconsTintColor by rememberIconTintColor(api)
     val position by rememberCalculated {
         WindowPosition.Absolute(
             panelState.bounds.x,
@@ -101,21 +95,23 @@ fun AppsMenu(
                 )
                 .dropShadow(
                     RoundedCornerShape(24.dp),
-                    borderColor.alpha(0.5f),
+                    borderColor.value.alpha(0.5f),
                     0.dp,
                     0.dp,
                     4.dp,
                     0.dp
                 )
                 .onPlaced(menuState::onPlaced)
-                .onPointerEvent(PointerEventType.Enter) { windowState.requestFocus() }
+                .onPointerEvent(PointerEventType.Enter) {
+                    windowState.requestFocus()
+                }
         ) {
             Box(
                 modifier = Modifier
                     .fillMaxSize()
                     .padding(menuPadding)
-                    .background(backgroundColor.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
-                    .border(2.dp, borderColor, RoundedCornerShape(24.dp))
+                    .background(backgroundColor.value.copy(alpha = 0.7f), RoundedCornerShape(24.dp))
+                    .border(2.dp, borderColor.value, RoundedCornerShape(24.dp))
                     .clip(RoundedCornerShape(24.dp))
             ) {
                 Column(
@@ -123,9 +119,9 @@ fun AppsMenu(
                 ) {
                     UserAvatar(
                         avatarSize = 64.dp,
-                        backgroundColor = backgroundColor,
-                        iconTintColor = iconsTintColor,
-                        textColor = textColor,
+                        backgroundColor = backgroundColor.value,
+                        iconTintColor = iconsTintColor.value,
+                        textColor = textColor.value,
                         orientation = Orientation.Horizontal,
                         onUserAvatarClick = onUserAvatarClick
                     )
@@ -137,7 +133,7 @@ fun AppsMenu(
                                 start = 2.dp,
                                 end = 2.dp
                             ),
-                        color = borderColor,
+                        color = borderColor.value,
                         thickness = 2.dp
                     )
                     LazyColumn(
@@ -151,9 +147,9 @@ fun AppsMenu(
                                 items(items) { item ->
                                     AppsMenuCategory(
                                         category = item as Category,
-                                        backgroundColor = backgroundColor,
-                                        iconTint = textColor,
-                                        textColor = textColor,
+                                        backgroundColor = backgroundColor.value,
+                                        iconTint = textColor.value,
+                                        textColor = textColor.value,
                                         onClick = { onCategoryClick(item) },
                                         onContextMenuClick = { onCategoryContextMenuClick(item) }
                                     )
@@ -164,9 +160,9 @@ fun AppsMenu(
                                 items(items) { item ->
                                     AppsMenuApp(
                                         app = item as App,
-                                        backgroundColor = backgroundColor,
-                                        iconTint = textColor,
-                                        textColor = textColor,
+                                        backgroundColor = backgroundColor.value,
+                                        iconTint = textColor.value,
+                                        textColor = textColor.value,
                                         onClick = {
                                             onAppClick(item)
                                         },
@@ -193,16 +189,16 @@ fun AppsMenu(
                                 start = 2.dp,
                                 end = 2.dp
                             ),
-                        color = borderColor,
+                        color = borderColor.value,
                         thickness = 2.dp
                     )
                     AppsBottomBar(
                         modifier = Modifier
                             .fillMaxWidth()
                             .wrapContentHeight()
-                            .background(backgroundColor),
-                        iconColor = borderColor,
-                        iconBackgroundColor = iconsTintColor,
+                            .background(backgroundColor.value),
+                        iconColor = borderColor.value,
+                        iconBackgroundColor = iconsTintColor.value,
                         backButtonVisible = items.first() is App,
                         onBackClick = {
                             items = api.appsProvider.appCategories
@@ -220,4 +216,4 @@ fun AppsMenu(
             }
         }
     }
-}
+}

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

@@ -22,7 +22,7 @@ import eu.mjdev.desktop.data.App
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
-@Suppress("DEPRECATION")
+@Suppress("DEPRECATION", "FunctionName")
 @OptIn(ExperimentalFoundationApi::class)
 @Preview
 @Composable

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

@@ -5,8 +5,6 @@ import androidx.compose.animation.core.TweenSpec
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -14,6 +12,7 @@ import androidx.compose.ui.text.TextStyle
 import com.aay.compose.baseComponents.model.LegendPosition
 import com.aay.compose.donutChart.DonutChart
 import com.aay.compose.donutChart.model.PieChartData
+import eu.mjdev.desktop.extensions.Compose.rememberState
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.isActive
 
@@ -30,16 +29,17 @@ fun DonutChart(
     refreshTimeout: Long = 5000L,
     animation: AnimationSpec<Float> = TweenSpec(durationMillis = animationDuration),
     legendPosition: LegendPosition = LegendPosition.BOTTOM,
-    data: () -> List<PieChartData>
+    dataHandler: () -> List<PieChartData>
 ) {
-    var data by remember { mutableStateOf(listOf<PieChartData>()) }
+    var data by rememberState(dataHandler())
+    val textStyle by rememberState(TextStyle(color = textColor))
     DonutChart(
         modifier = modifier,
         pieChartData = data,
         centerTitle = title,
-        centerTitleStyle = TextStyle(color = textColor),
-        descriptionStyle = TextStyle(color = textColor),
-        textRatioStyle = TextStyle(color = textColor),
+        centerTitleStyle = textStyle,
+        descriptionStyle = textStyle,
+        textRatioStyle = textStyle,
         outerCircularColor = outerCircularColor,
         innerCircularColor = innerCircularColor,
         ratioLineColor = ratioLineColor,
@@ -48,7 +48,7 @@ fun DonutChart(
     )
     LaunchedEffect(Unit) {
         while (isActive) {
-            data().let { newData ->
+            dataHandler().let { newData ->
                 if (data != newData) {
                     data = newData
                 }

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

@@ -73,7 +73,7 @@ fun UserAvatar(
                 horizontalAlignment = Alignment.CenterHorizontally
             ) {
                 TextAny(
-                    text = api.currentUser.name ?: api.homeDir?.name ?: "-",
+                    text = api.currentUser.name ?: api.homeDir.name ?: "-",
                     color = textColor,
                     textAlign = TextAlign.Center,
                     fontWeight = FontWeight.Bold
@@ -115,7 +115,7 @@ fun UserAvatar(
                 modifier = Modifier.padding(start = 8.dp).fillMaxWidth().padding(top = 8.dp),
             ) {
                 TextAny(
-                    text = api.currentUser.name ?: api.homeDir?.name ?: "-",
+                    text = api.currentUser.name ?: api.homeDir.name ?: "-",
                     color = textColor,
                     textAlign = TextAlign.Start,
                     fontWeight = FontWeight.Bold

+ 20 - 10
src/main/kotlin/eu/mjdev/desktop/components/desktop/Desktop.kt

@@ -1,32 +1,42 @@
 package eu.mjdev.desktop.components.desktop
 
 import androidx.compose.desktop.ui.tooling.preview.Preview
-import androidx.compose.foundation.layout.Box
+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
 import androidx.compose.ui.Alignment
 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
 
+@OptIn(ExperimentalLayoutApi::class)
 @Suppress("FunctionName")
 @Preview
 @Composable
 fun Desktop(
     modifier: Modifier = Modifier
-) = Box(
-    modifier = modifier
-) {
-    Box(
-        modifier = modifier
+) = withDesktopScope {
+    BoxWithConstraints(
+        modifier = modifier.padding(16.dp)
     ) {
-//        ComposeWebView(
-//            modifier = Modifier.size(640.dp, 480.dp)
-        //            .align(Alignment.Center)
-//        )
         MemoryChart(
             modifier = Modifier.size(350.dp, 300.dp)
                 .align(Alignment.BottomEnd)
         )
+        FolderView(
+            path = api.userDirs.desktopDirectory,
+            showHomeFolder = true,
+            orientation = Orientation.Vertical,
+            modifier = Modifier.size(constraints.maxWidth.dp, constraints.maxHeight.dp)
+        )
+//        ComposeWebView(
+//            modifier = Modifier.size(640.dp, 480.dp)
+//                .align(Alignment.Center)
+//        )
     }
 }

+ 30 - 29
src/main/kotlin/eu/mjdev/desktop/components/desktoppanel/DesktopPanel.kt

@@ -35,13 +35,12 @@ import eu.mjdev.desktop.extensions.Compose.height
 import eu.mjdev.desktop.extensions.Modifier.topShadow
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelEnterAnimation
 import eu.mjdev.desktop.helpers.animation.Animations.DesktopPanelExitAnimation
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
-import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import eu.mjdev.desktop.provider.DesktopProvider.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")
@@ -65,6 +64,7 @@ fun DesktopPanel(
             else -> TooltipData(title = item.toString())
         }
     },
+    position: WindowPosition.Aligned = WindowPosition.Aligned(Alignment.BottomCenter),
     onMenuIconClicked: () -> Unit = {
         // todo
     },
@@ -79,27 +79,26 @@ fun DesktopPanel(
     onLanguageClick: () -> Unit = {
         // todo
     },
-) {
-    val backgroundColor by rememberBackgroundColor(api)
-    val textColor by rememberTextColor(api)
-    val borderColor by rememberBorderColor(api)
-    val iconsTintColor by rememberIconTintColor(api)
-    var tooltipState = remember{ mutableStateOf<Any?>(null) }
+) = withDesktopScope {
+    var tooltipState = remember { mutableStateOf<Any?>(null) }
     val panelHeight: (visible: Boolean) -> Dp = { visible ->
         if (visible)
             iconSize.height + iconOuterPadding.height + tooltipHeight + panelContentPadding.height
         else
             dividerWidth
     }
+    val size = remember(panelState.isVisible) {
+        DpSize(
+            api.containerSize.width,
+            panelHeight(panelState.isVisible)
+        )
+    }
+    val windowState: ChromeWindowState = rememberChromeWindowState(position = position, size = size)
     ChromeWindow(
+        windowState = windowState,
         visible = true,
         enterAnimation = enterAnimation,
         exitAnimation = exitAnimation,
-        position = WindowPosition.Aligned(Alignment.BottomCenter),
-        size = DpSize(
-            api.containerSize.width,
-            panelHeight(panelState.isVisible)
-        ),
         onFocusChange = { focused ->
             panelState.onFocusChange(focused)
             onFocusChange(focused)
@@ -111,7 +110,9 @@ fun DesktopPanel(
             state = panelState,
             onPointerEnter = {
                 panelState.show()
-                // todo focus
+                if (!menuState.isVisible) {
+                    windowState.requestFocus()
+                }
             },
             onPointerLeave = {
                 if (!menuState.isVisible) {
@@ -131,11 +132,11 @@ fun DesktopPanel(
                     modifier = Modifier.fillMaxWidth().wrapContentHeight(),
                     tooltip = {
                         Tooltip(
-                            textColor = textColor,
-                            borderColor = borderColor,
+                            textColor = textColor.value,
+                            borderColor = borderColor.value,
                             state = tooltipState,
                             converter = tooltipConverter,
-                            backgroundColor = backgroundColor
+                            backgroundColor = backgroundColor.value
                         )
                     },
                     tooltipPlacement = TooltipPlacement.CursorPoint(
@@ -156,14 +157,14 @@ fun DesktopPanel(
                                 .background(
                                     brush = Brush.verticalGradient(
                                         colors = listOf(
-                                            backgroundColor.copy(alpha = 0.3f),
-                                            backgroundColor.copy(alpha = 0.7f),
-                                            backgroundColor.copy(alpha = 0.8f),
+                                            backgroundColor.value.copy(alpha = 0.3f),
+                                            backgroundColor.value.copy(alpha = 0.7f),
+                                            backgroundColor.value.copy(alpha = 0.8f),
                                         )
                                     )
                                 )
                                 .topShadow(
-                                    color = borderColor.alpha(0.3f),
+                                    color = borderColor.value.alpha(0.3f),
                                     blur = 4.dp
                                 )
                                 .onPlaced(panelState::onPlaced),
@@ -172,7 +173,7 @@ fun DesktopPanel(
                                 modifier = Modifier
                                     .fillMaxWidth()
                                     .height(2.dp),
-                                color = borderColor,
+                                color = borderColor.value,
                                 thickness = 2.dp
                             )
                             Box(
@@ -183,8 +184,8 @@ fun DesktopPanel(
                                 if (showMenuIcon) {
                                     DesktopMenuIcon(
                                         modifier = Modifier.align(Alignment.CenterStart),
-                                        iconColor = borderColor,
-                                        iconBackgroundColor = iconsTintColor,
+                                        iconColor = borderColor.value,
+                                        iconBackgroundColor = iconsTintColor.value,
                                         iconSize = iconSize,
                                         iconPadding = iconPadding,
                                         iconOuterPadding = iconOuterPadding,
@@ -195,9 +196,9 @@ fun DesktopPanel(
                                 }
                                 DesktopPanelFavoriteApps(
                                     modifier = Modifier.align(Alignment.Center),
-                                    iconColor = borderColor,
-                                    iconBackgroundColor = iconsTintColor,
-                                    iconColorRunning = iconsTintColor.lighter(0.3f),
+                                    iconColor = borderColor.value,
+                                    iconBackgroundColor = iconsTintColor.value,
+                                    iconColorRunning = iconsTintColor.value.lighter(0.3f),
                                     iconSize = iconSize,
                                     iconPadding = iconPadding,
                                     iconOuterPadding = iconOuterPadding,

+ 26 - 1
src/main/kotlin/eu/mjdev/desktop/components/draggable/DraggableView.kt

@@ -2,10 +2,12 @@ package eu.mjdev.desktop.components.draggable
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.runtime.*
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -13,8 +15,11 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
 import eu.mjdev.desktop.extensions.Compose.rememberState
+import eu.mjdev.desktop.extensions.Modifier.conditional
 
+@Suppress("FunctionName")
 @Composable
 fun DraggableView(
     modifier: Modifier = Modifier,
@@ -22,16 +27,34 @@ fun DraggableView(
     backgroundColor: Color = Color.Transparent,
     dragBackgroundColor: Color = Color.White.copy(alpha = 0.3f),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    zIndex: Float = Float.MIN_VALUE,
+    onDragStart:()->Unit={},
+    onDragEnd:()->Unit={},
+    onTap: () -> Unit = {},
+    onDoubleTap: () -> Unit = {},
+    onClick: () -> Unit = {},
+    onContextMenuClick: () -> Unit = {},
     content: @Composable BoxScope.() -> Unit = {}
 ) {
     var position by rememberState(Offset.Zero)
     var isDragging by rememberState(false)
     Box(
-        modifier = modifier
+        modifier = modifier.wrapContentSize()
+            .conditional(zIndex > Float.MIN_VALUE) {
+                zIndex(zIndex)
+            }
             .graphicsLayer(
                 translationX = position.x,
                 translationY = position.y
             )
+            .pointerInput(Unit) {
+                detectTapGestures(
+                    onTap = { onTap() },
+                    onPress = { onClick() },
+                    onDoubleTap = { onDoubleTap() },
+                    onLongPress = { onContextMenuClick() }
+                )
+            }
             .pointerInput(Unit) {
                 detectDragGestures(
                     onDrag = { change, dragAmount ->
@@ -42,9 +65,11 @@ fun DraggableView(
                     },
                     onDragStart = {
                         isDragging = true
+                        onDragStart()
                     },
                     onDragEnd = {
                         isDragging = false
+                        onDragEnd()
                     }
                 )
             }.padding(contentPadding),

+ 94 - 0
src/main/kotlin/eu/mjdev/desktop/components/file/FolderIcon.kt

@@ -0,0 +1,94 @@
+package eu.mjdev.desktop.components.file
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis
+import androidx.compose.ui.unit.DpSize
+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 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")
+@Composable
+fun FolderIcon(
+    path: File,
+    customName: String? = null,
+    dragEnabled: Boolean = true,
+    iconSize: DpSize = DpSize(128.dp, 128.dp),
+    zIndex: () -> Int = { 0 },
+    onDragStart: () -> Unit = {},
+    onDragEnd: () -> Unit = {},
+    onClick: () -> Unit = {},
+    onContextMenuClick: () -> Unit = {},
+) = withDesktopScope {
+    val iconName = remember(path) {
+        path.let {
+            it.extension.ifEmpty {
+                // todo
+//                if (it.list() != null)
+                "folder"
+//                else ""
+            }
+        }
+    }
+    val iconId: Int = remember(path) { api.currentUser.theme.iconSet.iconForName(iconName) ?: "?".toInt() }
+    val computedSize = remember(path) { iconSize + 2.dp }
+    DraggableView(
+        modifier = Modifier.wrapContentSize(),
+        dragEnabled = dragEnabled,
+        zIndex = zIndex().toFloat(),
+        onClick = onClick,
+        onContextMenuClick = onContextMenuClick,
+        onDragStart = onDragStart,
+        onDragEnd = onDragEnd
+    ) {
+        Column(
+            modifier = Modifier.wrapContentSize()
+        ) {
+            Box(
+                modifier = Modifier.size(computedSize)
+                    .circleBorder(2.dp, textColor.value)
+            ) {
+                FontIcon(
+                    iconId = iconId,
+                    iconSize = iconSize,
+                    iconColor = textColor.value,
+                    iconBackgroundColor = iconsTintColor.value.alpha(0.4f),
+                    outerPadding = PaddingValues(8.dp),
+                    innerPadding = PaddingValues(8.dp),
+                    onClick = onClick,
+                    onRightClick = onContextMenuClick
+                )
+            }
+            TextWithShadow(
+                modifier = Modifier.width(computedSize.width)
+                    .padding(start = 4.dp),
+                text = customName ?: path.name ?: "",
+                color = textColor.value,
+                fontWeight = FontWeight.Bold,
+                overflow = Ellipsis,
+                minLines = 2,
+                maxLines = 2,
+                textAlign = TextAlign.Center
+            )
+        }
+    }
+}

+ 110 - 0
src/main/kotlin/eu/mjdev/desktop/components/file/FolderView.kt

@@ -0,0 +1,110 @@
+package eu.mjdev.desktop.components.file
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowColumn
+import androidx.compose.foundation.layout.FlowColumnOverflow
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.FlowRowOverflow
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+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.provider.DesktopProvider
+import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import java.io.File
+
+// ScreencastHelper
+// DefaultShellFolder
+// ShellFolder
+// Clipboard
+// XBaseWindow
+// XWM
+// XWindow
+// XWrapperBase
+// ShellFolderManager
+// X11GraphicsEnvironment
+@Suppress("FunctionName")
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun FolderView(
+    modifier: Modifier = Modifier,
+    showHidden: Boolean = false,
+    directoryFirst: Boolean = true,
+    showHomeFolder: Boolean = false,
+    path: File?,
+    api: DesktopProvider = LocalDesktop.current,
+    files: List<DesktopFolderItem> = remember(path) {
+        path?.list()
+            ?.toList<String>()
+            ?.sorted()
+            ?.filter { s ->
+                (showHidden == true) || (!s.startsWith("."))
+            }?.map { filePath ->
+                File(filePath)
+            }?.sortedByDescending {
+                (directoryFirst == false) || (it.extension.isEmpty()) // todo isDirectory
+            }?.map { path ->
+                DesktopFolderItem(path)
+            }?.toMutableList()
+            ?.apply {
+                if (showHomeFolder) add(
+                    DesktopFolderItem(
+                        path = api.homeDir,
+                        customName = "Home",
+                        priority = 1
+                    )
+                )
+            }?.sortedByDescending {
+                it.priority
+            } ?: emptyList()
+    },
+    zIndexState: MutableState<Int> = remember { mutableStateOf(files.size) },
+    orientation: Orientation = Orientation.Vertical
+) = Box(
+    modifier = modifier.verticalScroll(rememberScrollState())
+) {
+    val content: @Composable () -> Unit = {
+        files.forEach { file ->
+            FolderIcon(
+                path = file.path,
+                customName=file.customName,
+                zIndex = { zIndexState.value },
+                onDragStart = {
+                    zIndexState.value += 1
+                }
+            )
+        }
+    }
+    if (orientation == Orientation.Horizontal) {
+        FlowRow(
+            modifier = Modifier.fillMaxSize(),
+            overflow = FlowRowOverflow.Visible,
+            horizontalArrangement = Arrangement.Start,
+            verticalArrangement = Arrangement.Top,
+            content = { content() }
+        )
+    } else {
+        FlowColumn(
+            modifier = Modifier.fillMaxSize(),
+            overflow = FlowColumnOverflow.Visible,
+            horizontalArrangement = Arrangement.Start,
+            verticalArrangement = Arrangement.Top,
+            content = { content() }
+        )
+    }
+}
+
+data class DesktopFolderItem(
+    val path: File,
+    val customName: String? = null,
+    var priority: Int = Int.MIN_VALUE,
+    var position: DpOffset = DpOffset.Unspecified
+)

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

@@ -11,11 +11,11 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
 import eu.mjdev.desktop.components.icon.ShapedIcon
 import eu.mjdev.desktop.components.text.AutoResizeText
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
 
+@Suppress("FunctionName")
 @Composable
 fun FontIcon(
     iconId: Int,

+ 17 - 0
src/main/kotlin/eu/mjdev/desktop/components/greeter/Greeter.kt

@@ -0,0 +1,17 @@
+package eu.mjdev.desktop.components.greeter
+
+import androidx.compose.desktop.ui.tooling.preview.Preview
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@Suppress("FunctionName", "unused")
+@Preview
+@Composable
+fun Greeter(
+    modifier: Modifier = Modifier
+) = Box(
+    modifier = modifier
+) {
+
+}

+ 23 - 0
src/main/kotlin/eu/mjdev/desktop/components/image/ImageAny.kt

@@ -7,6 +7,8 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.DefaultAlpha
 import androidx.compose.ui.graphics.FilterQuality
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asComposeImageBitmap
 import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultFilterQuality
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.graphics.vector.ImageVector
@@ -16,6 +18,7 @@ import eu.mjdev.desktop.components.video.VideoView
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
 import eu.mjdev.desktop.provider.DesktopProvider
 import eu.mjdev.desktop.provider.DesktopProvider.Companion.LocalDesktop
+import org.jetbrains.skia.Bitmap
 import java.io.File
 import androidx.compose.foundation.Image as ComposeImage
 
@@ -44,6 +47,26 @@ fun ImageAny(
             modifier = modifier
         )
 
+        src is Bitmap -> ComposeImage(
+            modifier = modifier,
+            bitmap = src.asComposeImageBitmap(),
+            alignment = alignment,
+            alpha = alpha,
+            contentDescription = contentDescription,
+            colorFilter = colorFilter,
+            contentScale = contentScale
+        )
+
+        src is ImageBitmap -> ComposeImage(
+            modifier = modifier,
+            bitmap = src,
+            alignment = alignment,
+            alpha = alpha,
+            contentDescription = contentDescription,
+            colorFilter = colorFilter,
+            contentScale = contentScale
+        )
+
         isGif -> GifView(
             modifier = modifier,
             src = when (src) {

+ 3 - 0
src/main/kotlin/eu/mjdev/desktop/components/text/TextAny.kt

@@ -17,6 +17,7 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.TextUnit
 import eu.mjdev.desktop.extensions.Compose.textFrom
 
+@Suppress("FunctionName")
 @Preview
 @Composable
 fun TextAny(
@@ -33,6 +34,7 @@ fun TextAny(
     lineHeight: TextUnit = TextUnit.Unspecified,
     overflow: TextOverflow = TextOverflow.Clip,
     softWrap: Boolean = true,
+    minLines: Int = 1,
     maxLines: Int = Int.MAX_VALUE,
     onTextLayout: (TextLayoutResult) -> Unit = {},
     style: TextStyle = LocalTextStyle.current
@@ -40,6 +42,7 @@ fun TextAny(
     text = textFrom(text),
     modifier = modifier,
     color = color,
+    minLines = minLines,
     fontSize = fontSize,
     fontStyle = fontStyle,
     fontWeight = fontWeight,

+ 10 - 1
src/main/kotlin/eu/mjdev/desktop/components/text/TextWithShadow.kt

@@ -9,11 +9,14 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
+@Suppress("FunctionName")
 @Preview
 @Composable
 fun TextWithShadow(
@@ -26,6 +29,9 @@ fun TextWithShadow(
     textAlign: TextAlign? = null,
     fontSize: TextUnit = 16.sp,
     color: Color = Color.White,
+    overflow: TextOverflow = Ellipsis,
+    minLines: Int = 2,
+    maxLines: Int = 2,
     text: Any? = "test"
 ) = TextAny(
     modifier = modifier,
@@ -40,5 +46,8 @@ fun TextWithShadow(
     textAlign = textAlign,
     fontWeight = fontWeight,
     fontSize = fontSize,
-    color = color
+    color = color,
+    overflow = overflow,
+    minLines = minLines,
+    maxLines = maxLines
 )

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

@@ -3,7 +3,7 @@ package eu.mjdev.desktop.data
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.AccountCircle
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.graphics.vector.ImageVector
+import eu.mjdev.desktop.helpers.system.Command
 
 @Suppress("MemberVisibilityCanBePrivate")
 class User(
@@ -25,7 +25,7 @@ class User(
     companion object {
         // todo
         fun load() = User(
-            name = null,
+            name = Command("whoami").execute(),
             picture = Icons.Filled.AccountCircle,
             config = DesktopConfig.Default,
             theme = Theme.Default

+ 0 - 21
src/main/kotlin/eu/mjdev/desktop/helpers/adb/Adb.kt

@@ -1,6 +1,5 @@
 package eu.mjdev.desktop.helpers.adb
 
-import eu.mjdev.desktop.helpers.adb.*
 import eu.mjdev.desktop.helpers.adb.adbserver.AdbServer
 import eu.mjdev.desktop.helpers.adb.forwarding.TcpForwardDescriptor
 import eu.mjdev.desktop.helpers.adb.forwarding.TcpForwarder
@@ -108,10 +107,8 @@ interface Adb : AutoCloseable {
                 val pattern = """\[(\w+)]""".toRegex()
                 val sessionId =
                     pattern.find(response)?.groups?.get(1)?.value ?: throw IOException("failed to create session")
-
                 var error: String? = null
                 apks.forEach { apk ->
-                    // install write every apk file to stream
                     execCmd(
                         "package",
                         "install-write",
@@ -124,7 +121,6 @@ interface Adb : AutoCloseable {
                     ).use { writeStream ->
                         writeStream.sink.writeAll(apk.source())
                         writeStream.sink.flush()
-
                         val writeResponse = writeStream.source.readString(Charsets.UTF_8)
                         if (!writeResponse.startsWith("Success")) {
                             error = writeResponse
@@ -132,8 +128,6 @@ interface Adb : AutoCloseable {
                         }
                     }
                 }
-
-                // commit the session
                 val finalCommand = if (error == null) "install-commit" else "install-abandon"
                 execCmd("package", finalCommand, sessionId, *options).use { commitStream ->
                     val finalResponse = commitStream.source.readString(Charsets.UTF_8)
@@ -141,49 +135,37 @@ interface Adb : AutoCloseable {
                         throw IOException("failed to finalize session: $commitStream")
                     }
                 }
-
                 if (error != null) {
                     throw IOException("Install failed: $error")
                 }
             }
         } else {
             val totalLength = apks.map { it.length() }.reduce { acc, l -> acc + l }
-            // step1: create session
             val response = shell("pm install-create -S $totalLength ${options.joinToString(" ")}")
             if (!response.allOutput.startsWith("Success")) {
                 throw IOException("pm create session failed: $response")
             }
-
             val pattern = """\[(\w+)]""".toRegex()
             val sessionId =
                 pattern.find(response.allOutput)?.groups?.get(1)?.value ?: throw IOException("failed to create session")
             var error: String? = null
-
             val fileNames = apks.map { it.name }
             val remotePaths = fileNames.map { "/data/local/tmp/$it" }
-
-            // step2: write apk to the session
             apks.zip(remotePaths).forEachIndexed { index, pair ->
                 val apk = pair.first
                 val remotePath = pair.second
-
                 try {
-                    // we should push the apk files to device, when push failed, it would stop the installation
                     push(apk, remotePath)
                 } catch (t: IOException) {
                     error = t.message
                     return@forEachIndexed
                 }
-
-                // pm install-write -S APK_SIZE SESSION_ID INDEX PATH
                 val writeResponse = shell("pm install-write -S ${apk.length()} $sessionId $index $remotePath")
                 if (!writeResponse.allOutput.startsWith("Success")) {
                     error = writeResponse.allOutput
                     return@forEachIndexed
                 }
             }
-
-            // step3: commit or abandon the session
             val finalCommand = if (error == null) "pm install-commit $sessionId" else "pm install-abandon $sessionId"
             val finalResponse = shell(finalCommand)
             if (!finalResponse.allOutput.startsWith("Success")) {
@@ -239,7 +221,6 @@ interface Adb : AutoCloseable {
     fun tcpForward(targetPort: Int, hostPort: Int): TcpForwardDescriptor {
         val forwarder = TcpForwarder(this, targetPort, hostPort)
         val localPort = forwarder.start()
-
         return TcpForwardDescriptor(forwarder, localPort)
     }
 
@@ -247,7 +228,6 @@ interface Adb : AutoCloseable {
     fun tcpForward(targetPort: Int): TcpForwardDescriptor {
         val forwarder = TcpForwarder(this, targetPort)
         val localPort = forwarder.start()
-
         return TcpForwardDescriptor(forwarder, localPort)
     }
 
@@ -279,7 +259,6 @@ interface Adb : AutoCloseable {
         fun list(host: String = "localhost", keyPair: AdbKeyPair? = AdbKeyPair.readDefault()): List<Adb> {
             val dadbs = AdbServer.listDadbs(adbServerHost = host)
             if (dadbs.isNotEmpty()) return dadbs
-
             return (MIN_EMULATOR_PORT..MAX_EMULATOR_PORT).mapNotNull { port ->
                 val dadb = create(host, port, keyPair)
                 val response = try {

+ 54 - 0
src/main/kotlin/eu/mjdev/desktop/helpers/system/UserDirs.kt

@@ -0,0 +1,54 @@
+package eu.mjdev.desktop.helpers.system
+
+import eu.mjdev.desktop.provider.DesktopProvider
+import java.io.File
+
+@Suppress("unused")
+class UserDirs(
+    api: DesktopProvider,
+    homeDir: File = api.homeDir,
+    configFile: File = File(api.homeDir, "/.config/user-dirs.dirs"),
+    configFileContent: List<String> = configFile.readLines().filter {
+        (!it.startsWith("#")) && (!it.trim().isEmpty())
+    }.map {
+        it.replace("\$HOME", homeDir.absolutePath)
+            .replace("\"", "")
+    }
+) : HashMap<String, File>() {
+    init {
+        configFileContent.map {
+            it.split("=").let { Pair(it[0], it.getOrNull(1)) }
+        }.forEach {
+            put(it.first, File(it.second ?: ""))
+        }
+        println(this)
+    }
+
+    val desktopDirectory
+        get() = this[XDG_DESKTOP_DIR]
+    val downloadDirectory
+        get() = this[XDG_DOWNLOAD_DIR]
+    val templatesDirectory
+        get() = this[XDG_TEMPLATES_DIR]
+    val publicShareDirectory
+        get() = this[XDG_PUBLICSHARE_DIR]
+    val documentsDirectory
+        get() = this[XDG_DOCUMENTS_DIR]
+    val musicDirectory
+        get() = this[XDG_MUSIC_DIR]
+    val picturesDirectory
+        get() = this[XDG_PICTURES_DIR]
+    val videosDirectory
+        get() = this[XDG_VIDEOS_DIR]
+
+    companion object {
+        const val XDG_DESKTOP_DIR = "XDG_DESKTOP_DIR"
+        const val XDG_DOWNLOAD_DIR = "XDG_DOWNLOAD_DIR"
+        const val XDG_TEMPLATES_DIR = "XDG_TEMPLATES_DIR"
+        const val XDG_PUBLICSHARE_DIR = "XDG_PUBLICSHARE_DIR"
+        const val XDG_DOCUMENTS_DIR = "XDG_DOCUMENTS_DIR"
+        const val XDG_MUSIC_DIR = "XDG_MUSIC_DIR"
+        const val XDG_PICTURES_DIR = "XDG_PICTURES_DIR"
+        const val XDG_VIDEOS_DIR = "XDG_VIDEOS_DIR"
+    }
+}

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

@@ -22,10 +22,10 @@ class AppsProvider(
     val scope
         get() = api.scope
 
-    private val configDir by lazy { api.homeDir?.resolve(".config") }
-    private val iconsDir by lazy { api.homeDir?.resolve(".icons") }
-    private val themesDir by lazy { api.homeDir?.resolve(".themes") }
-    private val localDir by lazy { api.homeDir?.resolve(".local") }
+    private val configDir by lazy { api.homeDir.resolve(".config") }
+    private val iconsDir by lazy { api.homeDir.resolve(".icons") }
+    private val themesDir by lazy { api.homeDir.resolve(".themes") }
+    private val localDir by lazy { api.homeDir.resolve(".local") }
     private val localShareDir by lazy { localDir?.resolve("share") }
     private val allAppsDesktopFilesDir by lazy { localShareDir?.resolve("applications") }
     private val autostartDesktopFilesDir by lazy { configDir?.resolve("autoStart") }

+ 32 - 6
src/main/kotlin/eu/mjdev/desktop/provider/DesktopProvider.kt

@@ -1,6 +1,7 @@
 package eu.mjdev.desktop.provider
 
 import androidx.compose.runtime.*
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import coil3.ImageLoader
@@ -10,8 +11,13 @@ import eu.mjdev.desktop.data.User
 import eu.mjdev.desktop.extensions.Compose.asyncImageLoader
 import eu.mjdev.desktop.helpers.adb.AdbDiscover.Companion.adbDevicesHandler
 import eu.mjdev.desktop.helpers.internal.Palette
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBackgroundColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberBorderColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberIconTintColor
+import eu.mjdev.desktop.helpers.internal.Palette.Companion.rememberTextColor
 import eu.mjdev.desktop.helpers.managers.*
 import eu.mjdev.desktop.helpers.system.Command
+import eu.mjdev.desktop.helpers.system.UserDirs
 import eu.mjdev.desktop.provider.AIProvider.AiPluginGemini
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -21,7 +27,7 @@ import java.awt.Toolkit
 import java.io.File
 import kotlin.system.exitProcess
 
-@Suppress("unused", "MemberVisibilityCanBePrivate", "PrivatePropertyName", "SameParameterValue")
+@Suppress("unused", "MemberVisibilityCanBePrivate", "SameParameterValue")
 class DesktopProvider(
     val scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
     val imageLoader: ImageLoader? = null,
@@ -31,12 +37,12 @@ class DesktopProvider(
     val ai: AIProvider = AIProvider(scope, AiPluginGemini(scope)),
     val windows: WindowsManager = WindowsManager(),
     val gnome: GnomeManager = GnomeManager(),
+    val currentUser: User = User.load() // todo manager
 ) {
-    val homeDir by lazy { runCatching { File(System.getProperty("user.home")) }.getOrNull() }
-    private val __currentUser: User by lazy { User.load() }
-    private val _currentUser: MutableState<User> = mutableStateOf(__currentUser)
-    val currentUser
-        get() = _currentUser.value
+    val homeDir
+        get() = File("/home/${currentUser.name}")
+
+    val userDirs  = UserDirs(this)
 
     val palette: Palette = Palette(scope, currentUser.theme.backgroundColor)
 
@@ -133,6 +139,14 @@ class DesktopProvider(
         exitProcess(0)
     }
 
+    class DesktopScope(
+        val api: DesktopProvider,
+        val backgroundColor: State<Color>,
+        val iconsTintColor: State<Color>,
+        val textColor: State<Color>,
+        val borderColor: State<Color>
+    )
+
     companion object {
         private val CONTROL_CENTER_PAGES = listOf(
             MainSettingsPage(),
@@ -156,5 +170,17 @@ class DesktopProvider(
         ) = remember {
             DesktopProvider(scope, imageLoader)
         }
+
+        @Composable
+        fun withDesktopScope(
+            api: DesktopProvider = LocalDesktop.current,
+            backgroundColor: MutableState<Color> = rememberBackgroundColor(api),
+            iconsTintColor: State<Color> = rememberIconTintColor(api),
+            textColor: State<Color> = rememberTextColor(api),
+            borderColor: State<Color> = rememberBorderColor(api),
+            block: @Composable DesktopScope.() -> Unit
+        ) = DesktopScope(api, backgroundColor, iconsTintColor, textColor, borderColor).apply {
+            block()
+        }
     }
 }

+ 77 - 2
src/main/kotlin/eu/mjdev/desktop/theme/gtk/GtkTheme.kt

@@ -4,15 +4,17 @@ import eu.mjdev.desktop.data.DesktopFile
 import eu.mjdev.desktop.provider.DesktopProvider
 import java.io.File
 
-@Suppress("unused")
+@Suppress("CanBeParameter", "unused")
 class GtkTheme(
     val themeName: String = "mjdev",
     val api: DesktopProvider,
     val themesDir: File = File(api.homeDir, ".themes"),
     val themeDir: File = File(themesDir, themeName),
-    val themeDesktopFile: File = File(themeDir, "index.theme")
+    val themeDesktopFile: File = File(themeDir, "index.theme"),
+    val themeCssFile: File = File(themeDir, "gtk.css")
 ) {
     val desktopFile: DesktopFile = DesktopFile.create(themeDesktopFile) // todo
+
     /*
     [Desktop Entry]
     Type=X-GNOME-Metatheme
@@ -28,4 +30,77 @@ class GtkTheme(
     ButtonLayout=close,minimize,maximize:
     X-Ubuntu-UseOverlayScrollbars=true
      */
+
+    val cssFile = themeCssFile
+
+    /*
+    /*default color scheme */
+    @define-color bg_color #f2f1f0;
+    @define-color fg_color #4c4c4c;
+    @define-color base_color #ffffff;
+    @define-color text_color #3C3C3C;
+    @define-color selected_bg_color #f07746;
+    @define-color selected_fg_color #ffffff;
+    @define-color tooltip_bg_color #000000;
+    @define-color tooltip_fg_color #ffffff;
+
+    /* misc colors used by gtk+
+     *
+     * Gtk doesn't currently expand color variables for style properties. Thus,
+     * gtk-widgets.css uses literal color names, but includes a comment containing
+     * the name of the variable. Please remember to change values there as well
+     * when changing one of the variables below.
+     */
+    @define-color info_fg_color rgb (181, 171, 156);
+    @define-color info_bg_color rgb (252, 252, 189);
+    @define-color warning_fg_color rgb (173, 120, 41);
+    @define-color warning_bg_color rgb (250, 173, 61);
+    @define-color question_fg_color rgb (97, 122, 214);
+    @define-color question_bg_color rgb (138, 173, 212);
+    @define-color error_fg_color rgb (235, 235, 235);
+    @define-color error_bg_color rgb (223, 56, 44);
+    @define-color link_color @selected_bg_color;
+    @define-color success_color #4e9a06;
+    @define-color error_color #df382c;
+
+    /* theme common colors */
+    @define-color button_bg_color shade (@bg_color, 1.02); /*shade (#cdcdcd, 1.08);*/
+    @define-color notebook_button_bg_color shade (@bg_color, 1.02);
+    @define-color button_insensitive_bg_color mix (@button_bg_color, @bg_color, 0.6);
+    @define-color dark_bg_color #3c3b37;
+    @define-color dark_fg_color #dfdbd2;
+
+    @define-color backdrop_fg_color mix (@bg_color, @fg_color, 0.8);
+    @define-color backdrop_text_color mix (@base_color, @text_color, 0.8);
+    @define-color backdrop_dark_fg_color mix (@dark_bg_color, @dark_fg_color, 0.75);
+    /*@define-color backdrop_dark_bg_color mix (@dark_bg_color, @dark_fg_color, 0.75);*/
+    @define-color backdrop_selected_bg_color shade (@bg_color, 0.92);
+    @define-color backdrop_selected_fg_color @fg_color;
+
+    @define-color focus_color alpha (@selected_bg_color, 0.5);
+    @define-color focus_bg_color alpha (@selected_bg_color, 0.1);
+
+    @define-color shadow_color alpha(black, 0.5);
+
+    @define-color osd_fg_color #eeeeec;
+    @define-color osd_bg_color alpha(#202526, 0.7);
+    @define-color osd_border_color alpha(black, 0.7);
+
+    @import url("gtk-widgets-borders.css");
+    @import url("gtk-widgets-assets.css");
+    @import url("gtk-widgets.css");
+    @import url("apps/geary.css");
+    @import url("apps/unity.css");
+    @import url("apps/baobab.css");
+    @import url("apps/gedit.css");
+    @import url("apps/nautilus.css");
+    @import url("apps/gnome-panel.css");
+    @import url("apps/gnome-terminal.css");
+    @import url("apps/gnome-system-log.css");
+    @import url("apps/unity-greeter.css");
+    @import url("apps/glade.css");
+    @import url("apps/california.css");
+    @import url("apps/software-center.css");
+    @import url("public-colors.css");
+     */
 }

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

@@ -14,6 +14,7 @@ import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberWnState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.updateAnimState
 import eu.mjdev.desktop.windows.ChromeWindowState.Companion.updateWindowState
 
+@Suppress("FunctionName", "unused")
 @Composable
 fun ChromeWindow(
     size: DpSize = DpSize(Dp.Unspecified, Dp.Unspecified),
@@ -57,7 +58,7 @@ fun ChromeWindow(
     content = content
 )
 
-@Suppress("unused")
+@Suppress("unused", "FunctionName")
 @Composable
 fun ChromeWindow(
     position: WindowPosition = WindowPosition.Aligned(Alignment.Center),
@@ -100,6 +101,7 @@ fun ChromeWindow(
     content = content
 )
 
+@Suppress("FunctionName")
 @Composable
 fun ChromeWindow(
     windowState: ChromeWindowState = rememberChromeWindowState(),
@@ -181,6 +183,6 @@ fun ChromeWindow(
             }
         }
     )
-    updateAnimState(visible, animState, wnState, windowState)
+    updateAnimState(visible, animState, wnState)
     updateWindowState(visible, animState, wnState, windowState)
 }

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

@@ -7,12 +7,13 @@ import androidx.compose.runtime.State
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.FrameWindowScope
 import androidx.compose.ui.window.WindowPosition
 
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 class ChromeWindowScope(
-    private val frameScope: FrameWindowScope,
+    val frameScope: FrameWindowScope,
     val windowState: ChromeWindowState,
     val animState: State<MutableTransitionState<Boolean>>,
     val windowVisibleState: MutableState<Boolean>
@@ -26,17 +27,17 @@ class ChromeWindowScope(
     val focusHelper: WindowFocusHelper
         get() = windowState.focusHelper
 
-    val x
-        get() = stateHelper.x
+    val x : Dp
+        get() = frameScope.window.x.dp
 
-    val y
-        get() = stateHelper.y
+    val y : Dp
+        get() = frameScope.window.y.dp
 
-    val width
-        get() = stateHelper.window
+    val width : Dp
+        get() = frameScope.window.width.dp
 
-    val height
-        get() = stateHelper.height
+    val height : Dp
+        get() = frameScope.window.height.dp
 
     fun apply(
         block: ChromeWindowScope.() -> Unit

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

@@ -119,7 +119,6 @@ class ChromeWindowState(
             visible: Boolean,
             animState: State<MutableTransitionState<Boolean>>,
             wnState: MutableState<Boolean>,
-            state: ChromeWindowState
         ) = launchedEffect(visible) {
             if (visible) {
                 wnState.value = true

+ 90 - 22
src/main/kotlin/eu/mjdev/desktop/windows/Window.kt

@@ -11,18 +11,30 @@ import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.*
 import androidx.compose.ui.window.*
+import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 import java.awt.*
 import java.awt.event.*
 import java.util.*
 import javax.swing.JFrame
 import kotlin.math.roundToInt
 
+@Suppress("FunctionName")
 @Composable
 fun Window(
 //    onOpen: (window: ComposeWindow) -> Unit = {},
     onCreate: (window: ComposeWindow) -> Unit = {},
+    onOpen: (window: ComposeWindow) -> Unit = {},
+    onClosing: (window: ComposeWindow) -> Unit = {},
+    onClosed: (window: ComposeWindow) -> Unit = {},
+    onActivated: (window: ComposeWindow) -> Unit = {},
+    onDeactivated: (window: ComposeWindow) -> Unit = {},
+    onIconified: (window: ComposeWindow) -> Unit = {},
+    onDeIconified: (window: ComposeWindow) -> Unit = {},
+    onGainFocus: (window: ComposeWindow) -> Unit = {},
+    onLostFocus: (window: ComposeWindow) -> Unit = {},
+    onStateChanged: (window: ComposeWindow) -> Unit = {},
     onCloseRequest: () -> Unit,
-    state: WindowState = rememberWindowState(),
+    state: ChromeWindowState =  rememberChromeWindowState(),
     visible: Boolean = true,
     title: String = "",
     icon: Painter? = null,
@@ -81,20 +93,20 @@ fun Window(
                 defaultCloseOperation = closeAction
                 listeners.windowListenerRef.registerWithAndSet(
                     this,
-                    object : WindowAdapter() {
-                        override fun windowOpened(e: WindowEvent?) {
-//                            onOpen(this@apply)
-                        }
-
-                        override fun windowGainedFocus(e: WindowEvent?) {
-                            super.windowGainedFocus(e)
-//                            onOpen(this@apply)
-                        }
-
-                        override fun windowClosing(e: WindowEvent) {
-                            currentOnCloseRequest()
-                        }
-                    }
+                    WindowEventsAdapter(
+                        this,
+                        currentOnCloseRequest,
+                        onOpen,
+                        onClosing,
+                        onClosed,
+                        onActivated,
+                        onDeactivated,
+                        onIconified,
+                        onDeIconified,
+                        onGainFocus,
+                        onLostFocus,
+                        onStateChanged,
+                    )
                 )
                 listeners.windowStateListenerRef.registerWithAndSet(this) {
                     currentState.placement = placement
@@ -140,8 +152,8 @@ fun Window(
 //                    set(false, window::setAlwaysOnTop)
 //                    set(false, window::setAutoRequestFocus)
 //                } else {
-                    set(currentFocusable, window::setFocusableWindowState)
-                    set(currentAlwaysOnTop, window::setAlwaysOnTop)
+                set(currentFocusable, window::setFocusableWindowState)
+                set(currentAlwaysOnTop, window::setAlwaysOnTop)
 //                }
             }
             if (state.size != appliedState.size) {
@@ -311,9 +323,9 @@ internal fun layoutDirectionFor(component: Component): LayoutDirection {
     }
 }
 
-internal val Component.density: Density get() = graphicsConfiguration.density
+val Component.density: Density get() = graphicsConfiguration.density
 
-internal fun Window.setIcon(painter: Painter?) {
+fun Window.setIcon(painter: Painter?) {
     setIconImage(
         painter?.toAwtImage(
             density,
@@ -323,14 +335,14 @@ internal fun Window.setIcon(painter: Painter?) {
     )
 }
 
-internal fun Frame.setUndecoratedSafely(value: Boolean) {
+fun Frame.setUndecoratedSafely(value: Boolean) {
     if (this.isUndecorated != value) {
         this.isUndecorated = value
     }
 }
 
 @OptIn(ExperimentalComposeUiApi::class)
-@Suppress("unused")
+@Suppress("unused", "FunctionName")
 @Composable
 fun Window(
     visible: Boolean = true,
@@ -371,8 +383,64 @@ fun Window(
     )
 }
 
-internal val LayoutDirection.componentOrientation: ComponentOrientation
+val LayoutDirection.componentOrientation: ComponentOrientation
     get() = when (this) {
         LayoutDirection.Ltr -> ComponentOrientation.LEFT_TO_RIGHT
         LayoutDirection.Rtl -> ComponentOrientation.RIGHT_TO_LEFT
     }
+
+class WindowEventsAdapter(
+    val window: ComposeWindow,
+    val currentOnCloseRequest: () -> Unit,
+    val onOpen: (window: ComposeWindow) -> Unit = {},
+    val onClosing: (window: ComposeWindow) -> Unit = {},
+    val onClosed: (window: ComposeWindow) -> Unit = {},
+    val onActivated: (window: ComposeWindow) -> Unit = {},
+    val onDeactivated: (window: ComposeWindow) -> Unit = {},
+    val onIconified: (window: ComposeWindow) -> Unit = {},
+    val onDeIconified: (window: ComposeWindow) -> Unit = {},
+    val onGainFocus: (window: ComposeWindow) -> Unit = {},
+    val onLostFocus: (window: ComposeWindow) -> Unit = {},
+    val onStateChanged: (window: ComposeWindow) -> Unit = {},
+) : WindowAdapter() {
+    override fun windowOpened(e: WindowEvent?) {
+        onOpen(window)
+    }
+
+    override fun windowActivated(e: WindowEvent?) {
+        onActivated(window)
+    }
+
+    override fun windowClosed(e: WindowEvent?) {
+        onClosed(window)
+    }
+
+    override fun windowDeactivated(e: WindowEvent?) {
+        onDeactivated(window)
+    }
+
+    override fun windowDeiconified(e: WindowEvent?) {
+        onDeIconified(window)
+    }
+
+    override fun windowIconified(e: WindowEvent?) {
+        onIconified(window)
+    }
+
+    override fun windowGainedFocus(e: WindowEvent?) {
+        onGainFocus(window)
+    }
+
+    override fun windowLostFocus(e: WindowEvent?) {
+        onLostFocus(window)
+    }
+
+    override fun windowStateChanged(e: WindowEvent?) {
+        onStateChanged(window)
+    }
+
+    override fun windowClosing(e: WindowEvent) {
+        onClosing(window)
+        currentOnCloseRequest()
+    }
+}

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

@@ -5,15 +5,14 @@ import androidx.compose.ui.awt.ComposeWindow
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.window.FrameWindowScope
-import androidx.compose.ui.window.WindowState
-import androidx.compose.ui.window.rememberWindowState
+import eu.mjdev.desktop.windows.ChromeWindowState.Companion.rememberChromeWindowState
 
+@Suppress("FunctionName")
 @Composable
 fun WindowEx(
     onCreate: (window: ComposeWindow) -> Unit = {},
-//    onOpen: (window: ComposeWindow) -> Unit = {},
     onCloseRequest: () -> Unit,
-    state: WindowState = rememberWindowState(),
+    state: ChromeWindowState =  rememberChromeWindowState(),
     visible: Boolean = true,
     title: String = "Untitled",
     icon: Painter? = null,
@@ -31,7 +30,6 @@ fun WindowEx(
     content: @Composable FrameWindowScope.() -> Unit = {}
 ) = Window(
     onCreate = onCreate,
-//    onOpen = onOpen,
     onCloseRequest = onCloseRequest,
     state = state,
     visible = visible,